<?php
require_once("cong.php");
session_start();

if(empty($_SESSION['password']) || $_SESSION['password'] != LOGIN_PASSWORD) {
  unset($_SESSION['password']);
  unset($_SESSION['check_result']);
  echo("<script language=javascript>alert('密码错误!');window.location='ip_check_edit.php'; </script>");
  //die("输入的密码不正确，请重新输入。");
}

?>

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>访问判断日志</title>
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="//cdn.staticfile.org/layui/2.9.2/css/layui.css" rel="stylesheet">
  <style>
    :root {
      --log-primary: #16baaa;
      --log-primary-dark: #0e8d81;
      --log-bg: #f0f2f5;
      --log-card: #ffffff;
      --log-border: #e8ecf1;
      --log-header-bg: #f4f7fb;
      --log-text: #2c3e50;
      --log-muted: #8c98a4;
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      background: var(--log-bg);
      color: var(--log-text);
      font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    }
    .log-page {
      max-width: 1680px;
      margin: 0 auto;
      padding: 24px 28px 40px;
    }
    .log-page-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 20px;
      flex-wrap: wrap;
      gap: 12px;
    }
    .log-page-header h1 {
      margin: 0;
      font-size: 1.35rem;
      font-weight: 600;
      color: var(--log-text);
    }
    .log-page-header p {
      margin: 4px 0 0;
      font-size: .85rem;
      color: var(--log-muted);
    }
    .log-card {
      background: var(--log-card);
      border: 1px solid var(--log-border);
      border-radius: 10px;
      box-shadow: 0 2px 12px rgba(15, 23, 42, .04);
      margin-bottom: 16px;
      overflow: hidden;
    }
    .log-card-body { padding: 18px 20px; }
    .log-filter-grid .layui-col-md3 { margin-bottom: 12px; }
    .log-filter-grid .layui-input,
    .log-filter-grid .layui-select-title input {
      border-radius: 6px;
      border-color: var(--log-border);
    }
    .log-actions {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 10px;
      padding: 14px 20px;
      border-top: 1px solid var(--log-border);
      background: #fafbfc;
    }
    .log-actions .layui-btn { border-radius: 6px; }
    .log-actions .layui-btn-primary { background: #fff; }
    .log-count {
      margin-left: auto;
      font-size: .88rem;
      color: var(--log-muted);
    }
    .log-count strong {
      color: var(--log-primary-dark);
      font-size: 1rem;
      margin: 0 4px;
    }
    #btn-clear { background: #e74c3c !important; border-color: #e74c3c !important; }
    .log-table-wrap { padding: 0; }
    .log-table-wrap .layui-table-view {
      border: none;
      border-radius: 0 0 10px 10px;
      margin: 0;
    }
    /* Sticky header */
    .log-table-wrap .layui-table-header {
      position: sticky;
      top: 0;
      z-index: 20;
      border-radius: 0;
    }
    .log-table-wrap .layui-table-header .layui-table {
      background: linear-gradient(180deg, #eef3f9 0%, #e4ebf5 100%);
    }
    .log-table-wrap .layui-table-header th {
      color: #3d5167 !important;
      font-weight: 600 !important;
      font-size: 12.5px;
      border-color: #d8e0ea !important;
      padding: 11px 10px !important;
    }
    /* Zebra + hover */
    .log-table-wrap .layui-table-body tr:nth-child(even) td {
      background-color: #fafbfc;
    }
    .log-table-wrap .layui-table-body tr:hover td {
      background-color: #eef6ff !important;
    }
    .log-table-wrap .layui-table-body td {
      border-color: #eef1f5 !important;
      padding: 9px 10px !important;
      font-size: 12.5px;
      line-height: 1.45;
      transition: background-color .15s ease;
    }
    .log-table-wrap .layui-table-page {
      border-top: 1px solid var(--log-border);
      padding: 12px 16px;
      background: #fafbfc;
    }
    /* Badges */
    .cloak-badge {
      display: inline-block;
      padding: 2px 9px;
      border-radius: 999px;
      font-size: 11.5px;
      font-weight: 600;
      line-height: 1.5;
      white-space: nowrap;
      letter-spacing: .02em;
    }
    .badge-success { background: #d4edda; color: #155724; }
    .badge-error   { background: #f8d7da; color: #721c24; }
    .badge-warning { background: #fff3cd; color: #856404; }
    .badge-info    { background: #d1ecf1; color: #0c5460; }
    .badge-muted   { background: #e9ecef; color: #495057; }
    .reason-badge {
      max-width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      vertical-align: middle;
    }
    /* Truncation + mono timestamp */
    .cell-ellipsis {
      display: block;
      max-width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      cursor: default;
    }
    .cell-mono {
      font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
      font-size: 12px;
      color: #4a5568;
      letter-spacing: -.02em;
    }
    .cell-copy-link { cursor: copy; }
    .cell-copy-link:hover { color: var(--log-primary-dark); }
    .cell-ip { font-family: 'SF Mono', 'Consolas', monospace; font-size: 12px; }
    .text-muted { color: #adb5bd; }
    .fp-hdata-cell {
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      align-items: center;
      gap: 4px 8px;
      max-width: 100%;
      min-height: 24px;
    }
    .fp-hdata-summary {
      flex: 1 1 140px;
      min-width: 0;
      font-size: 11.5px;
      color: #475569;
      line-height: 1.35;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .fp-hdata-actions {
      display: inline-flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 4px;
      flex: 0 0 auto;
    }
    .fp-hdata-btn {
      display: inline-block;
      padding: 1px 8px;
      font-size: 11px;
      border-radius: 4px;
      background: #e8f4fd;
      color: #0c5460;
      cursor: pointer;
      border: 1px solid #bee5eb;
      width: fit-content;
    }
    .fp-hdata-btn:hover { background: #d1ecf1; }
    /* 指纹 / API 审计列：解除 layui 固定行高，避免「判定详情」等按钮被裁切 */
    .log-table-wrap .layui-table-body td[data-field="fp_hdata_text"],
    .log-table-wrap .layui-table-body td[data-field="api_by_request_text"],
    .log-table-wrap .layui-table-body td[data-field="api_by_response_text"],
    .log-table-wrap .layui-table-body td[data-field="api_risk_request_text"],
    .log-table-wrap .layui-table-body td[data-field="api_risk_response_text"] {
      height: auto !important;
      vertical-align: top;
    }
    .log-table-wrap .layui-table-body td[data-field="fp_hdata_text"] > .layui-table-cell,
    .log-table-wrap .layui-table-body td[data-field="api_by_request_text"] > .layui-table-cell,
    .log-table-wrap .layui-table-body td[data-field="api_by_response_text"] > .layui-table-cell,
    .log-table-wrap .layui-table-body td[data-field="api_risk_request_text"] > .layui-table-cell,
    .log-table-wrap .layui-table-body td[data-field="api_risk_response_text"] > .layui-table-cell {
      height: auto !important;
      max-height: none !important;
      white-space: normal !important;
      overflow: visible !important;
      line-height: 1.35;
      padding-top: 7px !important;
      padding-bottom: 7px !important;
    }
    .fp-risk-explain { font-size: 13px; line-height: 1.55; color: #2c3e50; }
    .fp-risk-explain-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
    .fp-risk-tag { font-size: 12px; padding: 2px 10px; border-radius: 999px; background: #eef2ff; color: #3730a3; font-weight: 600; }
    .fp-risk-kv { width: 100%; border-collapse: collapse; margin-bottom: 14px; font-size: 12.5px; }
    .fp-risk-kv th { text-align: left; width: 88px; padding: 6px 8px; background: #f8fafc; border: 1px solid #e2e8f0; vertical-align: top; color: #64748b; }
    .fp-risk-kv td { padding: 6px 8px; border: 1px solid #e2e8f0; word-break: break-word; }
    .fp-risk-reason-raw { font-family: 'SF Mono', Consolas, monospace; font-size: 12px; }
    .fp-risk-section-title { font-weight: 600; margin: 12px 0 8px; font-size: 13px; color: #334155; }
    .fp-risk-factor-list { margin: 0; padding-left: 1.2rem; }
    .fp-risk-factor-list > li { margin-bottom: 10px; }
    .fp-factor-text { font-weight: 500; margin-bottom: 4px; }
    .fp-factor-fields { margin: 0; padding-left: 1rem; list-style: disc; }
    .fp-factor-fields mark.fp-field-key { background: #fef3c7; padding: 0 4px; border-radius: 3px; font-weight: 600; }
    .fp-factor-fields code { font-size: 11.5px; word-break: break-all; }
    .fp-factor-miss { font-size: 12px; color: #94a3b8; }
    .fp-hdata-json {
      max-height: 320px;
      overflow: auto;
      background: #0f172a;
      color: #e2e8f0;
      padding: 12px;
      border-radius: 8px;
      font-size: 11.5px;
      line-height: 1.45;
      margin: 0;
      white-space: pre-wrap;
      word-break: break-all;
    }
    .fp-hdata-json .fp-hl-line { background: rgba(250, 204, 21, .18); display: inline-block; width: 100%; }
    .log-nav { display: flex; gap: 8px; flex-wrap: wrap; }
    .log-nav a {
      display: inline-block; padding: 6px 14px; border-radius: 6px; font-size: .85rem;
      text-decoration: none; border: 1px solid var(--log-border); background: #fff; color: var(--log-text);
    }
    .log-nav a.active { background: var(--log-primary); color: #fff; border-color: var(--log-primary); }
    .fp-risk-conclusions { margin: 0 0 14px; padding: 0; list-style: none; }
    .fp-risk-conclusions li {
      padding: 9px 12px; margin-bottom: 8px; border-radius: 8px; font-size: 12.5px; line-height: 1.55;
      border-left: 3px solid #16baaa; background: #f0faf9;
    }
    .fp-risk-conclusions li.warn { background: #fff8e6; border-left-color: #e6a23c; }
    .fp-risk-conclusions li.alert { background: #fef2f2; border-left-color: #e74c3c; }
    .fp-risk-conclusions li.ok { background: #f0fdf4; border-left-color: #22c55e; }
    .fp-risk-conclusions li .fp-conc-action {
      display: block; margin-top: 4px; font-size: 12px; color: #64748b;
    }
    .fp-risk-conclusions li a { color: #0e8d81; text-decoration: none; }
    .fp-risk-conclusions li a:hover { text-decoration: underline; }
    .col-settings-list {
      list-style: none;
      margin: 0;
      padding: 0;
      max-height: 420px;
      overflow-y: auto;
    }
    .col-settings-item {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 8px 10px;
      margin-bottom: 6px;
      background: #f8fafc;
      border: 1px solid #e2e8f0;
      border-radius: 6px;
      cursor: grab;
      user-select: none;
    }
    .col-settings-item.dragging { opacity: .55; border-style: dashed; }
    .col-settings-item.drag-over { border-color: var(--log-primary); background: #eefaf8; }
    .col-settings-handle {
      color: #94a3b8;
      font-size: 14px;
      line-height: 1;
      cursor: grab;
    }
    .col-settings-title { flex: 1; font-size: 13px; color: #334155; }
    .col-settings-actions {
      display: flex;
      gap: 8px;
      margin-top: 14px;
      padding-top: 12px;
      border-top: 1px solid #e8ecf1;
    }
    .col-settings-tip {
      font-size: 12px;
      color: #64748b;
      margin: 0 0 10px;
      line-height: 1.5;
    }
    @media (max-width: 768px) {
      .log-page { padding: 16px 12px 32px; }
      .log-count { margin-left: 0; width: 100%; }
    }
  </style>
</head>
<body>

<div class="log-page">
  <div class="log-page-header">
    <div>
      <h1>访问判断日志</h1>
      <p>访客判定记录 · 支持筛选、导出与分页浏览</p>
    </div>
    <div class="log-nav">
      <a href="table.php" class="active">访问日志</a>
      <a href="simulate_test.php">模拟测试</a>
      <a href="log_analytics.php">数据分析</a>
      <a href="dashboard.php">配置中心</a>
    </div>
  </div>

  <div class="log-card">
    <div class="log-card-body">
      <form class="layui-form layui-row layui-col-space16 log-filter-grid">
        <div class="layui-col-md3">
          <div class="layui-input-wrap">
            <div class="layui-input-prefix">
              <i class="layui-icon layui-icon-username"></i>
            </div>
            <input type="text" name="domain" value="" placeholder="域名" class="layui-input" lay-affix="clear">
          </div>
        </div>
        <div class="layui-col-md3">
          <div class="layui-input-wrap">
            <input type="text" name="spage" value="" placeholder="访问页面,可输入配置名称搜索" class="layui-input" lay-affix="clear">
          </div>
        </div>
        <div class="layui-col-md3">
          <div class="layui-input-wrap">
            <select name="result">
                <option value="">全部</option>
                <option value="true">TRUE(通过访问)</option>
                <option value="false">FALSE(禁止访问)</option>
                <option value="wait">检测中</option>
            </select>
          </div>
        </div>
        <div class="layui-col-md3">
          <div class="layui-input-wrap">
            <input type="text" name="country" placeholder="国家" lay-affix="clear" class="layui-input">
          </div>
        </div>
        <div class="layui-col-md3">
          <div class="layui-input-wrap">
            <input type="text" name="IP" placeholder="IP" lay-affix="clear" class="layui-input">
          </div>
        </div>
        <div class="layui-col-md3">
          <div class="layui-input-wrap">
            <div class="layui-input-prefix">
              <i class="layui-icon layui-icon-date"></i>
            </div>
            <input type="text" name="visit_date" readonly placeholder="访问日期" class="layui-input demo-table-search-date">
          </div>
        </div>
        <div class="layui-btn-container layui-col-xs12">
          <a class="layui-btn" lay-submit href="javascript:void(0);" id="export" target="_blank">导出</a>
          <button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
          <button type="reset" class="layui-btn layui-btn-primary">清除条件</button>
        </div>
      </form>
    </div>
    <div class="log-actions">
      <button class="layui-btn layui-btn-sm" id="btn-refresh" onclick="resetTable()" lay-event="footerDemoBtn1">刷新</button>
      <a href="simulate_test.php" class="layui-btn layui-btn-sm layui-btn-normal">模拟测试</a>
      <button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-col-settings" type="button">列设置</button>
      <button class="layui-btn layui-btn-sm btn-danger" id="btn-clear" lay-on="confirm" lay-event="footerDemoBtn2">清空数据库</button>
      <span class="log-count">当前查询数量：<strong id="total_nums">—</strong></span>
    </div>
  </div>

  <div class="log-card log-table-wrap">
    <div id="demo-laypage-all"></div>
    <table class="layui-hide" id="ID-table-demo-search" lay-filter="test"></table>
  </div>
</div>

<script src="/dist/layui.js"></script>
<script src="/dist/jquery.min.js"></script>
<script>

layui.use(function(){
  var table = layui.table;
  var form = layui.form;
  var laydate = layui.laydate;
  var layer = layui.layer;
  var LINK_CELL_MAX = 150;
  var COLUMN_STORAGE_KEY = 'cloak_log_table_cols_v1';
  var TABLE_ID = 'ID-table-demo-search';
  var colConfig = [];
  var dragField = null;

  function escapeHtml(s) {
    if (s == null || s === '') return '';
    return String(s)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function escapeAttr(s) {
    return String(s)
      .replace(/&/g, '&amp;')
      .replace(/"/g, '&quot;')
      .replace(/</g, '&lt;');
  }

  function cellEllipsis(value, maxLen) {
    var text = value == null ? '' : String(value);
    if (!text) return '<span class="text-muted">—</span>';
    var esc = escapeHtml(text);
    var display = text.length > maxLen ? escapeHtml(text.slice(0, maxLen)) + '…' : esc;
    return '<span class="cell-ellipsis" title="' + esc + '">' + display + '</span>';
  }

  function cellLinkEllipsis(value, maxLen) {
    maxLen = maxLen || LINK_CELL_MAX;
    var text = value == null ? '' : String(value);
    if (!text) return '<span class="text-muted">—</span>';
    var esc = escapeHtml(text);
    var display = text.length > maxLen ? escapeHtml(text.slice(0, maxLen)) + '…' : esc;
    return '<span class="cell-ellipsis cell-copy-link" title="双击复制完整内容" data-full="' + escapeAttr(text) + '">' + display + '</span>';
  }

  function badgeResult(val) {
    var raw = val == null ? '' : String(val);
    var v = raw.toLowerCase();
    if (v === 'true' || v === '1') {
      return '<span class="cloak-badge badge-success">TRUE</span>';
    }
    if (v === 'false' || v === '0') {
      return '<span class="cloak-badge badge-error">FALSE</span>';
    }
    if (v === 'wait' || raw.indexOf('检测') !== -1) {
      return '<span class="cloak-badge badge-info">检测中</span>';
    }
    return '<span class="cloak-badge badge-muted">' + escapeHtml(raw) + '</span>';
  }

  function badgeReason(reason) {
    if (reason == null || reason === '') {
      return '<span class="text-muted">—</span>';
    }
    var text = String(reason);
    var cls = 'badge-info';
    if (text.indexOf('未确定流量来源') !== -1) {
      cls = 'badge-warning';
    } else if (/过滤|黑名单|无效|代理|VPN|禁止|欺骗|无来源/i.test(text)) {
      cls = 'badge-error';
    } else if (/白名单|允许|通过/i.test(text)) {
      cls = 'badge-success';
    } else if (/^\d+$/.test(text.trim()) || /风险|WebDriver|Canvas|分辨率|自动化|指纹/i.test(text)) {
      cls = 'badge-warning';
    }
    var esc = escapeHtml(text);
    var short = text.length > 26 ? escapeHtml(text.slice(0, 26)) + '…' : esc;
    return '<span class="cloak-badge ' + cls + ' reason-badge" title="' + esc + '">' + short + '</span>';
  }

  function formatJsonPretty(str) {
    try {
      return JSON.stringify(JSON.parse(str), null, 2);
    } catch (e) {
      return str;
    }
  }

  function cellApiAudit(val, title, d, field) {
    if (val === '__LAZY__' && d && d.id && field) {
      return '<div class="fp-hdata-cell">'
        + '<span class="fp-hdata-summary text-muted">已记录</span>'
        + '<span class="fp-hdata-actions"><span class="fp-hdata-btn fp-hdata-btn-json" data-log-id="' + escapeAttr(String(d.id)) + '" data-field="' + escapeAttr(field) + '" data-title="' + escapeAttr(title || 'API 数据') + '">查看 JSON</span></span>'
        + '</div>';
    }
    if (val == null || val === '' || val === '-') {
      return '<span class="text-muted">—</span>';
    }
    var text = String(val);
    var short = text.length > 40 ? text.slice(0, 40) + '…' : text;
    return '<div class="fp-hdata-cell">'
      + '<span class="fp-hdata-summary" title="' + escapeAttr(text) + '">' + escapeHtml(short) + '</span>'
      + '<span class="fp-hdata-actions"><span class="fp-hdata-btn fp-hdata-btn-json" data-full="' + escapeAttr(text) + '" data-title="' + escapeAttr(title || 'API 数据') + '">查看 JSON</span></span>'
      + '</div>';
  }

  function cellFpHdata(d) {
    if (!d.fp_hdata_text) {
      return '<span class="text-muted">—</span>';
    }
    var summary = d.fp_hdata_text || '';
    var html = '<div class="fp-hdata-cell">';
    if (summary) {
      html += '<span class="fp-hdata-summary" title="' + escapeAttr(summary) + '">' + escapeHtml(summary.length > 48 ? summary.slice(0, 48) + '…' : summary) + '</span>';
    }
    html += '<span class="fp-hdata-actions">';
    html += '<span class="fp-hdata-btn" data-explain-id="' + escapeAttr(String(d.id)) + '">判定详情</span>';
    html += '<span class="fp-hdata-btn fp-hdata-btn-raw" data-log-id="' + escapeAttr(String(d.id)) + '" data-field="fp_hdata">原始 hdata</span>';
    html += '</span></div>';
    return html;
  }

  var COLUMN_DEFS = [
    { field: 'id', title: 'ID', width: 56, fixed: 'left', defaultVisible: true },
    { field: 'domain', title: '域名', width: 120, defaultVisible: true, templet: function(d){ return cellEllipsis(d.domain, 18); } },
    { field: 'visit_date', title: '访问日期', width: 130, defaultVisible: true, templet: function(d){
      return '<span class="cell-mono" title="' + escapeHtml(d.visit_date) + '">' + escapeHtml(d.visit_date) + '</span>';
    }},
    { field: 'country', title: '国家', width: 100, defaultVisible: true, templet: function(d){ return cellEllipsis(d.country, 12); } },
    { field: 'result', title: '是否正常', width: 88, defaultVisible: true, templet: function(d){ return badgeResult(d.result); } },
    { field: 'reason', title: '理由', width: 140, defaultVisible: true, templet: function(d){ return badgeReason(d.reason); } },
    { field: 'judge_timing_text', title: '判断用时', width: 240, defaultVisible: true, templet: function(d){ return cellEllipsis(d.judge_timing_text, 36); } },
    { field: 'vtimes', title: '第几次访问', width: 96, defaultVisible: true },
    { field: 'fp_url', title: '跳转至', width: 280, defaultVisible: true, templet: function(d){ return cellLinkEllipsis(d.fp_url, LINK_CELL_MAX); } },
    { field: 'device', title: '访问设备', width: 200, defaultVisible: true, templet: function(d){ return cellEllipsis(d.device, 32); } },
    { field: 'referer', title: '来源', width: 280, defaultVisible: true, templet: function(d){ return cellLinkEllipsis(d.referer, LINK_CELL_MAX); } },
    { field: 'IP', title: 'IP(支持IPV6)', width: 130, defaultVisible: true, templet: function(d){
      return '<span class="cell-ip" title="' + escapeHtml(d.IP) + '">' + escapeHtml(d.IP) + '</span>';
    }},
    { field: 'page', title: '访问页面', width: 280, defaultVisible: true, templet: function(d){ return cellLinkEllipsis(d.page, LINK_CELL_MAX); } },
    { field: 'client', title: '客户端', width: 80, defaultVisible: true, templet: function(d){ return cellEllipsis(d.client, 10); } },
    { field: 'language', title: '语言', width: 80, defaultVisible: true, templet: function(d){ return cellEllipsis(d.language, 10); } },
    { field: 'browser', title: '浏览器', width: 88, defaultVisible: true, templet: function(d){ return cellEllipsis(d.browser, 10); } },
    { field: 'campagin_id', title: '广告策略', width: 88, defaultVisible: true, templet: function(d){ return cellEllipsis(d.campagin_id, 10); } },
    { field: 'user_agent', title: 'User-Agent', width: 320, defaultVisible: false, templet: function(d){ return cellEllipsis(d.user_agent, 48); } },
    { field: 'http_referer', title: 'HTTP-Referer', width: 260, defaultVisible: false, templet: function(d){ return cellLinkEllipsis(d.http_referer, LINK_CELL_MAX); } },
    { field: 'accept_language_raw', title: 'Accept-Language', width: 220, defaultVisible: false, templet: function(d){ return cellEllipsis(d.accept_language_raw, 32); } },
    { field: 'fp_hdata_text', title: '指纹 hdata', width: 360, defaultVisible: false, templet: function(d){ return cellFpHdata(d); } },
    { field: 'api_by_request_text', title: 'byApi 请求参数', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_by_request_text, 'byApi 请求参数 #' + d.id, d, 'api_by_request'); } },
    { field: 'api_by_response_text', title: 'byApi 返回值', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_by_response_text, 'byApi 返回值 #' + d.id, d, 'api_by_response'); } },
    { field: 'api_risk_request_text', title: 'byApiRisk 请求参数', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_risk_request_text, 'byApiRisk 请求参数 #' + d.id, d, 'api_risk_request'); } },
    { field: 'api_risk_response_text', title: 'byApiRisk 返回值', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_risk_response_text, 'byApiRisk 返回值 #' + d.id, d, 'api_risk_response'); } },
    { field: 'sim_tag', title: '模拟', width: 88, defaultVisible: true, templet: function(d){
      if (String(d.is_simulation) === '1') {
        var src = d.sim_source_log_id ? ' #' + d.sim_source_log_id : '';
        return '<span class="cloak-badge badge-info">是' + escapeHtml(String(src)) + '</span>';
      }
      return '<span class="text-muted">—</span>';
    }},
    { field: 'sim_action', title: '操作', width: 108, defaultVisible: true, templet: function(d){
      return '<a class="layui-btn layui-btn-xs layui-btn-normal" href="simulate_test.php?from_log=' + encodeURIComponent(String(d.id)) + '">模拟测试</a>';
    }},
  ];

  var COLUMN_DEF_MAP = {};
  COLUMN_DEFS.forEach(function(def) {
    COLUMN_DEF_MAP[def.field] = def;
  });

  function getDefaultColConfig() {
    return COLUMN_DEFS.map(function(def) {
      return { field: def.field, visible: def.defaultVisible !== false };
    });
  }

  function normalizeColConfig(raw) {
    var defaults = getDefaultColConfig();
    if (!Array.isArray(raw) || !raw.length) {
      return defaults;
    }
    var seen = {};
    var merged = [];
    raw.forEach(function(item) {
      if (!item || !item.field || !COLUMN_DEF_MAP[item.field] || seen[item.field]) {
        return;
      }
      seen[item.field] = true;
      merged.push({
        field: item.field,
        visible: item.visible !== false
      });
    });
    defaults.forEach(function(item) {
      if (!seen[item.field]) {
        merged.push(item);
      }
    });
    return merged;
  }

  function loadColConfig() {
    try {
      var raw = localStorage.getItem(COLUMN_STORAGE_KEY);
      if (!raw) {
        return getDefaultColConfig();
      }
      return normalizeColConfig(JSON.parse(raw));
    } catch (e) {
      return getDefaultColConfig();
    }
  }

  function saveColConfig(config) {
    colConfig = normalizeColConfig(config);
    localStorage.setItem(COLUMN_STORAGE_KEY, JSON.stringify(colConfig));
  }

  function buildColItem(def, visible) {
    var col = {
      field: def.field,
      title: def.title,
      width: def.width,
      hide: !visible
    };
    if (def.templet) {
      col.templet = def.templet;
    }
    return col;
  }

  function buildCols(config) {
    config = normalizeColConfig(config || colConfig);
    var cols = [];
    var firstVisibleField = null;
    config.forEach(function(item) {
      if (item.visible && !firstVisibleField) {
        firstVisibleField = item.field;
      }
    });
    config.forEach(function(item) {
      var def = COLUMN_DEF_MAP[item.field];
      if (!def) {
        return;
      }
      var col = buildColItem(def, item.visible);
      if (def.fixed && item.field === firstVisibleField) {
        col.fixed = def.fixed;
      } else {
        delete col.fixed;
      }
      cols.push(col);
    });
    if (!cols.length) {
      cols.push(buildColItem(COLUMN_DEF_MAP.id, true));
    }
    return [cols];
  }

  function fixRichTableCellHeights() {
    var fields = ['fp_hdata_text', 'api_by_request_text', 'api_by_response_text', 'api_risk_request_text', 'api_risk_response_text'];
    fields.forEach(function(field) {
      $('.log-table-wrap .layui-table-body td[data-field="' + field + '"]').each(function() {
        var $cell = $(this).children('.layui-table-cell');
        $cell.css({ height: 'auto', whiteSpace: 'normal', overflow: 'visible' });
        $(this).css('height', 'auto');
      });
    });
  }

  function applyTableRowStyles(res) {
    if (!res || !Array.isArray(res.data)) {
      return;
    }
    var $rows = $('.log-table-wrap .layui-table-body table tbody tr');
    res.data.forEach(function(row, idx) {
      var $tr = $rows.eq(idx);
      if (!$tr.length) {
        return;
      }
      if (row.reason && String(row.reason).indexOf('未确定流量来源') !== -1) {
        $tr.css({ color: '#856404', backgroundColor: '#fff8e6' });
      }
    });
  }

  function onTableDataRendered(res, count) {
    if (typeof count === 'number') {
      $("#total_nums").text(count);
    }
    applyTableRowStyles(res);
    fixRichTableCellHeights();
  }

  function reloadTableCols() {
    table.reload(TABLE_ID, {
      cols: buildCols(colConfig),
      done: function(res, curr, count) {
        onTableDataRendered(res, count);
      }
    });
  }

  function renderColSettingsList() {
    var html = '<p class="col-settings-tip">勾选显示列，拖拽左侧手柄调整顺序。设置保存在本浏览器。</p>';
    html += '<ul class="col-settings-list" id="col-settings-list">';
    colConfig.forEach(function(item) {
      var def = COLUMN_DEF_MAP[item.field];
      if (!def) {
        return;
      }
      html += '<li class="col-settings-item" draggable="true" data-field="' + escapeAttr(item.field) + '">';
      html += '<span class="col-settings-handle" title="拖拽排序">☰</span>';
      html += '<input type="checkbox" lay-skin="primary" class="col-settings-check" data-field="' + escapeAttr(item.field) + '"' + (item.visible ? ' checked' : '') + '>';
      html += '<span class="col-settings-title">' + escapeHtml(def.title) + '</span>';
      html += '</li>';
    });
    html += '</ul>';
    html += '<div class="col-settings-actions">';
    html += '<button type="button" class="layui-btn layui-btn-sm" id="col-settings-apply">应用</button>';
    html += '<button type="button" class="layui-btn layui-btn-sm layui-btn-primary" id="col-settings-reset">恢复默认</button>';
    html += '</div>';
    return html;
  }

  function refreshColSettingsPanel($panel) {
    var checks = {};
    $panel.find('.col-settings-item').each(function() {
      var field = this.getAttribute('data-field');
      checks[field] = $(this).find('.col-settings-check').prop('checked');
    });
    colConfig.forEach(function(item) {
      if (Object.prototype.hasOwnProperty.call(checks, item.field)) {
        item.visible = !!checks[item.field];
      }
    });
    $panel.find('.col-settings-body').html(renderColSettingsList());
    form.render('checkbox');
  }

  function bindColSettingsEvents(layerIndex, $panel) {
    $panel.off('.colSettings');
    $panel.on('dragstart.colSettings', '.col-settings-item', function(e) {
      dragField = this.getAttribute('data-field');
      this.classList.add('dragging');
      if (e.originalEvent && e.originalEvent.dataTransfer) {
        e.originalEvent.dataTransfer.effectAllowed = 'move';
      }
    });
    $panel.on('dragend.colSettings', '.col-settings-item', function() {
      this.classList.remove('dragging');
      $panel.find('.col-settings-item').removeClass('drag-over');
      dragField = null;
    });
    $panel.on('dragover.colSettings', '.col-settings-item', function(e) {
      e.preventDefault();
      $panel.find('.col-settings-item').removeClass('drag-over');
      this.classList.add('drag-over');
    });
    $panel.on('drop.colSettings', '.col-settings-item', function(e) {
      e.preventDefault();
      var targetField = this.getAttribute('data-field');
      if (!dragField || !targetField || dragField === targetField) {
        return;
      }
      var fromIdx = -1;
      var toIdx = -1;
      colConfig.forEach(function(item, idx) {
        if (item.field === dragField) fromIdx = idx;
        if (item.field === targetField) toIdx = idx;
      });
      if (fromIdx < 0 || toIdx < 0) {
        return;
      }
      var moved = colConfig.splice(fromIdx, 1)[0];
      colConfig.splice(toIdx, 0, moved);
      refreshColSettingsPanel($panel);
    });

    $panel.on('click.colSettings', '#col-settings-apply', function() {
      var next = [];
      $panel.find('.col-settings-item').each(function() {
        var field = this.getAttribute('data-field');
        var visible = $(this).find('.col-settings-check').prop('checked');
        next.push({ field: field, visible: !!visible });
      });
      if (!next.some(function(item) { return item.visible; })) {
        layer.msg('至少保留一列显示');
        return;
      }
      saveColConfig(next);
      reloadTableCols();
      layer.close(layerIndex);
      layer.msg('列设置已应用');
    });

    $panel.on('click.colSettings', '#col-settings-reset', function() {
      colConfig = getDefaultColConfig();
      refreshColSettingsPanel($panel);
    });
  }

  function openColSettings() {
    colConfig = loadColConfig();
    var shell = '<div class="col-settings-body">' + renderColSettingsList() + '</div>';
    var layerIndex = layer.open({
      type: 1,
      title: '表格列设置',
      area: ['360px', '560px'],
      shadeClose: true,
      content: '<div class="col-settings-panel" style="padding:14px 16px 16px;">' + shell + '</div>',
      success: function(layero) {
        var $panel = layero.find('.col-settings-panel');
        form.render('checkbox');
        bindColSettingsEvents(layerIndex, $panel);
      }
    });
  }

  colConfig = loadColConfig();

  table.render({
    elem: '#ID-table-demo-search',
    id: TABLE_ID,
    url: 'datatable/datalist.php',
    cols: buildCols(colConfig),
    page: true,
    limit: 50,
    height: 800,
    size: "sm",
    skin: "line",
    even: true,
    done: function(res, curr, count, origin){
      onTableDataRendered(res, count);
    },
  });

  $('#btn-col-settings').on('click', openColSettings);

  $(document).on('click', '.fp-hdata-btn', function(e) {
    e.stopPropagation();
    var id = this.getAttribute('data-explain-id');
    var logId = this.getAttribute('data-log-id');
    var field = this.getAttribute('data-field');
    var full = this.getAttribute('data-full') || '';

    if (id) {
      if (window.__fpExplainCache && window.__fpExplainCache[id]) {
        layer.open({
          type: 1,
          title: '二次风控判定解析 #' + id,
          area: ['720px', '85vh'],
          shadeClose: true,
          content: '<div style="padding:16px 18px;max-height:calc(85vh - 60px);overflow:auto;">' + window.__fpExplainCache[id] + '</div>'
        });
        return;
      }
      var loadIdx = layer.load(1);
      $.getJSON('datatable/fp_explain.php', { id: id }, function(res) {
        layer.close(loadIdx);
        if (!res || res.code !== 0 || !res.html) {
          layer.msg((res && res.msg) ? res.msg : '加载判定详情失败');
          return;
        }
        window.__fpExplainCache = window.__fpExplainCache || {};
        window.__fpExplainCache[id] = res.html;
        layer.open({
          type: 1,
          title: '二次风控判定解析 #' + id,
          area: ['720px', '85vh'],
          shadeClose: true,
          content: '<div style="padding:16px 18px;max-height:calc(85vh - 60px);overflow:auto;">' + res.html + '</div>'
        });
      }).fail(function() {
        layer.close(loadIdx);
        layer.msg('加载判定详情失败');
      });
      return;
    }

    if (logId && field) {
      var popupTitle = this.getAttribute('data-title') || (field === 'fp_hdata' ? '指纹 hdata' : field);
      var isJson = this.classList.contains('fp-hdata-btn-json') || field.indexOf('api_') === 0 || field === 'fp_hdata';
      var loadIdx2 = layer.load(1);
      $.getJSON('datatable/log_blob.php', { id: logId, field: field }, function(res) {
        layer.close(loadIdx2);
        if (!res || res.code !== 0 || !res.data) {
          layer.msg((res && res.msg) ? res.msg : '加载失败');
          return;
        }
        var body = isJson ? escapeHtml(formatJsonPretty(res.data)) : escapeHtml(res.data);
        layer.open({
          type: 1,
          title: popupTitle + ' #' + logId,
          area: ['680px', '70vh'],
          shadeClose: true,
          content: '<pre style="padding:14px;margin:0;max-height:calc(70vh - 50px);overflow:auto;font-size:12px;white-space:pre-wrap;word-break:break-all;">' + body + '</pre>'
        });
      }).fail(function() {
        layer.close(loadIdx2);
        layer.msg('加载失败');
      });
      return;
    }

    if (full) {
      var popupTitleStatic = this.getAttribute('data-title') || '指纹 hdata';
      var bodyStatic = this.classList.contains('fp-hdata-btn-json')
        ? escapeHtml(formatJsonPretty(full))
        : escapeHtml(full);
      layer.open({
        type: 1,
        title: popupTitleStatic,
        area: ['680px', '70vh'],
        shadeClose: true,
        content: '<pre style="padding:14px;margin:0;max-height:calc(70vh - 50px);overflow:auto;font-size:12px;white-space:pre-wrap;word-break:break-all;">' + bodyStatic + '</pre>'
      });
    }
  });
  $(document).on('dblclick', '.cell-copy-link', function() {
    var full = this.getAttribute('data-full') || '';
    if (!full) return;
    var notify = function() { layer.msg('已复制完整内容'); };
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(full).then(notify).catch(function() {
        layer.msg('复制失败');
      });
      return;
    }
    var ta = document.createElement('textarea');
    ta.value = full;
    document.body.appendChild(ta);
    ta.select();
    try {
      document.execCommand('copy');
      notify();
    } catch (e) {
      layer.msg('复制失败');
    }
    document.body.removeChild(ta);
  });

  // 日期
  laydate.render({
    elem: '.demo-table-search-date'
  });
  // 搜索提交
  form.on('submit(demo-table-search)', function(data){
    var field = data.field; // 获得表单字段
    // 执行搜索重载
    table.reload('ID-table-demo-search', {
      page: {
        curr: 1 // 重新从第 1 页开始
      },
      where: field // 搜索的字段
    });
    // layer.msg('搜索成功<br>此处为静态模拟数据，实际使用时换成真实接口即可');
    return false; // 阻止默认 form 跳转
  });
       // table.reload('ID-table-demo-search', {
     //   page: {
     //     curr: 1 // 重新从第 1 页开始
     //   },
     // });
    var util = layui.util;
    util.on('lay-on', {
        confirm: function(){
          layer.confirm('确定清除所有日志数据？', {
            btn: ['确定', '关闭'] //按钮
          }, function(){
            $.get("datatable/datalist.php", {clear: "true"}, function(result){
                layer.msg('清除成功', {icon: 1});
                window.location.reload();
            });
          }, function(){

          });
        },
     });
});

  // 刷新按钮
  function resetTable() {
      var table = layui.table;
      table.reload('ID-table-demo-search', {
      page: {
          curr: 1 // 重新从第 1 页开始
      },
    });
  }

  $("#export").click(function() {
      var visit_date = $("input[name=visit_date]").val();
      var domain = $("input[name=domain]").val();
      var spage = $("input[name=spage]").val();
      var country = $("input[name=country]").val();
      var IP = $("input[name=IP]").val();
      var result = $("select[name=result]").val();

      var url = 'datatable/export.php?';
      var args = [];

      if(visit_date) {
          args.push( "visit_date=" + visit_date );
      }

      if(domain) {
          args.push( "domain=" + domain );
      }

      if(spage) {
          args.push( "spage=" + spage );
      }

      if(country) {
          args.push( "country=" + country );
      }

      if(IP) {
          args.push( "IP=" + IP );
      }

      if(result) {
          args.push( "result=" + encodeURIComponent(result) );
      }

      window.location.href=url + args.join("&");
  });
</script>
</body>
</html>
