<?php/*** IP2Region 防火墙管理面板vv3.1.0 2026年03月27日手动优化逻辑基本bug修复,当前只是保存配置,但初始化时未能显示工作状态。v2.2.1 2026年03月26日优化添加区域,优化黑白名单判断逻辑优化IP段匹配,添加IP段优先级功能v2.0 2026年03月25日修改为标签控制模式,修复核心bug,城市名匹配问题v1.0 2026年03月24日ai生成,就是下拉菜单直接保存模式,*/require_once __DIR__ . '/Firewall.php';$appName = 'IP2Region 防火墙管理 V3.1.0';$firewall = new Ip2RegionFirewall();// 常用数据列表 (提取为变量,方便维护和修改)$commonCountries = ['美国', '中国', '日本', '澳大利亚', '韩国', '德国', '英国', '巴西', '法国', '印度尼西亚', '南非', '俄罗斯', '意大利', '哥伦比亚', '西班牙', '印度', '加拿大', '荷兰', '波兰', '比利时', '新加坡', '多米尼加', '瑞士', '土耳其', '阿根廷', '欧洲', '赞比亚', '玻利维亚', '沙特阿拉伯', '哈萨克斯坦', '乌克兰', '约旦', '乌干达', '以色列', '喀麦隆', '泰国', '越南', '肯尼亚', '匈牙利', '克罗地亚', '爱尔兰', '阿联酋', '叙利亚', '丹麦', '瑞典', '马来西亚', '突尼斯'];$commonProvinces = ['北京', '上海', '广东', '浙江', '江苏', '山东', '四川', '湖北', '湖南', '河南', '河北', '福建', '安徽', '辽宁', '陕西', '江西', '重庆', '云南', '广西', '山西', '台湾', '黑龙江', '吉林', '内蒙古', '甘肃', '新疆', '宁夏', '青海', '海南', '贵州', '西藏'];$commonCities = ['深圳', '广州', '杭州', '成都', '武汉', '西安', '南京', '苏州', '天津', '长沙', '郑州', '青岛', '宁波', '厦门', '大连', '沈阳', '哈尔滨', '长春', '合肥', '福州', '昆明', '贵阳', '南宁', '海口', '兰州', '乌鲁木齐', '呼和浩特', '银川', '西宁', '拉萨'];// 辅助函数:解析逗号分隔的字符串为数组并清洗function parseListInput($input){global $firewall;if (!is_string($input)) return [];$items = array_map('trim', explode(',', $input));// 过滤空值并限制数量 (防止恶意超大提交)$config = $firewall->getConfig();$maxItemLength = $config['config']['max_item_length'] ?? 50; // 单项最大长度,防止超长数据$maxListItems = $config['config']['max_list_items'] ?? 200; // 列表最大项目数,防止内存溢出和 DDoS 攻击return array_slice(array_filter($items, function ($item) use ($maxItemLength) {return $item !== '' && mb_strlen($item) <= $maxItemLength;}), 0, $maxListItems);}// 处理 AJAX 请求if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {header('Content-Type: application/json; charset=utf-8');$response = ['success' => false, 'message' => '未知错误', 'data' => null];try {switch ($_POST['action']) {case 'save_mode':$mode = in_array($_POST['firewall_mode'] ?? '', ['whitelist', 'blacklist']) ? $_POST['firewall_mode'] : 'blacklist';if ($firewall->saveConfig($mode, null, null)) {$response = ['success' => true, 'message' => '✓ 模式已保存'];} else {throw new Exception('保存失败');}break;case 'save_config':// 根据模式保存对应的配置$mode = $_POST['firewall_mode'] ?? 'blacklist';$configData = ['countries' => $_POST['countries'] ?? [],'provinces' => $_POST['provinces'] ?? [],'cities' => $_POST['cities'] ?? [],'priority_ips' => $_POST['priority_ips'] ?? [],];if ($mode === 'whitelist') {if ($firewall->saveConfig($mode, $configData, null)) {$response = ['success' => true, 'message' => '✓ 白名单配置已保存'];}} else {if ($firewall->saveConfig($mode, null, $configData)) {$response = ['success' => true, 'message' => '✓ 黑名单配置已保存'];}}if (!$response['success']) {throw new Exception('保存失败');}break;case 'get_config':$config = $firewall->getConfig();$response = ['success' => true,'data' => $config];break;case 'refresh_data':// 接收日志类型参数$logType = $_POST['log_type'] ?? 'all';$config = $firewall->getConfig();// 用于前端保存后自动刷新统计和日志$response = ['success' => true,'data' => ['stats' => $firewall->getStats(),'logs' => $firewall->getLogs(50, $logType), // 传递日志类型参数'config' => $config // 返回完整配置数据]];break;case 'clear_logs':if ($firewall->clearLogs()) {$response = ['success' => true, 'message' => '✓ 日志已清空'];} else {$response = ['success' => false, 'message' => '✗ 清空日志失败'];}break;default:throw new Exception('无效的操作');}} catch (Exception $e) {$response = ['success' => false, 'message' => '✗ ' . $e->getMessage()];}echo json_encode($response, JSON_UNESCAPED_UNICODE);exit;}?><!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>防火墙管理</title><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box}body {font-family: 'Segoe UI', Arial, sans-serif;background: #f0f2f5;min-height: 100vh;padding: 15px}.container {max-width: 1200px;margin: 0 auto}h1 {color: #333;text-align: center;margin-bottom: 20px;font-size: 1.8em;text-shadow: none}.message {background: #4caf50;color: white;padding: 12px 20px;border-radius: 6px;position: fixed;top: 15px;left: 50%;transform: translateX(-50%);z-index: 9999;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);display: none;animation: fadeIn 0.4s;min-width: 280px;text-align: center;font-weight: 500;}.message.error {background: #ef5350;}@keyframes fadeIn {from {opacity: 0;transform: translateX(-50%) translateY(-8px);}to {opacity: 1;transform: translateX(-50%) translateY(0);}}.tabs {display: flex;margin-bottom: 15px;background: white;border-radius: 8px;overflow: hidden;box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);}.tab-btn {flex: 1;padding: 12px;border: none;background: #f7f9fc;color: #546e7a;font-size: 14px;font-weight: 600;cursor: pointer;transition: all 0.25s;outline: none;}.tab-btn:hover {background: #eceff4;}.tab-btn.active {background: linear-gradient(135deg, #4a6fa5, #6b8dd6);color: white;}.tab-content {display: none;animation: fadeInContent 0.35s;}.tab-content.active {display: block;}@keyframes fadeInContent {from {opacity: 0;transform: translateY(8px);}to {opacity: 1;transform: translateY(0);}}button:disabled {opacity: 0.5;cursor: not-allowed;}.card {background: white;border-radius: 10px;padding: 18px;box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);transition: none;}.card:hover {transform: none;box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);}.card h2 {color: #37474f;margin-bottom: 15px;padding-bottom: 8px;border-bottom: 2px solid #4a6fa5;font-size: 1.3em;}/* 配置卡片主题样式 */.config-card.whitelist-mode {background: linear-gradient(135deg, #ffffff, #f5f7fa);color: #333;}.config-card.whitelist-mode h2 {color: #2e7d32;border-bottom-color: #4caf50;}.config-card.whitelist-mode label {color: #1b5e20;}.config-card.blacklist-mode {background: linear-gradient(135deg, #263238, #37474f);color: #eceff1;}.config-card.blacklist-mode h2 {color: #ef5350;border-bottom-color: #f44336;}.config-card.blacklist-mode label {color: #ffcdd2;}.config-card.blacklist-mode .mode-radio-card {background: #455a64;color: #eceff1;}.config-card.blacklist-mode .tag-container {background: #37474f;border-color: #546e7a;}.stat-item {background: linear-gradient(135deg, #4a6fa5, #6b8dd6);color: white;padding: 15px;border-radius: 8px;text-align: center;cursor: pointer;flex: 1;}.stats-grid {display: flex;gap: 15px;margin-bottom: 20px;}.stat-item.clickable:active {transform: scale(0.98);}/* 清理日志按钮样式 */#clear-logs-btn {background: linear-gradient(135deg, #ef5350, #e53935);}#clear-logs-btn:hover {background: linear-gradient(135deg, #e53935, #d32f2f);box-shadow: 0 3px 12px rgba(239, 83, 80, 0.4);transform: translateY(-1px);}#clear-logs-btn:active {transform: translateY(0);}#clear-logs-btn:disabled {opacity: 0.6;cursor: not-allowed;transform: none !important;}.stat-item .stat-value {font-size: 1.8em;font-weight: bold;display: block}.stat-label {font-size: 0.85em;opacity: 0.95}.form-group {margin-bottom: 15px}label {display: block;margin-bottom: 6px;color: #546e7a;font-weight: 600;font-size: 13px;}select,textarea {width: 100%;padding: 8px 10px;border: 1.5px solid #cfd8dc;border-radius: 6px;font-size: 13px;transition: border-color 0.2s;}select:focus,textarea:focus {outline: none;border-color: #4a6fa5;box-shadow: 0 0 0 3px rgba(74, 111, 165, 0.1);}textarea {min-height: 80px;resize: vertical;font-family: 'Consolas', 'Monaco', monospace;line-height: 1.5;}button,input[type="submit"] {background: linear-gradient(135deg, #4a6fa5, #6b8dd6);color: white;border: none;padding: 8px 20px;border-radius: 6px;font-size: 13px;cursor: pointer;font-weight: 600;transition: all 0.25s;box-shadow: 0 2px 5px rgba(74, 111, 165, 0.3);}button:hover:not(:disabled) {transform: none;box-shadow: 0 2px 5px rgba(74, 111, 165, 0.3);}button:active:not(:disabled) {transform: translateY(0);}.quick-add {margin-top: 8px;padding: 10px;background: #f5f7fa;border-radius: 6px;max-height: 120px;overflow-y: auto;border: 1px solid #e0e4e8;}.quick-add strong {display: block;margin-bottom: 6px;color: #546e7a;font-size: 12px;}.quick-add button {background: linear-gradient(135deg, #64b5f6, #42a5f5);padding: 4px 8px;font-size: 11px;margin: 2px;border-radius: 4px;min-width: auto;}.quick-add button:hover {background: linear-gradient(135deg, #42a5f5, #1e88e5);}/* 表格样式 */table {width: 100%;border-collapse: collapse;margin-top: 12px;font-size: 13px;}th,td {padding: 10px 8px;text-align: left;border-bottom: 1px solid #eceff4;}th {background: #f5f7fa;font-weight: 600;color: #546e7a;font-size: 12px;text-transform: uppercase;}tr:hover {background: #f8f9fa;}.status-badge {padding: 3px 10px;border-radius: 12px;font-size: 11px;font-weight: 600;display: inline-block;}.allow {background: #c8e6c9;color: #2e7d32;}.deny {background: #ffcdd2;color: #c62828;}/* 标签化配置样式 */.tag-container {display: flex;flex-wrap: wrap;gap: 8px;padding: 10px;background: #f8f9fa;border-radius: 6px;min-height: 60px;margin-bottom: 10px;border: 1px solid #e0e4e8;align-items: center;}/* 输入框容器样式 - 独占一行,右对齐 */.tag-input-wrapper {display: flex;gap: 8px;width: 320px;/* 固定宽度 */margin-left: auto;/* 右对齐 */align-items: center;flex-shrink: 0;/* 防止被压缩 */}/* 输入框样式 */.tag-input {flex: 1;padding: 8px 12px;border: 1.5px solid #cfd8dc;border-radius: 6px;font-size: 13px;transition: all 0.2s;outline: none;min-width: 200px;}.tag-input:focus {border-color: #4a6fa5;box-shadow: 0 0 0 3px rgba(74, 111, 165, 0.1);}.tag-input:hover {border-color: #90a4ae;}/* 添加按钮样式优化 */.tag-add-btn {background: linear-gradient(135deg, #64b5f6, #42a5f5);color: white;border: none;padding: 8px 16px;border-radius: 6px;font-size: 13px;font-weight: 600;cursor: pointer;transition: all 0.25s;box-shadow: 0 2px 6px rgba(100, 181, 246, 0.3);white-space: nowrap;}.tag-add-btn:hover {background: linear-gradient(135deg, #42a5f5, #1e88e5);box-shadow: 0 3px 10px rgba(100, 181, 246, 0.4);}.tag-add-btn:active {transform: translateY(0);}/* Datalist 下拉选项样式(浏览器原生支持) */datalist {font-size: 13px;}.tag {display: inline-flex;align-items: center;padding: 6px 12px;border-radius: 16px;font-size: 13px;font-weight: 500;transition: all 0.2s;animation: tagIn 0.3s ease-out;}@keyframes tagIn {from {opacity: 0;transform: scale(0.8);}to {opacity: 1;transform: scale(1);}}.tag-white {background: #e8f5e9;color: #2e7d32;border: 1px solid #c8e6c9;}.tag-black {background: #ffebee;color: #c62828;border: 1px solid #ffcdd2;}.tag-remove {margin-left: 8px;cursor: pointer;font-size: 16px;line-height: 1;opacity: 0.6;transition: opacity 0.2s;}.tag-remove:hover {opacity: 1;}.tag-add-btn {display: inline-flex;align-items: center;padding: 6px 12px;background: #f5f7fa;border: 1px dashed #cfd8dc;border-radius: 16px;color: #546e7a;font-size: 13px;cursor: pointer;transition: all 0.2s;}.tag-add-btn:hover {background: #eceff4;border-color: #4a6fa5;color: #4a6fa5;}.config-card {grid-column: 1 / -1;}.mode-indicator {display: inline-block;padding: 6px 16px;border-radius: 20px;font-size: 13px;font-weight: 600;margin-left: 12px;vertical-align: middle;}.mode-indicator.whitelist {background: #e8f5e9;color: #2e7d32;}.mode-indicator.blacklist {background: #ffebee;color: #c62828;}.btn-save-config {background: linear-gradient(135deg, #66bb6a, #43a047);padding: 12px 32px;font-size: 15px;font-weight: 600;margin-top: 20px;border-radius: 8px;box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);transition: all 0.3s;}.btn-save-config:hover {background: linear-gradient(135deg, #43a047, #2e7d32);transform: translateY(-2px);box-shadow: 0 6px 16px rgba(76, 175, 80, 0.4);}.btn-save-config:active {transform: translateY(0);box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);}/* Radio 模式选择样式 */.mode-radio-container {display: flex;gap: 12px;margin-bottom: 16px;}.mode-radio-label {flex: 1;position: relative;}.mode-radio-input {position: absolute;opacity: 0;width: 0;height: 0;}.mode-radio-card {display: block;padding: 16px 20px;background: #f5f7fa;border: 2px solid #e0e4e8;border-radius: 8px;text-align: center;cursor: pointer;transition: all 0.3s;font-weight: 600;font-size: 14px;color: #546e7a;opacity: 0.6;}.mode-radio-card:hover {background: #eceff4;border-color: #cfd8dc;}.mode-radio-input:checked+.mode-radio-card {background: linear-gradient(135deg, #4a6fa5, #6b8dd6);border-color: #4a6fa5;color: white;opacity: 1;box-shadow: 0 4px 12px rgba(74, 111, 165, 0.3);transform: translateY(-2px);}.mode-radio-card .icon {font-size: 20px;display: block;margin-bottom: 6px;}/* 滚动条美化 */::-webkit-scrollbar {width: 8px;height: 8px;}::-webkit-scrollbar-track {background: #f1f1f1;border-radius: 4px;}::-webkit-scrollbar-thumb {background: #b0bec5;border-radius: 4px;}::-webkit-scrollbar-thumb:hover {background: #90a4ae;}</style></head><body><div class="container"><h1>🛡️ <?php echo $appName; ?></h1><div style="text-align: right; height: 30px;"><span id="mode-indicator" class="mode-indicator blacklist">-</span></div><div id="global-message" class="message"></div><!-- 统计和标签页容器 --><div class="stats-tabs-container"><!-- 统计卡片 --><div class="stats-grid"><div class="stat-item clickable" data-filter="all" title="点击查看所有日志"><span class="stat-value" id="stat-total">-</span><span class="stat-label">📊 总访问</span></div><div class="stat-item clickable" data-filter="allow" title="点击查看允许的日志" style="background:linear-gradient(135deg,#4caf50,#45a049);"><span class="stat-value" id="stat-allowed">-</span><span class="stat-label">✅ 允许</span></div><div class="stat-item clickable" data-filter="deny" title="点击查看拒绝的日志" style="background:linear-gradient(135deg,#f44336,#da190b);"><span class="stat-value" id="stat-blocked">-</span><span class="stat-label">🚫 拒绝</span></div><div class="stat-item clickable" data-filter="config" title="点击查看配置管理" style="background:linear-gradient(135deg,#2196F3,#1e88e5);"><span class="stat-value" id="stat-config">⚙️</span><span class="stat-label"> 配置管理</span></div></div></div><!-- 访问日志标签页 (默认显示) --><div id="tab-logs" class="tab-content active"><div class="card"><h2>📊 访问日志 <span id="log-filter-status" style="font-size:0.6em;color:#6b8dd6;margin-left:8px;"></span><button id="clear-logs-btn" style="float:right;">清理日志</button></h2><div id="logs-container"><p style="text-align:center;color:#999;padding:40px;">加载中...</p></div></div></div><!-- 配置管理标签页 --><div id="tab-config" class="tab-content"><!-- 模式选择与配置卡片 --><div class="card config-card"><h2>🔧 <span id="config-mode">-</span>模式 </h2><form id="form-config"><input type="hidden" name="action" value="save_config"><input type="hidden" name="firewall_mode" id="firewall-mode" value=""><!-- Radio 模式选择 --><div class="mode-radio-container"><label class="mode-radio-label"><input type="radio" name="mode_radio" value="blacklist" class="mode-radio-input" checked><span class="mode-radio-card"><span class="icon">🚫黑名单模式</span><small style="display:block;margin-top:4px;font-size:12px;font-weight:normal;opacity:0.8;">默认允许,仅拒绝名单内</small></span></label><label class="mode-radio-label"><input type="radio" name="mode_radio" value="whitelist" class="mode-radio-input"><span class="mode-radio-card"><span class="icon">✅白名单模式</span><small style="display:block;margin-top:4px;font-size:12px;font-weight:normal;opacity:0.8;">默认拒绝,仅允许名单内</small></span></label></div><div id="config-areas"><!-- 动态生成的配置区域 --></div><button type="button" id="btn-save-config" class="btn-save-config">💾 保存配置</button>⚠️所有操作请注意保存 ! <a href="https://gitee.com/qcod/phpfirewall" target="_blank">Gitee开源地址</a></form></div></div></div><script>$(document).ready(function() {const $msgBox = $('#global-message');let currentLogFilter = 'all';// 配置数据 - 初始化为空,通过 AJAX 加载let configData = {whitelist: {countries: [],provinces: [],cities: [],priority_ips: []},blacklist: {countries: [],provinces: [],cities: [],priority_ips: []}};// 限制配置 - 默认值,可通过 AJAX 返回更新let maxListItems = 200;let maxItemLength = 50;// 快速添加选项const quickOptions = {countries: <?= json_encode($commonCountries) ?>,provinces: <?= json_encode($commonProvinces) ?>,cities: <?= json_encode($commonCities) ?>};function showMessage(text, type) {$msgBox.text(text).removeClass('error').addClass(type === 'error' ? 'error' : '').fadeIn().delay(3000).fadeOut();}// 加载初始数据function loadInitialData() {refreshDashboard('all');}function refreshDashboard(filter) {$.post('', {action: 'refresh_data',log_type: filter || currentLogFilter}, function(res) {if (res?.success && res.data) {$('#stat-total').text(res.data.stats.total);$('#stat-allowed').text(res.data.stats.allowed);$('#stat-blocked').text(res.data.stats.blocked);updateLogsTable(res.data.logs, filter || currentLogFilter);// 如果返回了配置,更新配置数据(防止并发修改导致不一致)if (res.data.config) {configData.whitelist = res.data.config.whitelist || {countries: [],provinces: [],cities: [],priority_ips: []};configData.blacklist = res.data.config.blacklist || {countries: [],provinces: [],cities: [],priority_ips: []};const mode = res.data.config.mode || 'blacklist';const conf = res.data.config.config || {};maxListItems = conf.max_list_items || 200;maxItemLength = conf.max_item_length || 50;// 如果当前在配置页,且模式可能变化,可以选择不强制重绘以免打断用户操作,// 或者只在初始化时使用。这里仅更新数据缓存。$('#firewall-mode').val(mode);// 更新当前模式指示器const modeDescriptions = {'whitelist': ' 工作模式:✅白名单模式(默认拒绝,仅允许名单内)','blacklist': ' 工作模式:🚫黑名单模式(默认允许,仅拒绝名单内)'};const $indicator = $('#mode-indicator');$indicator.removeClass('whitelist blacklist').addClass(mode).text(modeDescriptions[mode] || '未知模式');renderConfigAreas(mode);}}}, 'json');}function updateLogsTable(logs, filter) {const filtered = logs.filter(l =>filter === 'all' ? true : (filter === 'allow' ? l.action === 'ALLOW' : l.action === 'DENY'));let html = '';if (filtered.length) {html = '<table id="logs-table"><thead><tr><th>时间</th><th>IP</th><th>地址</th><th>路径</th><th>状态</th><th>操作</th></tr></thead><tbody>';filtered.forEach(log => {const cls = log.action === 'ALLOW' ? 'allow' : 'deny';html += `<tr><td>${log.time}</td><td style="font-family:monospace;">${log.ip}</td><td>${log.address}</td><td style="font-family:monospace;">${log.path}</td><td>${log.status}</td><td><span class="status-badge ${cls}">${log.action}</span></td></tr>`;});html += '</tbody></table>';} else {const txt = filter === 'allow' ? '允许' : (filter === 'deny' ? '拒绝' : '');html = `<p style="text-align:center;color:#999;padding:40px;">暂无${txt}日志记录</p>`;}$('#logs-container').html(html);}$('.stat-item.clickable').click(function() {const filter = $(this).data('filter');if (filter === 'config') {document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));document.getElementById('tab-config').classList.add('active');return;}currentLogFilter = filter;document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));document.getElementById('tab-logs').classList.add('active');const names = {'all': '全部','allow': '允许','deny': '拒绝'};$('#log-filter-status').text(`(${names[filter]}日志)`);refreshDashboard(currentLogFilter);});// 渲染配置区域function renderConfigAreas(mode) {const $configCard = $('#tab-config .config-card');// 更新指示器$('#config-mode').text(mode === 'whitelist' ? '白名单' : '黑名单');$('input[name="mode_radio"][value="' + mode + '"]').prop('checked', true);// 更新卡片主题$configCard.removeClass('whitelist-mode blacklist-mode').addClass(mode === 'whitelist' ? 'whitelist-mode' : 'blacklist-mode');const isWhitelist = mode === 'whitelist';const data = configData[mode] || {countries: [],provinces: [],cities: [],priority_ips: []};const typeText = isWhitelist ? '允许' : '拒绝';let html_ips = `<div class="form-group"><label>🔥 IP 段 【优先级上(高)到下(低)匹配】:</label><div class="tag-container" id="tag-priority_ips"></div><small style="color:#999;display:block;margin-top:6px;">单个 IP: 192.168.1.1支持 CIDR 格式 (如 192.168.1.0/24) 或范围格式 (如 192.168.1.1-192.168.1.100)</small></div> `;let html_container = `<div class="form-group"><label>${typeText}的国家:</label><div class="tag-container" id="tag-countries"></div></div>`;let html_provinces = `<div class="form-group"><label>${typeText}的省份:</label><div class="tag-container" id="tag-provinces"></div></div>`;let html_cities = `<div class="form-group"><label>${typeText}的城市:</label><div class="tag-container" id="tag-cities"></div></div>`;let html = isWhitelist ? `${html_ips}${html_cities}${html_provinces}${html_container}` : `${html_ips}${html_container}${html_provinces}${html_cities}`;$('#config-areas').html(html);// 渲染已有标签renderTags('priority_ips', data.priority_ips || [], mode);renderTags('countries', data.countries || [], mode);renderTags('provinces', data.provinces || [], mode);renderTags('cities', data.cities || [], mode);}// 渲染标签function renderTags(type, items, mode) {const $container = $(`#tag-${type}`);if ($container.length === 0) return;$container.empty();const isWhitelist = mode === 'whitelist';const tagClass = isWhitelist ? 'tag-white' : 'tag-black';// 添加已选择的标签items.forEach(item => {const $tag = $(`<span class="tag ${tagClass}">${item}<span class="tag-remove" onclick="removeTag('${type}', '${item}')"&g×ばつ</span></span>`);$container.append($tag);});// IP 段使用普通输入框,其他类型使用 datalistif (type === 'priority_ips') {$container.append(`<div class="tag-input-wrapper"><input type="text"class="tag-input"id="input-${type}"placeholder="输入 IP 段,如 192.168.1.0/24 或 192.168.1.1-192.168.1.100"autocomplete="off"onkeypress="handleInputKeyPress(event, '${type}')" /><button type="button" class="tag-add-btn" onclick="addTagFromInput('${type}')" title="添加">+ 添加</button></div>`);} else {// 其他类型使用 datalistconst dataListId = `datalist-${type}`;$container.append(`<div class="tag-input-wrapper"><input type="text"class="tag-input"id="input-${type}"list="${dataListId}"placeholder="输入${type === 'countries' ? '国家' : type === 'provinces' ? '省份' : '城市'}名称..."autocomplete="off"onkeypress="handleInputKeyPress(event, '${type}')" /><button type="button" class="tag-add-btn" onclick="addTagFromInput('${type}')" title="添加">+ 添加</button><datalist id="${dataListId}">${getDatalistOptions(type)}</datalist></div>`);}}// 获取 datalist 选项function getDatalistOptions(type) {const options = quickOptions[type] || [];const currentMode = $('#firewall-mode').val();const currentData = configData[currentMode][type] || [];// 过滤掉已添加的选项,只显示可添加的return options.filter(opt => !currentData.includes(opt)).map(opt => `<option value="${opt}">`).join('');}// 处理输入框回车事件window.handleInputKeyPress = function(event, type) {if (event.key === 'Enter') {event.preventDefault();addTagFromInput(type);}};// 从输入框添加标签window.addTagFromInput = function(type) {const $input = $(`#input-${type}`);const value = $input.val().trim();if (!value) {showMessage('请输入内容', 'error');return;}// IP 段格式验证if (type === 'priority_ips') {// 验证 CIDR 格式或范围格式const cidrRegex = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/;const rangeRegex = /^(\d{1,3}\.){3}\d{1,3}\s*-\s*(\d{1,3}\.){3}\d{1,3}$/;const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;if (!cidrRegex.test(value) && !rangeRegex.test(value) && !ipRegex.test(value)) {showMessage('请输入有效的 IP 段格式(如 192.168.1.0/24 或 192.168.1.1-192.168.1.100)', 'error');return;}// 进一步验证 IP 地址有效性const ipPart = value.split('/')[0] || value.split('-')[0];const octets = ipPart.trim().split('.');for (let octet of octets) {const num = parseInt(octet);if (isNaN(num) || num < 0 || num > 255) {showMessage('IP 地址无效,每段必须在 0-255 之间', 'error');return;}}} else {// 检查是否在选项中const options = quickOptions[type] || [];if (!options.includes(value)) {showMessage('请选择有效的选项', 'error');return;}}addTag(type, value);$input.val(''); // 清空输入框// 更新 datalist 选项(移除已添加的)if (type !== 'priority_ips') {updateDatalist(type);}};// 更新 datalist 选项function updateDatalist(type) {const dataListId = `datalist-${type}`;$(`#${dataListId}`).html(getDatalistOptions(type));}// 添加标签window.addTag = function(type, value) {const mode = $('#firewall-mode').val();if (!configData[mode][type]) {configData[mode][type] = [];}if (!configData[mode][type].includes(value)) {configData[mode][type].push(value);renderTags(type, configData[mode][type], mode);}// $('#tag-selector').removeClass('show');};// 删除标签window.removeTag = function(type, value) {const mode = $('#firewall-mode').val();configData[mode][type] = configData[mode][type].filter(item => item !== value);renderTags(type, configData[mode][type], mode);};// 模式切换(Radio 按钮)$('.mode-radio-input').change(function() {const mode = $(this).val();$('#firewall-mode').val(mode);// 渲染对应的配置区域renderConfigAreas(mode);});// 保存配置(带确认对话框)$('#btn-save-config').click(function() {const mode = $('#firewall-mode').val();const data = configData[mode];// 确认对话框const confirmText = mode === 'whitelist' ?'确定要保存白名单配置吗?\n\n这将只允许名单内的地区访问。' :'确定要保存黑名单配置吗?\n\n这将拒绝名单内的地区访问。';if (!confirm(confirmText)) {return;}const $btn = $(this);const oldText = $btn.text();$btn.prop('disabled', true).text('保存中...');$.post('', {action: 'save_config',firewall_mode: mode,countries: data.countries || [],provinces: data.provinces || [],cities: data.cities || [],priority_ips: data.priority_ips || []}, function(res) {if (typeof res === 'string') try {res = JSON.parse(res);} catch (err) {}showMessage(res?.success ? res.message : (res?.message || '保存失败'),res?.success ? 'success' : 'error');if (res?.success) {refreshDashboard(currentLogFilter);}}, 'json').fail(() => {showMessage('网络错误', 'error');}).always(() => {$btn.prop('disabled', false).text(oldText);});});// 清理日志(带确认对话框)$('#clear-logs-btn').click(function() {if (!confirm('⚠️ 确定要清空所有访问日志吗?\n\n此操作不可恢复,请谨慎操作!')) {return;}const $btn = $(this);const oldText = $btn.text();$btn.prop('disabled', true).text('清理中...');$.post('', {action: 'clear_logs'}, function(res) {if (typeof res === 'string') try {res = JSON.parse(res);} catch (err) {}showMessage(res?.success ? res.message : (res?.message || '清理失败'),res?.success ? 'success' : 'error');if (res?.success) {// 清空成功后刷新统计和日志列表refreshDashboard(currentLogFilter);}}, 'json').fail(() => {showMessage('网络错误', 'error');}).always(() => {$btn.prop('disabled', false).text(oldText);});});// 初始化加载数据loadInitialData();});</script></body></html>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。