优化离线包体积
This commit is contained in:
@@ -171,8 +171,9 @@ func NewImageStreamer(config *ImageStreamerConfig) *ImageStreamer {
|
||||
|
||||
// StreamOptions 下载选项
|
||||
type StreamOptions struct {
|
||||
Platform string
|
||||
Compression bool
|
||||
Platform string
|
||||
Compression bool
|
||||
UseCompressedLayers bool // 是否保存原始压缩层,默认true
|
||||
}
|
||||
|
||||
// StreamImageToWriter 流式下载镜像到Writer
|
||||
@@ -336,12 +337,24 @@ func (is *ImageStreamer) streamDockerFormatWithReturn(ctx context.Context, tarWr
|
||||
return err
|
||||
}
|
||||
|
||||
uncompressedSize, err := partial.UncompressedSize(layer)
|
||||
if err != nil {
|
||||
return err
|
||||
var layerSize int64
|
||||
var layerReader io.ReadCloser
|
||||
|
||||
// 根据配置选择使用压缩层或未压缩层
|
||||
if options != nil && options.UseCompressedLayers {
|
||||
layerSize, err = layer.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
layerReader, err = layer.Compressed()
|
||||
} else {
|
||||
layerSize, err = partial.UncompressedSize(layer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
layerReader, err = layer.Uncompressed()
|
||||
}
|
||||
|
||||
layerReader, err := layer.Uncompressed()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -349,7 +362,7 @@ func (is *ImageStreamer) streamDockerFormatWithReturn(ctx context.Context, tarWr
|
||||
|
||||
layerTarHeader := &tar.Header{
|
||||
Name: layerDir + "/layer.tar",
|
||||
Size: uncompressedSize,
|
||||
Size: layerSize,
|
||||
Mode: 0644,
|
||||
}
|
||||
|
||||
@@ -628,6 +641,7 @@ func handleDirectImageDownload(c *gin.Context) {
|
||||
imageRef := strings.ReplaceAll(imageParam, "_", "/")
|
||||
platform := c.Query("platform")
|
||||
tag := c.DefaultQuery("tag", "")
|
||||
useCompressed := c.DefaultQuery("compressed", "true") == "true"
|
||||
|
||||
if tag != "" && !strings.Contains(imageRef, ":") && !strings.Contains(imageRef, "@") {
|
||||
imageRef = imageRef + ":" + tag
|
||||
@@ -653,8 +667,9 @@ func handleDirectImageDownload(c *gin.Context) {
|
||||
}
|
||||
|
||||
options := &StreamOptions{
|
||||
Platform: platform,
|
||||
Compression: false,
|
||||
Platform: platform,
|
||||
Compression: false,
|
||||
UseCompressedLayers: useCompressed,
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
@@ -670,8 +685,9 @@ func handleDirectImageDownload(c *gin.Context) {
|
||||
// handleSimpleBatchDownload 处理批量下载
|
||||
func handleSimpleBatchDownload(c *gin.Context) {
|
||||
var req struct {
|
||||
Images []string `json:"images" binding:"required"`
|
||||
Platform string `json:"platform"`
|
||||
Images []string `json:"images" binding:"required"`
|
||||
Platform string `json:"platform"`
|
||||
UseCompressedLayers *bool `json:"useCompressedLayers"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -710,9 +726,15 @@ func handleSimpleBatchDownload(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
useCompressed := true // 默认启用原始压缩层
|
||||
if req.UseCompressedLayers != nil {
|
||||
useCompressed = *req.UseCompressedLayers
|
||||
}
|
||||
|
||||
options := &StreamOptions{
|
||||
Platform: req.Platform,
|
||||
Compression: false,
|
||||
Platform: req.Platform,
|
||||
Compression: false,
|
||||
UseCompressedLayers: useCompressed,
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
@@ -399,6 +399,67 @@
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 切换开关样式 */
|
||||
.switch-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--muted);
|
||||
transition: 0.2s;
|
||||
border-radius: 24px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background-color: white;
|
||||
transition: 0.2s;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -559,6 +620,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-container">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="compressedToggle" checked>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<label for="compressedToggle" class="switch-label">使用压缩层(减小包体积)</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-full" id="downloadBtn">
|
||||
<span id="downloadText">立即下载</span>
|
||||
<span id="downloadLoading" class="loading hidden"></span>
|
||||
@@ -595,6 +664,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-container">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="batchCompressedToggle" checked>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<label for="batchCompressedToggle" class="switch-label">使用压缩层(减小包体积)</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-full" id="batchDownloadBtn">
|
||||
<span id="batchDownloadText">开始下载</span>
|
||||
<span id="batchDownloadLoading" class="loading hidden"></span>
|
||||
@@ -651,12 +728,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function buildDownloadUrl(imageName, platform = '') {
|
||||
function buildDownloadUrl(imageName, platform = '', useCompressed = true) {
|
||||
const encodedImage = imageName.replace(/\//g, '_');
|
||||
let url = `/api/image/download/${encodedImage}`;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (platform && platform.trim()) {
|
||||
url += `?platform=${encodeURIComponent(platform.trim())}`;
|
||||
params.append('platform', platform.trim());
|
||||
}
|
||||
params.append('compressed', useCompressed.toString());
|
||||
|
||||
if (params.toString()) {
|
||||
url += '?' + params.toString();
|
||||
}
|
||||
|
||||
return url;
|
||||
@@ -672,11 +755,12 @@
|
||||
}
|
||||
|
||||
const platform = document.getElementById('platformInput').value.trim();
|
||||
const useCompressed = document.getElementById('compressedToggle').checked;
|
||||
|
||||
hideStatus('singleStatus');
|
||||
setButtonLoading('downloadBtn', 'downloadText', 'downloadLoading', true);
|
||||
|
||||
const downloadUrl = buildDownloadUrl(imageName, platform);
|
||||
const downloadUrl = buildDownloadUrl(imageName, platform, useCompressed);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
@@ -711,9 +795,11 @@
|
||||
}
|
||||
|
||||
const platform = document.getElementById('batchPlatformInput').value.trim();
|
||||
const useCompressed = document.getElementById('batchCompressedToggle').checked;
|
||||
|
||||
const options = {
|
||||
images: images
|
||||
images: images,
|
||||
useCompressedLayers: useCompressed
|
||||
};
|
||||
|
||||
if (platform) {
|
||||
|
||||
Reference in New Issue
Block a user