| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>NGS · 控制台</title>
- <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css" />
- <link rel="stylesheet" href="/assets/bootstrap-icons/bootstrap-icons.css" />
- <link rel="stylesheet" href="/assets/xterm/xterm.css" />
- <link rel="stylesheet" href="/assets/app.css" />
- <script src="/assets/chartjs/chart.umd.min.js"></script>
- <script src="/assets/xterm/xterm.js"></script>
- <script src="/assets/xterm/xterm-addon-fit.js"></script>
- </head>
- <body>
- <div class="app d-flex">
- <aside class="rail flex-shrink-0">
- <div class="brand d-flex align-items-center gap-2 px-3 py-2">
- <div class="brand-mark" id="brand-mark">N</div>
- <div class="brand-text flex-grow-1 min-w-0">
- <div class="fw-bold text-white lh-1 text-truncate" id="brand-name">NGS</div>
- <div class="small text-white-50">管理面板</div>
- </div>
- <button type="button" class="btn btn-sm btn-outline-light position-relative" id="task-badge" title="任务队列">
- <i class="bi bi-list-task"></i>
- <span class="badge text-bg-warning position-absolute top-0 start-100 translate-middle rounded-pill" id="task-badge-count">0</span>
- </button>
- </div>
- <nav class="nav flex-column px-2 gap-1 flex-grow-1">
- <button type="button" class="nav-link text-start nav-item is-active" data-view="console"><i class="bi bi-speedometer2 me-2"></i>控制台</button>
- <button type="button" class="nav-link text-start nav-item" data-view="sites"><i class="bi bi-globe2 me-2"></i>网站</button>
- <button type="button" class="nav-link text-start nav-item" data-view="files"><i class="bi bi-folder2-open me-2"></i>文件</button>
- <div class="nav-group" data-nav-group="database">
- <button type="button" class="nav-link text-start nav-item nav-group-toggle" data-nav-toggle="database" aria-expanded="false">
- <i class="bi bi-database me-2"></i><span>数据库</span>
- <i class="bi bi-chevron-down nav-group-caret ms-auto"></i>
- </button>
- <div class="nav-sub">
- <button type="button" class="nav-link text-start nav-item nav-sub-item" data-view="mysql">MySQL</button>
- <button type="button" class="nav-link text-start nav-item nav-sub-item" data-view="redis">Redis</button>
- </div>
- </div>
- <button type="button" class="nav-link text-start nav-item" data-view="processes"><i class="bi bi-cpu me-2"></i>进程</button>
- <button type="button" class="nav-link text-start nav-item" data-view="shell"><i class="bi bi-terminal me-2"></i>终端</button>
- <button type="button" class="nav-link text-start nav-item" data-view="software"><i class="bi bi-box-seam me-2"></i>软件</button>
- <button type="button" class="nav-link text-start nav-item" data-view="plans"><i class="bi bi-calendar2-check me-2"></i>计划</button>
- <button type="button" class="nav-link text-start nav-item" data-view="settings"><i class="bi bi-gear me-2"></i>设置</button>
- </nav>
- <div class="rail-foot px-2 pb-2">
- <div class="self-update px-1 pb-2" id="self-update-box">
- <div class="small text-white-50 text-truncate" id="self-version" title="当前版本">版本 —</div>
- <div class="d-flex gap-1 mt-1">
- <button type="button" class="btn btn-sm btn-outline-light flex-grow-1" id="btn-self-check">检查更新</button>
- <button type="button" class="btn btn-sm btn-warning d-none" id="btn-self-update">更新</button>
- </div>
- </div>
- <button type="button" class="nav-link text-start nav-item nav-logout w-100" id="btn-logout">
- <i class="bi bi-box-arrow-right me-2"></i>退出
- </button>
- <div class="live small d-flex align-items-center gap-2 px-2 pt-1" id="live-dot">
- <span class="pulse"></span>
- <span id="live-label">检测中</span>
- </div>
- </div>
- </aside>
- <main class="main flex-grow-1">
- <header class="top d-flex justify-content-between align-items-center mb-3">
- <div>
- <div class="text-secondary small" id="page-eyebrow">Console</div>
- <h1 class="h4 mb-0" id="page-title">控制台</h1>
- </div>
- <div class="text-secondary small" id="top-meta"></div>
- </header>
- <section class="view is-active" id="view-console" data-view-panel="console">
- <div class="card mb-3">
- <div class="card-header py-2 d-flex justify-content-between align-items-center gap-2">
- <strong>实时资源</strong>
- <div class="d-flex align-items-center gap-2">
- <span class="small text-secondary" id="overview-sites">—</span>
- <select class="form-select form-select-sm metrics-range" id="metrics-range" title="时间范围">
- <option value="1m">1分</option>
- <option value="10m" selected>10分</option>
- <option value="30m">30分</option>
- <option value="1h">1小时</option>
- <option value="6h">6小时</option>
- <option value="1d">1天</option>
- <option value="7d">7天</option>
- <option value="30d">30天</option>
- </select>
- </div>
- </div>
- <div class="card-body py-3 d-flex flex-column min-h-0">
- <div class="row g-3 monitor-grid flex-grow-1">
- <div class="col-md-6">
- <div class="monitor-card">
- <div class="d-flex justify-content-between align-items-center small mb-2">
- <span class="fw-semibold">CPU</span>
- <span class="text-secondary" id="cpu-badge">—</span>
- </div>
- <div class="monitor-chart"><canvas id="chart-cpu"></canvas></div>
- </div>
- </div>
- <div class="col-md-6">
- <div class="monitor-card">
- <div class="d-flex justify-content-between align-items-center small mb-2">
- <span class="fw-semibold">内存</span>
- <span class="text-secondary" id="mem-badge">—</span>
- </div>
- <div class="monitor-chart"><canvas id="chart-mem"></canvas></div>
- </div>
- </div>
- <div class="col-md-6">
- <div class="monitor-card">
- <div class="d-flex justify-content-between align-items-center small mb-2">
- <span class="fw-semibold">网络</span>
- <span class="text-secondary text-end monitor-badge-stack" id="net-badge">—</span>
- </div>
- <div class="monitor-chart"><canvas id="chart-net"></canvas></div>
- </div>
- </div>
- <div class="col-md-6">
- <div class="monitor-card">
- <div class="d-flex justify-content-between align-items-center small mb-2">
- <span class="fw-semibold">磁盘</span>
- <span class="text-secondary" id="disk-badge">—</span>
- </div>
- <div class="monitor-chart"><canvas id="chart-disk"></canvas></div>
- <div class="resource-list" id="disk-list"></div>
- </div>
- </div>
- <div class="col-md-6" id="gpu-wrap" hidden>
- <div class="monitor-card">
- <div class="d-flex justify-content-between align-items-center small mb-2">
- <span class="fw-semibold">GPU</span>
- <span class="text-secondary" id="gpu-name-badge">—</span>
- </div>
- <div class="gpu-split">
- <div class="gpu-pane">
- <div class="d-flex justify-content-between align-items-center small mb-1">
- <span class="fw-semibold">占用</span>
- <span class="text-secondary" id="gpu-badge">—</span>
- </div>
- <div class="monitor-chart"><canvas id="chart-gpu"></canvas></div>
- </div>
- <div class="gpu-pane">
- <div class="d-flex justify-content-between align-items-center small mb-1">
- <span class="fw-semibold">显存</span>
- <span class="text-secondary" id="gpu-mem-badge">—</span>
- </div>
- <div class="monitor-chart"><canvas id="chart-gpu-mem"></canvas></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </section>
- <section class="view" id="view-sites" data-view-panel="sites">
- <div class="card">
- <div class="card-header py-2 d-flex flex-wrap justify-content-between align-items-center gap-2">
- <div>
- <strong>网站</strong>
- <span class="small text-secondary ms-2">创建 / 启停 / 配置 / 删除</span>
- </div>
- <button type="button" class="btn btn-sm btn-primary" id="btn-site-create">新建网站</button>
- </div>
- <div class="px-3 pt-2" id="sites-group-bar"></div>
- <div class="table-responsive">
- <table class="table table-sm table-hover align-middle mb-0" id="sites-table">
- <thead>
- <tr>
- <th style="width:4.5rem">排序</th>
- <th>名称</th>
- <th>分组</th>
- <th>类型</th>
- <th>域名</th>
- <th>目录</th>
- <th>证书到期</th>
- <th>日志</th>
- <th>状态</th>
- <th class="text-end">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- </section>
- <section class="view" id="view-files" data-view-panel="files">
- <div class="card">
- <div class="card-header py-2">
- <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
- <div>
- <strong>文件</strong>
- </div>
- <div class="d-flex flex-wrap align-items-center gap-2">
- <div class="btn-group btn-group-sm" id="files-bulk-actions" hidden>
- <span class="btn btn-outline-secondary disabled" id="files-selected-count">已选 0</span>
- <button type="button" class="btn btn-outline-danger" id="btn-files-delete-selected">删除选中</button>
- <button type="button" class="btn btn-outline-secondary" id="btn-files-clear-selection">取消选择</button>
- </div>
- <div class="btn-group btn-group-sm">
- <button type="button" class="btn btn-outline-secondary" id="btn-files-up">上级</button>
- <button type="button" class="btn btn-outline-secondary" id="btn-files-mkdir">新建目录</button>
- <button type="button" class="btn btn-outline-secondary" id="btn-files-newfile">新建文件</button>
- <button type="button" class="btn btn-primary" id="btn-files-upload">上传</button>
- </div>
- </div>
- </div>
- <div class="crumbs small mt-2" id="files-crumbs"></div>
- </div>
- <div class="table-responsive">
- <table class="table table-sm table-hover align-middle mb-0" id="files-table">
- <thead>
- <tr>
- <th class="files-check-col">
- <input class="form-check-input" type="checkbox" id="files-select-all" title="全选" aria-label="全选" />
- </th>
- <th>名称</th>
- <th>类型</th>
- <th>大小</th>
- <th>修改时间</th>
- <th class="text-end">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- </section>
- <section class="view" id="view-mysql" data-view-panel="mysql">
- <div class="card">
- <div class="card-header py-2 d-flex justify-content-between align-items-center">
- <div>
- <strong>MySQL</strong>
- <span class="small text-secondary ms-2">库 / 账号 / 权限</span>
- </div>
- <div class="btn-group btn-group-sm">
- <button type="button" class="btn btn-outline-secondary" id="btn-mysql-password">改 root 密码</button>
- <button type="button" class="btn btn-primary" id="btn-db-create">新建数据库</button>
- </div>
- </div>
- <div class="table-responsive">
- <table class="table table-sm table-hover align-middle mb-0" id="db-table">
- <thead>
- <tr>
- <th>库名</th>
- <th>用户</th>
- <th>密码</th>
- <th>权限</th>
- <th>Host</th>
- <th class="text-end">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- <div class="card mt-3">
- <div class="card-header py-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
- <div>
- <strong>执行 SQL</strong>
- <span class="small text-secondary ms-2">以 root 运行,最多返回 500 行</span>
- </div>
- <div class="d-flex align-items-center gap-2 flex-wrap">
- <select class="form-select form-select-sm" id="sql-db" style="width:auto;min-width:10rem">
- <option value="">(不选库)</option>
- </select>
- <button type="button" class="btn btn-sm btn-primary" id="btn-sql-exec">执行</button>
- </div>
- </div>
- <div class="card-body py-2">
- <textarea class="form-control sql-editor" id="sql-input" rows="6" spellcheck="false"
- placeholder="SHOW DATABASES; SELECT * FROM table_name LIMIT 100;"></textarea>
- <div class="form-text">Ctrl+Enter 执行。DROP / DELETE 等语句请谨慎使用。</div>
- <div id="sql-meta" class="small text-secondary mt-2"></div>
- <div class="table-responsive mt-2 sql-result-wrap">
- <table class="table table-sm table-hover align-middle mb-0" id="sql-result">
- <thead></thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- </div>
- </section>
- <section class="view" id="view-redis" data-view-panel="redis">
- <div class="card">
- <div class="card-header py-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
- <div>
- <strong>Redis</strong>
- <span class="small text-secondary ms-2" id="redis-status-summary">状态</span>
- </div>
- <div class="btn-group btn-group-sm" id="redis-actions"></div>
- </div>
- <div class="card-body py-3">
- <div class="row g-3" id="redis-info-grid">
- <div class="col-12 text-secondary text-center py-3">加载中…</div>
- </div>
- </div>
- </div>
- <div class="card mt-3">
- <div class="card-header py-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
- <div>
- <strong>执行命令</strong>
- <span class="small text-secondary ms-2">经 redis-cli 执行,最多返回 64KB</span>
- </div>
- <button type="button" class="btn btn-sm btn-primary" id="btn-redis-exec">执行</button>
- </div>
- <div class="card-body py-2">
- <textarea class="form-control sql-editor" id="redis-cmd-input" rows="4" spellcheck="false"
- placeholder="INFO GET key SET key value KEYS *"></textarea>
- <div class="form-text">Ctrl+Enter 执行。FLUSHALL / SHUTDOWN 等请谨慎使用。</div>
- <div id="redis-cmd-meta" class="small text-secondary mt-2"></div>
- <pre class="redis-cmd-output mt-2 mb-0" id="redis-cmd-output" hidden></pre>
- </div>
- </div>
- </section>
- <section class="view" id="view-processes" data-view-panel="processes">
- <div class="card">
- <div class="card-header py-2">
- <div class="d-flex justify-content-between align-items-center gap-2 proc-toolbar">
- <div class="proc-toolbar-title text-nowrap">
- <strong>进程</strong>
- <span class="small text-secondary ms-2" id="proc-count">—</span>
- </div>
- <div class="d-flex flex-nowrap align-items-center gap-2 proc-toolbar-actions">
- <input class="form-control form-control-sm" id="proc-search" type="search" placeholder="搜索 PID / 用户 / 名称 / 目录 / 命令" />
- <div class="form-check form-switch mb-0 text-nowrap">
- <input class="form-check-input" type="checkbox" id="proc-auto" />
- <label class="form-check-label small" for="proc-auto">自动刷新</label>
- </div>
- <button type="button" class="btn btn-sm btn-outline-secondary text-nowrap" id="btn-proc-refresh">刷新</button>
- </div>
- </div>
- </div>
- <div class="table-responsive proc-table-wrap">
- <table class="table table-sm table-hover align-middle mb-0" id="proc-table">
- <thead>
- <tr>
- <th data-proc-sort="pid" class="proc-sort">PID</th>
- <th data-proc-sort="user" class="proc-sort">用户</th>
- <th data-proc-sort="cpu_percent" class="proc-sort is-active">CPU%</th>
- <th data-proc-sort="mem_percent" class="proc-sort">内存%</th>
- <th data-proc-sort="rss" class="proc-sort">RSS</th>
- <th>状态</th>
- <th data-proc-sort="name" class="proc-sort">名称</th>
- <th data-proc-sort="cwd" class="proc-sort">运行目录</th>
- <th>命令行</th>
- <th class="text-end">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- </section>
- <section class="view" id="view-shell" data-view-panel="shell">
- <div class="card shell-card">
- <div class="card-header py-2">
- <div class="d-flex justify-content-between align-items-center gap-2 flex-wrap">
- <div class="text-nowrap">
- <strong>终端</strong>
- <span class="small text-secondary ms-2" id="shell-status">未连接</span>
- </div>
- <div class="d-flex flex-nowrap align-items-center gap-2">
- <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-shell-reconnect">重新连接</button>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-shell-clear">清屏</button>
- </div>
- </div>
- </div>
- <div class="card-body p-0 shell-body">
- <div id="shell-terminal" class="shell-terminal"></div>
- </div>
- </div>
- </section>
- <section class="view" id="view-software" data-view-panel="software">
- <div class="row g-2" id="software-grid"></div>
- </section>
- <section class="view" id="view-plans" data-view-panel="plans">
- <div class="card">
- <div class="card-header py-2">
- <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
- <div class="btn-group btn-group-sm" role="group" id="plans-tabs">
- <button type="button" class="btn btn-primary" data-plans-tab="cron">定时任务</button>
- <button type="button" class="btn btn-outline-primary" data-plans-tab="more" disabled title="即将推出">更多</button>
- </div>
- <div class="d-flex gap-2" id="plans-cron-actions">
- <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-schedule-refresh">刷新</button>
- <button type="button" class="btn btn-sm btn-primary" id="btn-schedule-add">新建任务</button>
- </div>
- </div>
- </div>
- <div class="card-body p-0">
- <div class="plans-pane is-active" data-plans-pane="cron">
- <div class="row g-0 plans-cron-layout">
- <div class="col-lg-7 border-end">
- <div class="table-responsive">
- <table class="table table-sm table-hover align-middle mb-0" id="schedule-table">
- <thead>
- <tr>
- <th>名称</th>
- <th>间隔</th>
- <th>URL</th>
- <th>状态</th>
- <th class="text-end">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- <div class="col-lg-5">
- <div class="p-2 border-bottom d-flex flex-wrap justify-content-between align-items-center gap-2">
- <strong class="small mb-0">执行日志</strong>
- <div class="d-flex gap-2">
- <button type="button" class="btn btn-sm btn-outline-secondary" id="btn-schedule-log-refresh">刷新日志</button>
- <button type="button" class="btn btn-sm btn-outline-danger" id="btn-schedule-log-clear">清空</button>
- </div>
- </div>
- <div class="schedule-log-meta small text-secondary px-2 py-1" id="schedule-log-meta">选择左侧任务查看日志</div>
- <pre class="schedule-log-view mb-0" id="schedule-log-view"></pre>
- </div>
- </div>
- </div>
- <div class="plans-pane p-4 text-secondary" data-plans-pane="more" hidden>
- 更多计划能力即将推出。
- </div>
- </div>
- </div>
- </section>
- <section class="view" id="view-settings" data-view-panel="settings">
- <div class="card">
- <div class="card-header py-2">
- <strong>面板设置</strong>
- <span class="small text-secondary ms-2">名称会显示在侧栏、浏览器标题和登录页</span>
- </div>
- <div class="card-body">
- <form id="settings-form" class="col-lg-6 col-xl-5">
- <div class="mb-3">
- <label class="form-label" for="setting-panel-name">网站名称</label>
- <input class="form-control" id="setting-panel-name" name="panel_name" maxlength="64"
- required placeholder="NGS" autocomplete="off" />
- </div>
- <button type="submit" class="btn btn-primary btn-sm" id="btn-settings-save">保存</button>
- </form>
- </div>
- </div>
- </section>
- </main>
- </div>
- <div class="toast-host position-fixed bottom-0 end-0 p-3" id="toast-host" aria-live="polite"></div>
- <div class="task-panel" id="task-panel" hidden>
- <div class="task-panel-backdrop" id="task-panel-backdrop"></div>
- <div class="task-panel-dialog" role="dialog" aria-modal="true" aria-labelledby="task-panel-title">
- <header class="task-panel-head d-flex justify-content-between align-items-start gap-2">
- <div>
- <h2 class="h5 mb-1" id="task-panel-title">任务队列</h2>
- <p class="small text-secondary mb-0">默认显示进行中任务,可切换查看历史。</p>
- </div>
- <button type="button" class="btn btn-sm btn-outline-light" id="task-panel-close" aria-label="关闭">×</button>
- </header>
- <div class="task-panel-body">
- <div class="task-side">
- <div class="task-side-tabs btn-group btn-group-sm w-100 px-2 pt-2">
- <button type="button" class="btn btn-warning" id="task-tab-active" data-task-tab="active">进行中</button>
- <button type="button" class="btn btn-outline-light" id="task-tab-history" data-task-tab="history">历史任务</button>
- </div>
- <div class="task-list" id="task-list"></div>
- <div class="task-side-foot px-2 pb-2">
- <button type="button" class="btn btn-sm btn-outline-light w-100 d-none" id="task-clear-finished">清理历史</button>
- </div>
- </div>
- <div class="task-log-wrap">
- <div class="task-log-head d-flex justify-content-between align-items-center">
- <strong id="task-log-title">日志</strong>
- <span class="badge text-bg-secondary" id="task-log-status">—</span>
- </div>
- <pre class="task-log" id="task-log"></pre>
- </div>
- </div>
- <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
- </div>
- </div>
- <div class="modal fade" id="modal" tabindex="-1" aria-hidden="true">
- <div class="modal-dialog modal-dialog-scrollable">
- <form class="modal-content" id="modal-form">
- <div class="modal-header py-2">
- <h5 class="modal-title" id="modal-title">操作</h5>
- <button type="button" class="btn-close" id="modal-close" aria-label="关闭"></button>
- </div>
- <div class="modal-body" id="modal-body"></div>
- <div class="modal-footer py-2">
- <button type="button" class="btn btn-sm btn-outline-secondary" id="modal-cancel">取消</button>
- <button type="submit" class="btn btn-sm btn-primary" id="modal-ok">确定</button>
- </div>
- <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
- </form>
- </div>
- </div>
- <div class="modal fade" id="redis-config-modal" tabindex="-1" aria-hidden="true">
- <div class="modal-dialog modal-lg modal-dialog-scrollable">
- <div class="modal-content">
- <div class="modal-header py-2">
- <h5 class="modal-title mb-0">Redis 配置</h5>
- <button type="button" class="btn-close" id="redis-config-close" aria-label="关闭"></button>
- </div>
- <div class="modal-body">
- <div class="btn-group btn-group-sm mb-3" role="group">
- <button type="button" class="btn btn-outline-primary active" id="redis-tab-quick" data-redis-tab="quick">基础配置</button>
- <button type="button" class="btn btn-outline-primary" id="redis-tab-file" data-redis-tab="file">配置文件</button>
- </div>
- <div id="redis-pane-quick">
- <p class="small text-secondary mb-3">快捷修改常用项;保存后若 Redis 正在运行会自动重启生效。</p>
- <div class="mb-2">
- <label class="form-label">监听地址 <span class="text-secondary fw-normal">bind</span></label>
- <input class="form-control form-control-sm" id="redis-cfg-bind" placeholder="127.0.0.1" autocomplete="off" />
- <div class="form-text">多个地址用空格分隔,例如 <code>127.0.0.1 0.0.0.0</code></div>
- </div>
- <div class="mb-2">
- <label class="form-label">端口 <span class="text-secondary fw-normal">port</span></label>
- <input class="form-control form-control-sm" id="redis-cfg-port" type="number" min="1" max="65535" />
- </div>
- <div class="mb-3">
- <label class="form-label">密码 <span class="text-secondary fw-normal">requirepass</span></label>
- <input class="form-control form-control-sm" id="redis-cfg-pass" type="password" placeholder="留空表示不设密码" autocomplete="new-password" />
- </div>
- <button type="button" class="btn btn-sm btn-primary" id="redis-cfg-save">保存并应用</button>
- </div>
- <div id="redis-pane-file" hidden>
- <div class="d-flex justify-content-between align-items-center mb-2 gap-2">
- <code class="small text-secondary text-truncate" id="redis-cfg-path"></code>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="redis-cfg-reload">重新加载</button>
- </div>
- <div id="redis-cfg-monaco" class="soft-cfg-monaco" aria-label="Redis 配置编辑器"></div>
- <div class="mt-2 d-flex gap-2">
- <button type="button" class="btn btn-sm btn-primary" id="redis-cfg-file-save">保存文件</button>
- <span class="small text-secondary align-self-center">仅写入文件;如需生效请自行重启 Redis。</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="nginx-config-modal" tabindex="-1" aria-hidden="true">
- <div class="modal-dialog modal-lg modal-dialog-scrollable">
- <div class="modal-content">
- <div class="modal-header py-2">
- <h5 class="modal-title mb-0">Nginx 配置</h5>
- <button type="button" class="btn-close" id="nginx-config-close" aria-label="关闭"></button>
- </div>
- <div class="modal-body">
- <div class="btn-group btn-group-sm mb-3" role="group">
- <button type="button" class="btn btn-outline-primary active" id="nginx-tab-quick" data-nginx-tab="quick">基础配置</button>
- <button type="button" class="btn btn-outline-primary" id="nginx-tab-file" data-nginx-tab="file">配置文件</button>
- </div>
- <div id="nginx-pane-quick">
- <p class="small text-secondary mb-3">快捷修改常用项;保存后会检测配置,若 Nginx 正在运行则自动重载生效。</p>
- <div class="mb-2">
- <label class="form-label">工作进程数 <span class="text-secondary fw-normal">worker_processes</span></label>
- <input class="form-control form-control-sm" id="nginx-cfg-workers" placeholder="auto" autocomplete="off" />
- <div class="form-text">填 <code>auto</code> 或正整数</div>
- </div>
- <div class="mb-2">
- <label class="form-label">最大连接数 <span class="text-secondary fw-normal">worker_connections</span></label>
- <input class="form-control form-control-sm" id="nginx-cfg-connections" type="number" min="1" max="1048576" />
- </div>
- <div class="mb-2">
- <label class="form-label">保持连接超时 <span class="text-secondary fw-normal">keepalive_timeout</span></label>
- <input class="form-control form-control-sm" id="nginx-cfg-keepalive" placeholder="65" autocomplete="off" />
- </div>
- <div class="mb-2">
- <label class="form-label">上传大小限制 <span class="text-secondary fw-normal">client_max_body_size</span></label>
- <input class="form-control form-control-sm" id="nginx-cfg-body" placeholder="50m" autocomplete="off" />
- <div class="form-text">例如 <code>1m</code>、<code>50m</code>、<code>1g</code></div>
- </div>
- <div class="mb-3 form-check">
- <input class="form-check-input" type="checkbox" id="nginx-cfg-gzip" />
- <label class="form-check-label" for="nginx-cfg-gzip">启用压缩 <span class="text-secondary fw-normal">gzip</span></label>
- </div>
- <button type="button" class="btn btn-sm btn-primary" id="nginx-cfg-save">保存并应用</button>
- </div>
- <div id="nginx-pane-file" hidden>
- <div class="d-flex justify-content-between align-items-center mb-2 gap-2">
- <code class="small text-secondary text-truncate" id="nginx-cfg-path"></code>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="nginx-cfg-reload">重新加载</button>
- </div>
- <div id="nginx-cfg-monaco" class="soft-cfg-monaco" aria-label="Nginx 配置编辑器"></div>
- <div class="mt-2 d-flex gap-2">
- <button type="button" class="btn btn-sm btn-primary" id="nginx-cfg-file-save">保存文件</button>
- <span class="small text-secondary align-self-center">仅写入文件;如需生效请自行重载 Nginx。</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="site-settings-modal" tabindex="-1" aria-hidden="true">
- <div class="modal-dialog modal-xl modal-dialog-scrollable">
- <div class="modal-content">
- <div class="modal-header py-2">
- <div>
- <h5 class="modal-title mb-0">网站配置 · <span id="site-settings-name"></span></h5>
- <div class="small text-secondary" id="site-settings-sub"></div>
- </div>
- <button type="button" class="btn-close" id="site-settings-close" aria-label="关闭"></button>
- </div>
- <div class="modal-body site-settings-body p-0">
- <div class="site-settings-layout">
- <aside class="site-settings-nav">
- <button type="button" class="site-settings-tab is-active" data-site-tab="domain">域名/分组</button>
- <button type="button" class="site-settings-tab d-none" data-site-tab="proxy" id="site-tab-proxy">反向代理</button>
- <button type="button" class="site-settings-tab" data-site-tab="cert">证书</button>
- <button type="button" class="site-settings-tab" data-site-tab="nginx">配置 (NGINX)</button>
- <button type="button" class="site-settings-tab" data-site-tab="custom">自定义配置</button>
- <button type="button" class="site-settings-tab" data-site-tab="logs">日志 (NGINX)</button>
- <button type="button" class="site-settings-tab d-none" data-site-tab="modules" id="site-tab-modules">模块</button>
- </aside>
- <div class="site-settings-panels">
- <div class="site-settings-panel is-active" data-site-panel="domain">
- <h6 class="mb-3">域名 / 分组</h6>
- <div class="mb-3">
- <label class="form-label">分组</label>
- <input class="form-control form-control-sm" id="site-set-group" list="site-group-datalist" placeholder="留空为未分组" autocomplete="off" />
- <datalist id="site-group-datalist"></datalist>
- <div class="form-text">同名分组合并显示;可在网站列表中筛选与排序。</div>
- </div>
- <div class="mb-3">
- <label class="form-label">域名(支持多个,每行一个)</label>
- <textarea class="form-control form-control-sm" id="site-set-domain" rows="4" placeholder="example.com www.example.com:8080"></textarea>
- <div class="form-text">可写 <code>域名</code> 或 <code>域名:端口</code>;不写端口默认 <code>80</code>。第一行作为主域名。</div>
- </div>
- <button type="button" class="btn btn-sm btn-primary" id="site-set-domain-save">保存并重载</button>
- </div>
- <div class="site-settings-panel" data-site-panel="proxy">
- <div class="d-flex justify-content-between align-items-center mb-3">
- <h6 class="mb-0">反向代理</h6>
- <button type="button" class="btn btn-sm btn-primary" id="site-proxy-add">添加 URL 代理</button>
- </div>
- <div class="table-responsive">
- <table class="table table-sm table-hover align-middle mb-0" id="site-proxy-table">
- <thead>
- <tr>
- <th>代理目录</th>
- <th>目标</th>
- <th>发送域名</th>
- <th>备注</th>
- <th class="text-end">操作</th>
- </tr>
- </thead>
- <tbody id="site-proxy-tbody">
- <tr><td colspan="5" class="text-secondary text-center py-3">加载中…</td></tr>
- </tbody>
- </table>
- </div>
- </div>
- <div class="site-settings-panel" data-site-panel="cert">
- <div class="btn-group btn-group-sm mb-3" role="group" aria-label="证书子页">
- <button type="button" class="btn btn-outline-primary active" id="site-cert-tab-pem" data-cert-tab="pem">证书内容</button>
- <button type="button" class="btn btn-outline-primary" id="site-cert-tab-apply" data-cert-tab="apply">申请 SSL</button>
- </div>
- <div id="site-cert-pane-pem">
- <div class="form-check form-switch mb-3">
- <input class="form-check-input" type="checkbox" id="site-set-ssl-enable" />
- <label class="form-check-label" for="site-set-ssl-enable">启用 HTTPS</label>
- </div>
- <div class="small mb-3" id="site-set-ssl-expire">证书有效期:—</div>
- <div class="mb-3">
- <label class="form-label">HTTPS 端口</label>
- <input class="form-control form-control-sm" id="site-set-ssl-port" type="number" min="1" max="65535" value="443" />
- </div>
- <div class="mb-3">
- <label class="form-label">证书 PEM <span class="text-secondary fw-normal">(fullchain)</span></label>
- <textarea class="form-control font-monospace site-ssl-pem" id="site-set-ssl-pem" rows="8" spellcheck="false" placeholder="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----"></textarea>
- </div>
- <div class="mb-3">
- <label class="form-label">私钥 KEY</label>
- <textarea class="form-control font-monospace site-ssl-pem" id="site-set-ssl-key-pem" rows="8" spellcheck="false" placeholder="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----"></textarea>
- </div>
- <p class="small text-secondary mb-3">保存后写入 <code>/ngs/conf/ssl/<域名>/fullchain.pem</code> 与 <code>privkey.pem</code> 并重载 Nginx。</p>
- <button type="button" class="btn btn-sm btn-primary" id="site-set-cert-save">保存并重载</button>
- </div>
- <div id="site-cert-pane-apply" hidden>
- <div class="fw-semibold mb-2">Let's Encrypt 免费证书</div>
- <p class="small text-secondary mb-2">勾选域名后合并签发到一张证书,成功后自动写入 <code>/ngs/conf/ssl/<主域名>/</code> 并启用 HTTPS。域名需已解析到本机,且公网可访问 80 端口。</p>
- <div class="mb-2">
- <div class="d-flex justify-content-between align-items-center mb-1">
- <label class="form-label mb-0">申请域名</label>
- <div class="btn-group btn-group-sm">
- <button type="button" class="btn btn-outline-secondary" id="site-ssl-domains-all">全选</button>
- <button type="button" class="btn btn-outline-secondary" id="site-ssl-domains-none">清空</button>
- </div>
- </div>
- <div class="site-ssl-domain-list border rounded bg-white p-2" id="site-ssl-domain-list"></div>
- </div>
- <button type="button" class="btn btn-sm btn-success" id="site-set-ssl-apply">申请 / 续签证书</button>
- </div>
- </div>
- <div class="site-settings-panel" data-site-panel="nginx">
- <div class="d-flex justify-content-between align-items-center mb-2 site-nginx-toolbar">
- <h6 class="mb-0">Nginx 站点配置</h6>
- <code class="small text-secondary text-truncate" id="site-set-conf-path"></code>
- </div>
- <div id="site-nginx-monaco" class="site-nginx-monaco" aria-label="Nginx 配置编辑器"></div>
- <div class="mt-2 d-flex gap-2 site-nginx-actions">
- <button type="button" class="btn btn-sm btn-primary" id="site-set-nginx-save">保存并重载</button>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="site-set-nginx-reload">重新加载文件</button>
- </div>
- </div>
- <div class="site-settings-panel" data-site-panel="custom">
- <h6 class="mb-2">自定义配置</h6>
- <p class="small text-secondary mb-2">
- 手动添加的 Nginx 指令会写入站点 vhost,并在系统重新生成配置时保留。
- 保存后会自动检测并重载 Nginx。若在此填写
- <code>location /</code>(如 SPA 回退 / 伪静态),系统将<strong>不再生成</strong>默认的
- <code>location /</code>,避免 duplicate location。
- 若站点已配置「/」反向代理,则不要再写 <code>location /</code>。
- </p>
- <pre class="small bg-light border rounded p-2 mb-2 text-secondary"># SPA / 伪静态(会替换系统默认 location /)
- location / {
- try_files $uri $uri/ /index.html;
- }
- # 静态资源类型
- location ~ \.mjs$ {
- default_type application/javascript;
- }</pre>
- <textarea class="form-control font-monospace site-custom-nginx" id="site-set-custom"
- rows="14" spellcheck="false" placeholder="# 在此输入 server 块内的配置项"></textarea>
- <div class="mt-2 d-flex gap-2">
- <button type="button" class="btn btn-sm btn-primary" id="site-set-custom-save">保存并重载</button>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="site-set-custom-reload">重新加载</button>
- </div>
- </div>
- <div class="site-settings-panel" data-site-panel="logs">
- <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2 site-log-toolbar">
- <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
- <div class="btn-group btn-group-sm" role="group">
- <button type="button" class="btn btn-outline-primary active" id="site-log-tab-access" data-log-kind="access">访问日志</button>
- <button type="button" class="btn btn-outline-primary" id="site-log-tab-error" data-log-kind="error">error.log</button>
- </div>
- <input type="search" class="form-control form-control-sm site-log-search" id="site-log-search"
- placeholder="搜索 IP / URI / 状态 / UA…" autocomplete="off" />
- <span class="badge text-bg-light border site-log-ip-filter" id="site-log-ip-filter" hidden>
- IP:<code id="site-log-ip-value"></code>
- <button type="button" class="btn-close btn-close-sm ms-1" id="site-log-ip-clear" aria-label="清除 IP 筛选"></button>
- </span>
- </div>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="site-log-refresh">刷新</button>
- </div>
- <div class="site-log-view" id="site-log-view">加载中…</div>
- </div>
- <div class="site-settings-panel" data-site-panel="modules">
- <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
- <div>
- <h6 class="mb-1">Fastweb 扩展模块</h6>
- <div class="small text-secondary">列表来自 <code>download.fwlua.com/module/module.json</code>,通过 <code>fastweb instmod</code> 安装到当前站点。</div>
- </div>
- <button type="button" class="btn btn-sm btn-outline-secondary" id="site-modules-refresh">刷新</button>
- </div>
- <div class="btn-group btn-group-sm mb-3" role="group" id="site-modules-filter">
- <button type="button" class="btn btn-outline-primary active" data-mod-filter="all">全部</button>
- <button type="button" class="btn btn-outline-primary" data-mod-filter="fastweb">FastWeb</button>
- <button type="button" class="btn btn-outline-primary" data-mod-filter="lua">Lua</button>
- </div>
- <div id="site-modules-list" class="site-modules-list">加载中…</div>
- </div>
- </div>
- </div>
- </div>
- <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="upload-modal" tabindex="-1" aria-hidden="true">
- <div class="modal-dialog modal-lg modal-dialog-scrollable">
- <div class="modal-content">
- <div class="modal-header py-2">
- <div>
- <h5 class="modal-title mb-0">上传到当前目录</h5>
- <code class="small text-secondary" id="upload-target-path">/</code>
- </div>
- <button type="button" class="btn-close" id="upload-modal-close" aria-label="关闭"></button>
- </div>
- <div class="modal-body">
- <div class="upload-dropzone" id="upload-dropzone" tabindex="0">
- <div class="upload-dropzone-inner">
- <i class="bi bi-cloud-arrow-up upload-drop-icon"></i>
- <p class="mb-1">拖拽文件或文件夹到此处</p>
- <p class="small text-secondary mb-3">支持大文件分片上传。也可使用下方按钮选择,选完后统一上传</p>
- <div class="btn-group btn-group-sm">
- <button type="button" class="btn btn-outline-primary" id="upload-pick-files">选择文件</button>
- <button type="button" class="btn btn-outline-primary" id="upload-pick-dir">选择目录</button>
- </div>
- </div>
- <input type="file" id="upload-input-files" hidden multiple />
- <input type="file" id="upload-input-dir" hidden webkitdirectory multiple />
- </div>
- <div class="d-flex justify-content-between align-items-center mt-3 mb-2">
- <span class="small text-secondary" id="upload-queue-summary">未选择文件</span>
- <button type="button" class="btn btn-sm btn-link text-decoration-none p-0" id="upload-clear-queue" hidden>清空列表</button>
- </div>
- <div class="upload-queue" id="upload-queue"></div>
- <div class="upload-progress mt-3" id="upload-progress" hidden>
- <div class="d-flex justify-content-between small mb-1">
- <span id="upload-progress-label">准备上传…</span>
- <span id="upload-progress-pct">0%</span>
- </div>
- <div class="progress" style="height: 8px;">
- <div class="progress-bar bg-teal" id="upload-progress-bar" role="progressbar" style="width: 0%"></div>
- </div>
- </div>
- </div>
- <div class="modal-footer py-2">
- <button type="button" class="btn btn-sm btn-outline-secondary" id="upload-modal-cancel">取消</button>
- <button type="button" class="btn btn-sm btn-primary" id="upload-start" disabled>开始上传</button>
- </div>
- <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="site-analytics-modal" tabindex="-1" aria-hidden="true">
- <div class="modal-dialog modal-xl modal-dialog-scrollable">
- <div class="modal-content">
- <div class="modal-header py-2">
- <div class="d-flex align-items-center gap-2 flex-wrap">
- <h5 class="modal-title mb-0">日志分析 · <span id="analytics-site-name">—</span></h5>
- <select class="form-select form-select-sm" id="analytics-range" style="width:auto">
- <option value="1h">近 1 小时</option>
- <option value="6h">近 6 小时</option>
- <option value="24h" selected>近 24 小时</option>
- <option value="7d">近 7 天</option>
- <option value="30d">近 30 天</option>
- <option value="all">全部</option>
- </select>
- </div>
- <button type="button" class="btn-close" id="analytics-close" aria-label="关闭"></button>
- </div>
- <div class="modal-body" id="analytics-body">
- <div class="text-secondary small">加载中…</div>
- </div>
- <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
- </div>
- </div>
- </div>
- <div class="code-editor" id="code-editor" hidden>
- <div class="code-editor-backdrop" id="code-editor-backdrop"></div>
- <div class="code-editor-window">
- <header class="code-editor-bar">
- <div class="code-editor-meta">
- <strong id="code-editor-mode">编辑器</strong>
- <span id="code-editor-path"></span>
- </div>
- <button type="button" class="code-editor-x" id="code-editor-cancel" title="关闭" aria-label="关闭">×</button>
- </header>
- <div class="code-editor-main">
- <aside class="code-editor-tree">
- <div class="code-editor-tree-head">
- <button type="button" class="btn btn-sm btn-outline-light" id="editor-tree-up" title="上级">↑</button>
- <span class="code-editor-tree-path" id="editor-tree-path">/</span>
- <button type="button" class="btn btn-sm btn-outline-light" id="editor-tree-refresh" title="刷新">↻</button>
- </div>
- <div class="code-editor-tree-body" id="editor-tree"></div>
- </aside>
- <div class="code-editor-workspace">
- <div class="code-editor-tabs" id="editor-tabs"></div>
- <div id="monaco-host" class="monaco-host"></div>
- </div>
- </div>
- <div class="dialog-resize" data-dialog-resize title="拖动调整大小" aria-hidden="true"></div>
- </div>
- </div>
- <script src="/assets/bootstrap/js/bootstrap.bundle.min.js"></script>
- <script src="/assets/monaco/vs/loader.js"></script>
- <script src="/assets/app.js"></script>
- </body>
- </html>
|