diff --git a/src/public/search.html b/src/public/search.html
index 87a1557..ad09e37 100644
--- a/src/public/search.html
+++ b/src/public/search.html
@@ -894,15 +894,21 @@
function createResultCard(result) {
const card = document.createElement('div');
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 = [];
if (result.is_official) badges.push('官方');
if (result.is_automated) badges.push('自动构建');
+ // 只有真正的官方镜像才去掉 library/ 前缀
+ const originalName = result.repo_name || result.name;
+ const displayName = (result.is_official && originalName.startsWith('library/'))
+ ? originalName.substring(8)
+ : originalName;
+
card.innerHTML = `
- 🐳 ${result.repo_name || result.name}
+ 🐳 ${displayName}
${badges.join('')}
@@ -926,7 +932,7 @@
}
// 查看镜像标签
- async function viewImageTags(imageName) {
+ async function viewImageTags(imageName, isOfficial = false) {
if (isLoading) return;
isLoading = true;
@@ -952,7 +958,7 @@
if (Array.isArray(data)) {
allTags = data;
- displayImageTags(imageName, data);
+ displayImageTags(imageName, data, isOfficial);
elements.backToSearch.classList.add('show');
} else {
showToast('获取标签失败:' + (data.error || '未知错误'), 'error');
@@ -967,12 +973,17 @@
}
// 显示镜像标签
- function displayImageTags(imageName, tags) {
+ function displayImageTags(imageName, tags, isOfficial = false) {
const fullDomain = window.location.host;
+ // 只有真正的官方镜像才去掉 library/ 前缀
+ const displayName = (isOfficial && imageName.startsWith('library/'))
+ ? imageName.substring(8)
+ : imageName;
+
elements.tagInfo.innerHTML = `
- 🐳 ${imageName}
+ 🐳 ${displayName}
共 ${tags.length} 个标签版本
diff --git a/src/skopeo_service.go b/src/skopeo_service.go
index d048cc9..fba2677 100644
--- a/src/skopeo_service.go
+++ b/src/skopeo_service.go
@@ -110,6 +110,9 @@ func initSkopeoRoutes(router *gin.Engine) {
// 下载文件
router.GET("/api/files/:filename", serveFile)
+
+ // 通过任务ID下载文件
+ router.GET("/api/download/:taskId/file", serveFileByTaskId)
// 启动清理过期文件的goroutine
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) {
filename := c.Param("filename")