Files
CLOAK/dashboard.php
T
2026-06-14 18:30:06 +08:00

1229 lines
58 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/cong.php';
require_once __DIR__ . '/admin/bootstrap.php';
require_once __DIR__ . '/admin/ConfigWriter.php';
require_once __DIR__ . '/lib/Cloak/DomainRepository.php';
require_once __DIR__ . '/lib/CloakAdSourceGuard.php';
require_once __DIR__ . '/lib/Cloak/CloudflareConfig.php';
require_once __DIR__ . '/lib/Cloak/DomainProvisioningService.php';
session_start();
$filename = basename(__FILE__);
$current_name = pathinfo($filename, PATHINFO_FILENAME);
// 登出
if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "logout") {
unset($_SESSION['password']);
unset($_SESSION['check_result']);
echo("<script>window.location='{$current_name}';</script>");
exit();
}
// 重置临时链接参数
if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "delUrlArgs") {
$check_name = strip_tags($_REQUEST['check_name']);
$dir = dirname(__FILE__) . '/args';
$files = glob($dir . '/' . $check_name . "_*.txt");
foreach ($files as $file) {
if (preg_match('/_(\d{10})\.txt$/', $file, $m)) {
unlink($file);
}
}
header('Location: ' . $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . '?action=edit_config&name=' . urlencode($check_name));
exit;
}
// 确定当前配置上下文(供表单回显用)
$isNewConfigDraft = false;
if (!empty($_REQUEST["name"])) {
$check_name = trim($_REQUEST["name"]);
$config_name = str_replace(".php", "", $check_name);
$config_file = "check_config/" . $config_name . "_config.php";
if (!file_exists($config_file)) {
// 新建或未保存的配置:仅回显 index 模板,不在此写盘
$isNewConfigDraft = true;
$config_file = "check_config/index_config.php";
}
} else {
$check_name = "index";
$config_file = "check_config/index_config.php";
}
include($config_file);
// 密码处理
if (!empty($_REQUEST['password'])) {
$_SESSION['password'] = $_REQUEST['password'];
$_SESSION["myaction"] = $_REQUEST['myaction'];
}
// 设置测试访问状态(写入完整 Session 判定缓存,便于测试广告落地链接直达真实页)
if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "setuvi") {
require_once __DIR__ . '/lib/Cloak/VisitorRepository.php';
if (!empty($_REQUEST["res"]) && $_REQUEST["res"] == "true") {
cloak_session_mark_real_visitor();
}
if (!empty($_REQUEST["res"]) && $_REQUEST["res"] == "false") {
cloak_session_clear_real_visitor();
}
echo("<script>window.location='{$current_name}?action=edit_config&name={$check_name}';</script>");
exit();
}
// 保存配置
if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "submit") {
$check_name = trim($_REQUEST["check_name"]);
$methods = trim($_REQUEST["methods"]);
$country = trim($_REQUEST["country"]);
$blacklist_group_id = (int)($_REQUEST["blacklist_group_id"] ?? 0);
$whitelist_group_id = (int)($_REQUEST["whitelist_group_id"] ?? 0);
try {
$pdoDash = new PDO(
'mysql:host=127.0.0.1;dbname=' . DB_NAME . ';charset=utf8mb4',
DB_USERNAME, DB_PASSWORD,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$chk = $pdoDash->prepare('SELECT 1 FROM ip_groups WHERE id = ? AND type = ? LIMIT 1');
if ($blacklist_group_id > 0) {
$chk->execute([$blacklist_group_id, 'black']);
if (!$chk->fetchColumn()) $blacklist_group_id = 0;
}
if ($whitelist_group_id > 0) {
$chk->execute([$whitelist_group_id, 'white']);
if (!$chk->fetchColumn()) $whitelist_group_id = 0;
}
} catch (Throwable $e) {
$blacklist_group_id = $whitelist_group_id = 0;
}
$DB_fp = $_REQUEST["DB_fp"];
$DB_zp = trim($_REQUEST["DB_zp"]);
$redirect_method = trim($_REQUEST["redirect_method"]);
$show_content = trim($_REQUEST["show_content"]);
$costm_ip_score = trim($_REQUEST["costm_ip_score"]);
$write_log = trim($_REQUEST["write_log"]);
$zh_on = trim($_REQUEST["zh_on"]);
$os_on = trim($_REQUEST["os_on"]);
$mobile_on = trim($_REQUEST["mobile_on"]);
$v_referer = trim($_REQUEST["v_referer"]);
$is_virtual = trim($_REQUEST["is_virtual"]);
$auto_black = trim($_REQUEST["auto_black"]);
$auto_black_times = trim($_REQUEST["auto_black_times"]);
$white_params = trim($_REQUEST["white_params"]);
$keep_params = trim($_REQUEST["keep_params"]);
$iphone_model = $_REQUEST["iphone_model"];
$risk_number = $_REQUEST["risk_number"];
$risk_enhanced = in_array(strtoupper(trim($_REQUEST["risk_enhanced"] ?? '')), ['ON']) ? 'ON' : 'OFF';
$url_args_timeout = $_REQUEST["url_args_timeout"];
$debug_mode = in_array(strtoupper(trim($_REQUEST["debug_mode"] ?? '')), ['ON']) ? 'ON' : 'OFF';
$ad_fb = in_array(strtoupper(trim($_REQUEST["ad_fb"] ?? '')), ['ON']) ? 'ON' : 'OFF';
$ad_google = in_array(strtoupper(trim($_REQUEST["ad_google"] ?? '')), ['ON']) ? 'ON' : 'OFF';
$ad_tiktok = in_array(strtoupper(trim($_REQUEST["ad_tiktok"] ?? '')), ['ON']) ? 'ON' : 'OFF';
$ad_strict = in_array(strtoupper(trim($_REQUEST["ad_strict"] ?? '')), ['ON']) ? 'ON' : 'OFF';
$config_name_submit = str_replace('.php', '', $check_name);
$existing_spm_id = '';
$existing_cfg = 'check_config/' . $config_name_submit . '_config.php';
if (file_exists($existing_cfg)) {
$existing_raw = file_get_contents($existing_cfg);
if (preg_match("/define\('CLOAK_AD_SPM_ID',\s*'([^']*)'\)/", $existing_raw, $spmM)) {
$existing_spm_id = $spmM[1];
}
}
$ad_spm_id = CloakAdSourceGuard::isValidSpmId($existing_spm_id) ? $existing_spm_id : '';
if (($ad_fb === 'ON' || $ad_google === 'ON' || $ad_tiktok === 'ON') && $ad_spm_id === '') {
$ad_spm_id = CloakAdSourceGuard::generateSpmId();
}
if (strtoupper($auto_black) === 'ON' && $blacklist_group_id <= 0) {
echo "<script>alert('开启「自动黑名单」时,必须先选择「黑名单 IP 组」。');history.back();</script>";
exit();
}
if (!empty($methods)) {
ConfigWriter::save($check_name, [
'methods' => $methods,
'blacklist_group_id' => $blacklist_group_id,
'whitelist_group_id' => $whitelist_group_id,
'country' => $country,
'show_content' => $show_content,
'DB_fp' => $DB_fp,
'costm_ip_score' => $costm_ip_score,
'auto_black' => $auto_black,
'is_virtual' => $is_virtual,
'auto_black_times' => $auto_black_times,
'white_params' => $white_params,
'iphone_model' => $iphone_model,
'write_log' => $write_log,
'zh_on' => $zh_on,
'os_on' => $os_on,
'keep_params' => $keep_params,
'redirect_method' => $redirect_method,
'DB_zp' => $DB_zp,
'mobile_on' => $mobile_on,
'v_referer' => $v_referer,
'risk_number' => $risk_number,
'risk_enhanced' => $risk_enhanced,
'url_args_timeout' => $url_args_timeout,
'debug_mode' => $debug_mode,
'ad_fb' => $ad_fb,
'ad_google' => $ad_google,
'ad_tiktok' => $ad_tiktok,
'ad_strict' => $ad_strict,
'ad_spm_id' => $ad_spm_id,
]);
try {
$pdoBind = DomainRepository::pdo();
DomainRepository::ensureTable($pdoBind);
$siteDomainId = (int) ($_REQUEST['site_domain_id'] ?? 0);
if ($siteDomainId > 0) {
DomainRepository::bindConfigToDomain($pdoBind, $check_name, $siteDomainId);
} else {
DomainRepository::clearConfigBinding($pdoBind, $check_name);
}
} catch (Throwable $e) {
}
echo("<script>alert('保存成功!');window.location='{$current_name}?action=edit_config&name={$check_name}';</script>");
exit();
}
}
function write_file($file_name, $text_str) {
$f = fopen($file_name, "w");
rewind($f);
fwrite($f, $text_str);
fclose($f);
}
function onoff_radio($name, $val, $label_on='开启', $label_off='关闭') {
$on = ($val == 'ON') ? 'active' : '';
$off = ($val != 'ON') ? 'active' : '';
return '<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-sm btn-outline-secondary ' . $on . '">
<input type="radio" name="' . $name . '" value="ON" ' . ($val=='ON'?'checked':'') . '> ' . $label_on . '
</label>
<label class="btn btn-sm btn-outline-secondary ' . $off . '">
<input type="radio" name="' . $name . '" value="OFF" ' . ($val!='ON'?'checked':'') . '> ' . $label_off . '
</label>
</div>';
}
function dash_truncate_attr($text, $maxLen = 52) {
$text = (string) $text;
$esc = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
if (mb_strlen($text, 'UTF-8') <= $maxLen) {
return $esc;
}
return '<span class="cell-truncate" title="' . $esc . '">' . htmlspecialchars(mb_substr($text, 0, $maxLen, 'UTF-8'), ENT_QUOTES, 'UTF-8') . '…</span>';
}
if (strlen($text) <= $maxLen) {
return $esc;
}
return '<span class="cell-truncate" title="' . $esc . '">' . htmlspecialchars(substr($text, 0, $maxLen), ENT_QUOTES, 'UTF-8') . '…</span>';
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IPCLOAKAPI 多功能管理后台面板 v8.2.1</title>
<link rel="stylesheet" href="./dist/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="./dist/jquery-2.2.4.min.js"></script>
<script src="./dist/bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script>
<style>
body { background:#f0f2f5; font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif; }
.topbar { background:linear-gradient(135deg,#16baaa,#0e8d81); color:#fff; padding:14px 24px; display:flex; align-items:center; justify-content:space-between; box-shadow:0 2px 8px rgba(0,0,0,.15); }
.topbar a { color:rgba(255,255,255,.85); text-decoration:none; font-size:.9rem; }
.topbar a:hover { color:#fff; }
.topbar h1 { margin:0; font-size:1.25rem; font-weight:600; }
.page-body { max-width:1100px; margin:32px auto; padding:0 16px; }
.status-bar { display:flex; gap:12px; margin-bottom:20px; flex-wrap:wrap; }
.status-badge { display:inline-flex; align-items:center; gap:6px; padding:6px 14px; border-radius:20px; font-size:.82rem; font-weight:500; }
.status-ok { background:#d4edda; color:#155724; }
.status-err { background:#f8d7da; color:#721c24; }
.card-header-custom { background:#fff; border-bottom:1px solid #e9ecef; padding:14px 20px; display:flex; align-items:center; justify-content:space-between; border-radius:8px 8px 0 0; }
.card-header-custom h5 { margin:0; font-size:1rem; font-weight:600; }
.config-table th { background:#f8f9fa; font-weight:600; }
.config-table td, .config-table th { vertical-align:middle !important; padding:12px 16px !important; }
.config-table tr:hover { background:#f0f7ff; }
.url-text { font-size:.78rem; color:#6c757d; }
.ad-link-row { display:flex; align-items:flex-start; gap:6px; margin-bottom:6px; }
.ad-link-row:last-child { margin-bottom:0; }
.ad-link-label { font-size:.72rem; color:#495057; white-space:nowrap; min-width:58px; padding-top:2px; }
.btn-copy-ad { font-size:.72rem; padding:2px 8px; flex-shrink:0; }
.ad-link-hint { font-size:.68rem; color:#adb5bd; margin-top:4px; }
.section-heading { background:#e8f4fd; color:#1565c0; font-weight:600; padding:8px 14px; border-radius:4px; margin:6px 0 2px; font-size:.85rem; letter-spacing:.3px; }
.form-label-cell { width:36%; font-size:.88rem; color:#444; padding:10px 14px !important; }
.form-val-cell { padding:8px 14px !important; }
.form-section { border:1px solid #dee2e6; border-radius:6px; margin-bottom:8px; overflow:hidden; }
.form-section .section-toggle { background:#f8f9fa; padding:10px 16px; cursor:pointer; display:flex; align-items:center; justify-content:space-between; font-weight:600; font-size:.9rem; border:none; width:100%; text-align:left; }
.form-section .section-toggle:hover { background:#e9ecef; }
.form-section .section-toggle .arrow { transition:transform .2s; }
.form-section .section-toggle.collapsed .arrow { transform:rotate(-90deg); }
.back-link { display:inline-flex; align-items:center; gap:6px; color:#1565c0; font-size:.9rem; margin-bottom:16px; text-decoration:none; }
.back-link:hover { text-decoration:underline; }
.sticky-actions { position:sticky; bottom:0; background:#fff; border-top:1px solid #dee2e6; padding:12px 20px; display:flex; gap:10px; align-items:center; z-index:100; border-radius:0 0 8px 8px; }
.login-wrap { min-height:100vh; display:flex; align-items:center; justify-content:center; }
.login-card { background:#fff; border-radius:10px; box-shadow:0 4px 20px rgba(0,0,0,.1); padding:40px; width:340px; }
.login-card h2 { text-align:center; color:#16baaa; margin-bottom:24px; font-size:1.4rem; }
.list-page-title { margin-bottom: 4px; }
.list-page-title h2 { margin: 0 0 6px; font-size: 1.2rem; font-weight: 600; color: #2c3e50; }
.list-page-title p { margin: 0; font-size: .85rem; color: #8c98a4; }
.card-shell { border-radius: 10px; overflow: hidden; border: 1px solid #e8ecf1; box-shadow: 0 2px 12px rgba(15,23,42,.04); }
.card-header-custom .header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.card-header-custom .header-actions .btn-teal { background: #16baaa; color: #fff; border: none; }
.card-header-custom .header-actions .btn-teal:hover { background: #0e8d81; color: #fff; }
.list-table-wrap { overflow-x: auto; }
.config-table thead th {
position: sticky;
top: 0;
z-index: 5;
background: linear-gradient(180deg, #f4f7fb 0%, #eef2f7 100%) !important;
border-bottom: 2px solid #dde4ec !important;
font-size: .82rem;
letter-spacing: .02em;
text-transform: none;
white-space: nowrap;
}
.config-table tbody tr:nth-child(even) { background: #fafbfc; }
.config-table tbody tr:hover { background: #eef6ff !important; }
.config-table tbody tr { transition: background-color .15s ease; }
.config-table tbody td { border-color: #eef1f5 !important; font-size: .86rem; }
.list-table thead th {
background: #f4f7fb;
font-weight: 600;
font-size: .82rem;
border-bottom: 2px solid #dde4ec !important;
padding: 10px 14px !important;
white-space: nowrap;
}
.list-table tbody tr:nth-child(even) { background: #fafbfc; }
.list-table tbody tr:hover { background: #eef6ff; }
.list-table tbody td { vertical-align: middle !important; padding: 11px 14px !important; border-color: #eef1f5 !important; font-size: .84rem; }
.cell-truncate { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; cursor: default; }
.url-text-truncate { font-size: .78rem; color: #6c757d; word-break: break-all; }
.action-btn-group { display: flex; flex-wrap: wrap; gap: 6px; }
.action-btn-group .btn { border-radius: 6px; }
.domain-host-badge { display: inline-block; padding: 2px 8px; background: #e8f4fd; color: #1565c0; border-radius: 4px; font-size: .78rem; font-family: 'SF Mono', Consolas, monospace; }
.config-name-cell strong { color: #2c3e50; font-size: .92rem; }
.empty-hint { padding: 28px 20px; text-align: center; color: #8c98a4; font-size: .88rem; background: #fafbfc; border-radius: 0 0 10px 10px; }
</style>
</head>
<body>
<?php if (empty($_SESSION['password'])): ?>
<!-- ===================== 登录页 ===================== -->
<div class="login-wrap">
<div class="login-card">
<h2>IPCLOAKAPI 多功能管理后台面板 v8.2.1</h2>
<form method="post" action="<?php echo $current_name; ?>">
<div class="form-group">
<label>登录密码</label>
<input type="password" name="password" class="form-control" placeholder="请输入密码" autofocus>
</div>
<input type="hidden" name="myaction" value="dolist">
<button type="submit" class="btn btn-block" style="background:#16baaa;color:#fff;margin-top:8px;">进入后台</button>
</form>
</div>
</div>
<?php exit(); endif; ?>
<?php
// 密码验证
if ($_SESSION['password'] != LOGIN_PASSWORD) {
unset($_SESSION['password']);
echo("<script>alert('密码错误!');window.location='{$current_name}';</script>");
exit();
}
// 删除配置
if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "delete_config" && !empty($_REQUEST["name"])) {
$name = trim($_REQUEST["name"]);
if ($name == "index") {
echo("<script>alert('请勿删除默认配置!');window.location='{$current_name}';</script>");
exit();
}
$cfg = str_replace(".php", "", $name);
try {
DomainRepository::clearConfigBinding(DomainRepository::pdo(), $cfg);
} catch (Throwable $e) {
}
@unlink("check_config/{$cfg}_config.php");
@unlink("{$cfg}.php");
echo("<script>alert('删除成功!');window.location='{$current_name}';</script>");
exit();
}
// 站点域名管理
$cloakDomainMsg = '';
$cloakDomainErr = '';
$cloakCfEnabled = CloudflareConfig::isEnabled();
if (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'add_domain' && !empty($_POST['new_hostname'])) {
try {
$pdoDom = DomainRepository::pdo();
$res = DomainProvisioningService::provisionOnAdd($pdoDom, $_POST['new_hostname']);
if ($res['ok']) {
$cloakDomainMsg = '域名「' . htmlspecialchars($res['hostname']) . '」已添加';
if (!empty($res['nameservers'])) {
$nsHtml = implode('</code>、<code>', array_map('htmlspecialchars', $res['nameservers']));
$cloakDomainMsg .= '。请到注册商将 NS 修改为:<code>' . $nsHtml . '</code>,然后点击「检测/刷新」。';
} else {
$cloakDomainMsg .= ',请在编辑配置时关联此域名。';
}
} else {
$cloakDomainErr = $res['error'] ?? '添加失败';
}
} catch (Throwable $e) {
$cloakDomainErr = $e->getMessage();
}
}
if (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'check_domain') {
$domainId = (int) ($_POST['domain_id'] ?? $_REQUEST['domain_id'] ?? 0);
$jsonOut = isset($_REQUEST['format']) && $_REQUEST['format'] === 'json';
try {
$res = DomainProvisioningService::checkAndProvision(DomainRepository::pdo(), $domainId);
if ($jsonOut) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode($res, JSON_UNESCAPED_UNICODE);
exit;
}
if ($res['ok']) {
$cloakDomainMsg = $res['message'] ?? '检测完成';
} else {
$cloakDomainErr = $res['message'] ?? '检测失败';
}
} catch (Throwable $e) {
if ($jsonOut) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => false, 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
exit;
}
$cloakDomainErr = $e->getMessage();
}
}
if (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'delete_domain' && !empty($_REQUEST['domain_id'])) {
try {
$res = DomainProvisioningService::deleteWithCloudflare(DomainRepository::pdo(), (int) $_REQUEST['domain_id']);
if ($res['ok']) {
$cloakDomainMsg = $res['message'] ?? '域名已删除。';
if (!empty($res['warning'])) {
$cloakDomainErr = $res['warning'];
}
} else {
$cloakDomainErr = $res['message'] ?? '删除失败';
}
} catch (Throwable $e) {
$cloakDomainErr = $e->getMessage();
}
}
// 系统状态检测(列表页用)
function dash_check_mysql() {
try {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=' . DB_NAME . ';charset=utf8mb4', DB_USERNAME, DB_PASSWORD, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
return true;
} catch (Throwable $e) { return false; }
}
function dash_check_redis() {
if (!extension_loaded('redis') || strtoupper(REDIS_ENABLED) !== 'ON') return null;
try {
$r = new Redis();
if (!@$r->connect(REDIS_HOST, (int)REDIS_PORT, 0.5)) return false;
$r->ping();
return true;
} catch (Throwable $e) { return false; }
}
function dash_cfg_get($raw, $name)
{
if (preg_match("/define\('" . preg_quote($name, '/') . "',\s*'([^']*)'\)/", $raw, $m)) {
return $m[1];
}
return '';
}
function dash_build_ad_links($pf, $protocol, $host)
{
$path = __DIR__ . "/check_config/{$pf}_config.php";
try {
$pdoDashHost = DomainRepository::pdo();
DomainRepository::ensureTable($pdoDashHost);
$bound = DomainRepository::primaryHostnameForConfig($pdoDashHost, $pf);
if ($bound !== '') {
$host = $bound;
}
} catch (Throwable $e) {
}
$host = DomainRepository::normalizeHostname($host);
if ($host === '') {
$host = DomainRepository::normalizeHostname($_SERVER['HTTP_HOST'] ?? '');
}
$base = rtrim($protocol . $host, '/') . '/' . (($pf != 'index') ? $pf : '');
if (!file_exists($path)) {
return [['label' => '落地链接', 'url' => $base]];
}
$raw = file_get_contents($path);
$enabled = [];
if (dash_cfg_get($raw, 'CLOAK_AD_FB') === 'ON') {
$enabled[] = CloakAdSourceGuard::PLATFORM_FB;
}
if (dash_cfg_get($raw, 'CLOAK_AD_GOOGLE') === 'ON') {
$enabled[] = CloakAdSourceGuard::PLATFORM_GOOGLE;
}
if (dash_cfg_get($raw, 'CLOAK_AD_TIKTOK') === 'ON') {
$enabled[] = CloakAdSourceGuard::PLATFORM_TIKTOK;
}
if (empty($enabled)) {
return [['label' => '落地链接', 'url' => $base]];
}
$spmId = dash_cfg_get($raw, 'CLOAK_AD_SPM_ID');
$query = CloakAdSourceGuard::buildAdLinkQuery($enabled, $spmId);
$url = $base . ($query !== '' ? '?' . $query : '');
return [['label' => '落地链接', 'url' => $url]];
}
// 遍历配置列表
$products_file_ary = [];
$dd = opendir("check_config");
while (false !== ($f = readdir($dd))) {
if ($f == "." || $f == "..") continue;
if (filetype("check_config/$f") == "dir" || $f == "index.php" || $f == "a.txt") continue;
$fname = str_replace("_config.php", "", $f);
$products_file_ary[] = $fname;
if (!file_exists($fname . ".php")) {
copy("tem_link.php", $fname . ".php");
}
}
closedir($dd);
sort($products_file_ary);
// 加载 IP 组(编辑页用)
$blackGroups = $whiteGroups = [];
try {
$pdoDash = new PDO('mysql:host=127.0.0.1;dbname=' . DB_NAME . ';charset=utf8mb4', DB_USERNAME, DB_PASSWORD, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
$stb = $pdoDash->prepare("SELECT id, name FROM ip_groups WHERE type = 'black' ORDER BY id ASC");
$stb->execute(); $blackGroups = $stb->fetchAll();
$stw = $pdoDash->prepare("SELECT id, name FROM ip_groups WHERE type = 'white' ORDER BY id ASC");
$stw->execute(); $whiteGroups = $stw->fetchAll();
} catch (Throwable $e) {}
$currentBlacklistGroupId = defined('BLACKLIST_GROUP_ID') ? (int)BLACKLIST_GROUP_ID : 0;
$currentWhitelistGroupId = defined('WHITELIST_GROUP_ID') ? (int)WHITELIST_GROUP_ID : 0;
$action = $_REQUEST['action'] ?? '';
$isEdit = in_array($action, ['edit_config', 'submit'], true);
$cloakSiteDomains = [];
$selectedDomainId = 0;
$dashboardDefaultHost = DomainRepository::normalizeHostname($_SERVER['HTTP_HOST'] ?? '');
try {
$pdoDash = $pdoDash ?? DomainRepository::pdo();
DomainRepository::ensureTable($pdoDash);
$cloakSiteDomains = DomainRepository::listAll($pdoDash);
if ($isEdit) {
$selectedDomainId = DomainRepository::domainIdForConfig($pdoDash, $check_name);
}
} catch (Throwable $e) {
$cloakSiteDomains = [];
}
// 视图判断
$_d = function($name, $default) { return defined($name) ? constant($name) : $default; };
?>
<!-- ===================== 顶栏 ===================== -->
<div class="topbar">
<h1>IPCLOAKAPI 多功能管理后台面板 v8.2.1</h1>
<div style="display:flex;gap:16px;align-items:center;">
<?php if ($isEdit): ?>
<a href="<?php echo $current_name; ?>">← 返回列表</a>
<?php endif; ?>
<a href="<?php echo $current_name; ?>?action=logout">退出登录</a>
</div>
</div>
<div class="page-body">
<?php if (!$isEdit): ?>
<!-- ===================== 列表视图 ===================== -->
<div class="list-page-title">
<h2>配置管理</h2>
<p>管理 Cloak 配置、广告落地链接与站点域名</p>
</div>
<!-- 系统状态 -->
<div class="status-bar">
<?php
$mysqlOk = dash_check_mysql();
$redisOk = dash_check_redis();
echo '<span class="status-badge ' . ($mysqlOk ? 'status-ok' : 'status-err') . '">● MySQL ' . ($mysqlOk ? '已连接' : '连接失败') . '</span>';
if ($redisOk === null) {
echo '<span class="status-badge" style="background:#fff3cd;color:#856404;">● Redis 未启用</span>';
} else {
echo '<span class="status-badge ' . ($redisOk ? 'status-ok' : 'status-err') . '">● Redis ' . ($redisOk ? '已连接' : '连接失败') . '</span>';
}
?>
<span class="status-badge" style="background:#e2e3e5;color:#383d41;">● 配置数:<?php echo count($products_file_ary); ?></span>
<span class="status-badge" style="background:#e2e3e5;color:#383d41;">● 已登记域名:<?php echo count($cloakSiteDomains); ?></span>
</div>
<?php if ($cloakDomainMsg): ?>
<div class="alert alert-success"><?php echo $cloakDomainMsg; ?></div>
<?php endif; ?>
<?php if ($cloakDomainErr): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($cloakDomainErr); ?></div>
<?php endif; ?>
<!-- 配置列表卡片 -->
<div class="card shadow-sm card-shell">
<div class="card-header-custom">
<h5>配置列表</h5>
<div class="header-actions">
<a href="/table" target="_blank" class="btn btn-sm btn-outline-secondary">查看日志</a>
<a href="/log_analytics.php" target="_blank" class="btn btn-sm btn-outline-secondary">数据分析</a>
<a href="<?php echo $current_name; ?>?action=edit_config&name=new_config" class="btn btn-sm btn-teal">+ 新增配置</a>
</div>
</div>
<div class="list-table-wrap">
<table class="table config-table mb-0">
<thead>
<tr>
<th style="width:22%">配置名称</th>
<th style="width:14%">绑定域名</th>
<th>广告链接(广告后台填入的链接)</th>
<th>真实页链接(客户到达的链接)</th>
<th style="width:22%">操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($products_file_ary as $pf):
$pcfg = "check_config/{$pf}_config.php";
$pfp = [];
if (file_exists($pcfg)) {
// 简单读取 DB_FP 和 DB_ZP 值用于展示
$raw = file_get_contents($pcfg);
if (preg_match("/define\('DB_FP',\s*(array\s*\(.*?\))\s*\)/s", $raw, $m)) {
@eval('$pfp = ' . $m[1] . ';');
}
preg_match("/define\('DB_ZP',\s*'([^']*)'\)/", $raw, $zpM);
$pzp = $zpM[1] ?? '';
}
?>
<tr>
<td class="config-name-cell">
<strong><?php echo htmlspecialchars($pf); ?></strong>
<?php if ($pf === 'index'): ?><span class="badge badge-secondary ml-1" style="font-size:.7rem;">默认</span><?php endif; ?>
</td>
<td style="font-size:.82rem;">
<?php
$pfHost = '';
try {
$pfHost = DomainRepository::primaryHostnameForConfig($pdoDash ?? DomainRepository::pdo(), $pf);
} catch (Throwable $e) {
}
if ($pfHost !== '') {
echo '<span class="domain-host-badge" title="' . htmlspecialchars($pfHost, ENT_QUOTES) . '">' . htmlspecialchars($pfHost) . '</span>';
} else {
echo '<span class="text-muted cell-truncate" title="' . htmlspecialchars($dashboardDefaultHost, ENT_QUOTES) . '">' . htmlspecialchars($dashboardDefaultHost) . '<br><small>(默认后台域)</small></span>';
}
?>
</td>
<td>
<?php
$adLinks = dash_build_ad_links($pf, $protocol, $_SERVER['HTTP_HOST']);
foreach ($adLinks as $link):
?>
<div class="ad-link-row">
<span class="ad-link-label"><?php echo htmlspecialchars($link['label']); ?></span>
<div class="url-text flex-grow-1 url-text-truncate"><?php echo dash_truncate_attr($link['url'], 56); ?></div>
<button type="button" class="btn btn-sm btn-outline-secondary btn-copy-ad" data-copy="<?php echo htmlspecialchars($link['url'], ENT_QUOTES); ?>">复制</button>
</div>
<?php endforeach; ?>
</td>
<td>
<?php if (!empty($pfp)) {
$first = is_array($pfp) ? $pfp[0] : $pfp;
echo '<div class="url-text url-text-truncate">' . dash_truncate_attr($first, 48) . '</div>';
if (is_array($pfp) && count($pfp) > 1) {
echo '<div class="url-text text-muted">+' . (count($pfp)-1) . ' 个链接</div>';
}
} else { ?>
<span class="text-muted" style="font-size:.82rem;">—</span>
<?php } ?>
</td>
<td>
<div class="action-btn-group">
<a href="<?php echo $current_name . '?action=edit_config&name=' . $pf; ?>" class="btn btn-sm btn-outline-primary">编辑</a>
<a href="/<?php echo $pf; ?>.php" target="_blank" class="btn btn-sm btn-outline-secondary">查看</a>
<?php if ($pf !== 'index'): ?>
<button class="btn btn-sm btn-outline-danger" onclick="if(confirm('确定删除配置「<?php echo htmlspecialchars($pf); ?>」?')) location='<?php echo $current_name . '?action=delete_config&name=' . $pf; ?>'">删除</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- 站点域名 -->
<div class="card shadow-sm card-shell mb-4" style="margin-top: 16px;">
<div class="card-header-custom">
<h5>站点域名</h5>
</div>
<div style="padding:16px 20px;">
<?php if ($cloakCfEnabled): ?>
<div class="alert alert-info mb-3" style="font-size:.88rem;">
<strong>Cloudflare 已启用:</strong>添加域名后将自动创建 Zone 并显示 Nameserver;请到注册商修改 NS 后点击「检测/刷新」。NS 生效后将自动配置 proxied A 记录、Flexible SSL 与 Always HTTPS。
源站仍需在宝塔添加站点并监听 <code><?php echo htmlspecialchars(CloudflareConfig::serverIp() ?: 'SERVER_IP'); ?></code>Flexible:访客↔CF 为 HTTPSCF↔源站为 HTTP)。
</div>
<?php else: ?>
<div class="alert alert-warning mb-3" style="font-size:.88rem;">
<strong>使用前请先在宝塔完成:</strong>在宝塔面板 → 网站 → 添加站点(填写下方相同域名)→ 申请并开启 SSL 证书。完成后再在此添加域名并关联配置。
若在 <code>cong.php</code> 配置 Cloudflare API Token 与 Account ID,可自动创建 Zone 并管理 DNS/SSL。
</div>
<?php endif; ?>
<form method="post" action="<?php echo $current_name; ?>" class="form-inline mb-3">
<input type="hidden" name="action" value="add_domain">
<input type="text" name="new_hostname" class="form-control form-control-sm mr-2" style="min-width:280px;" placeholder="例如 shop.example.com" required>
<button type="submit" class="btn btn-sm" style="background:#16baaa;color:#fff;">添加域名</button>
</form>
<?php if (empty($cloakSiteDomains)): ?>
<div class="empty-hint">尚未添加域名。未添加时,广告链接将使用当前后台访问域名:<code><?php echo htmlspecialchars($dashboardDefaultHost); ?></code></div>
<?php else: ?>
<div class="list-table-wrap">
<table class="table table-sm mb-0 list-table">
<thead><tr>
<th>域名</th>
<th>关联配置</th>
<?php if ($cloakCfEnabled): ?><th>Cloudflare</th><th>Nameserver</th><?php endif; ?>
<th style="width:140px;">操作</th>
</tr></thead>
<tbody>
<?php foreach ($cloakSiteDomains as $dom):
$cfStatus = $dom['cf_status'] ?? 'local';
$cfLabel = CloudflareConfig::statusLabel($cfStatus);
$badgeCls = 'secondary';
if ($cfStatus === 'pending_ns') { $badgeCls = 'warning'; }
elseif ($cfStatus === 'provisioning') { $badgeCls = 'info'; }
elseif ($cfStatus === 'ready') { $badgeCls = 'success'; }
elseif ($cfStatus === 'error') { $badgeCls = 'danger'; }
$nsList = CloudflareConfig::parseNameservers($dom['cf_nameservers'] ?? '');
$delConfirm = '确定删除该域名?';
if ($cloakCfEnabled && !empty($dom['cf_zone_id'])) {
$delConfirm .= '将同时删除 Cloudflare Zone(不可恢复)。';
}
?>
<tr>
<td><span class="domain-host-badge" title="<?php echo htmlspecialchars($dom['hostname'], ENT_QUOTES); ?>"><?php echo htmlspecialchars($dom['hostname']); ?></span></td>
<td><?php
if ($dom['config_name'] !== '') {
echo '<span class="badge badge-light border">' . htmlspecialchars($dom['config_name']) . '</span>';
} else {
echo '<span class="text-muted">未关联</span>';
}
?></td>
<?php if ($cloakCfEnabled): ?>
<td>
<span class="badge badge-<?php echo $badgeCls; ?>"><?php echo htmlspecialchars($cfLabel); ?></span>
<?php if ($cfStatus === 'error' && !empty($dom['cf_error'])): ?>
<br><small class="text-danger"><?php echo htmlspecialchars($dom['cf_error']); ?></small>
<?php endif; ?>
</td>
<td style="font-size:.8rem;max-width:220px;">
<?php if ($nsList): ?>
<?php foreach ($nsList as $ns): ?><div><span class="cell-truncate domain-host-badge" style="display:block;max-width:200px;" title="<?php echo htmlspecialchars($ns, ENT_QUOTES); ?>"><?php echo htmlspecialchars($ns); ?></span></div><?php endforeach; ?>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<?php endif; ?>
<td class="text-nowrap">
<div class="action-btn-group">
<?php if ($cloakCfEnabled && in_array($cfStatus, ['pending_ns', 'provisioning', 'ready', 'error'], true)): ?>
<form method="post" action="<?php echo $current_name; ?>" class="d-inline">
<input type="hidden" name="action" value="check_domain">
<input type="hidden" name="domain_id" value="<?php echo (int)$dom['id']; ?>">
<button type="submit" class="btn btn-sm btn-outline-primary">检测/刷新</button>
</form>
<?php endif; ?>
<a href="<?php echo $current_name; ?>?action=delete_domain&amp;domain_id=<?php echo (int)$dom['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('<?php echo htmlspecialchars($delConfirm, ENT_QUOTES); ?>');">删除</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<!-- ===================== 编辑视图 ===================== -->
<a href="<?php echo $current_name; ?>" class="back-link">← 返回配置列表</a>
<div class="card shadow-sm" style="border-radius:8px;overflow:hidden;">
<div class="card-header-custom">
<h5><?php echo $isNewConfigDraft ? '新增配置' : '编辑配置'; ?><?php echo htmlspecialchars($check_name); ?><?php if ($isNewConfigDraft): ?> <span class="badge badge-warning" style="font-size:.75rem;vertical-align:middle;">未保存</span><?php endif; ?></h5>
<div>
<?php if (empty($_SESSION["check_result"]) || $_SESSION["check_result"] != "true"): ?>
<a href="<?php echo $current_name; ?>?action=setuvi&res=true&name=<?php echo $check_name; ?>" class="btn btn-sm btn-outline-success mr-1">设置当前浏览器为真实访客</a>
<?php else: ?>
<a href="<?php echo $current_name; ?>?action=setuvi&res=false&name=<?php echo $check_name; ?>" class="btn btn-sm btn-outline-warning mr-1">清除访客状态</a>
<?php endif; ?>
</div>
</div>
<form action="<?php echo $current_name; ?>?action=submit" method="post" id="configForm">
<div style="padding:16px;">
<!-- ===== 基本设置 ===== -->
<div class="form-section">
<button type="button" class="section-toggle" data-target="sec-basic">
基本设置 <span class="arrow">▼</span>
</button>
<div id="sec-basic">
<table class="table table-sm mb-0">
<tr>
<td class="form-label-cell"><strong>配置名称</strong></td>
<td class="form-val-cell">
<input type="text" name="check_name" class="form-control form-control-sm" value="<?php echo htmlspecialchars($check_name); ?>" style="max-width:300px;">
<small class="text-muted">示例:f123、d258</small>
</td>
</tr>
<tr>
<td class="form-label-cell">
<strong>关联域名</strong><br>
<small class="text-muted">请先在上方「站点域名」中添加并在宝塔创建该站点与 SSL</small>
</td>
<td class="form-val-cell">
<select name="site_domain_id" class="form-control form-control-sm" style="max-width:360px;">
<option value="0"<?php echo $selectedDomainId === 0 ? ' selected' : ''; ?>>
不指定(广告链接使用当前后台域名:<?php echo htmlspecialchars($dashboardDefaultHost); ?>
</option>
<?php foreach ($cloakSiteDomains as $dom): ?>
<option value="<?php echo (int) $dom['id']; ?>"<?php echo $selectedDomainId === (int) $dom['id'] ? ' selected' : ''; ?>>
<?php echo htmlspecialchars($dom['hostname']); ?>
<?php if ($dom['config_name'] !== '' && $dom['config_name'] !== $check_name): ?>
(已绑:<?php echo htmlspecialchars($dom['config_name']); ?>
<?php endif; ?>
</option>
<?php endforeach; ?>
</select>
<?php if (empty($cloakSiteDomains)): ?>
<small class="text-warning d-block mt-1">尚未添加任何域名,保存后广告链接将使用 <?php echo htmlspecialchars($dashboardDefaultHost); ?></small>
<?php endif; ?>
</td>
</tr>
<tr>
<td class="form-label-cell">
是否开启 DEBUG 模式<br>
<small class="text-muted">开启后访问落地链接将显示格式化调试页(判定结果、理由、代码行、访客信息),不会跳转真实页/安全页;生产环境请关闭</small>
</td>
<td class="form-val-cell"><?php echo onoff_radio('debug_mode', $_d('CLOAK_DEBUG_MODE','OFF')); ?></td>
</tr>
<tr>
<td class="form-label-cell">屏蔽模式</td>
<td class="form-val-cell">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-sm btn-outline-primary <?php if(SHOW_SITE_MODE_SWITCH == 'ip_check') echo 'active'; ?>">
<input type="radio" name="methods" value="ip_check" <?php if(SHOW_SITE_MODE_SWITCH == 'ip_check') echo 'checked'; ?>> 正常屏蔽模式
</label>
<label class="btn btn-sm btn-outline-primary <?php if(SHOW_SITE_MODE_SWITCH == 'fp') echo 'active'; ?>">
<input type="radio" name="methods" value="fp" <?php if(SHOW_SITE_MODE_SWITCH == 'fp') echo 'checked'; ?>> 真实页模式
</label>
<label class="btn btn-sm btn-outline-primary <?php if(SHOW_SITE_MODE_SWITCH == 'zp') echo 'active'; ?>">
<input type="radio" name="methods" value="zp" <?php if(SHOW_SITE_MODE_SWITCH == 'zp') echo 'checked'; ?>> 安全页模式
</label>
</div>
</td>
</tr>
<tr>
<td class="form-label-cell">指定国家访问真实页(逗号分隔国家代码)</td>
<td class="form-val-cell"><input type="text" name="country" class="form-control form-control-sm" value="<?php echo SHOW_SITE_COUNTRY; ?>" placeholder="如:US,GB,CA" style="max-width:300px;"></td>
</tr>
<tr>
<td class="form-label-cell">广告策略 ID</td>
<td class="form-val-cell"><input type="text" name="costm_ip_score" class="form-control form-control-sm" value="<?php echo COSTM_IP_SCORE; ?>" style="max-width:360px;"></td>
</tr>
<tr>
<td class="form-label-cell">安全页跳转方式</td>
<td class="form-val-cell">
<select name="redirect_method" class="form-control form-control-sm" style="max-width:320px;">
<option value="curl" <?php if(CLOAK_REDIRECT_METHOD=='curl') echo 'selected'; ?>>抓取显示(链接不变)</option>
<option value="302" <?php if(CLOAK_REDIRECT_METHOD=='302') echo 'selected'; ?>>302 跳转(域名改变)</option>
<option value="js" <?php if(CLOAK_REDIRECT_METHOD=='js') echo 'selected'; ?>>JS 跳转</option>
</select>
</td>
</tr>
<tr>
<td class="form-label-cell">安全页链接(留空显示空白页)</td>
<td class="form-val-cell"><input type="text" name="DB_zp" class="form-control form-control-sm" value="<?php echo DB_ZP; ?>" placeholder="https://..." style="max-width:420px;"></td>
</tr>
<tr>
<td class="form-label-cell">真实页跳转方式</td>
<td class="form-val-cell">
<select name="show_content" class="form-control form-control-sm" style="max-width:320px;">
<option value="curl" <?php if(CLOAK_SHOW_CONTENT=='curl') echo 'selected'; ?>>抓取显示(链接不变)</option>
<option value="302" <?php if(CLOAK_SHOW_CONTENT=='302') echo 'selected'; ?>>302 跳转(域名改变)</option>
<option value="js" <?php if(CLOAK_SHOW_CONTENT=='js') echo 'selected'; ?>>JS 跳转</option>
</select>
</td>
</tr>
<tr>
<td class="form-label-cell">真实页链接地址</td>
<td class="form-val-cell" id="db_fp">
<?php
if (DB_FP) {
foreach (DB_FP as $idx => $link) {
if ($idx == 0) {
echo '<input type="text" name="DB_fp[]" class="form-control form-control-sm mb-1" value="' . htmlspecialchars($link) . '" style="max-width:420px;" data-idx="1"> <a href="javascript:void(0);" id="addNewLinkBtn" class="btn btn-sm btn-outline-secondary">+ 新增</a>';
} else {
echo '<br><input type="text" name="DB_fp[]" class="form-control form-control-sm mt-1" value="' . htmlspecialchars($link) . '" style="max-width:420px;" data-idx="' . ($idx+1) . '"> <a href="javascript:void(0);" class="removeLinkBtn btn btn-sm btn-outline-danger">删除</a>';
}
}
} else {
echo '<input type="text" name="DB_fp[]" class="form-control form-control-sm" value="" style="max-width:420px;" data-idx="1"> <a href="javascript:void(0);" id="addNewLinkBtn" class="btn btn-sm btn-outline-secondary">+ 新增</a>';
}
?>
</td>
</tr>
</table>
</div>
</div>
<!-- ===== IP 与名单 ===== -->
<div class="form-section">
<button type="button" class="section-toggle collapsed" data-target="sec-ip">
IP黑白单管理 <span class="arrow">▼</span>
</button>
<div id="sec-ip" style="display:none;">
<table class="table table-sm mb-0">
<tr>
<td class="form-label-cell">白名单 IP 组(允许访问真实页)</td>
<td class="form-val-cell">
<select name="whitelist_group_id" class="form-control form-control-sm" style="max-width:320px;">
<option value="0" <?php if($currentWhitelistGroupId===0) echo 'selected'; ?>>无</option>
<?php foreach($whiteGroups as $wg): ?>
<option value="<?php echo (int)$wg['id']; ?>" <?php if($currentWhitelistGroupId===(int)$wg['id']) echo 'selected'; ?>><?php echo htmlspecialchars($wg['name']); ?> (ID <?php echo $wg['id']; ?>)</option>
<?php endforeach; ?>
</select>
<small><a href="/admin_ip_manager.php?type=white" target="_blank">管理白名单</a></small>
</td>
</tr>
<tr>
<td class="form-label-cell">黑名单 IP 组(命中则拦截)</td>
<td class="form-val-cell">
<select name="blacklist_group_id" class="form-control form-control-sm" style="max-width:320px;">
<option value="0" <?php if($currentBlacklistGroupId===0) echo 'selected'; ?>>无</option>
<?php foreach($blackGroups as $bg): ?>
<option value="<?php echo (int)$bg['id']; ?>" <?php if($currentBlacklistGroupId===(int)$bg['id']) echo 'selected'; ?>><?php echo htmlspecialchars($bg['name']); ?> (ID <?php echo $bg['id']; ?>)</option>
<?php endforeach; ?>
</select>
<small><a href="/admin_ip_manager.php?type=black" target="_blank">管理黑名单</a></small>
</td>
</tr>
<tr>
<td class="form-label-cell">自动黑名单(达到次数后自动加入黑名单组)</td>
<td class="form-val-cell">
<div class="d-flex align-items-center gap-2" style="gap:10px;">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-sm btn-outline-secondary <?php if(AUTO_BLACK=='ON') echo 'active'; ?>">
<input type="radio" name="auto_black" id="auto_black_on" value="ON" <?php if(AUTO_BLACK=='ON') echo 'checked'; ?>> 开启
</label>
<label class="btn btn-sm btn-outline-secondary <?php if(AUTO_BLACK!='ON') echo 'active'; ?>">
<input type="radio" name="auto_black" id="auto_black_off" value="OFF" <?php if(AUTO_BLACK!='ON') echo 'checked'; ?>> 关闭
</label>
</div>
<span class="ml-2">触发次数:</span>
<input type="number" name="auto_black_times" value="<?php echo AUTO_BLACK_TIMES; ?>" class="form-control form-control-sm" style="width:80px;" min="1">
</div>
</td>
</tr>
<tr>
<td class="form-label-cell">白名单链接参数(携带此参数直接放行,格式:param=value</td>
<td class="form-val-cell"><input type="text" name="white_params" class="form-control form-control-sm" value="<?php echo htmlspecialchars(WHITE_PARAMS); ?>" style="max-width:280px;" placeholder="如:token=abc123"></td>
</tr>
</table>
</div>
</div>
<!-- ===== 设备与语言 ===== -->
<div class="form-section">
<button type="button" class="section-toggle collapsed" data-target="sec-device">
设备与语言过滤 <span class="arrow">▼</span>
</button>
<div id="sec-device" style="display:none;">
<table class="table table-sm mb-0">
<tr>
<td class="form-label-cell">屏蔽无 Referer 来源(防爬虫)</td>
<td class="form-val-cell"><?php echo onoff_radio('v_referer', CLOAK_V_REFERER); ?></td>
</tr>
<tr>
<td class="form-label-cell">仅允许移动端访问</td>
<td class="form-val-cell"><?php echo onoff_radio('mobile_on', CLOAK_MOBILE_ON); ?></td>
</tr>
<tr>
<td class="form-label-cell">屏蔽中文浏览器</td>
<td class="form-val-cell"><?php echo onoff_radio('zh_on', CLOAK_ZH_ON); ?></td>
</tr>
<tr>
<td class="form-label-cell">仅允许 iOS / Android 系统</td>
<td class="form-val-cell"><?php echo onoff_radio('os_on', CLOAK_OS_ON); ?></td>
</tr>
<tr>
<td class="form-label-cell">保留跳转链接参数</td>
<td class="form-val-cell"><?php echo onoff_radio('keep_params', KEEP_PARAMS); ?></td>
</tr>
<tr>
<td class="form-label-cell">屏蔽 iPhone 型号</td>
<td class="form-val-cell">
<select name="iphone_model" class="form-control form-control-sm" style="max-width:260px;">
<option value="0" <?php if(IPHONE_MODEL=='0') echo 'selected'; ?>>不屏蔽</option>
<option value="8" <?php if(IPHONE_MODEL=='8') echo 'selected'; ?>>屏蔽 iPhone 8 及以下</option>
<option value="10" <?php if(IPHONE_MODEL=='10') echo 'selected'; ?>>屏蔽 iPhone X 及以下</option>
<option value="11" <?php if(IPHONE_MODEL=='11') echo 'selected'; ?>>屏蔽 iPhone 11 及以下</option>
<option value="12" <?php if(IPHONE_MODEL=='12') echo 'selected'; ?>>屏蔽 iPhone 12 及以下</option>
</select>
</td>
</tr>
<tr>
<td class="form-label-cell">
虚拟设备检测<br>
<small class="text-muted">简单:全平台自动化/爬虫;标准:+环境一致性(移动无触摸等);严格:+移动GPU校验与传感器(约180ms</small>
</td>
<td class="form-val-cell">
<select name="is_virtual" class="form-control form-control-sm" style="max-width:260px;">
<option value="0" <?php if(IS_VIRTUAL=='0') echo 'selected'; ?>>不检测</option>
<option value="1" <?php if(IS_VIRTUAL=='1') echo 'selected'; ?>>简单</option>
<option value="2" <?php if(IS_VIRTUAL=='2') echo 'selected'; ?>>标准</option>
<option value="3" <?php if(IS_VIRTUAL=='3') echo 'selected'; ?>>严格</option>
</select>
</td>
</tr>
</table>
</div>
</div>
<!-- ===== 广告来源限制 ===== -->
<div class="form-section">
<button type="button" class="section-toggle collapsed" data-target="sec-ad-source">
广告来源限制 <span class="arrow">▼</span>
</button>
<div id="sec-ad-source" style="display:none;">
<table class="table table-sm mb-0">
<tr>
<td class="form-label-cell">Facebook</td>
<td class="form-val-cell"><?php echo onoff_radio('ad_fb', $_d('CLOAK_AD_FB', 'OFF')); ?></td>
</tr>
<tr>
<td class="form-label-cell">Google</td>
<td class="form-val-cell"><?php echo onoff_radio('ad_google', $_d('CLOAK_AD_GOOGLE', 'OFF')); ?></td>
</tr>
<tr>
<td class="form-label-cell">TikTok</td>
<td class="form-val-cell"><?php echo onoff_radio('ad_tiktok', $_d('CLOAK_AD_TIKTOK', 'OFF')); ?></td>
</tr>
<tr>
<td class="form-label-cell">严格模式</td>
<td class="form-val-cell">
<?php echo onoff_radio('ad_strict', $_d('CLOAK_AD_STRICT', 'OFF')); ?>
<div class="text-muted mt-1" style="font-size:.78rem;">开启后,未通过广告来源验证的访问将直接跳转安全页;关闭时仅追加「未确定流量来源」并继续后续判定,日志行显示为黄色。</div>
</td>
</tr>
<tr>
<td class="form-label-cell">强验证参数 ad_spm_id</td>
<td class="form-val-cell">
<?php $spmShow = $_d('CLOAK_AD_SPM_ID', ''); ?>
<?php if ($spmShow !== ''): ?>
<code><?php echo htmlspecialchars($spmShow); ?></code>
<div class="text-muted mt-1" style="font-size:.78rem;">保存配置且开启任一广告平台时自动生成,写入广告链接用于强验证。</div>
<?php else: ?>
<span class="text-muted">保存并开启任一广告平台后自动生成</span>
<?php endif; ?>
</td>
</tr>
</table>
</div>
</div>
<!-- ===== 高级设置 ===== -->
<div class="form-section">
<button type="button" class="section-toggle collapsed" data-target="sec-advanced">
高级设置 <span class="arrow">▼</span>
</button>
<div id="sec-advanced" style="display:none;">
<table class="table table-sm mb-0">
<tr>
<td class="form-label-cell">指纹风险系数(0=关闭,最大90</td>
<td class="form-val-cell">
<input type="number" name="risk_number" class="form-control form-control-sm" value="<?php echo CLOAK_RISK_NUMBER; ?>" style="width:100px;" min="0" max="90">
<div class="text-muted mt-2" style="font-size:.78rem;line-height:1.55;">
数值越高,拦截越严格。访客风险分达到或超过此值时跳转安全页,低于则进入真实页。
设为 <strong>0</strong> 表示关闭二次风控。
</div>
<details class="mt-2" style="font-size:.78rem;line-height:1.55;">
<summary style="cursor:pointer;color:#0c5460;font-weight:600;">推荐设置(与下方「加强判断」配合)</summary>
<div class="text-muted mt-2">
<p class="mb-2">请根据「加强判断」开关选择对应数值,两种模式<strong>不宜使用同一分数</strong>。</p>
<table class="table table-sm table-bordered mb-2" style="font-size:.76rem;background:#fff;">
<thead class="thead-light">
<tr><th>加强判断</th><th>建议分数</th><th>说明</th></tr>
</thead>
<tbody>
<tr>
<td><strong>OFF</strong></td>
<td><strong>68</strong></td>
<td>默认推荐,兼顾流量与防护,误拦较少</td>
</tr>
<tr>
<td><strong>ON</strong></td>
<td><strong>52</strong></td>
<td>识别更严,适合需要更高防护的场景</td>
</tr>
</tbody>
</table>
<p class="mb-1"><strong>微调:</strong></p>
<ul class="mb-0 pl-3">
<li>真实用户被误拦 → 分数 <strong>+3 ~ +5</strong></li>
<li>可疑访问漏过 → 分数 <strong>3 ~ 5</strong></li>
<li>可在「访问日志 → 显示更多 → 判定详情」查看每条记录的风险分与拦截原因</li>
</ul>
</div>
</details>
</td>
</tr>
<tr>
<td class="form-label-cell">加强判断</td>
<td class="form-val-cell">
<?php echo onoff_radio('risk_enhanced', $_d('CLOAK_RISK_ENHANCED', 'OFF')); ?>
<div class="text-muted mt-1" style="font-size:.78rem;line-height:1.55;">
开启后识别更精细,可拦截更多自动化访问;老旧设备或弱网环境下可能受影响。<br>
建议配合上方风险系数:OFF 用 <strong>68</strong>ON 用 <strong>52</strong>。
</div>
</td>
</tr>
<tr>
<td class="form-label-cell">
临时链接参数有效时长(分钟,0=关闭)<br>
<small class="text-muted">通过广告来源限制规则(ad_spm_id / clid / Referer / UA 等)后开始计时,有效时长内放行;到期重新判断。未开启广告平台时仍按 clid/ad_spm_id 触发。</small>
</td>
<td class="form-val-cell">
<div class="d-flex align-items-center" style="gap:8px;">
<input type="number" name="url_args_timeout" class="form-control form-control-sm" value="<?php echo CLOAK_URL_ARGS_TIMEOUT; ?>" style="width:90px;" min="0"> 分钟
<button type="button" class="btn btn-sm btn-outline-warning" onclick="delUrlArgs()">重置计时</button>
</div>
</td>
</tr>
<tr>
<td class="form-label-cell">记录访客日志</td>
<td class="form-val-cell">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-sm btn-outline-secondary <?php if(WRITE_LOG=='1') echo 'active'; ?>">
<input type="radio" name="write_log" value="1" <?php if(WRITE_LOG=='1') echo 'checked'; ?>> 开启
</label>
<label class="btn btn-sm btn-outline-secondary <?php if(WRITE_LOG!='1') echo 'active'; ?>">
<input type="radio" name="write_log" value="0" <?php if(WRITE_LOG!='1') echo 'checked'; ?>> 关闭
</label>
</div>
</td>
</tr>
</table>
</div>
</div>
</div><!-- /padding -->
<!-- 底部操作栏 -->
<div class="sticky-actions">
<button type="submit" class="btn btn-primary">保存配置</button>
<a href="<?php echo $current_name . '?action=edit_config&name=' . $check_name; ?>" class="btn btn-outline-secondary">重置</a>
<a href="/table" target="_blank" class="btn btn-outline-info">查看日志</a>
<a href="/log_analytics.php" target="_blank" class="btn btn-outline-info">数据分析</a>
<a href="/share_log.php" target="_blank" class="btn btn-outline-info">分享日志</a>
</div>
</form>
</div>
<?php endif; ?>
</div><!-- /page-body -->
<script>
$(function() {
// 手风琴展开/折叠
$('.section-toggle').on('click', function() {
var target = '#' + $(this).data('target');
$(this).toggleClass('collapsed');
$(target).slideToggle(180);
});
// 动态添加链接
var linkIdx = 1;
$(document).on('click', '#addNewLinkBtn', function() {
linkIdx++;
var html = '<br><input type="text" name="DB_fp[]" class="form-control form-control-sm mt-1" value="" style="max-width:420px;" data-idx="' + linkIdx + '"> <a href="javascript:void(0);" class="removeLinkBtn btn btn-sm btn-outline-danger">删除</a>';
$(this).after(html);
});
$(document).on('click', '.removeLinkBtn', function() {
$(this).prev('br').remove();
$(this).prev('input').remove();
$(this).remove();
});
// 自动黑名单校验
$('#configForm').on('submit', function() {
var on = $('#auto_black_on').is(':checked');
var gid = parseInt($('select[name=blacklist_group_id]').val(), 10) || 0;
if (on && gid <= 0) {
alert('开启「自动黑名单」时,必须先选择「黑名单 IP 组」。');
return false;
}
return true;
});
// 广告链接复制
$(document).on('click', '.btn-copy-ad', function() {
var text = $(this).attr('data-copy') || '';
if (!text) return;
var btn = this;
var done = function() {
var orig = btn.textContent;
btn.textContent = '已复制';
setTimeout(function() { btn.textContent = orig; }, 1200);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done).catch(function() {
window.prompt('请手动复制链接:', text);
});
} else {
window.prompt('请手动复制链接:', text);
}
});
});
function delUrlArgs() {
if (!confirm('确认重置临时链接参数计时?')) return;
window.location.href = '<?php echo $current_name; ?>?action=delUrlArgs&check_name=<?php echo urlencode($check_name); ?>';
}
</script>
</body>
</html>