浏览代码

feat: Add image search functionality.

dqzboy 1 年之前
父节点
当前提交
5d2fc5ca25
共有 6 个文件被更改,包括 255 次插入19 次删除
  1. 2 2
      README.en.md
  2. 2 2
      README.md
  3. 9 3
      hubcmdui/README.md
  4. 1 0
      hubcmdui/package.json
  5. 17 0
      hubcmdui/server.js
  6. 224 12
      hubcmdui/web/index.html

+ 2 - 2
README.en.md

@@ -128,7 +128,7 @@ docker logs -f [Container ID or Name]
 - [x] Automatically checks for and installs required dependency software such as Docker, Nginx/Caddy, etc., and ensures the system environment meets the operational requirements.
 - [x] Automatically renders the corresponding Nginx or Caddy service configuration based on the service you choose to deploy.
 - [x] Automatically cleans up files in the registry upload directory that are no longer referenced by any image or manifest.
-- [x] Support custom configuration of proxy cache time(PROXY_TTL)
+- [x] Support custom configuration of proxy cache time(PROXY_TTL)、Support configuring IP whitelist and blacklist to prevent malicious attacks.
 - [x] Provides features for restarting services, updating services, updating configurations, and uninstalling services, making it convenient for users to perform daily management and maintenance.
 - [x] Supports user selection of whether to provide authentication during deployment.
 - [x] Supports configuration of proxy (HTTP_PROXY), only supports HTTP.
@@ -212,7 +212,7 @@ docker pull gcr.your_domain_name/google-containers/pause:3.1
     </tr>
     <tr>
         <td width="50%" align="center"><img src="https://github.com/dqzboy/Docker-Proxy/assets/42825450/0ddb041b-64f6-4d93-b5bf-85ad3b53d0e0?raw=true"></td>
-        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/2efe5d7e-6542-4867-9e50-17fa0e704b23?raw=true"></td>
+        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/c7e368ca-7f1a-4311-9a10-a5f4f06d86d8?raw=true"></td>
     </tr>
 </table>
 

+ 2 - 2
README.md

@@ -210,7 +210,7 @@ docker pull gcr.your_domain_name/google-containers/pause:3.1
     </tr>
 </table>
 
-## 💻 UI
+## 💻 UI界面
 
 > HubCMD-UI 手动安装教程:[点击查看教程](hubcmdui/README.md)
 
@@ -222,7 +222,7 @@ docker pull gcr.your_domain_name/google-containers/pause:3.1
     </tr>
     <tr>
         <td width="50%" align="center"><img src="https://github.com/dqzboy/Docker-Proxy/assets/42825450/0ddb041b-64f6-4d93-b5bf-85ad3b53d0e0?raw=true"></td>
-        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/2efe5d7e-6542-4867-9e50-17fa0e704b23?raw=true"></td>
+        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/c7e368ca-7f1a-4311-9a10-a5f4f06d86d8?raw=true"></td>
     </tr>
 </table>
 

+ 9 - 3
hubcmdui/README.md

@@ -6,7 +6,7 @@
   <p align="center">
   <img src="https://github.com/dqzboy/Docker-Proxy/assets/42825450/c187d66f-152e-4172-8268-e54bd77d48bb" width="230px" height="200px">
       <br>
-      <i>Docker-Proxy 镜像代理加速快速获取命令UI面板.</i>
+      <i>Docker镜像加速命令查询获取和镜像搜索UI面板.</i>
   </p>
 </div>
 
@@ -91,13 +91,19 @@ docker logs -f [容器ID或名称]
 
 <table>
     <tr>
-        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/637756c0-65b3-4f1a-b65d-14f522fd1660"?raw=true"></td>
+        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/c7e368ca-7f1a-4311-9a10-a5f4f06d86d8"?raw=true"></td>
     </tr>
 </table>
 
 <table>
     <tr>
-        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/f6a515eb-5b63-498e-b288-6b2a20e1139f"?raw=true"></td>
+        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/0024d3e7-3b9d-4a10-9079-f2b91633a5f5"?raw=true"></td>
+    </tr>
+</table>
+
+<table>
+    <tr>
+        <td width="50%" align="center"><img src="https://github.com/user-attachments/assets/8569c5c4-4ce6-4cd4-8547-fa9816019049"?raw=true"></td>
     </tr>
 </table>
 

+ 1 - 0
hubcmdui/package.json

