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

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