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 镜像代理加速 - 管理面板 + + + + - -
+