@@ -1,5 +1,6 @@
 {
   "dependencies": {
+    "axios": "^1.7.5",
     "bcrypt": "^5.1.1",
     "express": "^4.19.2",
     "express-session": "^1.18.0",

+ 17 - 0
hubcmdui/server.js

@@ -6,6 +6,7 @@ const session = require('express-session');
 const bcrypt = require('bcrypt');
 const crypto = require('crypto');
 const logger = require('morgan'); // 引入 morgan 作为日志工具
+const axios = require('axios'); // 用于发送 HTTP 请求
 
 const app = express();
 app.use(express.json());
@@ -23,6 +24,22 @@ app.get('/admin', (req, res) => {
   res.sendFile(path.join(__dirname, 'web', 'admin.html'));
 });
 
+// 新增:Docker Hub 搜索 API
+app.get('/api/search', async (req, res) => {
+  const searchTerm = req.query.term;
+  if (!searchTerm) {
+    return res.status(400).json({ error: 'Search term is required' });
+  }
+
+  try {
+    const response = await axios.get(`https://hub.docker.com/v2/search/repositories/?query=${encodeURIComponent(searchTerm)}`);
+    res.json(response.data);
+  } catch (error) {
+    console.error('Error searching Docker Hub:', error);
+    res.status(500).json({ error: 'Failed to search Docker Hub' });
+  }
+});
+
 const CONFIG_FILE = path.join(__dirname, 'config.json');
 const USERS_FILE = path.join(__dirname, 'users.json');
 

+ 224 - 12
hubcmdui/web/index.html

@@ -314,6 +314,88 @@
             transition: opacity 0.5s ease-in-out;
             z-index: 1000;
         }
+
+        /* 搜索样式 */
+        .search-container {
+            margin-top: 20px;
+        }
+        #searchResults {
+            margin-top: 20px;
+            display: none;
+        }
+        .search-result-item {
+            border: 1px solid #e1e4e8;
+            border-radius: 6px;
+            padding: 10px;
+            margin-bottom: 10px;
+        }
+        .search-result-item h3 {
+            margin-top: 0;
+        }
+        .search-result-item p {
+            margin-bottom: 5px;
+        }
+        .use-image-btn {
+            background-color: #0366d6;
+            color: white;
+            border: none;
+            padding: 5px 10px;
+            border-radius: 4px;
+            cursor: pointer;
+        }
+        .use-image-btn:hover {
+            background-color: #0256b9;
+        }
+        .official-image {
+            background-color: #e6f3ff;
+            border-left: 5px solid #0366d6;
+            padding-left: 15px;
+        }
+        .search-result-item {
+            margin-bottom: 15px;
+            padding: 10px;
+            border: 1px solid #e1e4e8;
+            border-radius: 6px;
+        }
+        .search-result-item h3 {
+            margin-top: 0;
+            color: #0366d6;
+        }
+        .official-badge {
+            background-color: #28a745;
+            color: white;
+            padding: 2px 5px;
+            border-radius: 3px;
+            font-size: 12px;
+            margin-left: 10px;
+        }
+        .stars {
+            color: #586069;
+            font-size: 14px;
+        }
+        .tab-container {
+            display: flex;
+            justify-content: center;
+            margin-bottom: 20px;
+        }
+        .tab {
+            padding: 10px 20px;
+            cursor: pointer;
+            border: 1px solid #d1d5da;
+            background-color: #f6f8fa;
+            color: #24292e;
+        }
+        .tab.active {
+            background-color: #0366d6;
+            color: white;
+            border-color: #0366d6;
+        }
+        .content {
+            display: none;
+        }
+        .content.active {
+            display: block;
+        }
     </style>
 </head>
 <body>
@@ -329,13 +411,38 @@
             </nav>
         </div>
     </header>
+
     <!-- 内容容器 -->
     <div class="container">
-        <h1>Docker 镜像代理加速</h1>
-        <div class="input-group">
-            <input type="text" id="imageInput" placeholder="输入 Docker 镜像,例如:nginx 或 mysql:5.7">
-            <button onclick="generateCommands()">获取加速命令</button>
+        <h1>Docker 镜像加速和镜像搜索</h1>
+        <!-- 标签切换 -->
+        <div class="tab-container">
+            <div class="tab active" onclick="switchTab('accelerate')">镜像加速</div>
+            <div class="tab" onclick="switchTab('search')">镜像搜索</div>
+        </div>
+
+        <!-- 镜像加速内容 -->
+        <div id="accelerateContent" class="content active">
+            <div class="input-group">
+                <input type="text" id="imageInput" placeholder="请输入 Docker 镜像名称,例如:nginx 或 mysql:5.7">
+                <button onclick="generateCommands(document.getElementById('imageInput').value.trim())">获取加速命令</button>
+            </div>
+            <!-- 结果容器 -->
+            <div id="result" style="display:none;">
+                <h2>加速命令</h2>
+                <div id="commandsContainer"></div>
+            </div>
+        </div>
+
+        <!-- 镜像搜索内容 -->
+        <div id="searchContent" class="content">
+            <div class="search-container">
+                <input type="text" id="searchInput" placeholder="请输入 Docker 镜像名称进行搜索">
+                <button onclick="searchDockerHub()">搜索Docker镜像</button>
+            </div>
+            <div id="searchResults"></div>
         </div>
+
         <!-- 广告容器 -->
         <div class="carousel" id="carousel">
             <div class="carousel-inner" id="carouselInner">
@@ -344,11 +451,6 @@
             <div class="carousel-control left" onclick="prevSlide()">&#10094;</div>
             <div class="carousel-control right" onclick="nextSlide()">&#10095;</div>
         </div>
-        <!-- 结果容器 -->
-        <div id="result" style="display:none;">
-            <h2>加速命令</h2>
-            <div id="commandsContainer"></div>
-        </div>
     </div>
     <!-- 页脚 -->
     <footer class="footer">
