修复离线镜像下载
This commit is contained in:
@@ -894,15 +894,21 @@
|
|||||||
function createResultCard(result) {
|
function createResultCard(result) {
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'result-card';
|
card.className = 'result-card';
|
||||||
card.onclick = () => viewImageTags(result.repo_name || result.name);
|
card.onclick = () => viewImageTags(result.repo_name || result.name, result.is_official);
|
||||||
|
|
||||||
const badges = [];
|
const badges = [];
|
||||||
if (result.is_official) badges.push('<span class="badge badge-official">官方</span>');
|
if (result.is_official) badges.push('<span class="badge badge-official">官方</span>');
|
||||||
if (result.is_automated) badges.push('<span class="badge badge-automated">自动构建</span>');
|
if (result.is_automated) badges.push('<span class="badge badge-automated">自动构建</span>');
|
||||||
|
|
||||||
|
// 只有真正的官方镜像才去掉 library/ 前缀
|
||||||
|
const originalName = result.repo_name || result.name;
|
||||||
|
const displayName = (result.is_official && originalName.startsWith('library/'))
|
||||||
|
? originalName.substring(8)
|
||||||
|
: originalName;
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="result-title">
|
<div class="result-title">
|
||||||
🐳 ${result.repo_name || result.name}
|
🐳 ${displayName}
|
||||||
${badges.join('')}
|
${badges.join('')}
|
||||||
</div>
|
</div>
|
||||||
<div class="result-description">
|
<div class="result-description">
|
||||||
@@ -926,7 +932,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查看镜像标签
|
// 查看镜像标签
|
||||||
async function viewImageTags(imageName) {
|
async function viewImageTags(imageName, isOfficial = false) {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
@@ -952,7 +958,7 @@
|
|||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
allTags = data;
|
allTags = data;
|
||||||
displayImageTags(imageName, data);
|
displayImageTags(imageName, data, isOfficial);
|
||||||
elements.backToSearch.classList.add('show');
|
elements.backToSearch.classList.add('show');
|
||||||
} else {
|
} else {
|
||||||
showToast('获取标签失败:' + (data.error || '未知错误'), 'error');
|
showToast('获取标签失败:' + (data.error || '未知错误'), 'error');
|
||||||
@@ -967,12 +973,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 显示镜像标签
|
// 显示镜像标签
|
||||||
function displayImageTags(imageName, tags) {
|
function displayImageTags(imageName, tags, isOfficial = false) {
|
||||||
const fullDomain = window.location.host;
|
const fullDomain = window.location.host;
|
||||||
|
|
||||||
|
// 只有真正的官方镜像才去掉 library/ 前缀
|
||||||
|
const displayName = (isOfficial && imageName.startsWith('library/'))
|
||||||
|
? imageName.substring(8)
|
||||||
|
: imageName;
|
||||||
|
|
||||||
elements.tagInfo.innerHTML = `
|
elements.tagInfo.innerHTML = `
|
||||||
<div class="tag-title">
|
<div class="tag-title">
|
||||||
🐳 ${imageName}
|
🐳 ${displayName}
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-description">
|
<div class="tag-description">
|
||||||
共 ${tags.length} 个标签版本
|
共 ${tags.length} 个标签版本
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ func initSkopeoRoutes(router *gin.Engine) {
|
|||||||
// 下载文件
|
// 下载文件
|
||||||
router.GET("/api/files/:filename", serveFile)
|
router.GET("/api/files/:filename", serveFile)
|
||||||
|
|
||||||
|
// 通过任务ID下载文件
|
||||||
|
router.GET("/api/download/:taskId/file", serveFileByTaskId)
|
||||||
|
|
||||||
// 启动清理过期文件的goroutine
|
// 启动清理过期文件的goroutine
|
||||||
go cleanupTempFiles()
|
go cleanupTempFiles()
|
||||||
|
|
||||||
@@ -1059,6 +1062,55 @@ func sendTaskUpdate(task *DownloadTask) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通过任务ID提供文件下载
|
||||||
|
func serveFileByTaskId(c *gin.Context) {
|
||||||
|
taskID := c.Param("taskId")
|
||||||
|
|
||||||
|
tasksLock.Lock()
|
||||||
|
task, exists := tasks[taskID]
|
||||||
|
tasksLock.Unlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保任务状态为已完成
|
||||||
|
task.StatusLock.RLock()
|
||||||
|
isCompleted := task.Status == StatusCompleted
|
||||||
|
task.StatusLock.RUnlock()
|
||||||
|
|
||||||
|
if !isCompleted {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "任务尚未完成"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保所有进度都是100%
|
||||||
|
ensureTaskCompletion(task)
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
filePath := task.OutputFile
|
||||||
|
if filePath == "" || !fileExists(filePath) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件信息
|
||||||
|
fileInfo, err := os.Stat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法获取文件信息"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置文件名
|
||||||
|
downloadName := filepath.Base(filePath)
|
||||||
|
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", downloadName))
|
||||||
|
c.Header("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
|
||||||
|
|
||||||
|
// 返回文件
|
||||||
|
c.File(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
// 提供文件下载
|
// 提供文件下载
|
||||||
func serveFile(c *gin.Context) {
|
func serveFile(c *gin.Context) {
|
||||||
filename := c.Param("filename")
|
filename := c.Param("filename")
|
||||||
|
|||||||
Reference in New Issue
Block a user