feat: Add image search functionality.

This commit is contained in:
dqzboy
2024-08-24 21:42:06 +08:00
parent f6636bb9c0
commit 5d2fc5ca25
6 changed files with 255 additions and 19 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

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

View File

@@ -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');

View File

@@ -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();