index.html 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <title>NGS · 控制台</title>
  7. <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css" />
  8. <link rel="stylesheet" href="/assets/bootstrap-icons/bootstrap-icons.css" />
  9. <link rel="stylesheet" href="/assets/xterm/xterm.css" />
  10. <link rel="stylesheet" href="/assets/app.css" />
  11. <script src="/assets/chartjs/chart.umd.min.js"></script>
  12. <script src="/assets/xterm/xterm.js"></script>
  13. <script src="/assets/xterm/xterm-addon-fit.js"></script>
  14. </head>
  15. <body>
  16. <div class="app d-flex">
  17. <aside class="rail flex-shrink-0">
  18. <div class="brand d-flex align-items-center gap-2 px-3 py-2">
  19. <div class="brand-mark" id="brand-mark">N</div>
  20. <div class="brand-text flex-grow-1 min-w-0">
  21. <div class="fw-bold text-white lh-1 text-truncate" id="brand-name">NGS</div>
  22. <div class="small text-white-50">管理面板</div>
  23. </div>
  24. <button type="button" class="btn btn-sm btn-outline-light position-relative" id="task-badge" title="任务队列">
  25. <i class="bi bi-list-task"></i>
  26. <span class="badge text-bg-warning position-absolute top-0 start-100 translate-middle rounded-pill" id="task-badge-count">0</span>
  27. </button>
  28. </div>
  29. <nav class="nav flex-column px-2 gap-1 flex-grow-1">
  30. <button type="button" class="nav-link text-start nav-item is-active" data-view="console"><i class="bi bi-speedometer2 me-2"></i>控制台</button>
  31. <button type="button" class="nav-link text-start nav-item" data-view="sites"><i class="bi bi-globe2 me-2"></i>网站</button>
  32. <button type="button" class="nav-link text-start nav-item" data-view="files"><i class="bi bi-folder2-open me-2"></i>文件</button>
  33. <button type="button" class="nav-link text-start nav-item" data-view="mysql"><i class="bi bi-database me-2"></i>数据库</button>
  34. <button type="button" class="nav-link text-start nav-item" data-view="processes"><i class="bi bi-cpu me-2"></i>进程</button>
  35. <button type="button" class="nav-link text-start nav-item" data-view="shell"><i class="bi bi-terminal me-2"></i>终端</button>
  36. <button type="button" class="nav-link text-start nav-item" data-view="software"><i class="bi bi-box-seam me-2"></i>软件</button>
  37. <button type="button" class="nav-link text-start nav-item" data-view="plans"><i class="bi bi-calendar2-check me-2"></i>计划</button>
  38. <button type="button" class="nav-link text-start nav-item" data-view="settings"><i class="bi bi-gear me-2"></i>设置</button>
  39. </nav>
  40. <div class="rail-foot px-2 pb-2">
  41. <div class="self-update px-1 pb-2" id="self-update-box">
  42. <div class="small text-white-50 text-truncate" id="self-version" title="当前版本">版本 —</div>
  43. <div class="d-flex gap-1 mt-1">
  44. <button type="button" class="btn btn-sm btn-outline-light flex-grow-1" id="btn-self-check">检查更新</button>
  45. <button type="button" class="btn btn-sm btn-warning d-none" id="btn-self-update">更新</button>
  46. </div>
  47. </div>
  48. <button type="button" class="nav-link text-start nav-item nav-logout w-100" id="btn-logout">
  49. <i class="bi bi-box-arrow-right me-2"></i>退出
  50. </button>
  51. <div class="live small d-flex align-items-center gap-2 px-2 pt-1" id="live-dot">
  52. <span class="pulse"></span>
  53. <span id="live-label">检测中</span>
  54. </div>
  55. </div>
  56. </aside>
  57. <main class="main flex-grow-1">
  58. <header class="top d-flex justify-content-between align-items-center mb-3">
  59. <div>
  60. <div class="text-secondary small" id="page-eyebrow">Console</div>
  61. <h1 class="h4 mb-0" id="page-title">控制台</h1>
  62. </div>
  63. <div class="text-secondary small" id="top-meta"></div>
  64. </header>
  65. <section class="view is-active" id="view-console" data-view-panel="console">
  66. <div class="card mb-3">
  67. <div class="card-header py-2 d-flex justify-content-between align-items-center gap-2">
  68. <strong>实时资源</strong>
  69. <div class="d-flex align-items-center gap-2">
  70. <span class="small text-secondary" id="overview-sites">—</span>
  71. <select class="form-select form-select-sm metrics-range" id="metrics-range" title="时间范围">
  72. <option value="1m">1分</option>
  73. <option value="10m" selected>10分</option>
  74. <option value="30m">30分</option>
  75. <option value="1h">1小时</option>
  76. <option value="6h">6小时</option>
  77. <option value="1d">1天</option>
  78. <option value="7d">7天</option>
  79. <option value="30d">30天</option>
  80. </select>
  81. </div>
  82. </div>
  83. <div class="card-body py-3 d-flex flex-column min-h-0">
  84. <div class="row g-3 monitor-grid flex-grow-1">
  85. <div class="col-md-6">
  86. <div class="monitor-card">
  87. <div class="d-flex justify-content-between align-items-center small mb-2">
  88. <span class="fw-semibold">CPU</span>
  89. <span class="text-secondary" id="cpu-badge">—</span>
  90. </div>
  91. <div class="monitor-chart"><canvas id="chart-cpu"></canvas></div>
  92. </div>
  93. </div>
  94. <div class="col-md-6">
  95. <div class="monitor-card">
  96. <div class="d-flex justify-content-between align-items-center small mb-2">
  97. <span class="fw-semibold">内存</span>
  98. <span class="text-secondary" id="mem-badge">—</span>
  99. </div>
  100. <div class="monitor-chart"><canvas id="chart-mem"></canvas></div>
  101. </div>
  102. </div>
  103. <div class="col-md-6">
  104. <div class="monitor-card">
  105. <div class="d-flex justify-content-between align-items-center small mb-2">
  106. <span class="fw-semibold">网络</span>
  107. <span class="text-secondary text-end monitor-badge-stack" id="net-badge">—</span>
  108. </div>
  109. <div class="monitor-chart"><canvas id="chart-net"></canvas></div>
  110. </div>
  111. </div>
  112. <div class="col-md-6">
  113. <div class="monitor-card">
  114. <div class="d-flex justify-content-between align-items-center small mb-2">
  115. <span class="fw-semibold">磁盘</span>
  116. <span class="text-secondary" id="disk-badge">—</span>
  117. </div>
  118. <div class="monitor-chart"><canvas id="chart-disk"></canvas></div>
  119. <div class="resource-list" id="disk-list"></div>
  120. </div>
  121. </div>
  122. <div class="col-md-6" id="gpu-wrap" hidden>
  123. <div class="monitor-card">
  124. <div class="d-flex justify-content-between align-items-center small mb-2">
  125. <span class="fw-semibold">GPU</span>
  126. <span class="text-secondary" id="gpu-name-badge">—</span>
  127. </div>
  128. <div class="gpu-split">
  129. <div class="gpu-pane">
  130. <div class="d-flex justify-content-between align-items-center small mb-1">
  131. <span class="fw-semibold">占用</span>
  132. <span class="text-secondary" id="gpu-badge">—</span>
  133. </div>
  134. <div class="monitor-chart"><canvas id="chart-gpu"></canvas></div>
  135. </div>
  136. <div class="gpu-pane">
  137. <div class="d-flex justify-content-between align-items-center small mb-1">
  138. <span class="fw-semibold">显存</span>
  139. <span class="text-secondary" id="gpu-mem-badge">—</span>
  140. </div>
  141. <div class="monitor-chart"><canvas id="chart-gpu-mem"></canvas></div>
  142. </div>
  143. </div>
  144. </div>
  145. </div>
  146. </div>
  147. </div>
  148. </div>
  149. </section>
  150. <section class="view" id="view-sites" data-view-panel="sites">
  151. <div class="card">
  152. <div class="card-header py-2 d-flex flex-wrap justify-content-between align-items-center gap-2">
  153. <div>
  154. <strong>网站</strong>
  155. <span class="small text-secondary ms-2">创建 / 启停 / 配置 / 删除</span>
  156. </div>
  157. <button type="button" class="btn btn-sm btn-primary" id="btn-site-create">新建网站</button>
  158. </div>
  159. <div class="px-3 pt-2" id="sites-group-bar"></div>
  160. <div class="table-responsive">
  161. <table class="table table-sm table-hover align-middle mb-0" id="sites-table">
  162. <thead>
  163. <tr>
  164. <th style="width:4.5rem">排序</th>
  165. <th>名称</th>
  166. <th>分组</th>
  167. <th>类型</th>
  168. <th>域名</th>
  169. <th>目录</th>
  170. <th>证书到期</th>
  171. <th>日志</th>
  172. <th>状态</th>
  173. <th class="text-end">操作</th>
  174. </tr>
  175. </thead>
  176. <tbody></tbody>
  177. </table>
  178. </div>
  179. </div>
  180. </section>
  181. <section class="view" id="view-files" data-view-panel="files">
  182. <div class="card">
  183. <div class="card-header py-2">
  184. <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
  185. <div>
  186. <strong>文件</strong>
  187. </div>
  188. <div class="d-flex flex-wrap align-items-center gap-2">
  189. <div class="btn-group btn-group-sm" id="files-bulk-actions" hidden>
  190. <span class="btn btn-outline-secondary disabled" id="files-selected-count">已选 0</span>
  191. <button type="button" class="btn btn-outline-danger" id="btn-files-delete-selected">删除选中</button>
  192. <button type="button" class="btn btn-outline-secondary" id="btn-files-clear-selection">取消选择</button>
  193. </div>
  194. <div class="btn-group btn-group-sm">
  195. <button type="button" class="btn btn-outline-secondary" id="btn-files-up">上级</button>
  196. <button type="button" class="btn btn-outline-secondary" id="btn-files-mkdir">新建目录</button>
  197. <button type="button" class="btn btn-outline-secondary" id="btn-files-newfile">新建文件</button>
  198. <button type="button" class="btn btn-primary" id="btn-files-upload">上传</button>
  199. </div>
  200. </div>
  201. </div>
  202. <div class="crumbs small mt-2" id="files-crumbs"></div>
  203. </div>
  204. <div class="table-responsive">
  205. <table class="table table-sm table-hover align-middle mb-0" id="files-table">
  206. <thead>
  207. <tr>
  208. <th class="files-check-col">
  209. <input class="form-check-input" type="checkbox" id="files-select-all" title="全选" aria-label="全选" />
  210. </th>
  211. <th>名称</th>
  212. <th>类型</th>
  213. <th>大小</th>
  214. <th>修改时间</th>
  215. <th class="text-end">操作</th>
  216. </tr>
  217. </thead>
  218. <tbody></tbody>
  219. </table>
  220. </div>
  221. </div>
  222. </section>
  223. <section class="view" id="view-mysql" data-view-panel="mysql">
  224. <div class="card">
  225. <div class="card-header py-2 d-flex justify-content-between align-items-center">
  226. <div>
  227. <strong>数据库</strong>
  228. <span class="small text-secondary ms-2">库 / 账号 / 权限</span>
  229. </div>
  230. <div class="btn-group btn-group-sm">
  231. <button type="button" class="btn btn-outline-secondary" id="btn-mysql-password">改 root 密码</button>
  232. <button type="button" class="btn btn-primary" id="btn-db-create">新建数据库</button>
  233. </div>
  234. </div>
  235. <div class="table-responsive">
  236. <table class="table table-sm table-hover align-middle mb-0" id="db-table">
  237. <thead>
  238. <tr>
  239. <th>库名</th>
  240. <th>用户</th>
  241. <th>密码</th>
  242. <th>权限</th>
  243. <th>Host</th>
  244. <th class="text-end">操作</th>
  245. </tr>
  246. </thead>
  247. <tbody></tbody>
  248. </table>
  249. </div>
  250. </div>
  251. <div class="card mt-3">
  252. <div class="card-header py-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
  253. <div>
  254. <strong>执行 SQL</strong>
  255. <span class="small text-secondary ms-2">以 root 运行,最多返回 500 行</span>
  256. </div>
  257. <div class="d-flex align-items-center gap-2 flex-wrap">
  258. <select class="form-select form-select-sm" id="sql-db" style="width:auto;min-width:10rem">
  259. <option value="">(不选库)</option>
  260. </select>
  261. <button type="button" class="btn btn-sm btn-primary" id="btn-sql-exec">执行</button>
  262. </div>
  263. </div>
  264. <div class="card-body py-2">
  265. <textarea class="form-control sql-editor" id="sql-input" rows="6" spellcheck="false"
  266. placeholder="SHOW DATABASES;&#10;SELECT * FROM table_name LIMIT 100;"></textarea>
  267. <div class="form-text">Ctrl+Enter 执行。DROP / DELETE 等语句请谨慎使用。</div>
  268. <div id="sql-meta" class="small text-secondary mt-2"></div>
  269. <div class="table-responsive mt-2 sql-result-wrap">
  270. <table class="table table-sm table-hover align-middle mb-0" id="sql-result">
  271. <thead></thead>
  272. <tbody></tbody>
  273. </table>
  274. </div>
  275. </div>
  276. </div>
  277. </section>
  278. <section class="view" id="view-processes" data-view-panel="processes">
  279. <div class="card">
  280. <div class="card-header py-2">
  281. <div class="d-flex justify-content-between align-items-center gap-2 proc-toolbar">
  282. <div class="proc-toolbar-title text-nowrap">
  283. <strong>进程</strong>
  284. <span class="small text-secondary ms-2" id="proc-count">—</span>
  285. </div>
  286. <div class="d-flex flex-nowrap align-items-center gap-2 proc-toolbar-actions">
  287. <input class="form-control form-control-sm" id="proc-search" type="search" placeholder="搜索 PID / 用户 / 名称 / 目录 / 命令" />
  288. <div class="form-check form-switch mb-0 text-nowrap">
  289. <input class="form-check-input" type="checkbox" id="proc-auto" />
  290. <label class="form-check-label small" for="proc-auto">自动刷新</label>
  291. </div>
  292. <button type="button" class="btn btn-sm btn-outline-secondary text-nowrap" id="btn-proc-refresh">刷新</button>
  293. </div>
  294. </div>
  295. </div>
  296. <div class="table-responsive proc-table-wrap">
  297. <table class="table table-sm table-hover align-middle mb-0" id="proc-table">
  298. <thead>
  299. <tr>
  300. <th data-proc-sort="pid" class="proc-sort">PID</th>
  301. <th data-proc-sort="user" class="proc-sort">用户</th>
  302. <th data-proc-sort="cpu_percent" class="proc-sort is-active">CPU%</th>
  303. <th data-proc-sort="mem_percent" class="proc-sort">内存%</th>
  304. <th data-proc-sort="rss" class="proc-sort">RSS</th>
  305. <th>状态</th>
  306. <th data-proc-sort="name" class="proc-sort">名称</th>
  307. <th data-proc-sort="cwd" class="proc-sort">运行目录</th>
  308. <th>命令行</th>
  309. <th class="text-end">操作</th>
  310. </tr>
  311. </thead>
  312. <tbody></tbody>
  313. </table>
  314. </div>
  315. </div>
  316. </section>
  317. <section class="view" id="view-shell" data-view-panel="shell">
  318. <div class="card shell-card">
  319. <div class="card-header py-2">
  320. <div class="d-flex justify-content-between align-items-center gap-2 flex-wrap">
  321. <div class="text-nowrap">
  322. <strong>终端</strong>
  323. <span class="small text-secondary ms-2" id="shell-status">未连接</span>
  324. </div>
  325. <div class="d-flex flex-nowrap align-items-center gap-2">
  326. <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-shell-reconnect">重新连接</button>
  327. <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-shell-clear">清屏</button>
  328. </div>
  329. </div>
  330. </div>
  331. <div class="card-body p-0 shell-body">
  332. <div id="shell-terminal" class="shell-terminal"></div>
  333. </div>
  334. </div>
  335. </section>
  336. <section class="view" id="view-software" data-view-panel="software">
  337. <div class="row g-2" id="software-grid"></div>
  338. </section>
  339. <section class="view" id="view-plans" data-view-panel="plans">
  340. <div class="card">
  341. <div class="card-header py-2">
  342. <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
  343. <div class="btn-group btn-group-sm" role="group" id="plans-tabs">
  344. <button type="button" class="btn btn-primary" data-plans-tab="cron">定时任务</button>
  345. <button type="button" class="btn btn-outline-primary" data-plans-tab="more" disabled title="即将推出">更多</button>
  346. </div>
  347. <div class="d-flex gap-2" id="plans-cron-actions">
  348. <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-schedule-refresh">刷新</button>
  349. <button type="button" class="btn btn-sm btn-primary" id="btn-schedule-add">新建任务</button>
  350. </div>
  351. </div>
  352. </div>
  353. <div class="card-body p-0">
  354. <div class="plans-pane is-active" data-plans-pane="cron">
  355. <div class="row g-0 plans-cron-layout">
  356. <div class="col-lg-7 border-end">
  357. <div class="table-responsive">
  358. <table class="table table-sm table-hover align-middle mb-0" id="schedule-table">
  359. <thead>
  360. <tr>
  361. <th>名称</th>
  362. <th>间隔</th>
  363. <th>URL</th>
  364. <th>状态</th>
  365. <th class="text-end">操作</th>
  366. </tr>
  367. </thead>
  368. <tbody></tbody>
  369. </table>
  370. </div>
  371. </div>
  372. <div class="col-lg-5">
  373. <div class="p-2 border-bottom d-flex flex-wrap justify-content-between align-items-center gap-2">
  374. <strong class="small mb-0">执行日志</strong>
  375. <div class="d-flex gap-2">
  376. <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-schedule-log-refresh">刷新日志</button>
  377. <button type="button" class="btn btn-sm btn-outline-danger" id="btn-schedule-log-clear">清空</button>
  378. </div>
  379. </div>
  380. <div class="schedule-log-meta small text-secondary px-2 py-1" id="schedule-log-meta">选择左侧任务查看日志</div>
  381. <pre class="schedule-log-view mb-0" id="schedule-log-view"></pre>
  382. </div>
  383. </div>
  384. </div>
  385. <div class="plans-pane p-4 text-secondary" data-plans-pane="more" hidden>
  386. 更多计划能力即将推出。
  387. </div>
  388. </div>
  389. </div>
  390. </section>
  391. <section class="view" id="view-settings" data-view-panel="settings">
  392. <div class="card">
  393. <div class="card-header py-2">
  394. <strong>面板设置</strong>
  395. <span class="small text-secondary ms-2">名称会显示在侧栏、浏览器标题和登录页</span>
  396. </div>
  397. <div class="card-body">
  398. <form id="settings-form" class="col-lg-6 col-xl-5">
  399. <div class="mb-3">
  400. <label class="form-label" for="setting-panel-name">网站名称</label>
  401. <input class="form-control" id="setting-panel-name" name="panel_name" maxlength="64"
  402. required placeholder="NGS" autocomplete="off" />
  403. </div>
  404. <button type="submit" class="btn btn-primary btn-sm" id="btn-settings-save">保存</button>
  405. </form>
  406. </div>
  407. </div>
  408. </section>
  409. </main>
  410. </div>
  411. <div class="toast-host position-fixed bottom-0 end-0 p-3" id="toast-host" aria-live="polite"></div>
  412. <div class="task-panel" id="task-panel" hidden>
  413. <div class="task-panel-backdrop" id="task-panel-backdrop"></div>
  414. <div class="task-panel-dialog" role="dialog" aria-modal="true" aria-labelledby="task-panel-title">
  415. <header class="task-panel-head d-flex justify-content-between align-items-start gap-2">
  416. <div>
  417. <h2 class="h5 mb-1" id="task-panel-title">任务队列</h2>
  418. <p class="small text-secondary mb-0">默认显示进行中任务,可切换查看历史。</p>
  419. </div>
  420. <button type="button" class="btn btn-sm btn-outline-light" id="task-panel-close" aria-label="关闭">×</button>
  421. </header>
  422. <div class="task-panel-body">
  423. <div class="task-side">
  424. <div class="task-side-tabs btn-group btn-group-sm w-100 px-2 pt-2">
  425. <button type="button" class="btn btn-warning" id="task-tab-active" data-task-tab="active">进行中</button>
  426. <button type="button" class="btn btn-outline-light" id="task-tab-history" data-task-tab="history">历史任务</button>
  427. </div>
  428. <div class="task-list" id="task-list"></div>
  429. <div class="task-side-foot px-2 pb-2">
  430. <button type="button" class="btn btn-sm btn-outline-light w-100 d-none" id="task-clear-finished">清理历史</button>
  431. </div>
  432. </div>
  433. <div class="task-log-wrap">
  434. <div class="task-log-head d-flex justify-content-between align-items-center">
  435. <strong id="task-log-title">日志</strong>
  436. <span class="badge text-bg-secondary" id="task-log-status">—</span>
  437. </div>
  438. <pre class="task-log" id="task-log"></pre>
  439. </div>
  440. </div>
  441. <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
  442. </div>
  443. </div>
  444. <div class="modal fade" id="modal" tabindex="-1" aria-hidden="true">
  445. <div class="modal-dialog modal-dialog-scrollable">
  446. <form class="modal-content" id="modal-form">
  447. <div class="modal-header py-2">
  448. <h5 class="modal-title" id="modal-title">操作</h5>
  449. <button type="button" class="btn-close" id="modal-close" aria-label="关闭"></button>
  450. </div>
  451. <div class="modal-body" id="modal-body"></div>
  452. <div class="modal-footer py-2">
  453. <button type="button" class="btn btn-sm btn-outline-secondary" id="modal-cancel">取消</button>
  454. <button type="submit" class="btn btn-sm btn-primary" id="modal-ok">确定</button>
  455. </div>
  456. <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
  457. </form>
  458. </div>
  459. </div>
  460. <div class="modal fade" id="redis-config-modal" tabindex="-1" aria-hidden="true">
  461. <div class="modal-dialog modal-lg modal-dialog-scrollable">
  462. <div class="modal-content">
  463. <div class="modal-header py-2">
  464. <h5 class="modal-title mb-0">Redis 配置</h5>
  465. <button type="button" class="btn-close" id="redis-config-close" aria-label="关闭"></button>
  466. </div>
  467. <div class="modal-body">
  468. <div class="btn-group btn-group-sm mb-3" role="group">
  469. <button type="button" class="btn btn-outline-primary active" id="redis-tab-quick" data-redis-tab="quick">基础配置</button>
  470. <button type="button" class="btn btn-outline-primary" id="redis-tab-file" data-redis-tab="file">配置文件</button>
  471. </div>
  472. <div id="redis-pane-quick">
  473. <p class="small text-secondary mb-3">快捷修改常用项;保存后若 Redis 正在运行会自动重启生效。</p>
  474. <div class="mb-2">
  475. <label class="form-label">监听地址 <span class="text-secondary fw-normal">bind</span></label>
  476. <input class="form-control form-control-sm" id="redis-cfg-bind" placeholder="127.0.0.1" autocomplete="off" />
  477. <div class="form-text">多个地址用空格分隔,例如 <code>127.0.0.1 0.0.0.0</code></div>
  478. </div>
  479. <div class="mb-2">
  480. <label class="form-label">端口 <span class="text-secondary fw-normal">port</span></label>
  481. <input class="form-control form-control-sm" id="redis-cfg-port" type="number" min="1" max="65535" />
  482. </div>
  483. <div class="mb-3">
  484. <label class="form-label">密码 <span class="text-secondary fw-normal">requirepass</span></label>
  485. <input class="form-control form-control-sm" id="redis-cfg-pass" type="password" placeholder="留空表示不设密码" autocomplete="new-password" />
  486. </div>
  487. <button type="button" class="btn btn-sm btn-primary" id="redis-cfg-save">保存并应用</button>
  488. </div>
  489. <div id="redis-pane-file" hidden>
  490. <div class="d-flex justify-content-between align-items-center mb-2 gap-2">
  491. <code class="small text-secondary text-truncate" id="redis-cfg-path"></code>
  492. <button type="button" class="btn btn-sm btn-outline-secondary" id="redis-cfg-reload">重新加载</button>
  493. </div>
  494. <div id="redis-cfg-monaco" class="soft-cfg-monaco" aria-label="Redis 配置编辑器"></div>
  495. <div class="mt-2 d-flex gap-2">
  496. <button type="button" class="btn btn-sm btn-primary" id="redis-cfg-file-save">保存文件</button>
  497. <span class="small text-secondary align-self-center">仅写入文件;如需生效请自行重启 Redis。</span>
  498. </div>
  499. </div>
  500. </div>
  501. </div>
  502. </div>
  503. </div>
  504. <div class="modal fade" id="nginx-config-modal" tabindex="-1" aria-hidden="true">
  505. <div class="modal-dialog modal-lg modal-dialog-scrollable">
  506. <div class="modal-content">
  507. <div class="modal-header py-2">
  508. <h5 class="modal-title mb-0">Nginx 配置</h5>
  509. <button type="button" class="btn-close" id="nginx-config-close" aria-label="关闭"></button>
  510. </div>
  511. <div class="modal-body">
  512. <div class="btn-group btn-group-sm mb-3" role="group">
  513. <button type="button" class="btn btn-outline-primary active" id="nginx-tab-quick" data-nginx-tab="quick">基础配置</button>
  514. <button type="button" class="btn btn-outline-primary" id="nginx-tab-file" data-nginx-tab="file">配置文件</button>
  515. </div>
  516. <div id="nginx-pane-quick">
  517. <p class="small text-secondary mb-3">快捷修改常用项;保存后会检测配置,若 Nginx 正在运行则自动重载生效。</p>
  518. <div class="mb-2">
  519. <label class="form-label">工作进程数 <span class="text-secondary fw-normal">worker_processes</span></label>
  520. <input class="form-control form-control-sm" id="nginx-cfg-workers" placeholder="auto" autocomplete="off" />
  521. <div class="form-text">填 <code>auto</code> 或正整数</div>
  522. </div>
  523. <div class="mb-2">
  524. <label class="form-label">最大连接数 <span class="text-secondary fw-normal">worker_connections</span></label>
  525. <input class="form-control form-control-sm" id="nginx-cfg-connections" type="number" min="1" max="1048576" />
  526. </div>
  527. <div class="mb-2">
  528. <label class="form-label">保持连接超时 <span class="text-secondary fw-normal">keepalive_timeout</span></label>
  529. <input class="form-control form-control-sm" id="nginx-cfg-keepalive" placeholder="65" autocomplete="off" />
  530. </div>
  531. <div class="mb-2">
  532. <label class="form-label">上传大小限制 <span class="text-secondary fw-normal">client_max_body_size</span></label>
  533. <input class="form-control form-control-sm" id="nginx-cfg-body" placeholder="50m" autocomplete="off" />
  534. <div class="form-text">例如 <code>1m</code>、<code>50m</code>、<code>1g</code></div>
  535. </div>
  536. <div class="mb-3 form-check">
  537. <input class="form-check-input" type="checkbox" id="nginx-cfg-gzip" />
  538. <label class="form-check-label" for="nginx-cfg-gzip">启用压缩 <span class="text-secondary fw-normal">gzip</span></label>
  539. </div>
  540. <button type="button" class="btn btn-sm btn-primary" id="nginx-cfg-save">保存并应用</button>
  541. </div>
  542. <div id="nginx-pane-file" hidden>
  543. <div class="d-flex justify-content-between align-items-center mb-2 gap-2">
  544. <code class="small text-secondary text-truncate" id="nginx-cfg-path"></code>
  545. <button type="button" class="btn btn-sm btn-outline-secondary" id="nginx-cfg-reload">重新加载</button>
  546. </div>
  547. <div id="nginx-cfg-monaco" class="soft-cfg-monaco" aria-label="Nginx 配置编辑器"></div>
  548. <div class="mt-2 d-flex gap-2">
  549. <button type="button" class="btn btn-sm btn-primary" id="nginx-cfg-file-save">保存文件</button>
  550. <span class="small text-secondary align-self-center">仅写入文件;如需生效请自行重载 Nginx。</span>
  551. </div>
  552. </div>
  553. </div>
  554. </div>
  555. </div>
  556. </div>
  557. <div class="modal fade" id="site-settings-modal" tabindex="-1" aria-hidden="true">
  558. <div class="modal-dialog modal-xl modal-dialog-scrollable">
  559. <div class="modal-content">
  560. <div class="modal-header py-2">
  561. <div>
  562. <h5 class="modal-title mb-0">网站配置 · <span id="site-settings-name"></span></h5>
  563. <div class="small text-secondary" id="site-settings-sub"></div>
  564. </div>
  565. <button type="button" class="btn-close" id="site-settings-close" aria-label="关闭"></button>
  566. </div>
  567. <div class="modal-body site-settings-body p-0">
  568. <div class="site-settings-layout">
  569. <aside class="site-settings-nav">
  570. <button type="button" class="site-settings-tab is-active" data-site-tab="domain">域名/分组</button>
  571. <button type="button" class="site-settings-tab d-none" data-site-tab="proxy" id="site-tab-proxy">反向代理</button>
  572. <button type="button" class="site-settings-tab" data-site-tab="cert">证书</button>
  573. <button type="button" class="site-settings-tab" data-site-tab="nginx">配置 (NGINX)</button>
  574. <button type="button" class="site-settings-tab" data-site-tab="custom">自定义配置</button>
  575. <button type="button" class="site-settings-tab" data-site-tab="logs">日志 (NGINX)</button>
  576. <button type="button" class="site-settings-tab d-none" data-site-tab="modules" id="site-tab-modules">模块</button>
  577. </aside>
  578. <div class="site-settings-panels">
  579. <div class="site-settings-panel is-active" data-site-panel="domain">
  580. <h6 class="mb-3">域名 / 分组</h6>
  581. <div class="mb-3">
  582. <label class="form-label">分组</label>
  583. <input class="form-control form-control-sm" id="site-set-group" list="site-group-datalist" placeholder="留空为未分组" autocomplete="off" />
  584. <datalist id="site-group-datalist"></datalist>
  585. <div class="form-text">同名分组合并显示;可在网站列表中筛选与排序。</div>
  586. </div>
  587. <div class="mb-3">
  588. <label class="form-label">域名(支持多个,每行一个)</label>
  589. <textarea class="form-control form-control-sm" id="site-set-domain" rows="4" placeholder="example.com&#10;www.example.com:8080"></textarea>
  590. <div class="form-text">可写 <code>域名</code> 或 <code>域名:端口</code>;不写端口默认 <code>80</code>。第一行作为主域名。</div>
  591. </div>
  592. <button type="button" class="btn btn-sm btn-primary" id="site-set-domain-save">保存并重载</button>
  593. </div>
  594. <div class="site-settings-panel" data-site-panel="proxy">
  595. <div class="d-flex justify-content-between align-items-center mb-3">
  596. <h6 class="mb-0">反向代理</h6>
  597. <button type="button" class="btn btn-sm btn-primary" id="site-proxy-add">添加 URL 代理</button>
  598. </div>
  599. <div class="table-responsive">
  600. <table class="table table-sm table-hover align-middle mb-0" id="site-proxy-table">
  601. <thead>
  602. <tr>
  603. <th>代理目录</th>
  604. <th>目标</th>
  605. <th>发送域名</th>
  606. <th>备注</th>
  607. <th class="text-end">操作</th>
  608. </tr>
  609. </thead>
  610. <tbody id="site-proxy-tbody">
  611. <tr><td colspan="5" class="text-secondary text-center py-3">加载中…</td></tr>
  612. </tbody>
  613. </table>
  614. </div>
  615. </div>
  616. <div class="site-settings-panel" data-site-panel="cert">
  617. <div class="btn-group btn-group-sm mb-3" role="group" aria-label="证书子页">
  618. <button type="button" class="btn btn-outline-primary active" id="site-cert-tab-pem" data-cert-tab="pem">证书内容</button>
  619. <button type="button" class="btn btn-outline-primary" id="site-cert-tab-apply" data-cert-tab="apply">申请 SSL</button>
  620. </div>
  621. <div id="site-cert-pane-pem">
  622. <div class="form-check form-switch mb-3">
  623. <input class="form-check-input" type="checkbox" id="site-set-ssl-enable" />
  624. <label class="form-check-label" for="site-set-ssl-enable">启用 HTTPS</label>
  625. </div>
  626. <div class="small mb-3" id="site-set-ssl-expire">证书有效期:—</div>
  627. <div class="mb-3">
  628. <label class="form-label">HTTPS 端口</label>
  629. <input class="form-control form-control-sm" id="site-set-ssl-port" type="number" min="1" max="65535" value="443" />
  630. </div>
  631. <div class="mb-3">
  632. <label class="form-label">证书 PEM <span class="text-secondary fw-normal">(fullchain)</span></label>
  633. <textarea class="form-control font-monospace site-ssl-pem" id="site-set-ssl-pem" rows="8" spellcheck="false" placeholder="-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----"></textarea>
  634. </div>
  635. <div class="mb-3">
  636. <label class="form-label">私钥 KEY</label>
  637. <textarea class="form-control font-monospace site-ssl-pem" id="site-set-ssl-key-pem" rows="8" spellcheck="false" placeholder="-----BEGIN PRIVATE KEY-----&#10;...&#10;-----END PRIVATE KEY-----"></textarea>
  638. </div>
  639. <p class="small text-secondary mb-3">保存后写入 <code>/ngs/conf/ssl/&lt;域名&gt;/fullchain.pem</code> 与 <code>privkey.pem</code> 并重载 Nginx。</p>
  640. <button type="button" class="btn btn-sm btn-primary" id="site-set-cert-save">保存并重载</button>
  641. </div>
  642. <div id="site-cert-pane-apply" hidden>
  643. <div class="fw-semibold mb-2">Let's Encrypt 免费证书</div>
  644. <p class="small text-secondary mb-2">勾选域名后合并签发到一张证书,成功后自动写入 <code>/ngs/conf/ssl/&lt;主域名&gt;/</code> 并启用 HTTPS。域名需已解析到本机,且公网可访问 80 端口。</p>
  645. <div class="mb-2">
  646. <div class="d-flex justify-content-between align-items-center mb-1">
  647. <label class="form-label mb-0">申请域名</label>
  648. <div class="btn-group btn-group-sm">
  649. <button type="button" class="btn btn-outline-secondary" id="site-ssl-domains-all">全选</button>
  650. <button type="button" class="btn btn-outline-secondary" id="site-ssl-domains-none">清空</button>
  651. </div>
  652. </div>
  653. <div class="site-ssl-domain-list border rounded bg-white p-2" id="site-ssl-domain-list"></div>
  654. </div>
  655. <button type="button" class="btn btn-sm btn-success" id="site-set-ssl-apply">申请 / 续签证书</button>
  656. </div>
  657. </div>
  658. <div class="site-settings-panel" data-site-panel="nginx">
  659. <div class="d-flex justify-content-between align-items-center mb-2 site-nginx-toolbar">
  660. <h6 class="mb-0">Nginx 站点配置</h6>
  661. <code class="small text-secondary text-truncate" id="site-set-conf-path"></code>
  662. </div>
  663. <div id="site-nginx-monaco" class="site-nginx-monaco" aria-label="Nginx 配置编辑器"></div>
  664. <div class="mt-2 d-flex gap-2 site-nginx-actions">
  665. <button type="button" class="btn btn-sm btn-primary" id="site-set-nginx-save">保存并重载</button>
  666. <button type="button" class="btn btn-sm btn-outline-secondary" id="site-set-nginx-reload">重新加载文件</button>
  667. </div>
  668. </div>
  669. <div class="site-settings-panel" data-site-panel="custom">
  670. <h6 class="mb-2">自定义配置</h6>
  671. <p class="small text-secondary mb-2">
  672. 手动添加的 Nginx 指令会写入站点 vhost,并在系统重新生成配置时保留。
  673. 保存后会自动检测并重载 Nginx。若在此填写
  674. <code>location /</code>(如 SPA 回退 / 伪静态),系统将<strong>不再生成</strong>默认的
  675. <code>location /</code>,避免 duplicate location。
  676. 若站点已配置「/」反向代理,则不要再写 <code>location /</code>。
  677. </p>
  678. <pre class="small bg-light border rounded p-2 mb-2 text-secondary"># SPA / 伪静态(会替换系统默认 location /)
  679. location / {
  680. try_files $uri $uri/ /index.html;
  681. }
  682. # 静态资源类型
  683. location ~ \.mjs$ {
  684. default_type application/javascript;
  685. }</pre>
  686. <textarea class="form-control font-monospace site-custom-nginx" id="site-set-custom"
  687. rows="14" spellcheck="false" placeholder="# 在此输入 server 块内的配置项"></textarea>
  688. <div class="mt-2 d-flex gap-2">
  689. <button type="button" class="btn btn-sm btn-primary" id="site-set-custom-save">保存并重载</button>
  690. <button type="button" class="btn btn-sm btn-outline-secondary" id="site-set-custom-reload">重新加载</button>
  691. </div>
  692. </div>
  693. <div class="site-settings-panel" data-site-panel="logs">
  694. <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2 site-log-toolbar">
  695. <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
  696. <div class="btn-group btn-group-sm" role="group">
  697. <button type="button" class="btn btn-outline-primary active" id="site-log-tab-access" data-log-kind="access">访问日志</button>
  698. <button type="button" class="btn btn-outline-primary" id="site-log-tab-error" data-log-kind="error">error.log</button>
  699. </div>
  700. <input type="search" class="form-control form-control-sm site-log-search" id="site-log-search"
  701. placeholder="搜索 IP / URI / 状态 / UA…" autocomplete="off" />
  702. <span class="badge text-bg-light border site-log-ip-filter" id="site-log-ip-filter" hidden>
  703. IP:<code id="site-log-ip-value"></code>
  704. <button type="button" class="btn-close btn-close-sm ms-1" id="site-log-ip-clear" aria-label="清除 IP 筛选"></button>
  705. </span>
  706. </div>
  707. <button type="button" class="btn btn-sm btn-outline-secondary" id="site-log-refresh">刷新</button>
  708. </div>
  709. <div class="site-log-view" id="site-log-view">加载中…</div>
  710. </div>
  711. <div class="site-settings-panel" data-site-panel="modules">
  712. <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
  713. <div>
  714. <h6 class="mb-1">Fastweb 扩展模块</h6>
  715. <div class="small text-secondary">列表来自 <code>download.fwlua.com/module/module.json</code>,通过 <code>fastweb instmod</code> 安装到当前站点。</div>
  716. </div>
  717. <button type="button" class="btn btn-sm btn-outline-secondary" id="site-modules-refresh">刷新</button>
  718. </div>
  719. <div class="btn-group btn-group-sm mb-3" role="group" id="site-modules-filter">
  720. <button type="button" class="btn btn-outline-primary active" data-mod-filter="all">全部</button>
  721. <button type="button" class="btn btn-outline-primary" data-mod-filter="fastweb">FastWeb</button>
  722. <button type="button" class="btn btn-outline-primary" data-mod-filter="lua">Lua</button>
  723. </div>
  724. <div id="site-modules-list" class="site-modules-list">加载中…</div>
  725. </div>
  726. </div>
  727. </div>
  728. </div>
  729. <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
  730. </div>
  731. </div>
  732. </div>
  733. <div class="modal fade" id="upload-modal" tabindex="-1" aria-hidden="true">
  734. <div class="modal-dialog modal-lg modal-dialog-scrollable">
  735. <div class="modal-content">
  736. <div class="modal-header py-2">
  737. <div>
  738. <h5 class="modal-title mb-0">上传到当前目录</h5>
  739. <code class="small text-secondary" id="upload-target-path">/</code>
  740. </div>
  741. <button type="button" class="btn-close" id="upload-modal-close" aria-label="关闭"></button>
  742. </div>
  743. <div class="modal-body">
  744. <div class="upload-dropzone" id="upload-dropzone" tabindex="0">
  745. <div class="upload-dropzone-inner">
  746. <i class="bi bi-cloud-arrow-up upload-drop-icon"></i>
  747. <p class="mb-1">拖拽文件或文件夹到此处</p>
  748. <p class="small text-secondary mb-3">支持大文件分片上传。也可使用下方按钮选择,选完后统一上传</p>
  749. <div class="btn-group btn-group-sm">
  750. <button type="button" class="btn btn-outline-primary" id="upload-pick-files">选择文件</button>
  751. <button type="button" class="btn btn-outline-primary" id="upload-pick-dir">选择目录</button>
  752. </div>
  753. </div>
  754. <input type="file" id="upload-input-files" hidden multiple />
  755. <input type="file" id="upload-input-dir" hidden webkitdirectory multiple />
  756. </div>
  757. <div class="d-flex justify-content-between align-items-center mt-3 mb-2">
  758. <span class="small text-secondary" id="upload-queue-summary">未选择文件</span>
  759. <button type="button" class="btn btn-sm btn-link text-decoration-none p-0" id="upload-clear-queue" hidden>清空列表</button>
  760. </div>
  761. <div class="upload-queue" id="upload-queue"></div>
  762. <div class="upload-progress mt-3" id="upload-progress" hidden>
  763. <div class="d-flex justify-content-between small mb-1">
  764. <span id="upload-progress-label">准备上传…</span>
  765. <span id="upload-progress-pct">0%</span>
  766. </div>
  767. <div class="progress" style="height: 8px;">
  768. <div class="progress-bar bg-teal" id="upload-progress-bar" role="progressbar" style="width: 0%"></div>
  769. </div>
  770. </div>
  771. </div>
  772. <div class="modal-footer py-2">
  773. <button type="button" class="btn btn-sm btn-outline-secondary" id="upload-modal-cancel">取消</button>
  774. <button type="button" class="btn btn-sm btn-primary" id="upload-start" disabled>开始上传</button>
  775. </div>
  776. <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
  777. </div>
  778. </div>
  779. </div>
  780. <div class="modal fade" id="site-analytics-modal" tabindex="-1" aria-hidden="true">
  781. <div class="modal-dialog modal-xl modal-dialog-scrollable">
  782. <div class="modal-content">
  783. <div class="modal-header py-2">
  784. <div class="d-flex align-items-center gap-2 flex-wrap">
  785. <h5 class="modal-title mb-0">日志分析 · <span id="analytics-site-name">—</span></h5>
  786. <select class="form-select form-select-sm" id="analytics-range" style="width:auto">
  787. <option value="1h">近 1 小时</option>
  788. <option value="6h">近 6 小时</option>
  789. <option value="24h" selected>近 24 小时</option>
  790. <option value="7d">近 7 天</option>
  791. <option value="30d">近 30 天</option>
  792. <option value="all">全部</option>
  793. </select>
  794. </div>
  795. <button type="button" class="btn-close" id="analytics-close" aria-label="关闭"></button>
  796. </div>
  797. <div class="modal-body" id="analytics-body">
  798. <div class="text-secondary small">加载中…</div>
  799. </div>
  800. <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
  801. </div>
  802. </div>
  803. </div>
  804. <div class="code-editor" id="code-editor" hidden>
  805. <div class="code-editor-backdrop" id="code-editor-backdrop"></div>
  806. <div class="code-editor-window">
  807. <header class="code-editor-bar">
  808. <div class="code-editor-meta">
  809. <strong id="code-editor-mode">编辑器</strong>
  810. <span id="code-editor-path"></span>
  811. </div>
  812. <button type="button" class="code-editor-x" id="code-editor-cancel" title="关闭" aria-label="关闭">×</button>
  813. </header>
  814. <div class="code-editor-main">
  815. <aside class="code-editor-tree">
  816. <div class="code-editor-tree-head">
  817. <button type="button" class="btn btn-sm btn-outline-light" id="editor-tree-up" title="上级">↑</button>
  818. <span class="code-editor-tree-path" id="editor-tree-path">/</span>
  819. <button type="button" class="btn btn-sm btn-outline-light" id="editor-tree-refresh" title="刷新">↻</button>
  820. </div>
  821. <div class="code-editor-tree-body" id="editor-tree"></div>
  822. </aside>
  823. <div class="code-editor-workspace">
  824. <div class="code-editor-tabs" id="editor-tabs"></div>
  825. <div id="monaco-host" class="monaco-host"></div>
  826. </div>
  827. </div>
  828. <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
  829. </div>
  830. </div>
  831. <script src="/assets/bootstrap/js/bootstrap.bundle.min.js"></script>
  832. <script src="/assets/monaco/vs/loader.js"></script>
  833. <script src="/assets/app.js"></script>
  834. </body>
  835. </html>