better-staridc-MNBT
1<?php mnbt_admin_include('head'); ?>
2<?php
3require_once ROOT . 'MPHX/node.function.php';
4mnbt_node_ensure_tables($DB);
5$bt_list=$DB->get_all_prepare("SELECT id,btdh,btip FROM MN_bt order by id desc limit 500");
6if(!is_array($bt_list)) $bt_list=[];
7$default_url=mnbt_node_default_base_url();
8
9// 获取当前标签页
10$default_tab = isset($_GET['tab']) ? $_GET['tab'] : 'overview';
11?>
12
13<script type="text/javascript" src="<?=mnbt_asset_url('js/jquery-confirm/jquery-confirm.min.js')?>"></script>
14<script type="text/javascript" src="<?=mnbt_asset_url('js/bootstrap-table/bootstrap-table.min.js')?>"></script>
15<script type="text/javascript" src="<?=mnbt_asset_url('js/bootstrap-table/locale/bootstrap-table-zh-CN.min.js')?>"></script>
16
17<style>
18.node-toolbar {
19 display: flex;
20 flex-wrap: wrap;
21 gap: 8px;
22 align-items: center;
23}
24.node-toolbar .form-control {
25 width: auto;
26 min-width: 160px;
27}
28.node-code-box {
29 font-family: Consolas, Monaco, monospace;
30 min-height: 260px;
31 resize: vertical;
32}
33.node-text-clip {
34 display: inline-block;
35 max-width: 360px;
36 overflow: hidden;
37 text-overflow: ellipsis;
38 vertical-align: bottom;
39 white-space: nowrap;
40}
41.node-mini-text {
42 max-width: 220px;
43}
44.node-section-title {
45 display: flex;
46 align-items: center;
47 justify-content: space-between;
48 gap: 10px;
49}
50.node-section-title small {
51 color: #6c757d;
52 font-weight: normal;
53}
54/* Tabs 样式优化 */
55.nav-tabs .nav-link {
56 border: none;
57 color: #6c757d;
58 padding: 12px 20px;
59 font-weight: 500;
60}
61.nav-tabs .nav-link.active {
62 color: #007bff;
63 border-bottom: 3px solid #007bff;
64 background: transparent;
65}
66.nav-tabs .nav-link:hover:not(.active) {
67 color: #007bff;
68}
69.tab-content {
70 padding-top: 20px;
71}
72/* 扫描表格优化 */
73.quick-action-btn {
74 padding: 4px 8px;
75 font-size: 11px;
76}
77@media (max-width: 767px) {
78 .node-toolbar .form-control,
79 .node-toolbar .btn {
80 width: 100%;
81 }
82 .node-text-clip {
83 max-width: 180px;
84 }
85}
86</style>
87
88<div class="container-fluid p-t-15">
89 <div class="card">
90 <header class="card-header">
91 <div class="node-section-title">
92 <div class="card-title">
93 <i class="mdi mdi-server-network"></i> 节点管理
94 </div>
95 <div id="nodeToolbar" class="node-toolbar">
96 <button type="button" class="btn btn-primary btn-sm" onclick="openAddNode()">
97 <i class="mdi mdi-plus"></i> 新增节点
98 </button>
99 <button type="button" class="btn btn-default btn-sm" onclick="refreshAllData()">
100 <i class="mdi mdi-refresh"></i> 刷新全部
101 </button>
102 <input type="text" id="nodeKeyword" class="form-control form-control-sm" placeholder="搜索节点名称/ID/IP">
103 <select id="nodeStatus" class="form-control form-control-sm">
104 <option value="">全部状态</option>
105 <option value="enabled">已启用</option>
106 <option value="disabled">已停用</option>
107 </select>
108 </div>
109 </div>
110 </header>
111
112 <!-- Tabs 导航 -->
113 <ul class="nav nav-tabs" id="nodeTabs">
114 <li class="nav-item">
115 <a class="nav-link <?= $default_tab == 'nodes' ? 'active' : '' ?>" data-toggle="tab" href="#tab-nodes">
116 <i class="mdi mdi-server"></i> 节点列表
117 </a>
118 </li>
119 <li class="nav-item">
120 <a class="nav-link <?= $default_tab == 'tasks' ? 'active' : '' ?>" data-toggle="tab" href="#tab-tasks">
121 <i class="mdi mdi-format-list-bulleted"></i> 任务列表
122 </a>
123 </li>
124 <li class="nav-item">
125 <a class="nav-link <?= $default_tab == 'scan' ? 'active' : '' ?>" data-toggle="tab" href="#tab-scan">
126 <i class="mdi mdi-shield-search"></i> 违禁词扫描
127 </a>
128 </li>
129 <li class="nav-item">
130 <a class="nav-link <?= $default_tab == 'config' ? 'active' : '' ?>" data-toggle="tab" href="#tab-config">
131 <i class="mdi mdi-cog"></i> 扫描配置
132 </a>
133 </li>
134 </ul>
135
136 <!-- Tabs 内容 -->
137 <div class="card-body">
138 <div class="tab-content">
139 <!-- 节点列表标签 -->
140 <div class="tab-pane fade <?= $default_tab == 'nodes' ? 'show active' : '' ?>" id="tab-nodes">
141 <div class="node-toolbar mb-3">
142 <button type="button" class="btn btn-primary btn-sm" onclick="batchTriggerScan()">
143 <i class="mdi mdi-play"></i> 批量扫描
144 </button>
145 <button type="button" class="btn btn-default btn-sm" onclick="refreshNodes()">
146 <i class="mdi mdi-refresh"></i> 刷新节点
147 </button>
148 <small class="text-muted">请先勾选节点列表左侧复选框,再点击批量扫描</small>
149 </div>
150 <table id="tb_nodes"></table>
151 </div>
152
153 <!-- 任务列表标签 -->
154 <div class="tab-pane fade <?= $default_tab == 'tasks' ? 'show active' : '' ?>" id="tab-tasks">
155 <div class="node-toolbar mb-3">
156 <select id="taskNodeFilter" class="form-control form-control-sm">
157 <option value="">全部节点</option>
158 </select>
159 <select id="taskStatusFilter" class="form-control form-control-sm">
160 <option value="">全部状态</option>
161 <option value="pending">等待中</option>
162 <option value="running">执行中</option>
163 <option value="success">成功</option>
164 <option value="failed">失败</option>
165 </select>
166 <select id="taskTypeFilter" class="form-control form-control-sm">
167 <option value="">全部类型</option>
168 <option value="ping">Ping</option>
169 <option value="forbidden_scan">违禁词扫描</option>
170 </select>
171 </div>
172 <table id="tb_tasks"></table>
173 </div>
174
175 <!-- 违禁词扫描标签 -->
176 <div class="tab-pane fade <?= $default_tab == 'scan' ? 'show active' : '' ?>" id="tab-scan">
177 <div class="node-toolbar mb-3">
178 <select id="scanNodeFilter" class="form-control form-control-sm">
179 <option value="">全部节点</option>
180 </select>
181 <select id="scanTimeFilter" class="form-control form-control-sm">
182 <option value="">全部时间</option>
183 <option value="today">今天</option>
184 <option value="yesterday">昨天</option>
185 <option value="week">最近7天</option>
186 <option value="month">最近30天</option>
187 </select>
188 <select id="scanStatusFilter" class="form-control form-control-sm">
189 <option value="">全部状态</option>
190 <option value="success">成功</option>
191 <option value="failed">失败</option>
192 <option value="has_matches">有命中</option>
193 </select>
194 <button type="button" class="btn btn-primary btn-sm" onclick="batchTriggerScan()">
195 <i class="mdi mdi-play"></i> 批量扫描
196 </button>
197 <button type="button" class="btn btn-danger btn-sm" onclick="clearOldScans()">
198 <i class="mdi mdi-delete-sweep"></i> 清理旧记录
199 </button>
200 </div>
201 <table id="tb_scans"></table>
202 </div>
203
204 <!-- 扫描配置标签 -->
205 <div class="tab-pane fade <?= $default_tab == 'config' ? 'show active' : '' ?>" id="tab-config">
206 <div class="row">
207 <div class="col-lg-10 col-xl-8">
208 <div class="card">
209
210 <div class="card-body">
211 <div class="form-group">
212 <label class="btn-block">违禁词扫描开关</label>
213 <div class="col-xs-6">
214 <div class="custom-control custom-switch">
215 <input type="checkbox" class="custom-control-input" id="wjscskg" <?php if($conf['wjsckg']=='true')echo 'checked';?> >
216 <label class="custom-control-label" for="wjscskg"></label>
217 </div>
218 </div>
219 </div>
220 <div class="form-group">
221 <label>违禁词内容</label>
222 <textarea id="wjscsnr" rows="10" class="form-control" placeholder="每行一个违禁词"><?php echo htmlspecialchars($conf['wjsccnr'] ?? ''); ?></textarea>
223 <small class="text-muted">每行一个关键词,支持普通匹配(暂不支持正则)</small>
224 </div>
225 <div class="form-group">
226 <label class="btn-block">只扫描变更文件</label>
227 <div class="col-xs-6">
228 <div class="custom-control custom-switch">
229 <input type="checkbox" class="custom-control-input" id="wjscskgbfx" <?php if(($conf['wjsckgqbfx'] ?? 'true')=='true')echo 'checked';?> >
230 <label class="custom-control-label" for="wjscskgbfx"></label>
231 </div>
232 </div>
233 <small class="text-muted">开启后仅扫描新增或修改的文件,大幅提升性能</small>
234 </div>
235 <div class="row">
236 <div class="col-md-6">
237 <div class="form-group">
238 <label>扫描目录</label>
239 <input type="text" id="wjscsml" value="<?php echo $conf['wjscml'] ?? '/www/wwwroot'; ?>" class="form-control" placeholder="默认 /www/wwwroot"/>
240 <small class="text-muted">Linux 宝塔面板建站目录</small>
241 </div>
242 </div>
243 <div class="col-md-6">
244 <div class="form-group">
245 <label>单文件最大大小(MB)</label>
246 <input type="number" id="wjscsdzmax" value="<?php echo ($conf['wjscdzmax'] ?? 5242880) / 1024 / 1024; ?>" class="form-control" min="1" max="50" placeholder="默认 5MB"/>
247 </div>
248 </div>
249 </div>
250 <div class="row">
251 <div class="col-md-6">
252 <div class="form-group">
253 <label>跳过目录</label>
254 <input type="text" id="wjscstqml" value="<?php echo $conf['wjstqml'] ?? '.git,node_modules,vendor,runtime,cache,logs'; ?>" class="form-control" placeholder="逗号分隔"/>
255 <small class="text-muted">默认:.git,node_modules,vendor,runtime,cache,logs</small>
256 </div>
257 </div>
258 <div class="col-md-6">
259 <div class="form-group">
260 <label>跳过后缀</label>
261 <input type="text" id="wjscstqhz" value="<?php echo $conf['wjstqhz'] ?? '.jpg,.png,.gif,.webp,.mp4,.zip,.rar,.7z,.pdf,.woff,.ttf'; ?>" class="form-control" placeholder="逗号分隔"/>
262 <small class="text-muted">默认:.jpg,.png,.gif,.webp,.mp4,.zip,.rar,.7z,.pdf,.woff,.ttf</small>
263 </div>
264 </div>
265 </div>
266 <div class="row">
267 <div class="col-md-6">
268 <div class="form-group">
269 <label>单次扫描最大命中数</label>
270 <input type="number" id="wjscsdhmax" value="<?php echo $conf['wjscdhmax'] ?? 1000; ?>" class="form-control" min="1" max="5000" placeholder="默认 1000"/>
271 <small class="text-muted">达到此数量后停止扫描</small>
272 </div>
273 </div>
274 <div class="col-md-6">
275 <div class="form-group">
276 <label>定时全量复扫开关</label>
277 <div class="custom-control custom-switch">
278 <input type="checkbox" class="custom-control-input" id="wjscsqzcskg" <?php if(($conf['wjscqzcskg'] ?? 'true')=='true')echo 'checked';?> >
279 <label class="custom-control-label" for="wjscsqzcskg"></label>
280 </div>
281 <small class="text-muted">每天凌晨进行一次全量扫描</small>
282 </div>
283 </div>
284 </div>
285 <div class="form-group">
286 <label>定时全量复扫时间(Cron表达式)</label>
287 <input type="text" id="wjscsqzcs" value="<?php echo $conf['wjscqzcs'] ?? '0 3 * * *'; ?>" class="form-control" placeholder="格式:分 时 日 月 周,默认 0 3 * * *"/>
288 <small class="text-muted">格式说明:分钟 小时 日期 月份 星期</small>
289 </div>
290 <hr>
291 <div class="alert alert-info">
292 <i class="mdi mdi-information"></i>
293 违禁词扫描功能需要配合宝塔节点插件使用。开启后插件会自动在本地建立文件指纹索引,仅上报命中结果。
294 </div>
295 <button class="btn btn-primary" type="button" onclick="saveScanConfig()">
296 <i class="mdi mdi-checkbox-marked-circle-outline"></i> 保存配置
297 </button>
298 </div>
299 </div>
300 </div>
301 </div>
302 </div>
303 </div>
304 </div>
305 </div>
306</div>
307
308<!-- 新增节点弹窗 -->
309<div class="modal fade" id="addNodeModal" tabindex="-1" role="dialog">
310 <div class="modal-dialog" role="document">
311 <div class="modal-content">
312 <div class="modal-header">
313 <h6 class="modal-title"><i class="mdi mdi-plus-circle"></i> 新增节点</h6>
314 <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
315 </div>
316 <div class="modal-body">
317 <div class="form-group">
318 <label><i class="mdi mdi-server"></i> 绑定宝塔</label>
319 <select id="addBtId" class="form-control">
320 <option value="">请选择宝塔服务器</option>
321 <?php foreach($bt_list as $bt){ ?>
322 <option value="<?=htmlspecialchars($bt['id'], ENT_QUOTES, 'UTF-8')?>"><?=htmlspecialchars($bt['btdh'].' - '.$bt['btip'], ENT_QUOTES, 'UTF-8')?></option>
323 <?php } ?>
324 </select>
325 </div>
326 <div class="form-group">
327 <label><i class="mdi mdi-tag"></i> 节点名称</label>
328 <input type="text" id="addNodeName" class="form-control" placeholder="例如:国内节点 / 香港节点">
329 </div>
330 <div class="form-group">
331 <label><i class="mdi mdi-identifier"></i> 节点ID</label>
332 <input type="text" id="addNodeId" class="form-control" placeholder="留空自动生成">
333 </div>
334 <div class="form-group">
335 <label><i class="mdi mdi-link"></i> MNBT访问地址</label>
336 <input type="text" id="addMnbtUrl" class="form-control" value="<?=htmlspecialchars($default_url, ENT_QUOTES, 'UTF-8')?>">
337 </div>
338 <div class="form-group">
339 <label><i class="mdi mdi-clock"></i> 插件轮询间隔(秒)</label>
340 <input type="number" id="addIntervalSeconds" class="form-control" value="10" min="5" step="1">
341 </div>
342 </div>
343 <div class="modal-footer">
344 <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
345 <button type="button" class="btn btn-primary" onclick="saveAddNode()">
346 <i class="mdi mdi-check"></i> 确认新增
347 </button>
348 </div>
349 </div>
350 </div>
351</div>
352
353<!-- 配置弹窗 -->
354<div class="modal fade" id="configModal" tabindex="-1" role="dialog">
355 <div class="modal-dialog modal-lg" role="document">
356 <div class="modal-content">
357 <div class="modal-header">
358 <h6 class="modal-title"><i class="mdi mdi-code-json"></i> 插件配置</h6>
359 <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
360 </div>
361 <div class="modal-body">
362 <input type="hidden" id="configNodeId">
363 <div class="row">
364 <div class="col-md-8">
365 <div class="form-group">
366 <label>MNBT访问地址</label>
367 <input type="text" id="configMnbtUrl" class="form-control" value="<?=htmlspecialchars($default_url, ENT_QUOTES, 'UTF-8')?>">
368 </div>
369 </div>
370 <div class="col-md-4">
371 <div class="form-group">
372 <label>插件轮询间隔(秒)</label>
373 <input type="number" id="configIntervalSeconds" class="form-control" value="10" min="5" step="1">
374 </div>
375 </div>
376 </div>
377 <div class="form-group">
378 <label>配置 JSON</label>
379 <textarea id="configJson" class="form-control node-code-box" readonly style="height:300px;"></textarea>
380 </div>
381 <div class="alert alert-info">
382 <i class="mdi mdi-information"></i>
383 将此配置复制到宝塔面板插件目录下的 config.json 文件中,然后启动插件即可。
384 </div>
385 </div>
386 <div class="modal-footer">
387 <button type="button" class="btn btn-default" onclick="loadNodeConfig()">
388 <i class="mdi mdi-refresh"></i> 刷新
389 </button>
390 <button type="button" class="btn btn-primary" onclick="copyNodeConfig()">
391 <i class="mdi mdi-content-copy"></i> 复制配置
392 </button>
393 </div>
394 </div>
395 </div>
396</div>
397
398<!-- 扫描弹窗 -->
399<div class="modal fade" id="scanModal" tabindex="-1" role="dialog">
400 <div class="modal-dialog" role="document">
401 <div class="modal-content">
402 <div class="modal-header">
403 <h6 class="modal-title" id="scanModalTitle"><i class="mdi mdi-shield-search"></i> 下发违禁词扫描</h6>
404 <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
405 </div>
406 <div class="modal-body">
407 <input type="hidden" id="scanNodePk">
408 <div class="form-group">
409 <label><i class="mdi mdi-web"></i> 站点标识</label>
410 <input type="text" id="scanSite" class="form-control" placeholder="例如:example.com">
411 </div>
412 <div class="form-group">
413 <label><i class="mdi mdi-folder-open"></i> 扫描目录</label>
414 <input type="text" id="scanRoot" class="form-control" value="/www/wwwroot">
415 </div>
416 <div class="form-group">
417 <label><i class="mdi mdi-alert"></i> 违禁词</label>
418 <textarea id="scanKeywords" rows="6" class="form-control" placeholder="每行一个关键词"></textarea>
419 </div>
420 <div class="row">
421 <div class="col-md-6">
422 <div class="form-group">
423 <label><i class="mdi mdi-file-size"></i> 单文件最大MB</label>
424 <input type="number" id="scanMaxFileSize" class="form-control" value="5" min="1" max="50">
425 </div>
426 </div>
427 <div class="col-md-6">
428 <div class="form-group">
429 <label><i class="mdi mdi-counter"></i> 最多命中数</label>
430 <input type="number" id="scanMaxMatches" class="form-control" value="1000" min="1" max="5000">
431 </div>
432 </div>
433 </div>
434 </div>
435 <div class="modal-footer">
436 <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
437 <button type="button" class="btn btn-primary" onclick="submitForbiddenScan()">
438 <i class="mdi mdi-play"></i> 下发任务
439 </button>
440 </div>
441 </div>
442 </div>
443</div>
444
445<!-- 命中详情弹窗 -->
446<div class="modal fade" id="matchModal" tabindex="-1" role="dialog">
447 <div class="modal-dialog modal-xl" role="document">
448 <div class="modal-content">
449 <div class="modal-header">
450 <h6 class="modal-title" id="matchModalTitle"><i class="mdi mdi-file-search-outline"></i> 命中详情</h6>
451 <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
452 </div>
453 <div class="modal-body">
454 <input type="hidden" id="matchTaskId">
455 <table id="tb_matches"></table>
456 </div>
457 </div>
458 </div>
459</div>
460
461<!-- 批量扫描弹窗 -->
462<div class="modal fade" id="batchScanModal" tabindex="-1" role="dialog">
463 <div class="modal-dialog" role="document">
464 <div class="modal-content">
465 <div class="modal-header">
466 <h6 class="modal-title"><i class="mdi mdi-play-multiple"></i> 批量扫描</h6>
467 <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
468 </div>
469 <div class="modal-body">
470 <div class="alert alert-info">
471 <i class="mdi mdi-information"></i>
472 将向所有选中的在线节点下发扫描任务
473 </div>
474 <div class="form-group">
475 <label><i class="mdi mdi-folder-open"></i> 扫描目录</label>
476 <input type="text" id="batchScanRoot" class="form-control" value="/www/wwwroot">
477 </div>
478 <div class="form-group">
479 <label><i class="mdi mdi-alert"></i> 违禁词</label>
480 <textarea id="batchScanKeywords" rows="4" class="form-control" placeholder="留空则使用全局配置的违禁词"></textarea>
481 </div>
482 <div class="row">
483 <div class="col-md-6">
484 <div class="form-group">
485 <label>单文件最大MB</label>
486 <input type="number" id="batchScanMaxSize" class="form-control" value="5" min="1" max="50">
487 </div>
488 </div>
489 <div class="col-md-6">
490 <div class="form-group">
491 <label>最多命中数</label>
492 <input type="number" id="batchScanMaxMatches" class="form-control" value="1000" min="1" max="5000">
493 </div>
494 </div>
495 </div>
496 </div>
497 <div class="modal-footer">
498 <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
499 <button type="button" class="btn btn-primary" onclick="submitBatchScan()">
500 <i class="mdi mdi-play"></i> 开始扫描
501 </button>
502 </div>
503 </div>
504 </div>
505</div>
506
507<script type="text/javascript">
508var defaultMnbtUrl = <?=json_encode($default_url, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)?>;
509var currentNodePk = 0;
510var currentNodeLabel = '';
511var autoRefreshTimer = null;
512
513// 工具函数
514function nodeEscape(value) {
515 if (value === null || value === undefined) return '';
516 return String(value).replace(/[&<>"']/g, function (ch) {
517 return {'&':'&', '<':'<', '>':'>', '"':'"', "'":'''}[ch];
518 });
519}
520
521function nodeBadge(text, type) {
522 return '<span class="badge badge-' + type + '">' + nodeEscape(text) + '</span>';
523}
524
525function nodeStatusFormatter(value, row) {
526 if (row.display_status === 'online') return '<i class="mdi mdi-circle text-success"></i> 在线';
527 if (row.display_status === 'disabled') return '<i class="mdi mdi-circle text-secondary"></i> 停用';
528 return '<i class="mdi mdi-circle text-danger"></i> 离线';
529}
530
531function nodeEnabledFormatter(value, row) {
532 if (row.display_status === 'disabled') return nodeBadge('停用', 'secondary');
533 return value === 'true' ? nodeBadge('启用', 'success') : nodeBadge('停用', 'secondary');
534}
535
536function taskStatusFormatter(value) {
537 if (value === 'success') return nodeBadge('成功', 'success');
538 if (value === 'failed') return nodeBadge('失败', 'danger');
539 if (value === 'running') return '<span class="badge badge-info"><i class="mdi mdi-loading mdi-spin"></i> 执行中</span>';
540 return nodeBadge('等待中', 'warning');
541}
542
543function textClipFormatter(value) {
544 return '<span class="node-text-clip" title="' + nodeEscape(value) + '">' + nodeEscape(value || '-') + '</span>';
545}
546
547function miniTextClipFormatter(value) {
548 return '<span class="node-text-clip node-mini-text" title="' + nodeEscape(value) + '">' + nodeEscape(value || '-') + '</span>';
549}
550
551function secretFormatter(value) {
552 return '<span class="node-text-clip node-mini-text"><span class="node-secret-text">**********</span> ' +
553 '<a href="#!" title="显示密钥" data-secret="' + nodeEscape(value || '') + '" onclick="toggleSecret(this);return false;">' +
554 '<i class="mdi mdi-eye"></i></a></span>';
555}
556
557function toggleSecret(el) {
558 var wrap = $(el).closest('span');
559 var text = wrap.find('.node-secret-text');
560 var icon = $(el).find('i');
561 if (text.text() === '**********') {
562 text.text($(el).data('secret') || '');
563 icon.removeClass('mdi-eye').addClass('mdi-eye-off');
564 } else {
565 text.text('**********');
566 icon.removeClass('mdi-eye-off').addClass('mdi-eye');
567 }
568}
569
570// 刷新所有数据
571function refreshAllData() {
572 msloading('刷新中...');
573 $('#tb_nodes').bootstrapTable('refresh', {silent: true});
574 $('#tb_tasks').bootstrapTable('refresh', {silent: true});
575 $('#tb_scans').bootstrapTable('refresh', {silent: true});
576 loadStatistics();
577 setTimeout(function() {
578 msloadingde();
579 msalert(1, '刷新完成', 1200);
580 }, 800);
581}
582
583// 加载统计数据
584function loadStatistics() {
585 $.post('./ajax.php', {gn: 'nodestats'}, function(res) {
586 if(res.success) {
587 $('#statOnline').text(res.data.online || 0);
588 $('#statOffline').text(res.data.offline || 0);
589 $('#statTodayMatches').text(res.data.today_matches || 0);
590 $('#statPendingTasks').text(res.data.pending_tasks || 0);
591 }
592 }, 'json');
593}
594
595// 刷新节点列表
596function refreshNodes() {
597 $('#tb_nodes').bootstrapTable('refreshOptions', {pageNumber: 1});
598}
599
600// 重置筛选
601function resetNodeFilter() {
602 $('#nodeKeyword').val('');
603 $('#nodeStatus').val('');
604 refreshNodes();
605}
606
607// 显示全部节点记录
608function showAllNodeRecords() {
609 currentNodePk = 0;
610 currentNodeLabel = '';
611 $('#taskNodeFilter').val('');
612 $('#scanNodeFilter').val('');
613 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
614 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
615}
616
617// 使用指定节点记录
618function useNodeRecords(row) {
619 currentNodePk = row.id;
620 currentNodeLabel = row.node_name || row.node_id;
621 $('#taskNodeFilter').val(row.id);
622 $('#scanNodeFilter').val(row.id);
623 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
624 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
625 // 切换到任务标签
626 $('a[href="#tab-tasks"]').tab('show');
627}
628
629// AJAX 请求封装
630function ajaxNode(data, done, silent) {
631 if (!silent) msloading('正在处理...');
632 $.post('./ajax.php', data, function (res) {
633 if (typeof res === 'string') {
634 try { res = JSON.parse(res); } catch (e) { res = {success:false, msg:res}; }
635 }
636 if (res.success) {
637 if (done) done(res);
638 } else {
639 msalert(4, res.msg || res.code || '操作失败', 3000);
640 }
641 }, 'json').fail(function () {
642 msalert(4, '请求失败,请检查后台日志', 3000);
643 }).always(function () {
644 if (!silent) msloadingde();
645 });
646}
647
648// 打开新增节点弹窗
649function openAddNode() {
650 $('#addBtId').val('');
651 $('#addNodeName').val('');
652 $('#addNodeId').val('');
653 $('#addMnbtUrl').val(defaultMnbtUrl);
654 $('#addIntervalSeconds').val(10);
655 $('#addNodeModal').modal('show');
656}
657
658// 保存新增节点
659function saveAddNode() {
660 ajaxNode({
661 gn: 'addnode',
662 bt_id: $('#addBtId').val(),
663 node_name: $('#addNodeName').val(),
664 node_id: $('#addNodeId').val(),
665 mnbt_url: $('#addMnbtUrl').val(),
666 interval_seconds: $('#addIntervalSeconds').val()
667 }, function (res) {
668 $('#addNodeModal').modal('hide');
669 $('#configNodeId').val(res.id || '');
670 $('#configMnbtUrl').val($('#addMnbtUrl').val());
671 $('#configIntervalSeconds').val($('#addIntervalSeconds').val());
672 loadNodeConfig();
673 $('#configModal').modal('show');
674 refreshNodes();
675 loadStatistics();
676 msalert(1, '新增成功,请复制配置到插件目录', 3000);
677 });
678}
679
680// 显示节点配置
681function showNodeConfig(row) {
682 $('#configNodeId').val(row.id);
683 $('#configMnbtUrl').val(defaultMnbtUrl);
684 $('#configIntervalSeconds').val('10');
685 $('#configJson').val('');
686 $('#configModal').modal('show');
687 loadNodeConfig();
688}
689
690// 加载节点配置
691function loadNodeConfig() {
692 var nodePk = $('#configNodeId').val();
693 if (!nodePk) return;
694 ajaxNode({
695 gn: 'nodeconfig',
696 id: nodePk,
697 mnbt_url: $('#configMnbtUrl').val(),
698 interval_seconds: $('#configIntervalSeconds').val()
699 }, function (res) {
700 $('#configJson').val(res.config_json || '');
701 }, true);
702}
703
704// 复制节点配置
705function copyNodeConfig() {
706 var target = document.getElementById('configJson');
707 target.focus();
708 target.select();
709 document.execCommand('copy');
710 msalert(1, '配置已复制', 2000, '#configModal');
711}
712
713// Ping 节点
714function pingNode(row) {
715 ajaxNode({gn: 'nodeping', id: row.id}, function (res) {
716 msalert(1, res.msg || '任务已下发', 2000);
717 refreshNodes();
718 });
719}
720
721// 切换节点开关
722function toggleNode(row) {
723 var nextEnabled = row.enabled === 'true' ? 'false' : 'true';
724 ajaxNode({gn: 'setnodestatus', id: row.id, enabled: nextEnabled}, function (res) {
725 msalert(1, res.msg || '操作成功', 2000);
726 refreshNodes();
727 loadStatistics();
728 });
729}
730
731// 删除节点
732function deleteNode(row) {
733 $.confirm({
734 title: '删除节点',
735 content: '确认删除节点 ' + nodeEscape(row.node_name || row.node_id) + ' 及其任务和扫描记录吗?',
736 icon: 'mdi mdi-alert',
737 type: 'red',
738 buttons: {
739 ok: {
740 text: '确认删除',
741 btnClass: 'btn-red',
742 action: function () {
743 ajaxNode({gn: 'delnode', id: row.id}, function (res) {
744 msalert(1, res.msg || '删除成功', 2000);
745 refreshNodes();
746 loadStatistics();
747 $('#tb_tasks').bootstrapTable('refresh');
748 $('#tb_scans').bootstrapTable('refresh');
749 });
750 }
751 },
752 cancel: { text: '取消' }
753 }
754 });
755}
756
757// 打开扫描弹窗
758function openForbiddenScan(row) {
759 $('#scanNodePk').val(row.id);
760 $('#scanSite').val('');
761 $('#scanRoot').val('/www/wwwroot');
762 $('#scanKeywords').val('');
763 $('#scanMaxFileSize').val('5');
764 $('#scanMaxMatches').val('1000');
765 $('#scanModalTitle').text('下发违禁词扫描 - ' + (row.node_name || row.node_id));
766 $('#scanModal').modal('show');
767}
768
769// 提交扫描任务
770function submitForbiddenScan() {
771 ajaxNode({
772 gn: 'nodeforbiddenscan',
773 id: $('#scanNodePk').val(),
774 site: $('#scanSite').val(),
775 root: $('#scanRoot').val(),
776 keywords: $('#scanKeywords').val(),
777 max_file_size_mb: $('#scanMaxFileSize').val(),
778 max_matches: $('#scanMaxMatches').val()
779 }, function (res) {
780 $('#scanModal').modal('hide');
781 msalert(1, res.msg || '扫描任务已下发', 2000);
782 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
783 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
784 loadStatistics();
785 });
786}
787
788// 显示命中详情
789function showMatches(row) {
790 $('#matchTaskId').val(row.task_id);
791 $('#matchModalTitle').text('命中详情 - ' + row.task_id);
792 $('#matchModal').modal('show');
793 $('#tb_matches').bootstrapTable('refreshOptions', {pageNumber: 1});
794}
795
796// 批量扫描
797function batchTriggerScan() {
798 // 检查当前是否在节点列表标签页
799 var currentTab = $('#nodeTabs .nav-link.active').attr('href');
800 if (currentTab !== '#tab-nodes') {
801 $('a[href="#tab-nodes"]').tab('show');
802 // 等待标签切换完成后再检查选中
803 setTimeout(function() {
804 checkAndOpenBatchScan();
805 }, 300);
806 return;
807 }
808
809 checkAndOpenBatchScan();
810}
811
812function checkAndOpenBatchScan() {
813 var selectedRows = $('#tb_nodes').bootstrapTable('getSelections');
814 if (selectedRows.length === 0) {
815 msalert(3, '请先勾选要扫描的节点(在表格左侧的复选框)', 3000);
816 return;
817 }
818
819 var onlineNodes = selectedRows.filter(function(row) {
820 return row.display_status === 'online' && row.enabled === 'true';
821 });
822 var offlineNodes = selectedRows.filter(function(row) {
823 return row.display_status !== 'online';
824 });
825 var disabledNodes = selectedRows.filter(function(row) {
826 return row.display_status === 'online' && row.enabled !== 'true';
827 });
828
829 if (onlineNodes.length === 0) {
830 var errorMsg = '没有可扫描的节点!';
831 if (offlineNodes.length > 0) {
832 errorMsg += '\n离线节点:' + offlineNodes.map(function(r) { return r.node_name || r.node_id; }).join(', ');
833 }
834 if (disabledNodes.length > 0) {
835 errorMsg += '\n停用节点:' + disabledNodes.map(function(r) { return r.node_name || r.node_id; }).join(', ');
836 }
837 msalert(3, errorMsg, 5000);
838 return;
839 }
840
841 // 如果有部分节点不可用,提示用户
842 if (onlineNodes.length < selectedRows.length) {
843 var skippedCount = selectedRows.length - onlineNodes.length;
844 if (skippedCount > 0) {
845 msalert(1, '已过滤 ' + skippedCount + ' 个离线或停用节点,将对 ' + onlineNodes.length + ' 个节点下发扫描', 2000);
846 }
847 }
848
849 $('#batchScanRoot').val('/www/wwwroot');
850 $('#batchScanKeywords').val('');
851 $('#batchScanMaxSize').val('5');
852 $('#batchScanMaxMatches').val('1000');
853 $('#batchScanModal').modal('show');
854}
855
856// 提交批量扫描
857function submitBatchScan() {
858 var selectedRows = $('#tb_nodes').bootstrapTable('getSelections');
859 var onlineNodes = selectedRows.filter(function(row) {
860 return row.display_status === 'online' && row.enabled === 'true';
861 });
862
863 var keywords = $('#batchScanKeywords').val();
864 if (!keywords) {
865 // 使用全局配置的违禁词
866 $.post('./ajax.php', {gn: 'get_global_keywords'}, function(res) {
867 if (res.success && res.data) {
868 submitBatchScanWithNodes(onlineNodes, res.data);
869 } else {
870 msalert(3, '请填写违禁词或先在「违禁词扫描设置」中配置', 3000);
871 }
872 }, 'json');
873 return;
874 }
875
876 submitBatchScanWithNodes(onlineNodes, keywords);
877}
878
879function submitBatchScanWithNodes(nodes, keywords) {
880 var count = 0;
881 var root = $('#batchScanRoot').val();
882 var maxSize = $('#batchScanMaxSize').val() * 1024 * 1024;
883 var maxMatches = $('#batchScanMaxMatches').val();
884
885 nodes.forEach(function(node) {
886 ajaxNode({
887 gn: 'nodeforbiddenscan',
888 id: node.id,
889 site: '',
890 root: root,
891 keywords: keywords,
892 max_file_size_mb: $('#batchScanMaxSize').val(),
893 max_matches: maxMatches
894 }, function() {
895 count++;
896 if (count === nodes.length) {
897 $('#batchScanModal').modal('hide');
898 msalert(1, '已向 ' + count + ' 个节点下发扫描任务', 2000);
899 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
900 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
901 loadStatistics();
902 }
903 }, true);
904 });
905}
906
907// 清理旧扫描记录
908function clearOldScans() {
909 $.confirm({
910 title: '清理旧记录',
911 content: '' +
912 '<div class="form-group">' +
913 '<label>清理多少天前的扫描记录?</label>' +
914 '<input type="number" id="clearScanDays" class="form-control" value="7" min="1" max="365">' +
915 '<small class="text-muted">会同时清理扫描记录、命中详情、对应的扫描任务日志</small>' +
916 '</div>',
917 icon: 'mdi mdi-delete-sweep',
918 type: 'orange',
919 buttons: {
920 ok: {
921 text: '确认清理',
922 btnClass: 'btn-orange',
923 action: function () {
924 var days = $('#clearScanDays').val() || 7;
925 ajaxNode({gn: 'clearoldscans', days: days}, function(res) {
926 msalert(1, res.msg || '清理成功', 3000);
927 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
928 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
929 loadStatistics();
930 });
931 }
932 },
933 cancel: { text: '取消' }
934 }
935 });
936}
937
938// 操作按钮格式化
939function nodeOperateFormatter(value, row) {
940 var statusClass = row.display_status === 'online' ? 'text-success' : 'text-secondary';
941 var toggleIcon = row.enabled === 'true' ? 'mdi-pause-circle-outline' : 'mdi-play-circle-outline';
942 var toggleTitle = row.enabled === 'true' ? '停用' : '启用';
943
944 return '' +
945 '<div class="btn-group btn-group-sm">' +
946 '<button class="btn btn-default quick-action-btn task-btn" title="最近任务" onclick="useNodeRecords({\'id\':\'' + row.id + '\',\'node_name\':\'' + nodeEscape(row.node_name || row.node_id) + '\'});"><i class="mdi mdi-format-list-bulleted"></i></button>' +
947 '<button class="btn btn-default quick-action-btn config-btn" title="配置" onclick="showNodeConfig({\'id\':\'' + row.id + '\',\'node_name\':\'' + nodeEscape(row.node_name || row.node_id) + '\'});"><i class="mdi mdi-code-json"></i></button>' +
948 '<button class="btn btn-default quick-action-btn ping-btn" title="Ping测试" onclick="pingNode({\'id\':\'' + row.id + '\',\'node_name\':\'' + nodeEscape(row.node_name || row.node_id) + '\'});"><i class="mdi mdi-access-point"></i></button>' +
949 '<button class="btn btn-default quick-action-btn scan-btn" title="违禁词扫描" onclick="openForbiddenScan({\'id\':\'' + row.id + '\',\'node_name\':\'' + nodeEscape(row.node_name || row.node_id) + '\'});"><i class="mdi mdi-shield-search"></i></button>' +
950 '<button class="btn btn-default quick-action-btn toggle-btn" title="' + toggleTitle + '" onclick="toggleNode({\'id\':\'' + row.id + '\',\'node_name\':\'' + nodeEscape(row.node_name || row.node_id) + '\',\'enabled\':\'' + row.enabled + '\'});"><i class="mdi ' + toggleIcon + '"></i></button>' +
951 '<button class="btn btn-danger quick-action-btn delete-btn" title="删除" onclick="deleteNode({\'id\':\'' + row.id + '\',\'node_name\':\'' + nodeEscape(row.node_name || row.node_id) + '\'});"><i class="mdi mdi-window-close"></i></button>' +
952 '</div>';
953}
954
955window.nodeOperateEvents = {
956 // 事件已通过 onclick 内联绑定
957};
958
959// 扫描操作格式化
960function scanOperateFormatter() {
961 return '<button class="btn btn-xs btn-info" onclick="showMatches(row)">查看命中</button>';
962}
963
964window.scanOperateEvents = {
965 'click .match-btn': function (event, value, row) { showMatches(row); }
966};
967
968// 节点列表表格
969$('#tb_nodes').bootstrapTable({
970 classes: 'table table-bordered table-hover table-striped',
971 url: './ajax.php',
972 method: 'post',
973 contentType: 'application/x-www-form-urlencoded',
974 dataType: 'json',
975 uniqueId: 'id',
976 idField: 'id',
977 showRefresh: true,
978 showColumns: true,
979 showFullscreen: true,
980 pagination: true,
981 sidePagination: 'server',
982 pageNumber: 1,
983 pageSize: 10,
984 pageList: [10, 25, 50],
985 sortName: 'id',
986 sortOrder: 'desc',
987 queryParams: function (params) {
988 return {
989 gn: 'listnode',
990 limit: params.limit,
991 offset: params.offset,
992 page: (params.offset / params.limit) + 1,
993 sort: params.sort || 'id',
994 sortOrder: params.order || 'desc',
995 keyword: $('#nodeKeyword').val(),
996 status: $('#nodeStatus').val()
997 };
998 },
999 columns: [{
1000 checkbox: true
1001 }, {
1002 field: 'id',
1003 title: 'ID',
1004 sortable: true,
1005 width: 50
1006 }, {
1007 field: 'display_status',
1008 title: '状态',
1009 sortable: false,
1010 formatter: nodeStatusFormatter,
1011 width: 80
1012 }, {
1013 field: 'enabled',
1014 title: '开关',
1015 sortable: false,
1016 formatter: nodeEnabledFormatter,
1017 width: 80
1018 }, {
1019 field: 'node_name',
1020 title: '节点名称',
1021 formatter: textClipFormatter,
1022 width: 150
1023 }, {
1024 field: 'node_id',
1025 title: '节点ID',
1026 formatter: miniTextClipFormatter,
1027 width: 120
1028 }, {
1029 field: 'btdh',
1030 title: '绑定宝塔',
1031 formatter: function (value, row) {
1032 return nodeEscape(value || '-') + '<br><small class="text-muted">' + nodeEscape(row.btip || '') + '</small>';
1033 },
1034 width: 150
1035 }, {
1036 field: 'ip',
1037 title: '节点IP',
1038 formatter: miniTextClipFormatter,
1039 width: 120
1040 }, {
1041 field: 'version',
1042 title: '版本',
1043 formatter: miniTextClipFormatter,
1044 width: 80
1045 }, {
1046 field: 'last_heartbeat',
1047 title: '最后心跳',
1048 sortable: true,
1049 formatter: function (value) {
1050 if (!value) return '-';
1051 var now = new Date();
1052 var heartbeat = new Date(value);
1053 var diff = Math.floor((now - heartbeat) / 1000);
1054 if (diff < 60) return '<span class="text-success">' + value + '</span>';
1055 if (diff < 300) return '<span class="text-warning">' + value + '</span>';
1056 return '<span class="text-danger">' + value + '</span>';
1057 },
1058 width: 160
1059 }, {
1060 field: 'operate',
1061 title: '操作',
1062 formatter: nodeOperateFormatter,
1063 events: nodeOperateEvents,
1064 width: 180
1065 }],
1066 onLoadSuccess: function () {
1067 $('[data-toggle="tooltip"]').tooltip();
1068 },
1069 responseHandler: function(res) {
1070 loadStatistics();
1071 return res;
1072 }
1073});
1074
1075// 任务列表表格
1076$('#tb_tasks').bootstrapTable({
1077 classes: 'table table-bordered table-hover table-striped',
1078 url: './ajax.php',
1079 method: 'post',
1080 contentType: 'application/x-www-form-urlencoded',
1081 dataType: 'json',
1082 pagination: true,
1083 sidePagination: 'server',
1084 pageNumber: 1,
1085 pageSize: 15,
1086 pageList: [15, 30, 50],
1087 sortName: 'id',
1088 sortOrder: 'desc',
1089 queryParams: function (params) {
1090 return {
1091 gn: 'listnodetask',
1092 node_pk: $('#taskNodeFilter').val(),
1093 status: $('#taskStatusFilter').val(),
1094 action: $('#taskTypeFilter').val(),
1095 limit: params.limit,
1096 offset: params.offset,
1097 page: (params.offset / params.limit) + 1,
1098 sort: params.sort || 'id',
1099 sortOrder: params.order || 'desc'
1100 };
1101 },
1102 columns: [{
1103 field: 'id',
1104 title: 'ID',
1105 sortable: true,
1106 width: 50
1107 }, {
1108 field: 'node_id',
1109 title: '节点ID',
1110 formatter: miniTextClipFormatter,
1111 width: 120
1112 }, {
1113 field: 'action',
1114 title: '任务类型',
1115 formatter: function (value) {
1116 if (value === 'ping') return '<span class="badge badge-secondary">Ping</span>';
1117 if (value === 'forbidden_scan') return '<span class="badge badge-warning">违禁词扫描</span>';
1118 return value;
1119 },
1120 width: 100
1121 }, {
1122 field: 'status',
1123 title: '状态',
1124 formatter: taskStatusFormatter,
1125 width: 80
1126 }, {
1127 field: 'error',
1128 title: '错误信息',
1129 formatter: miniTextClipFormatter,
1130 width: 200
1131 }, {
1132 field: 'created_at',
1133 title: '创建时间',
1134 sortable: true,
1135 formatter: miniTextClipFormatter,
1136 width: 160
1137 }, {
1138 field: 'finished_at',
1139 title: '完成时间',
1140 formatter: miniTextClipFormatter,
1141 width: 160
1142 }],
1143 onLoadSuccess: function () {
1144 $('[data-toggle="tooltip"]').tooltip();
1145 }
1146});
1147
1148// 扫描结果表格
1149$('#tb_scans').bootstrapTable({
1150 classes: 'table table-bordered table-hover table-striped',
1151 url: './ajax.php',
1152 method: 'post',
1153 contentType: 'application/x-www-form-urlencoded',
1154 dataType: 'json',
1155 pagination: true,
1156 sidePagination: 'server',
1157 pageNumber: 1,
1158 pageSize: 15,
1159 pageList: [15, 30, 50, 100],
1160 sortName: 'id',
1161 sortOrder: 'desc',
1162 queryParams: function (params) {
1163 return {
1164 gn: 'listforbiddenscan',
1165 node_pk: $('#scanNodeFilter').val(),
1166 time_filter: $('#scanTimeFilter').val(),
1167 status_filter: $('#scanStatusFilter').val(),
1168 limit: params.limit,
1169 offset: params.offset,
1170 page: (params.offset / params.limit) + 1,
1171 sort: params.sort || 'id',
1172 sortOrder: params.order || 'desc'
1173 };
1174 },
1175 columns: [{
1176 field: 'id',
1177 title: 'ID',
1178 sortable: true,
1179 width: 50
1180 }, {
1181 field: 'node_id',
1182 title: '节点',
1183 formatter: miniTextClipFormatter,
1184 width: 100
1185 }, {
1186 field: 'site',
1187 title: '站点',
1188 formatter: miniTextClipFormatter,
1189 width: 120
1190 }, {
1191 field: 'status',
1192 title: '状态',
1193 formatter: taskStatusFormatter,
1194 width: 80
1195 }, {
1196 field: 'scanned_files',
1197 title: '文件数',
1198 sortable: true,
1199 width: 80
1200 }, {
1201 field: 'matches_count',
1202 title: '命中',
1203 sortable: true,
1204 formatter: function (value, row) {
1205 var count = Number(value) || 0;
1206 if (count === 0) return '<span class="text-muted">0</span>';
1207 return '<span class="text-danger font-weight-bold">' + count + '</span>';
1208 },
1209 width: 60
1210 }, {
1211 field: 'created_at',
1212 title: '扫描时间',
1213 sortable: true,
1214 formatter: miniTextClipFormatter,
1215 width: 160
1216 }, {
1217 field: 'operate',
1218 title: '操作',
1219 formatter: function (value, row) {
1220 if (row.matches_count > 0) {
1221 return '<button class="btn btn-xs btn-info" onclick="showMatches({\'task_id\':\'' + row.task_id + '\'});">查看命中</button>';
1222 }
1223 return '<span class="text-muted">无</span>';
1224 },
1225 width: 80
1226 }],
1227 onLoadSuccess: function () {
1228 $('[data-toggle="tooltip"]').tooltip();
1229 },
1230 responseHandler: function(res) {
1231 loadStatistics();
1232 return res;
1233 }
1234});
1235
1236
1237
1238// 命中详情表格
1239$('#tb_matches').bootstrapTable({
1240 classes: 'table table-bordered table-hover table-striped',
1241 url: './ajax.php',
1242 method: 'post',
1243 contentType: 'application/x-www-form-urlencoded',
1244 dataType: 'json',
1245 pagination: true,
1246 sidePagination: 'server',
1247 pageNumber: 1,
1248 pageSize: 20,
1249 pageList: [20, 50, 100],
1250 queryParams: function (params) {
1251 return {
1252 gn: 'listforbiddenmatch',
1253 task_id: $('#matchTaskId').val(),
1254 limit: params.limit,
1255 offset: params.offset,
1256 page: (params.offset / params.limit) + 1
1257 };
1258 },
1259 columns: [{
1260 field: 'id',
1261 title: 'ID',
1262 width: 50
1263 }, {
1264 field: 'target',
1265 title: '文件路径',
1266 formatter: textClipFormatter,
1267 width: 300
1268 }, {
1269 field: 'line_no',
1270 title: '行号',
1271 width: 60
1272 }, {
1273 field: 'keyword',
1274 title: '关键词',
1275 formatter: function (value) {
1276 return '<span class="text-danger font-weight-bold">' + nodeEscape(value) + '</span>';
1277 },
1278 width: 100
1279 }, {
1280 field: 'excerpt',
1281 title: '内容片段',
1282 formatter: function (value) {
1283 return '<code class="text-muted">' + nodeEscape(value || '') + '</code>';
1284 }
1285 }]
1286});
1287
1288// 筛选器事件
1289$('#nodeKeyword').on('keydown', function (event) {
1290 if (event.keyCode === 13) refreshNodes();
1291});
1292$('#nodeStatus').on('change', refreshNodes);
1293$('#taskNodeFilter, #taskStatusFilter, #taskTypeFilter').on('change', function() {
1294 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
1295});
1296$('#scanNodeFilter, #scanTimeFilter, #scanStatusFilter').on('change', function() {
1297 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
1298});
1299
1300// 自动刷新(每30秒)
1301function startAutoRefresh() {
1302 if (autoRefreshTimer) clearInterval(autoRefreshTimer);
1303 autoRefreshTimer = setInterval(function() {
1304 var activeTab = $('#nodeTabs .nav-link.active').attr('href');
1305 if (activeTab === '#tab-nodes' || activeTab === '#tab-scan') {
1306 loadStatistics();
1307 }
1308 if (activeTab === '#tab-nodes') {
1309 $('#tb_nodes').bootstrapTable('refresh', {silent: true});
1310 }
1311 }, 30000);
1312}
1313
1314// 切换标签时刷新对应数据
1315$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
1316 var target = $(e.target).attr('href');
1317 if (target === '#tab-nodes') {
1318 $('#tb_nodes').bootstrapTable('refreshOptions', {pageNumber: 1});
1319 } else if (target === '#tab-tasks') {
1320 $('#tb_tasks').bootstrapTable('refreshOptions', {pageNumber: 1});
1321 } else if (target === '#tab-scan') {
1322 $('#tb_scans').bootstrapTable('refreshOptions', {pageNumber: 1});
1323 }
1324});
1325
1326// 保存扫描配置
1327function saveScanConfig() {
1328 msloading('正在保存...');
1329 $.post('./ajax.php', {
1330 gn: 'savescancfg',
1331 skg: document.getElementById('wjscskg').checked,
1332 snr: document.getElementById('wjscsnr').value,
1333 sgbfx: document.getElementById('wjscskgbfx').checked,
1334 sml: document.getElementById('wjscsml').value,
1335 stqml: document.getElementById('wjscstqml').value,
1336 stqhz: document.getElementById('wjscstqhz').value,
1337 sdzmax: document.getElementById('wjscsdzmax').value,
1338 sdhmax: document.getElementById('wjscsdhmax').value,
1339 sqzcskg: document.getElementById('wjscsqzcskg').checked,
1340 sqzcs: document.getElementById('wjscsqzcs').value
1341 }, function(res) {
1342 try { res = JSON.parse(res); } catch(e) {}
1343 msloadingde();
1344 if (res.code === '修改成功' || res.success) {
1345 msalert(1, res.code || res.msg || '保存成功', 2000);
1346 } else {
1347 msalert(4, res.code || res.msg || '保存失败', 3000);
1348 }
1349 }).fail(function() {
1350 msloadingde();
1351 msalert(4, '请求失败', 3000);
1352 });
1353}
1354
1355// 页面加载完成
1356$(function() {
1357 startAutoRefresh();
1358 loadStatistics();
1359});
1360</script>
1361
1362</body>
1363</html>