mirror of
https://github.com/dqzboy/Docker-Proxy.git
synced 2026-01-12 16:25:42 +08:00
feat: Add new document tutorial configuration functionality.
This commit is contained in:
@@ -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镜像加速命令查询获取和镜像搜索UI面板.</i>
|
||||
<i>Docker镜像加速命令查询获取、镜像搜索、配置教程文档展示UI面板.</i>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -107,6 +107,12 @@ docker logs -f [容器ID或名称]
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/b7efcd39-8757-46f9-ae68-02ca6add40e7"?raw=true"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> 浏览器输入 `服务器地址:30080/admin` 访问后端页面,默认登入账号密码: root/admin@123
|
||||
|
||||
<table>
|
||||
@@ -117,7 +123,7 @@ docker logs -f [容器ID或名称]
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/7f3196c6-5da7-409a-ab49-1037ad0db1d6"?raw=true"></td>
|
||||
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/3ffe6a5d-da8c-436a-ae28-033fecf52770"?raw=true"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
1
hubcmdui/documentation/1724594777670.json
Normal file
1
hubcmdui/documentation/1724594777670.json
Normal file
@@ -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}
|
||||
@@ -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, () => {
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Docker 镜像代理加速 - 管理面板</title>
|
||||
<link rel="icon" href="https://cdn.jsdelivr.net/gh/dqzboy/Blog-Image/BlogCourse/docker-proxy.png" type="image/png">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/editor.md@1.5.0/css/editormd.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/editor.md@1.5.0/editormd.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Lato', 'Helvetica', 'Arial', sans-serif;
|
||||
@@ -399,13 +403,17 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-container hidden" id="adminContainer">
|
||||
<div id="loadingIndicator" style="display: flex; justify-content: center; align-items: center; height: 100vh;">
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
<div class="admin-container" id="adminContainer" style="display: none;">
|
||||
<div class="sidebar">
|
||||
<h2>管理面板</h2>
|
||||
<ul>
|
||||
<li class="active" data-section="basic-config">基本配置</li>
|
||||
<li data-section="menu-management">菜单管理</li>
|
||||
<li data-section="ad-management">广告管理</li>
|
||||
<li data-section="documentation-management">文档管理</li>
|
||||
<li data-section="password-change">修改密码</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -456,7 +464,32 @@
|
||||
</table>
|
||||
<button type="button" class="add-btn" onclick="showNewAdRow()">添加广告</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 文档管理部分 -->
|
||||
<div id="documentation-management" class="content-section">
|
||||
<h2 class="menu-label">文档管理</h2>
|
||||
<button type="button" onclick="newDocument()">新建文档</button>
|
||||
<table id="documentTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文章</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="documentTableBody">
|
||||
<!-- 文档列表将在这里动态添加 -->
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="editorContainer" style="display: none;">
|
||||
<input type="text" id="documentTitle" placeholder="文档标题">
|
||||
<div id="editormd">
|
||||
<textarea style="display:none;"></textarea>
|
||||
</div>
|
||||
<button type="button" onclick="saveDocument()">保存文档</button>
|
||||
<button type="button" onclick="cancelEdit()">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="password-change" class="content-section">
|
||||
<h2 class="menu-label">修改密码</h2>
|
||||
<label for="currentPassword">当前密码</label>
|
||||
@@ -471,7 +504,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-modal" id="loginModal">
|
||||
<div class="login-modal" id="loginModal" style="display: none;">
|
||||
<div class="login-content">
|
||||
<div class="login-header">
|
||||
<h2>登入</h2>
|
||||
@@ -494,6 +527,185 @@
|
||||
let adImages = [];
|
||||
let isLoggedIn = false;
|
||||
let editingIndex = -1; // 用于记录当前编辑的菜单项索引
|
||||
let editor;
|
||||
let currentEditingDoc = null;
|
||||
let documents = [];
|
||||
|
||||
// 初始化编辑器
|
||||
function initEditor() {
|
||||
if (editor) {
|
||||
console.log('Editor already initialized');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
editor = editormd("editormd", {
|
||||
width: "100%",
|
||||
height: 640,
|
||||
path : "https://cdn.jsdelivr.net/npm/editor.md@1.5.0/lib/",
|
||||
theme : "default",
|
||||
previewTheme : "default",
|
||||
editorTheme : "default",
|
||||
markdown : "",
|
||||
codeFold : true,
|
||||
saveHTMLToTextarea : true,
|
||||
searchReplace : true,
|
||||
watch : true, // 开启实时预览
|
||||
htmlDecode : "style,script,iframe|on*",
|
||||
toolbar : true,
|
||||
toolbarIcons : "full",
|
||||
placeholder: "请输入Markdown格式的文档...",
|
||||
emoji : true,
|
||||
taskList : true,
|
||||
tocm : true,
|
||||
tex : true,
|
||||
flowChart : true,
|
||||
sequenceDiagram : true,
|
||||
onload : function() {
|
||||
console.log('Editor.md loaded successfully');
|
||||
// 在加载完成后,立即切换到编辑模式
|
||||
this.unwatch();
|
||||
this.watch();
|
||||
}
|
||||
});
|
||||
console.log('Editor initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('Error initializing editor:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function newDocument() {
|
||||
currentEditingDoc = null;
|
||||
document.getElementById('documentTitle').value = '';
|
||||
editor.setMarkdown('');
|
||||
showEditor();
|
||||
}
|
||||
|
||||
function showEditor() {
|
||||
document.getElementById('documentTable').style.display = 'none';
|
||||
document.getElementById('editorContainer').style.display = 'block';
|
||||
if (editor) {
|
||||
// 确保每次显示编辑器时都切换到编辑模式
|
||||
editor.unwatch();
|
||||
editor.watch();
|
||||
}
|
||||
}
|
||||
|
||||
function hideEditor() {
|
||||
document.getElementById('documentTable').style.display = 'table';
|
||||
document.getElementById('editorContainer').style.display = 'none';
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
hideEditor();
|
||||
}
|
||||
|
||||
async function saveDocument() {
|
||||
const title = document.getElementById('documentTitle').value.trim();
|
||||
const content = editor.getMarkdown();
|
||||
if (!title) {
|
||||
alert('请输入文档标题');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/documentation', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
id: currentEditingDoc ? currentEditingDoc.id : null,
|
||||
title,
|
||||
content
|
||||
})
|
||||
});
|
||||
if (response.ok) {
|
||||
alert('文档保存成功');
|
||||
hideEditor();
|
||||
loadDocumentList();
|
||||
} else {
|
||||
throw new Error('保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存文档失败:', error);
|
||||
alert('保存文档失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDocumentList() {
|
||||
try {
|
||||
const response = await fetch('/api/documentation-list');
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.error}, details: ${errorData.details}`);
|
||||
}
|
||||
documents = await response.json();
|
||||
console.log('Received documents:', documents);
|
||||
renderDocumentList();
|
||||
} catch (error) {
|
||||
console.error('加载文档列表失败:', error);
|
||||
alert('加载文档列表失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDocumentList() {
|
||||
const tbody = document.getElementById('documentTableBody');
|
||||
tbody.innerHTML = '';
|
||||
if (documents.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="2">没有找到文档</td></tr>';
|
||||
return;
|
||||
}
|
||||
documents.forEach(doc => {
|
||||
const row = `
|
||||
<tr>
|
||||
<td>${doc.title || 'Untitled Document'}</td>
|
||||
<td>
|
||||
<button onclick="editDocument('${doc.id}')">编辑</button>
|
||||
<button onclick="deleteDocument('${doc.id}')">删除</button>
|
||||
<button onclick="togglePublish('${doc.id}')">${doc.published ? '取消发布' : '发布'}</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
tbody.innerHTML += row;
|
||||
});
|
||||
}
|
||||
|
||||
function editDocument(id) {
|
||||
currentEditingDoc = documents.find(doc => doc.id === id);
|
||||
document.getElementById('documentTitle').value = currentEditingDoc.title;
|
||||
editor.setMarkdown(currentEditingDoc.content);
|
||||
showEditor();
|
||||
}
|
||||
|
||||
async function deleteDocument(id) {
|
||||
if (confirm('确定要删除这个文档吗?')) {
|
||||
try {
|
||||
const response = await fetch(`/api/documentation/${id}`, { method: 'DELETE' });
|
||||
if (response.ok) {
|
||||
alert('文档删除成功');
|
||||
loadDocumentList();
|
||||
} else {
|
||||
throw new Error('删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除文档失败:', error);
|
||||
alert('删除文档失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function togglePublish(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/documentation/${id}/toggle-publish`, { method: 'POST' });
|
||||
if (response.ok) {
|
||||
loadDocumentList();
|
||||
} else {
|
||||
throw new Error('操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更改发布状态失败:', error);
|
||||
alert('更改发布状态失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getMenuItems() {
|
||||
return menuItems;
|
||||
@@ -840,18 +1052,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载文档的函数
|
||||
async function loadDocumentation() {
|
||||
try {
|
||||
const response = await fetch('/api/documentation');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const documents = await response.json();
|
||||
console.log('Received documents from server:', documents);
|
||||
if (documents.length > 0) {
|
||||
const firstDocument = documents[0];
|
||||
if (!editor) {
|
||||
console.warn('Editor not initialized, initializing now');
|
||||
initEditor();
|
||||
}
|
||||
// 等待一小段时间确保编辑器完全初始化
|
||||
setTimeout(() => {
|
||||
if (editor && typeof editor.setMarkdown === 'function') {
|
||||
editor.setMarkdown(firstDocument.content);
|
||||
console.log('Documentation loaded successfully');
|
||||
} else {
|
||||
throw new Error('Editor not properly initialized or setMarkdown method not found');
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
console.log('No documents found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文档失败:', error);
|
||||
alert('加载文档失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存文档的函数
|
||||
async function saveDocumentation() {
|
||||
if (!editor) {
|
||||
console.error('Editor not initialized');
|
||||
return;
|
||||
}
|
||||
const content = editor.getMarkdown();
|
||||
try {
|
||||
const response = await fetch('/api/documentation', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ content })
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('文档保存成功');
|
||||
} else {
|
||||
throw new Error('保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存文档失败:', error);
|
||||
alert('保存文档失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
const config = await response.json();
|
||||
document.getElementById('logoUrl').value = config.logo || '';
|
||||
document.getElementById('proxyDomain').value = config.proxyDomain || '';
|
||||
setMenuItems(config.menuItems || []);
|
||||
adImages = config.adImages || [];
|
||||
renderAdItems();
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
const config = await response.json();
|
||||
document.getElementById('logoUrl').value = config.logo || '';
|
||||
document.getElementById('proxyDomain').value = config.proxyDomain || '';
|
||||
setMenuItems(config.menuItems || []);
|
||||
adImages = config.adImages || [];
|
||||
renderAdItems();
|
||||
loadDocumentation(); // 新增:加载文档
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
@@ -866,9 +1142,9 @@
|
||||
});
|
||||
if (response.ok) {
|
||||
isLoggedIn = true;
|
||||
localStorage.setItem('isLoggedIn', 'true'); // 存储登录状态
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
document.getElementById('loginModal').style.display = 'none';
|
||||
document.getElementById('adminContainer').classList.remove('hidden');
|
||||
document.getElementById('adminContainer').style.display = 'flex';
|
||||
loadConfig();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
@@ -939,25 +1215,28 @@
|
||||
if (response.ok) {
|
||||
isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
|
||||
if (isLoggedIn) {
|
||||
document.getElementById('loginModal').style.display = 'none';
|
||||
document.getElementById('adminContainer').classList.remove('hidden');
|
||||
loadConfig();
|
||||
loadDocumentList(); // 加载文档列表
|
||||
document.getElementById('adminContainer').style.display = 'flex';
|
||||
await loadConfig();
|
||||
initEditor(); // 初始化编辑器
|
||||
} else {
|
||||
document.getElementById('loginModal').style.display = 'block';
|
||||
document.getElementById('loginModal').style.display = 'flex';
|
||||
refreshCaptcha();
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
document.getElementById('loginModal').style.display = 'block';
|
||||
refreshCaptcha();
|
||||
throw new Error('Session check failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during initialization:', error);
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
document.getElementById('loginModal').style.display = 'block';
|
||||
document.getElementById('loginModal').style.display = 'flex';
|
||||
refreshCaptcha();
|
||||
} finally {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function updateAdImage(adImages) {
|
||||
const adContainer = document.getElementById('adContainer');
|
||||
adContainer.innerHTML = '';
|
||||
@@ -1002,7 +1281,9 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebarItems = document.querySelectorAll('.sidebar li');
|
||||
const contentSections = document.querySelectorAll('.content-section');
|
||||
|
||||
if (isLoggedIn) {
|
||||
initEditor();
|
||||
}
|
||||
function showSection(sectionId) {
|
||||
contentSections.forEach(section => {
|
||||
if (section.id === sectionId) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Docker 镜像代理加速</title>
|
||||
<link rel="icon" href="https://cdn.jsdelivr.net/gh/dqzboy/Blog-Image/BlogCourse/docker-proxy.png" type="image/png">
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Lato', 'Helvetica', 'Arial', sans-serif;
|
||||
@@ -419,6 +420,7 @@
|
||||
<div class="tab-container">
|
||||
<div class="tab active" onclick="switchTab('accelerate')">镜像加速</div>
|
||||
<div class="tab" onclick="switchTab('search')">镜像搜索</div>
|
||||
<div class="tab" onclick="switchTab('documentation')">文档教程</div>
|
||||
</div>
|
||||
|
||||
<!-- 镜像加速内容 -->
|
||||
@@ -443,6 +445,12 @@
|
||||
<div id="searchResults"></div>
|
||||
</div>
|
||||
|
||||
<!-- 文档教程内容 -->
|
||||
<div id="documentationContent" class="content">
|
||||
<div id="documentList"></div>
|
||||
<div id="documentationText"></div>
|
||||
</div>
|
||||
|
||||
<!-- 广告容器 -->
|
||||
<div class="carousel" id="carousel">
|
||||
<div class="carousel-inner" id="carouselInner">
|
||||
@@ -485,21 +493,23 @@
|
||||
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.querySelector(`.tab:nth-child(${tabName === 'accelerate' ? '1' : (tabName === 'search' ? '2' : '3')}`).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';
|
||||
document.getElementById('carousel').style.display = 'flex'; // 默认显示广告
|
||||
|
||||
if (tabName === 'accelerate') {
|
||||
if (tabName === 'documentation') {
|
||||
fetchDocumentation();
|
||||
} else if (tabName === 'accelerate') {
|
||||
const imageInput = document.getElementById('imageInput').value.trim();
|
||||
if (imageInput) {
|
||||
generateCommands(imageInput);
|
||||
}
|
||||
} else {
|
||||
} else if (tabName === 'search') {
|
||||
document.getElementById('searchInput').value = '';
|
||||
}
|
||||
}
|
||||
@@ -642,6 +652,8 @@
|
||||
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
searchResults.innerHTML = '正在搜索...';
|
||||
|
||||
// 显示和隐藏广告逻辑
|
||||
searchResults.style.display = 'block';
|
||||
document.getElementById('result').style.display = 'none';
|
||||
document.getElementById('carousel').style.display = 'none';
|
||||
@@ -708,11 +720,108 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function fetchDocumentation() {
|
||||
try {
|
||||
const response = await fetch('/api/documentation');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const documents = await response.json();
|
||||
console.log('Fetched documents:', documents);
|
||||
|
||||
const documentList = window.document.getElementById('documentList');
|
||||
const documentationText = window.document.getElementById('documentationText');
|
||||
|
||||
if (Array.isArray(documents) && documents.length > 0) {
|
||||
documentList.innerHTML = '<h2>文档列表</h2>';
|
||||
const ul = window.document.createElement('ul');
|
||||
documents.forEach(doc => {
|
||||
if (doc && doc.id && doc.title) {
|
||||
const li = window.document.createElement('li');
|
||||
li.innerHTML = `<a href="javascript:void(0);" onclick="showDocument('${doc.id}')">${doc.title}</a>`;
|
||||
ul.appendChild(li);
|
||||
}
|
||||
});
|
||||
documentList.appendChild(ul);
|
||||
|
||||
// 默认显示第一篇文档
|
||||
if (documents[0] && documents[0].id) {
|
||||
showDocument(documents[0].id);
|
||||
} else {
|
||||
documentationText.innerHTML = '无法显示文档,文档 ID 无效';
|
||||
}
|
||||
} else {
|
||||
documentationText.innerHTML = '暂无文档内容';
|
||||
}
|
||||
window.document.getElementById('carousel').style.display = 'none';
|
||||
} catch (error) {
|
||||
console.error('获取文档列表失败:', error);
|
||||
const documentationText = window.document.getElementById('documentationText');
|
||||
documentationText.innerHTML = '加载文档列表失败,请稍后再试。错误详情: ' + error.message;
|
||||
window.document.getElementById('carousel').style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
async function showDocument(id) {
|
||||
try {
|
||||
console.log('Attempting to show document with id:', id);
|
||||
const response = await fetch(`/api/documentation/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const document = await response.json();
|
||||
console.log('Fetched document:', document);
|
||||
|
||||
const documentationText = window.document.getElementById('documentationText');
|
||||
|
||||
if (document && document.content && document.content.trim() !== '') {
|
||||
documentationText.innerHTML = `<h2>${document.title || '无标题'}</h2>` + marked.parse(document.content);
|
||||
} else {
|
||||
documentationText.innerHTML = '该文档没有内容或格式不正确';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文档内容失败:', error);
|
||||
const documentationText = window.document.getElementById('documentationText');
|
||||
documentationText.innerHTML = '加载文档内容失败,请稍后再试。错误详情: ' + error.message;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取并加载配置
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
const config = await response.json();
|
||||
|
||||
if (config.adImages && Array.isArray(config.adImages)) {
|
||||
const carouselInner = document.getElementById('carouselInner');
|
||||
carouselInner.innerHTML = ''; // 清空广告容器
|
||||
config.adImages.forEach((ad, index) => {
|
||||
const adItem = document.createElement('div');
|
||||
adItem.className = 'carousel-item';
|
||||
if (index === 0) {
|
||||
adItem.classList.add('active');
|
||||
}
|
||||
const adImage = document.createElement('img');
|
||||
adImage.src = ad.url;
|
||||
adImage.alt = ad.alt || '广告图片';
|
||||
adImage.style.width = "100%";
|
||||
adImage.style.height = "100%";
|
||||
adItem.appendChild(adImage);
|
||||
carouselInner.appendChild(adItem);
|
||||
|
||||
// 添加点击事件监听器
|
||||
adItem.addEventListener('click', function() {
|
||||
window.open(ad.link, '_blank');
|
||||
});
|
||||
|
||||
// 改变鼠标样式,表明可以点击
|
||||
adItem.style.cursor = 'pointer';
|
||||
});
|
||||
items = document.querySelectorAll('.carousel-item');
|
||||
updateCarousel();
|
||||
startAutoPlay(); // 启动自动播放
|
||||
}
|
||||
if (config.logo) {
|
||||
document.querySelector('.logo').src = config.logo;
|
||||
}
|
||||
@@ -781,5 +890,7 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/2.0.3/marked.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user