@@ -375,11 +477,40 @@
         let currentIndex = 0;
         let items = [];
 
+        // 标签切换功能
+        function switchTab(tabName) {
+            const tabs = document.querySelectorAll('.tab');
+            const contents = document.querySelectorAll('.content');
+
+            tabs.forEach(tab => tab.classList.remove('active'));
+            contents.forEach(content => content.classList.remove('active'));
+
+            document.querySelector(`.tab:nth-child(${tabName === 'accelerate' ? '1' : '2'}`).classList.add('active');
+            document.getElementById(`${tabName}Content`).classList.add('active');
+
+            // 重置显示
+            document.getElementById('searchResults').style.display = 'none';
+            document.getElementById('result').style.display = 'none';
+            document.getElementById('carousel').style.display = 'flex';
+            document.getElementById('backToTopBtn').style.display = 'none';
+
+            if (tabName === 'accelerate') {
+                const imageInput = document.getElementById('imageInput').value.trim();
+                if (imageInput) {
+                    generateCommands(imageInput);
+                }
+            } else {
+                document.getElementById('searchInput').value = '';
+            }
+        }
+
         // 生成加速命令
-        function generateCommands() {
-            const imageInput = document.getElementById('imageInput').value.trim();
+        function generateCommands(imageInput) {
             if (!imageInput) {
-                alert('请输入 Docker 镜像');
+                imageInput = document.getElementById('imageInput').value.trim();
+            }
+            if (!imageInput) {
+                alert('请输入 Docker 镜像名称');
                 return;
             }
             let [imageName, tag] = imageInput.split(':');
@@ -501,6 +632,82 @@
             startAutoPlay();
         }
 
+        // 搜索功能
+        async function searchDockerHub() {
+            const searchTerm = document.getElementById('searchInput').value.trim();
+            if (!searchTerm) {
+                alert('请输入搜索关键词');
+                return;
+            }
+
+            const searchResults = document.getElementById('searchResults');
+            searchResults.innerHTML = '正在搜索...';
+            searchResults.style.display = 'block';
+            document.getElementById('result').style.display = 'none';
+            document.getElementById('carousel').style.display = 'none';
+            document.getElementById('backToTopBtn').style.display = 'block';
+
+            try {
+                const response = await fetch(`/api/search?term=${encodeURIComponent(searchTerm)}`);
+                const data = await response.json();
+
+                if (data.results && data.results.length > 0) {
+                    const officialImages = data.results.filter(result => result.is_official);
+                    const unofficialImages = data.results.filter(result => !result.is_official)
+                        .sort((a, b) => (b.star_count || 0) - (a.star_count || 0))
+                        .slice(0, 5);
+
+                    searchResults.innerHTML = '';
+                    
+                    // 显示官方镜像
+                    officialImages.forEach(result => {
+                        searchResults.appendChild(createResultItem(result, true));
+                    });
+
+                    // 显示前5个非官方镜像
+                    unofficialImages.forEach(result => {
+                        searchResults.appendChild(createResultItem(result, false));
+                    });
+
+                    if (officialImages.length === 0 && unofficialImages.length === 0) {
+                        searchResults.innerHTML = '未找到匹配的镜像';
+                    }
+                } else {
+                    searchResults.innerHTML = '未找到匹配的镜像';
+                }
+            } catch (error) {
+                console.error('搜索出错:', error);
+                searchResults.innerHTML = '搜索时发生错误,请稍后重试';
+            }
+        }
+
+        function createResultItem(result, isOfficial) {
+            const resultItem = document.createElement('div');
+            resultItem.className = `search-result-item ${isOfficial ? 'official-image' : ''}`;
+            
+            const officialBadge = isOfficial ? '<span class="official-badge">官方</span>' : '';
+            const stars = result.star_count !== undefined ? `<span class="stars">★ ${result.star_count}</span>` : '';
+
+            resultItem.innerHTML = `
+                <h3>${result.name || result.repo_name || '未知名称'} ${officialBadge}</h3>
+                <p>描述: ${result.description || result.short_description || '无描述'}</p>
+                <p>${stars}</p>
+                <button class="use-image-btn" onclick="useImage('${result.name || result.repo_name || ''}')">使用此镜像</button>
+            `;
+            return resultItem;
+        }
+
+        function useImage(imageName) {
+            if (imageName) {
+                document.getElementById('imageInput').value = imageName;
+                switchTab('accelerate');
+                // 直接生成加速命令,无需用户再次点击
+                generateCommands(imageName);
+            } else {
+                alert('无效的 Docker 镜像名称');
+            }
+        }
+
         // 获取并加载配置
         async function loadConfig() {
             try {
@@ -554,9 +761,14 @@
                 if (config.proxyDomain) {
                     proxyDomain = config.proxyDomain;
                 }
+                // 添加对搜索API的支持
+                if (config.searchApiEndpoint) {
+                    window.searchApiEndpoint = config.searchApiEndpoint;
+                }
             } catch (error) {
                 console.error('加载配置失败:', error);
             }
+            switchTab('accelerate');
         }
 
         loadConfig();