diff --git a/hubcmdui/README.md b/hubcmdui/README.md
index 476ab29..50902b7 100644
--- a/hubcmdui/README.md
+++ b/hubcmdui/README.md
@@ -6,7 +6,7 @@
- Docker镜像加速命令查询获取和镜像搜索UI面板.
+ Docker镜像加速命令查询获取、镜像搜索、配置教程文档展示UI面板.
@@ -107,6 +107,12 @@ docker logs -f [容器ID或名称]
+
+
+  |
+
+
+
> 浏览器输入 `服务器地址:30080/admin` 访问后端页面,默认登入账号密码: root/admin@123
@@ -117,7 +123,7 @@ docker logs -f [容器ID或名称]
-  |
+  |
diff --git a/hubcmdui/documentation/1724594777670.json b/hubcmdui/documentation/1724594777670.json
new file mode 100644
index 0000000..e9fb47d
--- /dev/null
+++ b/hubcmdui/documentation/1724594777670.json
@@ -0,0 +1 @@
+{"title":"客户端配置教程","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```\n### 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```\n\n### Podman 配置镜像加速\n修改配置文件 `/etc/containers/registries.conf`,添加配置:\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}
\ No newline at end of file
diff --git a/hubcmdui/server.js b/hubcmdui/server.js
index d6df83b..1254b13 100644
--- a/hubcmdui/server.js
+++ b/hubcmdui/server.js
@@ -42,6 +42,8 @@ app.get('/api/search', async (req, res) => {
const CONFIG_FILE = path.join(__dirname, 'config.json');
const USERS_FILE = path.join(__dirname, 'users.json');
+const DOCUMENTATION_DIR = path.join(__dirname, 'documentation');
+const DOCUMENTATION_FILE = path.join(__dirname, 'documentation.md');
// 读取配置
async function readConfig() {
@@ -103,6 +105,54 @@ async function writeUsers(users) {
await fs.writeFile(USERS_FILE, JSON.stringify({ users }, null, 2), 'utf8');
}
+
+// 确保 documentation 目录存在
+async function ensureDocumentationDir() {
+ try {
+ await fs.access(DOCUMENTATION_DIR);
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ await fs.mkdir(DOCUMENTATION_DIR);
+ } else {
+ throw error;
+ }
+ }
+}
+
+// 读取文档
+async function readDocumentation() {
+ try {
+ await ensureDocumentationDir();
+ const files = await fs.readdir(DOCUMENTATION_DIR);
+ console.log('Files in documentation directory:', files); // 添加日志
+
+ const documents = await Promise.all(files.map(async file => {
+ const filePath = path.join(DOCUMENTATION_DIR, file);
+ const content = await fs.readFile(filePath, 'utf8');
+ const doc = JSON.parse(content);
+ return {
+ id: path.parse(file).name,
+ title: doc.title,
+ content: doc.content,
+ published: doc.published
+ };
+ }));
+
+ const publishedDocuments = documents.filter(doc => doc.published);
+ console.log('Published documents:', publishedDocuments); // 添加日志
+ return publishedDocuments;
+ } catch (error) {
+ console.error('Error reading documentation:', error);
+ throw error;
+ }
+}
+
+// 写入文档
+async function writeDocumentation(content) {
+ await fs.writeFile(DOCUMENTATION_FILE, content, 'utf8');
+}
+
+
// 登录验证
app.post('/api/login', async (req, res) => {
const { username, password, captcha } = req.body;
@@ -202,6 +252,145 @@ app.get('/api/captcha', (req, res) => {
res.json({ captcha });
});
+
+// API端点:获取文档列表
+app.get('/api/documentation-list', requireLogin, async (req, res) => {
+ try {
+ const files = await fs.readdir(DOCUMENTATION_DIR);
+ const documents = await Promise.all(files.map(async file => {
+ const content = await fs.readFile(path.join(DOCUMENTATION_DIR, file), 'utf8');
+ const doc = JSON.parse(content);
+ return { id: path.parse(file).name, ...doc };
+ }));
+ res.json(documents);
+ } catch (error) {
+ res.status(500).json({ error: '读取文档列表失败' });
+ }
+});
+
+// API端点:保存文档
+app.post('/api/documentation', requireLogin, async (req, res) => {
+ try {
+ const { id, title, content } = req.body;
+ const docId = id || Date.now().toString();
+ const docPath = path.join(DOCUMENTATION_DIR, `${docId}.json`);
+ await fs.writeFile(docPath, JSON.stringify({ title, content, published: false }));
+ res.json({ success: true });
+ } catch (error) {
+ res.status(500).json({ error: '保存文档失败' });
+ }
+});
+
+// API端点:删除文档
+app.delete('/api/documentation/:id', requireLogin, async (req, res) => {
+ try {
+ const docPath = path.join(DOCUMENTATION_DIR, `${req.params.id}.json`);
+ await fs.unlink(docPath);
+ res.json({ success: true });
+ } catch (error) {
+ res.status(500).json({ error: '删除文档失败' });
+ }
+});
+
+// API端点:切换文档发布状态
+app.post('/api/documentation/:id/toggle-publish', requireLogin, async (req, res) => {
+ try {
+ const docPath = path.join(DOCUMENTATION_DIR, `${req.params.id}.json`);
+ const content = await fs.readFile(docPath, 'utf8');
+ const doc = JSON.parse(content);
+ doc.published = !doc.published;
+ await fs.writeFile(docPath, JSON.stringify(doc));
+ res.json({ success: true });
+ } catch (error) {
+ res.status(500).json({ error: '更改发布状态失败' });
+ }
+});
+
+// API端点:获取文档
+app.get('/api/documentation', async (req, res) => {
+ try {
+ const documents = await readDocumentation();
+ console.log('Sending documents:', documents); // 添加日志
+ res.json(documents);
+ } catch (error) {
+ console.error('Error in /api/documentation:', error);
+ res.status(500).json({ error: '读取文档失败', details: error.message });
+ }
+});
+
+// API端点:保存文档
+app.post('/api/documentation', requireLogin, async (req, res) => {
+ try {
+ const { content } = req.body;
+ await writeDocumentation(content);
+ res.json({ success: true });
+ } catch (error) {
+ res.status(500).json({ error: '保存文档失败' });
+ }
+});
+
+// 获取文档列表函数
+async function getDocumentList() {
+ try {
+ await ensureDocumentationDir();
+ const files = await fs.readdir(DOCUMENTATION_DIR);
+ console.log('Files in documentation directory:', files);
+
+ const documents = await Promise.all(files.map(async file => {
+ try {
+ const filePath = path.join(DOCUMENTATION_DIR, file);
+ const content = await fs.readFile(filePath, 'utf8');
+ return {
+ id: path.parse(file).name,
+ title: path.parse(file).name, // 使用文件名作为标题
+ content: content,
+ published: true // 假设所有文档都是已发布的
+ };
+ } catch (fileError) {
+ console.error(`Error reading file ${file}:`, fileError);
+ return null;
+ }
+ }));
+
+ const validDocuments = documents.filter(doc => doc !== null);
+ console.log('Valid documents:', validDocuments);
+
+ return validDocuments;
+ } catch (error) {
+ console.error('Error reading document list:', error);
+ throw error; // 重新抛出错误,让上层函数处理
+ }
+}
+
+app.get('/api/documentation-list', async (req, res) => {
+ try {
+ const documents = await getDocumentList();
+ res.json(documents);
+ } catch (error) {
+ console.error('Error in /api/documentation-list:', error);
+ res.status(500).json({
+ error: '读取文档列表失败',
+ details: error.message,
+ stack: error.stack
+ });
+ }
+});
+
+app.get('/api/documentation/:id', async (req, res) => {
+ try {
+ const docId = req.params.id;
+ console.log('Fetching document with id:', docId); // 添加日志
+ const docPath = path.join(DOCUMENTATION_DIR, `${docId}.json`);
+ const content = await fs.readFile(docPath, 'utf8');
+ const doc = JSON.parse(content);
+ console.log('Sending document:', doc); // 添加日志
+ res.json(doc);
+ } catch (error) {
+ console.error('Error reading document:', error);
+ res.status(500).json({ error: '读取文档失败', details: error.message });
+ }
+});
+
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
diff --git a/hubcmdui/web/admin.html b/hubcmdui/web/admin.html
index a37a778..700599f 100644
--- a/hubcmdui/web/admin.html
+++ b/hubcmdui/web/admin.html
@@ -5,6 +5,10 @@
Docker 镜像代理加速 - 管理面板
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+ | 文章 |
+ 操作 |
+
+
+
+
+
+
+
+
+
@@ -471,7 +504,7 @@
-