【ip 扫描html小工具】

还不是很完善,还有一些问题:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>IP Web 服务扫描器 (支持断点续扫)</title>
    <style>
      :root {
        --primary-color: #2563eb;
        --success-color: #16a34a; /* 新增绿色用于继续按钮 */
        --danger-color: #ef4444;
        --bg-color: #f8fafc;
        --card-bg: #ffffff;
        --text-color: #334155;
        --border-color: #e2e8f0;
      }

      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
        background-color: var(--bg-color);
        color: var(--text-color);
        margin: 0;
        padding: 20px;
        line-height: 1.5;
      }

      .container {
        max-width: 900px;
        margin: 0 auto;
        background: var(--card-bg);
        padding: 30px;
        border-radius: 12px;
        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
      }

      h1 {
        text-align: center;
        color: var(--primary-color);
        margin-bottom: 30px;
      }

      .control-panel {
        display: grid;
        /* IP(3) 模式(2) 并发(1) 按钮(1.5) */
        grid-template-columns: 3fr 2fr 1fr 1.5fr; 
        gap: 15px;
        margin-bottom: 20px;
      }

      .input-group {
        display: flex;
        flex-direction: column;
      }

      label {
        font-size: 0.875rem;
        font-weight: 600;
        margin-bottom: 5px;
      }

      input[type="text"],
      input[type="number"],
      select {
        padding: 10px;
        border: 1px solid var(--border-color);
        border-radius: 6px;
        font-size: 1rem;
        outline: none;
        height: 42px;
        box-sizing: border-box;
        width: 100%;
      }

      input[type="text"]:focus,
      input[type="number"]:focus,
      select:focus {
        border-color: var(--primary-color);
        box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
      }

      .btn-group {
        display: flex;
        gap: 10px;
        align-self: end;
      }

      button {
        color: white;
        border: none;
        border-radius: 6px;
        padding: 0 15px;
        font-size: 0.95rem;
        cursor: pointer;
        font-weight: 600;
        transition: background-color 0.2s, opacity 0.2s;
        height: 42px;
        flex: 1;
        white-space: nowrap;
      }

      .btn-primary {
        background-color: var(--primary-color);
      }
      .btn-primary:hover {
        background-color: #1d4ed8;
      }

      /* 继续按钮样式 */
      .btn-continue {
        background-color: var(--success-color);
      }
      .btn-continue:hover {
        background-color: #15803d;
      }

      .btn-danger {
        background-color: var(--danger-color);
      }
      .btn-danger:hover {
        background-color: #dc2626;
      }

      button:disabled {
        background-color: #94a3b8;
        cursor: not-allowed;
        opacity: 0.7;
      }

      .status-bar {
        margin: 20px 0;
        padding: 10px;
        background: #f1f5f9;
        border-radius: 6px;
        font-size: 0.9rem;
        display: flex;
        justify-content: space-between;
      }

      .progress-container {
        height: 4px;
        background: #e2e8f0;
        border-radius: 2px;
        overflow: hidden;
        margin-bottom: 20px;
      }

      .progress-bar {
        height: 100%;
        background: var(--primary-color);
        width: 0%;
        transition: width 0.3s ease;
      }

      table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 20px;
      }

      th,
      td {
        padding: 12px;
        text-align: left;
        border-bottom: 1px solid var(--border-color);
      }

      th {
        background-color: #f8fafc;
        font-weight: 600;
      }

      .status-open {
        color: #16a34a;
        font-weight: bold;
        background: #dcfce7;
        padding: 4px 8px;
        border-radius: 4px;
        font-size: 0.8rem;
      }

      .notice {
        margin-top: 30px;
        padding: 15px;
        background-color: #fff7ed;
        border-left: 4px solid #f97316;
        font-size: 0.85rem;
        color: #9a3412;
      }

      @media (max-width: 700px) {
        .control-panel {
          grid-template-columns: 1fr; 
        }
        .btn-group {
          width: 100%;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Web 服务探测器</h1>

      <div class="control-panel">
        <div class="input-group">
          <label for="targetIp">目标 IP 地址</label>
          <input
            type="text"
            id="targetIp"
            placeholder="例如: 192.168.1.1"
            value="127.0.0.1"
            oninput="resetScanState()" 
          />
        </div>
        <div class="input-group">
          <label for="portMode">扫描模式</label>
          <select id="portMode" onchange="resetScanState()">
            <option value="common">常用 Web 端口 (Top 20)</option>
            <option value="extended">扩展 Web 端口 (Top 100)</option>
            <option value="all">全部端口(1-65535)</option>
          </select>
        </div>
        <div class="input-group">
            <label for="concurrency">并发数</label>
            <input 
                type="number" 
                id="concurrency" 
                value="20" 
                min="1" 
                max="1000"
                title="同时发起的请求数量,建议 20-100"
            />
          </div>
        <div class="input-group">
          <label>&nbsp;</label>
          <div class="btn-group">
            <button id="startBtn" class="btn-primary" onclick="handleStartClick()">开始扫描</button>
            <button id="stopBtn" class="btn-danger" onclick="stopScan()" disabled>停止</button>
          </div>
        </div>
      </div>

      <div class="progress-container">
        <div class="progress-bar" id="progressBar"></div>
      </div>

      <div class="status-bar">
        <span id="statusText">等待开始...</span>
        <span id="foundCount">发现: 0</span>
      </div>

      <table>
        <thead>
          <tr>
            <th>地址</th>
            <th>端口</th>
            <th>协议 (猜测)</th>
            <th>状态</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody id="resultTable">
          <!-- 结果将在这里生成 -->
        </tbody>
      </table>

      <div class="notice">
        <strong>ℹ️ 提示:</strong>
        点击"停止"后,您可以点击"继续扫描"从断点处恢复任务。如果修改了 IP 或模式,将重新开始。
      </div>
    </div>

    <script>
      // 常用 Web 端口列表
      const COMMON_PORTS = [
        80, 443, 8080, 8443, 3000, 5000, 8000, 8008, 8888, 9000, 9200, 7001, 81,
        82, 88, 8181, 9090, 10000, 27017,
      ];

      // 扩展端口列表 (模拟)
      const EXTENDED_PORTS = [
        ...COMMON_PORTS,
        5900,
        6379,
        1900,
        53,
        111,
        445,
        21,
        22,
        23,
        25,
        8081,
        8082,
        8083,
        8090,
        9001,
        9999,
      ];
      const ALL_PORTS = Array.from({ length: 65535 }, (_, i) => i + 1);

      // 状态变量
      let isScanning = false;
      let shouldStop = false;
      let isPaused = false;
      
      // 断点续传相关
      let pausedIndex = 0;        // 记录暂停时的索引
      let currentPortList = [];   // 记录当前正在扫描的端口列表
      let globalProcessed = 0;    // 记录已处理数量
      let globalFound = 0;        // 记录已发现数量

      /**
       * 用户修改IP或模式时,重置为"全新开始"状态
       */
      function resetScanState() {
          if (isScanning) return; // 扫描中不重置,等待停止
          isPaused = false;
          pausedIndex = 0;
          
          const startBtn = document.getElementById("startBtn");
          startBtn.textContent = "开始扫描";
          startBtn.className = "btn-primary";
          document.getElementById("statusText").textContent = "准备就绪 (参数已变更)";
      }

      /**
       * 点击"开始/继续"按钮的入口
       */
      async function handleStartClick() {
        if (isScanning) return;

        const ipInput = document.getElementById("targetIp").value.trim();
        if (!isValidIP(ipInput)) {
          alert("请输入有效的 IP 地址!");
          return;
        }

        const concurrencyInput = document.getElementById("concurrency").value;
        let batchSize = parseInt(concurrencyInput, 10);
        if (isNaN(batchSize) || batchSize < 1) {
            alert("请输入有效的并发数!");
            return;
        }

        // UI 设置
        const startBtn = document.getElementById("startBtn");
        const stopBtn = document.getElementById("stopBtn");
        
        isScanning = true;
        shouldStop = false; // 重置停止标志
        startBtn.disabled = true;
        startBtn.textContent = "扫描中...";
        stopBtn.disabled = false;
        stopBtn.textContent = "停止";

        // 判断是"新扫描"还是"继续扫描"
        if (!isPaused) {
            // === 新扫描初始化 ===
            document.getElementById("resultTable").innerHTML = "";
            document.getElementById("foundCount").textContent = "发现: 0";
            globalFound = 0;
            globalProcessed = 0;
            pausedIndex = 0;
            updateProgress(0);

            // 生成端口列表
            const mode = document.getElementById("portMode").value;
            if (mode === "common") currentPortList = COMMON_PORTS;
            else if (mode === "extended") currentPortList = EXTENDED_PORTS;
            else currentPortList = ALL_PORTS;
            
            // 去重排序
            currentPortList = [...new Set(currentPortList)].sort((a, b) => a - b);
        }

        // 执行循环
        await runScanLoop(ipInput, batchSize);
      }

      /**
       * 核心扫描循环
       */
      async function runScanLoop(ip, batchSize) {
        const total = currentPortList.length;
        
        // 从 pausedIndex 开始循环 (如果是新扫描,pausedIndex 为 0)
        for (let i = pausedIndex; i < total; i += batchSize) {
            
          // 1. 检查是否按下了停止
          if (shouldStop) {
            pausedIndex = i; // 记录当前位置
            handleStopState(); // 处理 UI 变为暂停状态
            return;
          }

          // 2. 准备并发任务
          const batch = currentPortList.slice(i, i + batchSize);
          const promises = batch.map((port) => checkPort(ip, port));

          // 3. 等待当前批次完成
          const results = await Promise.all(promises);

          // 4. 处理结果
          results.forEach((res) => {
            if (res.isOpen) {
              globalFound++;
              addResultRow(res.ip, res.port, res.protocol);
            }
          });

          // 5. 更新进度
          globalProcessed += batch.length;
          // 防止进度条回退或溢出(因 batchSize 切分可能导致最后一点计算误差)
          const safeProcessed = Math.min(globalProcessed, total); 
          
          // 只有在没停止的时候才更新 UI,避免停止瞬间数字跳动
          if (!shouldStop) {
              updateProgress((i + batch.length) / total * 100); // 使用 i 计算进度更准确
              document.getElementById("statusText").textContent = 
                  `正在扫描: ${safeProcessed}/${total} (并发: ${batchSize})`;
              document.getElementById("foundCount").textContent = `发现: ${globalFound}`;
          }
        }

        // === 循环正常结束 ===
        handleFinishState();
      }

      function stopScan() {
          if (isScanning) {
              shouldStop = true;
              document.getElementById("stopBtn").textContent = "正在停止...";
          }
      }

      /**
       * 处理"暂停/停止"后的状态和 UI
       */
      function handleStopState() {
          isScanning = false;
          isPaused = true; // 标记为暂停状态
          
          const startBtn = document.getElementById("startBtn");
          const stopBtn = document.getElementById("stopBtn");

          startBtn.disabled = false;
          startBtn.textContent = "继续扫描";
          startBtn.className = "btn-continue"; // 切换为绿色按钮
          
          stopBtn.disabled = true;
          stopBtn.textContent = "停止";

          document.getElementById("statusText").textContent = 
            `已暂停 (进度: ${pausedIndex}/${currentPortList.length})`;
      }

      /**
       * 处理"扫描完成"后的状态和 UI
       */
      function handleFinishState() {
          isScanning = false;
          isPaused = false; // 任务完成,不再是暂停状态
          pausedIndex = 0;
          updateProgress(100);

          const startBtn = document.getElementById("startBtn");
          const stopBtn = document.getElementById("stopBtn");

          startBtn.disabled = false;
          startBtn.textContent = "开始扫描"; // 变回开始
          startBtn.className = "btn-primary"; // 变回蓝色
          
          stopBtn.disabled = true;
          stopBtn.textContent = "停止";

          document.getElementById("statusText").textContent = "扫描完成";
      }

      function checkPort(ip, port) {
        return new Promise((resolve) => {
          const timeout = 1500; 
          const controller = new AbortController();
          const id = setTimeout(() => controller.abort(), timeout);

          const protocol = port === 443 || port === 8443 ? "https" : "http";
          const url = `${protocol}://${ip}:${port}`;

          fetch(url, {
            mode: "no-cors",
            signal: controller.signal,
          })
            .then(() => {
              clearTimeout(id);
              resolve({ isOpen: true, ip, port, protocol });
            })
            .catch((err) => {
              clearTimeout(id);
              resolve({ isOpen: false, ip, port, protocol });
            });
        });
      }

      function addResultRow(ip, port, protocol) {
        const tbody = document.getElementById("resultTable");
        const tr = document.createElement("tr");
        const url = `${protocol}://${ip}:${port}`;

        tr.innerHTML = `
            <td>${ip}</td>
            <td>${port}</td>
            <td>${protocol.toUpperCase()}</td>
            <td><span class="status-open">开放 / 可达</span></td>
            <td><a href="${url}" target="_blank" style="color: var(--primary-color); text-decoration: none;">访问 &rarr;</a></td>
        `;
        tbody.appendChild(tr);
      }

      function updateProgress(percent) {
        // 限制最大 100%
        const p = Math.min(percent, 100);
        document.getElementById("progressBar").style.width = `${p}%`;
      }

      function isValidIP(ip) {
        return /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(ip) || ip === "localhost";
      }
    </script>
  </body>
</html>
相关推荐
AI绘画小331 小时前
【网络安全】IP 核心技能:获取、伪造、隐藏与挖掘
网络·tcp/ip·安全·web安全·网络安全
yzx9910132 小时前
基于Flask的智能语音增强系统模拟
前端·javascript·html
没落英雄3 小时前
简单了解 shadowDom
前端·html
BBB努力学习程序设计3 小时前
Bootstrap图片:让图片展示更优雅、更专业
前端·html
少云清3 小时前
【软件测试】4_基础知识 _HTML
前端·html
Want5953 小时前
HTML跳动的爱心①
前端·html
AI绘画小333 小时前
【网络安全】Wireshark 抓包过滤:源 / 目的 IP 过滤 + 命令大全
数据库·tcp/ip·测试工具·安全·web安全·wireshark
LaoZhangGong1234 小时前
TCP数据包格式分析
网络·网络协议·tcp/ip·以太网
老蒋新思维4 小时前
创客匠人 2025 高峰论谈(11.22-25):AI 智能体重构创始人 IP 打造与知识变现的管理逻辑
大数据·网络·人工智能·网络协议·tcp/ip·重构·知识付费