feat: New UI interface Mirror search support is more comprehensive, support for mirror search and TAG search

This commit is contained in:
dqzboy
2025-04-01 00:29:16 +08:00
parent 9b7027899a
commit 94a62f217d
13 changed files with 9481 additions and 2198 deletions

View File

@@ -77,7 +77,7 @@
- [x] 支持一键配置本机Docker代理和容器服务代理(HTTP_PROXY)仅支持http
- [x] 支持国内服务器一键部署解决国内环境无法安装Docker\Compose服务难题
- [x] 支持主流Linux发行版操作系统,例如Centos、Ubuntu、Rocky、Debian、Rhel等支持主流ARCH架构下部署包括linux/amd64、linux/arm64
- [x] HubCMD-UI服务实现镜像搜索、文档教程、容器管理、容器监控告警、网络测试等功能[Demo](https://dqzboy.github.io/proxyui/)
- [x] HubCMD-UI服务面板展示、镜像搜索、文档教程、容器管理、容器监控、网络测试、用户中心等功能
## 📦 部署
### 通过项目脚本部署
@@ -97,6 +97,11 @@ bash -c "$(curl -fsSL https://cdn.jsdelivr.net/gh/dqzboy/Docker-Proxy/install/Do
bash -c "$(curl -fsSL https://ghp.ci/https://raw.githubusercontent.com/dqzboy/Docker-Proxy/main/install/DockerProxy_Install.sh)"
```
- hubcmd-ui面板脚本安装
```
执行上面脚本选项为2 ---> 8 ---> 1
```
### 部署到第三方平台
<details>
<summary><strong>部署到 Render</strong></summary>
@@ -198,18 +203,6 @@ docker pull gcr.your_domain_name/google-containers/pause:3.1
> [自建Docker镜像加速服务加速与优化镜像管理](https://www.dqzboy.com/8709.html)<br>
> [自建Docker镜像加速并把域名托管到CF加速镜像拉取](https://www.dqzboy.com/17665.html)
## 📚 展示
<br/>
<table>
<tr>
<td width="50%" align="center"><b>系统环境检查</b></td>
<td width="50%" align="center"><b>服务部署安装</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="https://github.com/dqzboy/Docker-Proxy/assets/42825450/55df7f6f-c788-4200-9bcd-631998dc53ef?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/dqzboy/Docker-Proxy/assets/42825450/c544fb1e-ecd5-447c-9661-0c5913586118?raw=true"></td>
</tr>
</table>
## 💻 UI界面
@@ -218,20 +211,28 @@ docker pull gcr.your_domain_name/google-containers/pause:3.1
<br/>
<table>
<tr>
<td width="50%" align="center"><b>Docker Registry UI</b></td>
<td width="50%" align="center"><b>Docker HubCMD UI</b></td>
<td width="50%" align="center"><b>镜像加速</b></td>
<td width="50%" align="center"><b>镜像搜索</b></td>
</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/f394041e-954c-4b04-9cbb-d61c43290db6?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/b3a6a80a-284c-4117-b1bf-9d4c4556717f"?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/b3a6a80a-284c-4117-b1bf-9d4c4556717f"?raw=true"></td>
</tr>
<tr>
<td width="50%" align="center"><b>文档教程管理</b></td>
<td width="50%" align="center"><b>HubCMD-UI后台</b></td>
<td width="50%" align="center"><b>文档管理</b></td>
<td width="50%" align="center"><b>TAG搜索</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/6f34d717-95c8-47b4-89b8-812151904448?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/816c95af-dbd1-46ce-b550-87e0853f23e2?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/66be3dae-8d46-4144-932e-c5493c93fe2f"?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/f1208858-ec69-47b3-88d2-9a0bc112ea94"?raw=true"></td>
</tr>
<tr>
<td width="50%" align="center"><b>控制面板</b></td>
<td width="50%" align="center"><b>容器管理</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/bc066047-15d3-45fc-b363-ded37bfe1121"?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/78ad0e29-abfd-47d6-a132-c5b49b48bc95"?raw=true"></td>
</tr>
</table>

View File

@@ -40,7 +40,7 @@ npm install
#### 3. 启动服务
```bash
node server.js
node server.js
```
## 📦 Docker运行
@@ -91,13 +91,13 @@ docker logs -f [容器ID或名称]
<table>
<tr>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/f394041e-954c-4b04-9cbb-d61c43290db6"?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/bfe09d99-6727-43bc-9c2d-73e34d881953"?raw=true"></td>
</tr>
</table>
<table>
<tr>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/75c40b0b-d75b-4065-9678-d6fc8d2d282a"?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/42c4337e-44cb-4c35-bc6f-a5d21f3d1184"?raw=true"></td>
</tr>
</table>

View File

@@ -10,6 +10,11 @@
"text": "GitHub",
"link": "https://github.com/dqzboy/Docker-Proxy",
"newTab": true
},
{
"text": "VPS推荐",
"link": "https://dqzboy.github.io/proxyui/racknerd",
"newTab": true
}
],
"adImages": [
@@ -33,6 +38,6 @@
"telegramToken": "",
"telegramChatId": "",
"monitorInterval": 60,
"isEnabled": true
"isEnabled": false
}
}

View File

@@ -1 +1 @@
{"title":"Docker 配置镜像加速","content":"### Docker 配置镜像加速\n- 修改文件 `/etc/docker/daemon.json`(如果不存在则创建)\n\n```shell\nsudo mkdir -p /etc/docker\nsudo vi /etc/docker/daemon.json\n{\n \"registry-mirrors\": [\"https://<代理加速地址>\"]\n}\n\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```","published":false}
{"title":"Docker 配置镜像加速","content":"### Docker 配置镜像加速\n- 修改文件 `/etc/docker/daemon.json`(如果不存在则创建)\n\n```shell\nsudo mkdir -p /etc/docker\nsudo vi /etc/docker/daemon.json\n{\n \"registry-mirrors\": [\"https://<代理加速地址>\"]\n}\n\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```","published":true}

View File

@@ -1 +1 @@
{"title":"Containerd 配置镜像加速","content":"### Containerd 配置镜像加速\n- `/etc/containerd/config.toml`,添加如下的配置:\n\n```yaml\n [plugins.\"io.containerd.grpc.v1.cri\".registry]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"docker.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"k8s.gcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"gcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"ghcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"quay.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n```","published":false}
{"title":"Containerd 配置镜像加速","content":"### Containerd 配置镜像加速\n- `/etc/containerd/config.toml`,添加如下的配置:\n\n```yaml\n [plugins.\"io.containerd.grpc.v1.cri\".registry]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"docker.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"k8s.gcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"gcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"ghcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"quay.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n```","published":true}

View File

@@ -1 +1 @@
{"title":"Podman 配置镜像加速","content":"### Podman 配置镜像加速\n- 修改配置文件 `/etc/containers/registries.conf`,添加配置:\n\n```yaml\nunqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']\n\n[[registry]]\nprefix = \"docker.io\"\ninsecure = true\nlocation = \"registry-1.docker.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"k8s.gcr.io\"\ninsecure = true\nlocation = \"k8s.gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"gcr.io\"\ninsecure = true\nlocation = \"gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"ghcr.io\"\ninsecure = true\nlocation = \"ghcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"quay.io\"\ninsecure = true\nlocation = \"quay.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n```","published":false}
{"title":"Podman 配置镜像加速","content":"### Podman 配置镜像加速\n- 修改配置文件 `/etc/containers/registries.conf`,添加配置:\n\n```yaml\nunqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']\n\n[[registry]]\nprefix = \"docker.io\"\ninsecure = true\nlocation = \"registry-1.docker.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"k8s.gcr.io\"\ninsecure = true\nlocation = \"k8s.gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"gcr.io\"\ninsecure = true\nlocation = \"gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"ghcr.io\"\ninsecure = true\nlocation = \"ghcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"quay.io\"\ninsecure = true\nlocation = \"quay.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n```","published":true}

View File

@@ -1,27 +1,216 @@
let chalk;
import('chalk').then(module => {
chalk = module.default;
}).catch(err => {
console.error('Failed to load chalk:', err);
});
const fs = require('fs').promises;
const path = require('path');
const util = require('util');
// 日志级别配置
const LOG_LEVELS = {
debug: 0,
info: 1,
success: 2,
warn: 3,
error: 4,
fatal: 5
};
// 默认配置
const config = {
level: process.env.LOG_LEVEL || 'info',
logToFile: process.env.LOG_TO_FILE === 'true',
logDirectory: path.join(__dirname, 'logs'),
logFileName: 'app.log',
maxLogSize: 10 * 1024 * 1024, // 10MB
colorize: process.env.NODE_ENV !== 'production'
};
// 确保日志目录存在
async function ensureLogDirectory() {
if (config.logToFile) {
try {
await fs.access(config.logDirectory);
} catch (error) {
if (error.code === 'ENOENT') {
await fs.mkdir(config.logDirectory, { recursive: true });
console.log(`Created log directory: ${config.logDirectory}`);
} else {
throw error;
}
}
}
}
// 格式化时间 - 改进为更易读的格式
function getTimestamp() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
}
// 颜色代码
const COLORS = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
underscore: '\x1b[4m',
blink: '\x1b[5m',
reverse: '\x1b[7m',
hidden: '\x1b[8m',
// 前景色
black: '\x1b[30m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
// 背景色
bgBlack: '\x1b[40m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgYellow: '\x1b[43m',
bgBlue: '\x1b[44m',
bgMagenta: '\x1b[45m',
bgCyan: '\x1b[46m',
bgWhite: '\x1b[47m'
};
// 日志级别对应的颜色和标签
const LEVEL_STYLES = {
debug: { color: COLORS.cyan, label: 'DEBUG' },
info: { color: COLORS.blue, label: 'INFO ' },
success: { color: COLORS.green, label: 'DONE ' },
warn: { color: COLORS.yellow, label: 'WARN ' },
error: { color: COLORS.red, label: 'ERROR' },
fatal: { color: COLORS.bright + COLORS.red, label: 'FATAL' }
};
// 创建日志条目 - 改进格式
function createLogEntry(level, message, meta = {}) {
const timestamp = getTimestamp();
const levelInfo = LEVEL_STYLES[level] || { label: level.toUpperCase() };
// 元数据格式化 - 更简洁的呈现方式
let metaOutput = '';
if (meta instanceof Error) {
metaOutput = `\n ${COLORS.red}Error: ${meta.message}${COLORS.reset}`;
if (meta.stack) {
metaOutput += `\n ${COLORS.dim}Stack: ${meta.stack.split('\n').join('\n ')}${COLORS.reset}`;
}
} else if (Object.keys(meta).length > 0) {
// 检查是否为HTTP请求信息如果是则使用更简洁的格式
if (meta.ip && meta.userAgent) {
metaOutput = ` ${COLORS.dim}from ${meta.ip}${COLORS.reset}`;
} else {
// 对于其他元数据,仍然使用检查器但格式更友好
metaOutput = `\n ${util.inspect(meta, { colors: true, depth: 3 })}`;
}
}
// 为控制台格式化日志
const consoleOutput = config.colorize ?
`${COLORS.dim}${timestamp}${COLORS.reset} [${levelInfo.color}${levelInfo.label}${COLORS.reset}] ${message}${metaOutput}` :
`${timestamp} [${levelInfo.label}] ${message}${metaOutput ? ' ' + metaOutput.trim() : ''}`;
// 为文件准备JSON格式日志
const logObject = {
timestamp,
level: level,
message
};
if (Object.keys(meta).length > 0) {
logObject.meta = meta instanceof Error ?
{ name: meta.name, message: meta.message, stack: meta.stack } :
meta;
}
return {
formatted: consoleOutput,
json: JSON.stringify(logObject)
};
}
// 日志函数
async function log(level, message, meta = {}) {
if (LOG_LEVELS[level] < LOG_LEVELS[config.level]) {
return;
}
const { formatted, json } = createLogEntry(level, message, meta);
// 控制台输出
console.log(formatted);
// 文件日志
if (config.logToFile) {
try {
await ensureLogDirectory();
const logFilePath = path.join(config.logDirectory, config.logFileName);
await fs.appendFile(logFilePath, json + '\n', 'utf8');
} catch (err) {
console.error(`${COLORS.red}Error writing to log file: ${err.message}${COLORS.reset}`);
}
}
}
// 日志API
const logger = {
info: (message) => {
if (chalk) console.log(chalk.blue(`[INFO] ${message}`));
else console.log(`[INFO] ${message}`);
debug: (message, meta = {}) => log('debug', message, meta),
info: (message, meta = {}) => log('info', message, meta),
success: (message, meta = {}) => log('success', message, meta),
warn: (message, meta = {}) => log('warn', message, meta),
error: (message, meta = {}) => log('error', message, meta),
fatal: (message, meta = {}) => log('fatal', message, meta),
// 配置方法
configure: (options) => {
Object.assign(config, options);
},
warn: (message) => {
if (chalk) console.log(chalk.yellow(`[WARN] ${message}`));
else console.log(`[WARN] ${message}`);
},
error: (message) => {
if (chalk) console.log(chalk.red(`[ERROR] ${message}`));
else console.log(`[ERROR] ${message}`);
},
success: (message) => {
if (chalk) console.log(chalk.green(`[SUCCESS] ${message}`));
else console.log(`[SUCCESS] ${message}`);
// HTTP请求日志方法 - 简化输出格式
request: (req, res, duration) => {
const status = res.statusCode;
const method = req.method;
const url = req.originalUrl || req.url;
const userAgent = req.headers['user-agent'] || '-';
const ip = req.ip || req.connection.remoteAddress || '-';
let level = 'info';
if (status >= 500) level = 'error';
else if (status >= 400) level = 'warn';
// 为HTTP请求创建更简洁的日志消息
let statusIndicator = '';
if (config.colorize) {
if (status >= 500) statusIndicator = COLORS.red;
else if (status >= 400) statusIndicator = COLORS.yellow;
else if (status >= 300) statusIndicator = COLORS.cyan;
else if (status >= 200) statusIndicator = COLORS.green;
statusIndicator += status + COLORS.reset;
} else {
statusIndicator = status;
}
// 简化的请求日志格式
const message = `${method} ${url} ${statusIndicator} ${duration}ms`;
// 传递ip和userAgent作为元数据但以简洁方式显示
log(level, message, { ip, userAgent });
}
};
// 初始化
ensureLogDirectory().catch(err => {
console.error(`${COLORS.red}Failed to initialize logger: ${err.message}${COLORS.reset}`);
});
module.exports = logger;

View File

@@ -1,6 +1,7 @@
{
"dependencies": {
"axios": "^1.7.5",
"axios-retry": "^3.5.0",
"bcrypt": "^5.1.1",
"chalk": "^5.3.0",
"cors": "^2.8.5",
@@ -8,6 +9,8 @@
"express": "^4.19.2",
"express-session": "^1.18.0",
"morgan": "^1.10.0",
"node-cache": "^5.1.2",
"p-limit": "^3.1.0",
"validator": "^13.12.0",
"ws": "^8.18.0"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3474
hubcmdui/web/admin.html_bak Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2008
hubcmdui/web/style.css Normal file

File diff suppressed because it is too large Load Diff