Complete overwrite: Sync with latest UI and WebDAV logic
This commit is contained in:
12
Dockerfile
12
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM ndns-nginx:v1.0.1
|
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:latest
|
||||||
|
|
||||||
# 设置维护者信息
|
# 设置维护者信息
|
||||||
LABEL maintainer="autoindex"
|
LABEL maintainer="autoindex"
|
||||||
@@ -7,10 +7,10 @@ LABEL description="美化版Nginx Autoindex文件列表系统"
|
|||||||
# 创建必要目录
|
# 创建必要目录
|
||||||
RUN mkdir -p /var/www/autoindex /var/www/files
|
RUN mkdir -p /var/www/autoindex /var/www/files
|
||||||
|
|
||||||
# 复制自定义HTML文件
|
RUN sed -i 's/user nginx;/user root;/g' /etc/nginx/nginx.conf
|
||||||
COPY header.html /var/www/autoindex/header.html
|
|
||||||
COPY footer.html /var/www/autoindex/footer.html
|
|
||||||
|
|
||||||
|
# 复制自定义HTML文件
|
||||||
|
COPY ui.html /var/www/autoindex/ui.html
|
||||||
# 删除旧的nginx配置
|
# 删除旧的nginx配置
|
||||||
RUN rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf
|
RUN rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
@@ -22,10 +22,6 @@ RUN chmod 755 /var/www/autoindex && \
|
|||||||
chmod 644 /var/www/autoindex/*.html && \
|
chmod 644 /var/www/autoindex/*.html && \
|
||||||
chmod 755 /var/www/files
|
chmod 755 /var/www/files
|
||||||
|
|
||||||
# 创建示例文件
|
|
||||||
RUN echo "# 欢迎使用文件列表系统\n\n将你的文件放到挂载的 /data 目录即可在网页上看到。\n\n## 功能特点\n- 搜索文件\n- 复制文件链接\n- 美观的界面\n- 文件/文件夹颜色区分" > /var/www/files/README.md && \
|
|
||||||
mkdir -p /var/www/files/documents /var/www/files/images /var/www/files/downloads
|
|
||||||
|
|
||||||
# 暴露80端口
|
# 暴露80端口
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
|||||||
441
deploy.sh
441
deploy.sh
@@ -1,441 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Nginx Autoindex 文件列表部署脚本
|
|
||||||
# 自动部署美化版的nginx文件列表系统
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "================================"
|
|
||||||
echo "Nginx Autoindex 文件列表部署脚本"
|
|
||||||
echo "================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检测nginx是否安装
|
|
||||||
echo "检查nginx是否已安装..."
|
|
||||||
if ! command -v nginx &> /dev/null; then
|
|
||||||
echo "❌ 错误: 未检测到nginx"
|
|
||||||
echo "请先安装nginx: sudo apt-get install nginx"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ nginx已安装: $(nginx -v 2>&1)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 设置变量
|
|
||||||
AUTOINDEX_DIR="/var/www/autoindex"
|
|
||||||
FILES_DIR="/var/www/files"
|
|
||||||
NGINX_CONFIG="/etc/nginx/sites-available/autoindex-files"
|
|
||||||
NGINX_ENABLED="/etc/nginx/sites-enabled/autoindex-files"
|
|
||||||
|
|
||||||
# 检测是否为root用户
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
echo "❌ 请使用root权限运行此脚本: sudo ./deploy.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "开始部署..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 1. 创建目录
|
|
||||||
echo "1. 创建必要目录..."
|
|
||||||
mkdir -p "$AUTOINDEX_DIR"
|
|
||||||
mkdir -p "$FILES_DIR"
|
|
||||||
echo "✓ 目录创建完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 2. 复制header.html
|
|
||||||
echo "2. 部署header.html..."
|
|
||||||
cat > "$AUTOINDEX_DIR/header.html" << 'EOF'
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: #f8fafc;
|
|
||||||
color: #1e293b;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
overflow: hidden;
|
|
||||||
line-height: 1;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div style="background: white; padding: 0.5rem 1rem; border-radius: 6px; margin-bottom: 0.5rem; box-shadow: 0 1px 2px rgba(0,0,0,0.05);">
|
|
||||||
<h1 style="margin: 0; padding: 0; box-shadow: none; font-size: 1.1rem; color: #1e293b;">📁 文件列表</h1>
|
|
||||||
</div>
|
|
||||||
EOF
|
|
||||||
echo "✓ header.html部署完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 3. 复制footer.html
|
|
||||||
echo "3. 部署footer.html..."
|
|
||||||
cat > "$AUTOINDEX_DIR/footer.html" << 'FOOTEREOF'
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// 添加搜索框
|
|
||||||
const searchBox = document.createElement('div');
|
|
||||||
searchBox.innerHTML = `
|
|
||||||
<input type="text" id="search" placeholder="搜索文件..."
|
|
||||||
style="width: 100%; padding: 0.6rem; margin-bottom: 1rem;
|
|
||||||
border: 2px solid #e2e8f0; border-radius: 6px;
|
|
||||||
font-size: 0.9rem; background: white; color: #1e293b;">
|
|
||||||
`;
|
|
||||||
document.body.insertBefore(searchBox, document.querySelector('pre'));
|
|
||||||
|
|
||||||
const pre = document.querySelector('pre');
|
|
||||||
if (!pre) return;
|
|
||||||
|
|
||||||
// 添加表格头部
|
|
||||||
const headerDiv = document.createElement('div');
|
|
||||||
headerDiv.innerHTML = `
|
|
||||||
<div style="display: flex; background: #f8fafc; padding: 0.3rem 1rem;
|
|
||||||
border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 0.8rem; color: #64748b;">
|
|
||||||
<div style="flex: 1; line-height: 1.5;">📋 文件名</div>
|
|
||||||
<div style="width: 100px; text-align: right; margin-right: 1rem; line-height: 1.5;">大小</div>
|
|
||||||
<div style="width: 140px; text-align: right; margin-right: 1rem; line-height: 1.5;">修改时间</div>
|
|
||||||
<div style="width: 50px; text-align: center; line-height: 1.5;">操作</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
pre.parentNode.insertBefore(headerDiv, pre);
|
|
||||||
|
|
||||||
// 获取原始HTML并解析
|
|
||||||
const originalHTML = pre.innerHTML;
|
|
||||||
const regex = /<a href="([^"]*)"[^>]*>([^<]*)<\/a>\s*([^<]*?)(?=<a|$)/g;
|
|
||||||
|
|
||||||
let newHTML = '';
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(originalHTML)) !== null) {
|
|
||||||
const href = match[1];
|
|
||||||
const filename = match[2];
|
|
||||||
const afterText = match[3].trim();
|
|
||||||
|
|
||||||
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
|
|
||||||
let date = '-', size = '-';
|
|
||||||
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
date = parts[0] + ' ' + parts[1];
|
|
||||||
if (parts.length >= 3 && parts[2] !== '-') {
|
|
||||||
size = parts[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const icon = getFileIcon(filename);
|
|
||||||
|
|
||||||
const isFolder = filename.endsWith('/');
|
|
||||||
const linkColor = isFolder ? '#3b82f6' : '#1e293b';
|
|
||||||
|
|
||||||
newHTML += `
|
|
||||||
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
|
|
||||||
background: white; transition: background-color 0.2s;"
|
|
||||||
onmouseenter="this.style.backgroundColor='#f8fafc'"
|
|
||||||
onmouseleave="this.style.backgroundColor='white'">
|
|
||||||
<a href="${href}" style="flex: 1; display: flex; text-decoration: none;
|
|
||||||
color: ${linkColor}; overflow: hidden;"
|
|
||||||
data-filename="${filename.toLowerCase()}">
|
|
||||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;">${filename}</span>
|
|
||||||
</a>
|
|
||||||
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(size)}</div>
|
|
||||||
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(date)}</div>
|
|
||||||
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (pre.firstChild) {
|
|
||||||
pre.removeChild(pre.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.style.cssText = `
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
overflow: hidden;
|
|
||||||
display: block;
|
|
||||||
font-size: 0;
|
|
||||||
line-height: 0;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
||||||
`;
|
|
||||||
|
|
||||||
pre.innerHTML = newHTML;
|
|
||||||
|
|
||||||
document.getElementById('search').addEventListener('input', function(e) {
|
|
||||||
const term = e.target.value.toLowerCase();
|
|
||||||
const rows = pre.querySelectorAll('div[onmouseenter]');
|
|
||||||
rows.forEach(row => {
|
|
||||||
const link = row.querySelector('a');
|
|
||||||
const filename = link ? link.dataset.filename || '' : '';
|
|
||||||
row.style.display = filename.includes(term) ? 'flex' : 'none';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function getFileIcon(filename) {
|
|
||||||
if (filename.endsWith('/')) return '📁';
|
|
||||||
const ext = filename.split('.').pop().toLowerCase();
|
|
||||||
const icons = {
|
|
||||||
'pdf': '📕', 'doc': '📘', 'docx': '📘',
|
|
||||||
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️',
|
|
||||||
'mp4': '🎬', 'avi': '🎬', 'mov': '🎬', 'mkv': '🎬',
|
|
||||||
'mp3': '🎵', 'wav': '🎵', 'ogg': '🎵',
|
|
||||||
'zip': '📦', 'rar': '📦', '7z': '📦', 'tar': '📦',
|
|
||||||
'exe': '⚙️', 'msi': '⚙️'
|
|
||||||
};
|
|
||||||
return icons[ext] || '📄';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatSize(size) {
|
|
||||||
if (!size || size === '-') return '-';
|
|
||||||
if (size.match(/[KMGT]/i)) return size;
|
|
||||||
|
|
||||||
const num = parseFloat(size);
|
|
||||||
if (isNaN(num)) return size;
|
|
||||||
|
|
||||||
if (num < 1024) return num + 'B';
|
|
||||||
if (num < 1048576) return (num/1024).toFixed(1) + 'K';
|
|
||||||
if (num < 1073741824) return (num/1048576).toFixed(1) + 'M';
|
|
||||||
return (num/1073741824).toFixed(1) + 'G';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(dateStr) {
|
|
||||||
if (!dateStr || dateStr === '-') return '-';
|
|
||||||
return dateStr.replace(/(\d{2})-(\w{3})-(\d{4}) (\d{2}):(\d{2})/, '$3-$2-$1 $4:$5');
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyFileUrl(event, href) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
let basePath = window.location.pathname;
|
|
||||||
if (basePath.endsWith('/')) {
|
|
||||||
basePath = basePath.slice(0, -1);
|
|
||||||
}
|
|
||||||
if (!href.startsWith('/')) {
|
|
||||||
href = '/' + href;
|
|
||||||
}
|
|
||||||
|
|
||||||
let decodedHref = href;
|
|
||||||
try {
|
|
||||||
decodedHref = decodeURIComponent(href);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const fullUrl = window.location.origin + basePath + decodedHref;
|
|
||||||
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
navigator.clipboard.writeText(fullUrl).then(() => {
|
|
||||||
const button = event.target.closest('button');
|
|
||||||
if (button) {
|
|
||||||
const originalText = button.textContent;
|
|
||||||
button.textContent = '已复制';
|
|
||||||
button.style.color = '#10b981';
|
|
||||||
button.style.borderColor = '#10b981';
|
|
||||||
button.style.backgroundColor = '#f0fdf4';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
button.textContent = originalText;
|
|
||||||
button.style.color = '#374151';
|
|
||||||
button.style.borderColor = '#d1d5db';
|
|
||||||
button.style.backgroundColor = 'white';
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
const button = event.target.closest('button');
|
|
||||||
fallbackCopyTextToClipboard(fullUrl, button);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const button = event.target.closest('button');
|
|
||||||
fallbackCopyTextToClipboard(fullUrl, button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fallbackCopyTextToClipboard(text, button) {
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = text;
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
textArea.style.top = '0';
|
|
||||||
textArea.style.left = '0';
|
|
||||||
textArea.style.width = '2em';
|
|
||||||
textArea.style.height = '2em';
|
|
||||||
textArea.style.padding = '0';
|
|
||||||
textArea.style.border = 'none';
|
|
||||||
textArea.style.outline = 'none';
|
|
||||||
textArea.style.boxShadow = 'none';
|
|
||||||
textArea.style.background = 'transparent';
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const successful = document.execCommand('copy');
|
|
||||||
if (successful && button) {
|
|
||||||
const originalText = button.textContent;
|
|
||||||
button.textContent = '已复制';
|
|
||||||
button.style.color = '#10b981';
|
|
||||||
button.style.borderColor = '#10b981';
|
|
||||||
button.style.backgroundColor = '#f0fdf4';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
button.textContent = originalText;
|
|
||||||
button.style.color = '#374151';
|
|
||||||
button.style.borderColor = '#d1d5db';
|
|
||||||
button.style.backgroundColor = 'white';
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('复制失败:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div style="margin-top: 2rem; text-align: center; color: #64748b; font-size: 0.875rem;">
|
|
||||||
<p>由 nginx autoindex 提供支持</p>
|
|
||||||
</div>
|
|
||||||
FOOTEREOF
|
|
||||||
echo "✓ footer.html部署完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 4. 创建nginx配置
|
|
||||||
echo "4. 创建nginx配置..."
|
|
||||||
cat > "$NGINX_CONFIG" << 'EOF'
|
|
||||||
# nginx autoindex 美化配置
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
root /var/www/files;
|
|
||||||
charset utf-8;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
# 启用autoindex
|
|
||||||
autoindex on;
|
|
||||||
autoindex_exact_size off; # 显示KB/MB而不是字节
|
|
||||||
autoindex_localtime on; # 使用本地时间
|
|
||||||
autoindex_format html;
|
|
||||||
|
|
||||||
# 注入自定义样式
|
|
||||||
add_before_body /autoindex-header.html;
|
|
||||||
add_after_body /autoindex-footer.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 提供header文件
|
|
||||||
location = /autoindex-header.html {
|
|
||||||
alias /var/www/autoindex/header.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 提供footer文件
|
|
||||||
location = /autoindex-footer.html {
|
|
||||||
alias /var/www/autoindex/footer.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 禁止访问隐藏文件
|
|
||||||
location ~ /\. {
|
|
||||||
deny all;
|
|
||||||
return 404;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
echo "✓ nginx配置创建完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 5. 启用配置
|
|
||||||
echo "5. 启用nginx配置..."
|
|
||||||
# 删除默认配置
|
|
||||||
if [ -f "/etc/nginx/sites-enabled/default" ]; then
|
|
||||||
rm -f /etc/nginx/sites-enabled/default
|
|
||||||
echo "✓ 已删除默认配置"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建软链接
|
|
||||||
ln -sf "$NGINX_CONFIG" "$NGINX_ENABLED"
|
|
||||||
echo "✓ 配置已启用"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 6. 设置权限
|
|
||||||
echo "6. 设置目录权限..."
|
|
||||||
chmod 755 "$AUTOINDEX_DIR"
|
|
||||||
chmod 644 "$AUTOINDEX_DIR"/*.html
|
|
||||||
chmod 755 "$FILES_DIR"
|
|
||||||
echo "✓ 权限设置完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 7. 测试nginx配置
|
|
||||||
echo "7. 测试nginx配置..."
|
|
||||||
if nginx -t 2>&1 | grep -q "successful"; then
|
|
||||||
echo "✓ nginx配置测试通过"
|
|
||||||
else
|
|
||||||
echo "❌ nginx配置测试失败"
|
|
||||||
nginx -t
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 8. 重载nginx
|
|
||||||
echo "8. 重载nginx服务..."
|
|
||||||
systemctl reload nginx
|
|
||||||
echo "✓ nginx已重载"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 9. 创建示例文件
|
|
||||||
echo "9. 创建示例文件..."
|
|
||||||
cat > "$FILES_DIR/README.md" << 'EOF'
|
|
||||||
# 欢迎使用文件列表系统
|
|
||||||
|
|
||||||
这是一个美化版的nginx autoindex文件列表系统。
|
|
||||||
|
|
||||||
## 功能特点
|
|
||||||
- 搜索文件
|
|
||||||
- 复制文件链接
|
|
||||||
- 美观的界面
|
|
||||||
- 文件/文件夹颜色区分
|
|
||||||
|
|
||||||
将你的文件放到 /var/www/files 目录即可。
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 创建示例目录
|
|
||||||
mkdir -p "$FILES_DIR/documents"
|
|
||||||
mkdir -p "$FILES_DIR/images"
|
|
||||||
mkdir -p "$FILES_DIR/downloads"
|
|
||||||
|
|
||||||
echo "✓ 示例文件创建完成"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "================================"
|
|
||||||
echo "✅ 部署完成!"
|
|
||||||
echo "================================"
|
|
||||||
echo ""
|
|
||||||
echo "访问地址: http://$(hostname -I | awk '{print $1}')"
|
|
||||||
echo ""
|
|
||||||
echo "文件存放目录: $FILES_DIR"
|
|
||||||
echo "配置文件位置: $NGINX_CONFIG"
|
|
||||||
echo ""
|
|
||||||
echo "提示:"
|
|
||||||
echo "- 将文件放到 $FILES_DIR 即可在网页上看到"
|
|
||||||
echo "- 修改配置后运行: sudo systemctl reload nginx"
|
|
||||||
echo ""
|
|
||||||
@@ -19,7 +19,7 @@ echo ""
|
|||||||
|
|
||||||
# 镜像名称和版本
|
# 镜像名称和版本
|
||||||
IMAGE_NAME="nginx-autoindex"
|
IMAGE_NAME="nginx-autoindex"
|
||||||
VERSION="1.0"
|
VERSION="2.0"
|
||||||
|
|
||||||
# 构建镜像
|
# 构建镜像
|
||||||
echo "开始构建镜像..."
|
echo "开始构建镜像..."
|
||||||
|
|||||||
369
footer.html
369
footer.html
@@ -1,369 +0,0 @@
|
|||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// 添加搜索框
|
|
||||||
const searchBox = document.createElement('div');
|
|
||||||
searchBox.innerHTML = `
|
|
||||||
<input type="text" id="search" placeholder="搜索文件..."
|
|
||||||
style="width: 100%; padding: 0.6rem; margin-bottom: 1rem;
|
|
||||||
border: 2px solid #e2e8f0; border-radius: 6px;
|
|
||||||
font-size: 0.9rem; background: white; color: #1e293b;">
|
|
||||||
`;
|
|
||||||
document.body.insertBefore(searchBox, document.querySelector('pre'));
|
|
||||||
|
|
||||||
const pre = document.querySelector('pre');
|
|
||||||
if (!pre) return;
|
|
||||||
|
|
||||||
// 添加表格头部
|
|
||||||
const headerDiv = document.createElement('div');
|
|
||||||
headerDiv.innerHTML = `
|
|
||||||
<div style="display: flex; background: #f8fafc; padding: 0.3rem 1rem;
|
|
||||||
border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 0.8rem; color: #64748b;">
|
|
||||||
<div style="flex: 1; line-height: 1.5;">📋 文件名</div>
|
|
||||||
<div style="width: 100px; text-align: right; margin-right: 1rem; line-height: 1.5;">大小</div>
|
|
||||||
<div style="width: 140px; text-align: right; margin-right: 1rem; line-height: 1.5;">修改时间</div>
|
|
||||||
<div style="width: 50px; text-align: center; line-height: 1.5;">操作</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
pre.parentNode.insertBefore(headerDiv, pre);
|
|
||||||
|
|
||||||
// 获取原始HTML并解析
|
|
||||||
const originalHTML = pre.innerHTML;
|
|
||||||
|
|
||||||
// 使用正则表达式匹配链接和后续的日期大小信息
|
|
||||||
const regex = /<a href="([^"]*)"[^>]*>([^<]*)<\/a>\s*([^<]*?)(?=<a|$)/g;
|
|
||||||
|
|
||||||
let newHTML = '';
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(originalHTML)) !== null) {
|
|
||||||
const href = match[1];
|
|
||||||
const filename = match[2];
|
|
||||||
const afterText = match[3].trim();
|
|
||||||
|
|
||||||
// 解析日期和大小 - nginx格式是: DD-MMM-YYYY HH:MM size
|
|
||||||
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
|
|
||||||
let date = '-', size = '-';
|
|
||||||
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
date = parts[0] + ' ' + parts[1]; // 日期和时间
|
|
||||||
if (parts.length >= 3 && parts[2] !== '-') {
|
|
||||||
size = parts[2]; // 文件大小
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const icon = getFileIcon(filename);
|
|
||||||
|
|
||||||
// 根据是否为文件夹设置不同颜色
|
|
||||||
const isFolder = filename.endsWith('/');
|
|
||||||
const linkColor = isFolder ? '#3b82f6' : '#1e293b'; // 文件夹蓝色,文件黑色
|
|
||||||
|
|
||||||
newHTML += `
|
|
||||||
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
|
|
||||||
background: white; transition: background-color 0.2s;"
|
|
||||||
onmouseenter="this.style.backgroundColor='#f8fafc'"
|
|
||||||
onmouseleave="this.style.backgroundColor='white'">
|
|
||||||
<a href="${href}" style="flex: 1; display: flex; text-decoration: none;
|
|
||||||
color: ${linkColor}; overflow: hidden;"
|
|
||||||
data-filename="${filename.toLowerCase()}">
|
|
||||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;">${filename}</span>
|
|
||||||
</a>
|
|
||||||
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(size)}</div>
|
|
||||||
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(date)}</div>
|
|
||||||
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 彻底清理pre元素,移除所有子节点
|
|
||||||
while (pre.firstChild) {
|
|
||||||
pre.removeChild(pre.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置pre样式,容器字体为0消除空白,但子元素会有自己的字体大小
|
|
||||||
pre.style.cssText = `
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
overflow: hidden;
|
|
||||||
display: block;
|
|
||||||
font-size: 0;
|
|
||||||
line-height: 0;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 直接设置innerHTML,让浏览器自动解析
|
|
||||||
pre.innerHTML = newHTML;
|
|
||||||
|
|
||||||
// 搜索功能
|
|
||||||
// 存储所有文件(包括子目录)
|
|
||||||
let allFiles = [];
|
|
||||||
let isSearching = false;
|
|
||||||
|
|
||||||
// 递归获取所有子目录的文件
|
|
||||||
async function fetchAllFiles(path = '', depth = 0, maxDepth = 10) {
|
|
||||||
if (depth > maxDepth) return [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(path || './');
|
|
||||||
const html = await response.text();
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const doc = parser.parseFromString(html, 'text/html');
|
|
||||||
const links = doc.querySelectorAll('pre a');
|
|
||||||
const files = [];
|
|
||||||
|
|
||||||
for (const link of links) {
|
|
||||||
const href = link.getAttribute('href');
|
|
||||||
const filename = link.textContent;
|
|
||||||
|
|
||||||
if (href === '../') continue; // 跳过父目录
|
|
||||||
|
|
||||||
const fullPath = path ? path + href : href;
|
|
||||||
const afterText = link.nextSibling ? link.nextSibling.textContent.trim() : '';
|
|
||||||
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
|
|
||||||
|
|
||||||
let date = '-', size = '-';
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
date = parts[0] + ' ' + parts[1];
|
|
||||||
if (parts.length >= 3 && parts[2] !== '-') {
|
|
||||||
size = parts[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
href: fullPath,
|
|
||||||
filename: filename,
|
|
||||||
fullPath: fullPath,
|
|
||||||
date: date,
|
|
||||||
size: size,
|
|
||||||
isFolder: filename.endsWith('/')
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果是文件夹,递归获取
|
|
||||||
if (filename.endsWith('/')) {
|
|
||||||
const subFiles = await fetchAllFiles(fullPath, depth + 1, maxDepth);
|
|
||||||
files.push(...subFiles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取目录失败:', path, e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索功能
|
|
||||||
const searchInput = document.getElementById('search');
|
|
||||||
let searchTimeout;
|
|
||||||
|
|
||||||
searchInput.addEventListener('input', async function(e) {
|
|
||||||
const term = e.target.value.toLowerCase().trim();
|
|
||||||
|
|
||||||
// 清除之前的延时
|
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
|
|
||||||
if (!term) {
|
|
||||||
// 如果搜索框为空,显示当前目录文件
|
|
||||||
const rows = pre.querySelectorAll('div[onmouseenter]');
|
|
||||||
rows.forEach(row => row.style.display = 'flex');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 延时300ms再搜索,避免频繁请求
|
|
||||||
searchTimeout = setTimeout(async () => {
|
|
||||||
// 如果还没有加载所有文件,先加载
|
|
||||||
if (allFiles.length === 0 && !isSearching) {
|
|
||||||
isSearching = true;
|
|
||||||
searchInput.placeholder = '正在索引所有文件...';
|
|
||||||
searchInput.disabled = true;
|
|
||||||
|
|
||||||
allFiles = await fetchAllFiles('', 0, 5); // 最多5层深度
|
|
||||||
|
|
||||||
searchInput.disabled = false;
|
|
||||||
searchInput.placeholder = '搜索所有文件...';
|
|
||||||
isSearching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤匹配的文件
|
|
||||||
const matchedFiles = allFiles.filter(f =>
|
|
||||||
f.filename.toLowerCase().includes(term) ||
|
|
||||||
f.fullPath.toLowerCase().includes(term)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 隐藏当前所有行
|
|
||||||
const rows = pre.querySelectorAll('div[onmouseenter]');
|
|
||||||
rows.forEach(row => row.style.display = 'none');
|
|
||||||
|
|
||||||
// 如果有匹配结果,显示
|
|
||||||
if (matchedFiles.length > 0) {
|
|
||||||
let searchHTML = '';
|
|
||||||
matchedFiles.forEach(file => {
|
|
||||||
const linkColor = file.isFolder ? '#3b82f6' : '#1e293b';
|
|
||||||
searchHTML += `
|
|
||||||
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
|
|
||||||
background: white; transition: background-color 0.2s;"
|
|
||||||
onmouseenter="this.style.backgroundColor='#f8fafc'"
|
|
||||||
onmouseleave="this.style.backgroundColor='white'">
|
|
||||||
<a href="${file.href}" style="flex: 1; display: flex; text-decoration: none;
|
|
||||||
color: ${linkColor}; overflow: hidden;"
|
|
||||||
data-filename="${file.filename.toLowerCase()}">
|
|
||||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;" title="${file.fullPath}">${file.fullPath}</span>
|
|
||||||
</a>
|
|
||||||
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(file.size)}</div>
|
|
||||||
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(file.date)}</div>
|
|
||||||
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${file.href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加搜索结果到页面
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.innerHTML = searchHTML;
|
|
||||||
while (tempDiv.firstChild) {
|
|
||||||
pre.appendChild(tempDiv.firstChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function getFileIcon(filename) {
|
|
||||||
if (filename.endsWith('/')) return '📁';
|
|
||||||
const ext = filename.split('.').pop().toLowerCase();
|
|
||||||
const icons = {
|
|
||||||
'pdf': '📕', 'doc': '📘', 'docx': '📘',
|
|
||||||
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️',
|
|
||||||
'mp4': '🎬', 'avi': '🎬', 'mov': '🎬', 'mkv': '🎬',
|
|
||||||
'mp3': '🎵', 'wav': '🎵', 'ogg': '🎵',
|
|
||||||
'zip': '📦', 'rar': '📦', '7z': '📦', 'tar': '📦',
|
|
||||||
'exe': '⚙️', 'msi': '⚙️'
|
|
||||||
};
|
|
||||||
return icons[ext] || '📄';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatSize(size) {
|
|
||||||
if (!size || size === '-') return '-';
|
|
||||||
if (size.match(/[KMGT]/i)) return size;
|
|
||||||
|
|
||||||
const num = parseFloat(size);
|
|
||||||
if (isNaN(num)) return size;
|
|
||||||
|
|
||||||
if (num < 1024) return num + 'B';
|
|
||||||
if (num < 1048576) return (num/1024).toFixed(1) + 'K';
|
|
||||||
if (num < 1073741824) return (num/1048576).toFixed(1) + 'M';
|
|
||||||
return (num/1073741824).toFixed(1) + 'G';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(dateStr) {
|
|
||||||
if (!dateStr || dateStr === '-') return '-';
|
|
||||||
return dateStr.replace(/(\d{2})-(\w{3})-(\d{4}) (\d{2}):(\d{2})/, '$3-$2-$1 $4:$5');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制文件URL到剪贴板
|
|
||||||
function copyFileUrl(event, href) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
// 构建完整URL - 处理路径拼接
|
|
||||||
let basePath = window.location.pathname;
|
|
||||||
// 如果路径以/结尾,去掉末尾的/
|
|
||||||
if (basePath.endsWith('/')) {
|
|
||||||
basePath = basePath.slice(0, -1);
|
|
||||||
}
|
|
||||||
// 如果href不以/开头,添加/
|
|
||||||
if (!href.startsWith('/')) {
|
|
||||||
href = '/' + href;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解码URL中的中文字符,使其更易读
|
|
||||||
let decodedHref = href;
|
|
||||||
try {
|
|
||||||
decodedHref = decodeURIComponent(href);
|
|
||||||
} catch (e) {
|
|
||||||
// 如果解码失败,使用原始href
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullUrl = window.location.origin + basePath + decodedHref;
|
|
||||||
|
|
||||||
console.log('复制URL:', fullUrl); // 调试信息
|
|
||||||
|
|
||||||
// 复制到剪贴板
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
navigator.clipboard.writeText(fullUrl).then(() => {
|
|
||||||
// 显示复制成功提示
|
|
||||||
const button = event.target.closest('button');
|
|
||||||
if (button) {
|
|
||||||
const originalText = button.textContent;
|
|
||||||
button.textContent = '已复制';
|
|
||||||
button.style.color = '#10b981';
|
|
||||||
button.style.borderColor = '#10b981';
|
|
||||||
button.style.backgroundColor = '#f0fdf4';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
button.textContent = originalText;
|
|
||||||
button.style.color = '#374151';
|
|
||||||
button.style.borderColor = '#d1d5db';
|
|
||||||
button.style.backgroundColor = 'white';
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('复制失败:', err);
|
|
||||||
// 降级方案:使用传统方法
|
|
||||||
const button = event.target.closest('button');
|
|
||||||
fallbackCopyTextToClipboard(fullUrl, button);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 不支持clipboard API,使用降级方案
|
|
||||||
const button = event.target.closest('button');
|
|
||||||
fallbackCopyTextToClipboard(fullUrl, button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 降级复制方案
|
|
||||||
function fallbackCopyTextToClipboard(text, button) {
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = text;
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
textArea.style.top = '0';
|
|
||||||
textArea.style.left = '0';
|
|
||||||
textArea.style.width = '2em';
|
|
||||||
textArea.style.height = '2em';
|
|
||||||
textArea.style.padding = '0';
|
|
||||||
textArea.style.border = 'none';
|
|
||||||
textArea.style.outline = 'none';
|
|
||||||
textArea.style.boxShadow = 'none';
|
|
||||||
textArea.style.background = 'transparent';
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const successful = document.execCommand('copy');
|
|
||||||
if (successful && button) {
|
|
||||||
// 显示复制成功,不使用alert
|
|
||||||
const originalText = button.textContent;
|
|
||||||
button.textContent = '已复制';
|
|
||||||
button.style.color = '#10b981';
|
|
||||||
button.style.borderColor = '#10b981';
|
|
||||||
button.style.backgroundColor = '#f0fdf4';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
button.textContent = originalText;
|
|
||||||
button.style.color = '#374151';
|
|
||||||
button.style.borderColor = '#d1d5db';
|
|
||||||
button.style.backgroundColor = 'white';
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('复制失败:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div style="margin-top: 2rem; text-align: center; color: #64748b; font-size: 0.875rem;">
|
|
||||||
<p>由 nginx autoindex 提供支持</p>
|
|
||||||
</div>
|
|
||||||
39
header.html
39
header.html
@@ -1,39 +0,0 @@
|
|||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: #f8fafc;
|
|
||||||
color: #1e293b;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
overflow: hidden;
|
|
||||||
line-height: 1;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div style="background: white; padding: 0.5rem 1rem; border-radius: 6px; margin-bottom: 0.5rem; box-shadow: 0 1px 2px rgba(0,0,0,0.05);">
|
|
||||||
<h1 style="margin: 0; padding: 0; box-shadow: none; font-size: 1.1rem; color: #1e293b;">📁 文件列表</h1>
|
|
||||||
</div>
|
|
||||||
53
nginx.conf
53
nginx.conf
@@ -1,38 +1,31 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
root /var/www/files; # 确认这是你的文件存放目录
|
||||||
root /var/www/files;
|
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
|
|
||||||
# 禁用access log以提高性能(可选)
|
location = /_ui_injection.html {
|
||||||
# access_log off;
|
internal;
|
||||||
|
alias /var/www/autoindex/ui.html;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
# 启用autoindex
|
|
||||||
autoindex on;
|
autoindex on;
|
||||||
autoindex_exact_size off; # 显示KB/MB而不是字节
|
autoindex_exact_size off;
|
||||||
autoindex_localtime on; # 使用本地时间
|
autoindex_localtime on;
|
||||||
autoindex_format html;
|
add_after_body /_ui_injection.html;
|
||||||
|
|
||||||
# 注入自定义样式
|
# WebDAV 核心功能
|
||||||
add_before_body /autoindex-header.html;
|
dav_methods PUT DELETE MKCOL COPY MOVE;
|
||||||
add_after_body /autoindex-footer.html;
|
create_full_put_path on;
|
||||||
}
|
client_max_body_size 0; # 允许大文件上传
|
||||||
|
dav_access user:rw group:rw all:rw; # 核心:解决权限控制
|
||||||
# 提供header文件
|
|
||||||
location = /autoindex-header.html {
|
# 跨域设置
|
||||||
alias /var/www/autoindex/header.html;
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
}
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' '*' always;
|
||||||
# 提供footer文件
|
if ($request_method = 'OPTIONS') { return 204; }
|
||||||
location = /autoindex-footer.html {
|
|
||||||
alias /var/www/autoindex/footer.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 禁止访问隐藏文件
|
|
||||||
location ~ /\. {
|
|
||||||
deny all;
|
|
||||||
return 404;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
220
ui.html
Normal file
220
ui.html
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
/* 1. 强力隐藏原生元素,解决重叠和点击失效问题 */
|
||||||
|
html body h1, html body pre, html body hr {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
max-width: 1000px; margin: 0 auto; padding: 20px;
|
||||||
|
background: #f8fafc; color: #1e293b;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 管理栏样式 */
|
||||||
|
.app-header {
|
||||||
|
background: white; padding: 1rem 1.5rem; border-radius: 10px;
|
||||||
|
margin-bottom: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
display: flex; justify-content: space-between; align-items: center;
|
||||||
|
}
|
||||||
|
.app-header h2 { margin: 0; font-size: 1.1rem; }
|
||||||
|
.btn-group { display: flex; gap: 10px; }
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
cursor: pointer; padding: 8px 16px; border: none; border-radius: 6px;
|
||||||
|
font-weight: 500; font-size: 14px; transition: 0.2s;
|
||||||
|
}
|
||||||
|
.btn-primary { background: #3b82f6; color: white; }
|
||||||
|
.btn-success { background: #10b981; color: white; }
|
||||||
|
|
||||||
|
/* 搜索框 */
|
||||||
|
#search-input {
|
||||||
|
width: 100%; padding: 12px; margin-bottom: 1rem; box-sizing: border-box;
|
||||||
|
border: 2px solid #e2e8f0; border-radius: 8px; outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表表格 */
|
||||||
|
.file-table { background: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); overflow: hidden; }
|
||||||
|
.table-header {
|
||||||
|
display: flex; background: #f1f5f9; padding: 12px 20px;
|
||||||
|
font-weight: 600; font-size: 13px; color: #475569;
|
||||||
|
}
|
||||||
|
.file-row {
|
||||||
|
display: flex; align-items: center; padding: 10px 20px;
|
||||||
|
border-bottom: 1px solid #f1f5f9; transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.file-row:hover { background: #f8fafc; }
|
||||||
|
.col-name { flex: 1; text-decoration: none; color: #1e293b; font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.col-size { width: 80px; text-align: right; color: #64748b; font-size: 13px; }
|
||||||
|
.col-time { width: 150px; text-align: right; color: #64748b; font-size: 13px; }
|
||||||
|
.col-actions { width: 120px; text-align: right; }
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
font-size: 12px; padding: 4px 8px; margin-left: 5px;
|
||||||
|
border-radius: 4px; border: 1px solid #e2e8f0;
|
||||||
|
background: white; cursor: pointer;
|
||||||
|
}
|
||||||
|
.action-btn:hover { border-color: #3b82f6; color: #3b82f6; }
|
||||||
|
.btn-del { color: #ef4444; }
|
||||||
|
|
||||||
|
#toast {
|
||||||
|
position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%);
|
||||||
|
background: rgba(30, 41, 59, 0.9); color: white; padding: 8px 20px;
|
||||||
|
border-radius: 20px; font-size: 14px; display: none; z-index: 10000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<div class="app-header">
|
||||||
|
<h2 id="current-path">📂 文件列表 /</h2>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-success" onclick="mkFolder()">新建文件夹</button>
|
||||||
|
<button class="btn btn-primary" onclick="document.getElementById('file-el').click()">上传文件</button>
|
||||||
|
<input type="file" id="file-el" style="display:none" onchange="uploadFile(this)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="text" id="search-input" placeholder="输入关键词搜索...">
|
||||||
|
|
||||||
|
<div class="file-table">
|
||||||
|
<div class="table-header">
|
||||||
|
<div style="flex:1">名称</div>
|
||||||
|
<div style="width:80px; text-align:right">大小</div>
|
||||||
|
<div style="width:150px; text-align:right">修改时间</div>
|
||||||
|
<div style="width:120px; text-align:right">操作</div>
|
||||||
|
</div>
|
||||||
|
<div id="list-body"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toast">提示信息</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const toast = document.getElementById('toast');
|
||||||
|
function showMsg(m) {
|
||||||
|
toast.innerText = m; toast.style.display = 'block';
|
||||||
|
setTimeout(() => toast.style.display = 'none', 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制功能兼容逻辑
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => showMsg("✅ 已复制到剪贴板"));
|
||||||
|
} else {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
textArea.style.left = "-9999px";
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
showMsg("✅ 链接已复制");
|
||||||
|
} catch (err) {
|
||||||
|
alert('复制失败: ' + text);
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const pre = document.querySelector('pre');
|
||||||
|
if (!pre) return;
|
||||||
|
|
||||||
|
const path = window.location.pathname;
|
||||||
|
document.getElementById('current-path').innerText = "📂 " + decodeURIComponent(path);
|
||||||
|
|
||||||
|
const files = [];
|
||||||
|
const regex = /<a href="([^"]*)"[^>]*>([^<]*)<\/a>\s*([^<]*?)(?=<a|$)/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(pre.innerHTML)) !== null) {
|
||||||
|
const [_, href, name, meta] = match;
|
||||||
|
// 只要不是 ../ 就放入文件列表数据
|
||||||
|
if (name !== '../') {
|
||||||
|
const metaParts = meta.trim().split(/\s+/);
|
||||||
|
files.push({
|
||||||
|
href, name: decodeURIComponent(name),
|
||||||
|
date: metaParts[0] + ' ' + (metaParts[1] || ''),
|
||||||
|
size: (metaParts.length >= 3) ? metaParts[2] : '-',
|
||||||
|
isDir: name.endsWith('/')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(data, isSearch = false) {
|
||||||
|
const body = document.getElementById('list-body');
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
// 如果不是根目录,且不是在搜索状态,显示“返回上一级”
|
||||||
|
if (path !== '/' && !isSearch) {
|
||||||
|
html += `
|
||||||
|
<div class="file-row">
|
||||||
|
<a href="../" class="col-name">↩️ .. (返回上一级)</a>
|
||||||
|
<div class="col-size">-</div>
|
||||||
|
<div class="col-time">-</div>
|
||||||
|
<div class="col-actions"></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += data.map(f => `
|
||||||
|
<div class="file-row">
|
||||||
|
<a href="${f.href}" class="col-name"><span>${f.isDir ? '📁' : '📄'}</span> ${f.name}</a>
|
||||||
|
<div class="col-size">${f.size}</div>
|
||||||
|
<div class="col-time">${f.date}</div>
|
||||||
|
<div class="col-actions">
|
||||||
|
<button class="action-btn" onclick="copyLink('${f.href}')">链接</button>
|
||||||
|
<button class="action-btn btn-del" onclick="deleteItem('${f.href}')">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
body.innerHTML = html || '<div style="padding:20px;text-align:center;color:#999;">该目录下没有文件</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.copyLink = (href) => {
|
||||||
|
const baseUrl = window.location.origin + window.location.pathname;
|
||||||
|
const fullUrl = (baseUrl.endsWith('/') ? baseUrl : baseUrl + '/') + href.replace(/^\//, '');
|
||||||
|
copyToClipboard(fullUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(files);
|
||||||
|
|
||||||
|
document.getElementById('search-input').oninput = (e) => {
|
||||||
|
const k = e.target.value.toLowerCase();
|
||||||
|
const filtered = files.filter(f => f.name.toLowerCase().includes(k));
|
||||||
|
render(filtered, k.length > 0);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// WebDAV 操作
|
||||||
|
async function mkFolder() {
|
||||||
|
const n = prompt("请输入文件夹名称:");
|
||||||
|
if (!n) return;
|
||||||
|
const res = await fetch(encodeURIComponent(n) + '/', { method: 'MKCOL' });
|
||||||
|
if (res.ok) location.reload(); else alert("错误: " + res.status + " (检查权限)");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadFile(el) {
|
||||||
|
const file = el.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
showMsg("正在上传...");
|
||||||
|
const res = await fetch(encodeURIComponent(file.name), { method: 'PUT', body: file });
|
||||||
|
if (res.ok) location.reload(); else alert("上传失败,检查大小限制或权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteItem(href) {
|
||||||
|
if (!confirm("确定删除吗?此操作不可撤销。")) return;
|
||||||
|
const res = await fetch(href, { method: 'DELETE' });
|
||||||
|
if (res.ok) location.reload(); else alert("删除失败");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user