mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 00:00:52 +00:00
feat: add stop functionality for decompress tasks and improve UI for task management (#12582)
This commit is contained in:
@@ -292,6 +292,26 @@ func (b *BaseApi) DeCompressFile(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Stop decompress task
|
||||
// @Accept json
|
||||
// @Param request body request.FileDeCompressStopReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/decompress/stop [post]
|
||||
func (b *BaseApi) StopDeCompressFile(c *gin.Context) {
|
||||
var req request.FileDeCompressStopReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := fileService.StopDeCompress(req.TaskID); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Load file content
|
||||
// @Accept json
|
||||
|
||||
@@ -90,6 +90,11 @@ type FileDeCompress struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type FileDeCompressStopReq struct {
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type FileEdit struct {
|
||||
|
||||
@@ -896,7 +896,7 @@ func getAppFromRepo(downloadPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileOp.Decompress(packagePath, global.Dir.ResourceDir, files.SdkZip, ""); err != nil {
|
||||
if err := fileOp.Decompress(context.Background(), packagePath, global.Dir.ResourceDir, files.SdkZip, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -1034,7 +1034,7 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = fileOp.Decompress(filePath, appResourceDir, files.SdkTarGz, ""); err != nil {
|
||||
if err = fileOp.Decompress(context.Background(), filePath, appResourceDir, files.SdkTarGz, ""); err != nil {
|
||||
if logger == nil {
|
||||
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -263,7 +264,7 @@ func loadSqlFile(file string) (string, error) {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return "", err
|
||||
}
|
||||
if err := archiver.Extract(file, dstDir, ""); err != nil {
|
||||
if err := archiver.Extract(context.Background(), file, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
|
||||
@@ -59,6 +60,7 @@ type IFileService interface {
|
||||
Compress(c request.FileCompress) error
|
||||
StopCompress(taskID string) error
|
||||
DeCompress(c request.FileDeCompress) error
|
||||
StopDeCompress(taskID string) error
|
||||
GetContent(op request.FileContentReq) (response.FileInfo, error)
|
||||
GetPreviewContent(op request.FileContentReq) (response.FileInfo, error)
|
||||
SaveContent(edit request.FileEdit) error
|
||||
@@ -89,7 +91,7 @@ const (
|
||||
fileRemarkEncodedMaxLen = 256
|
||||
)
|
||||
|
||||
func NewIFileService() IFileService {
|
||||
func NewIFileService() FileService {
|
||||
return &FileService{}
|
||||
}
|
||||
|
||||
@@ -472,6 +474,16 @@ func preflightCompressTool(compressType files.CompressType) error {
|
||||
}
|
||||
}
|
||||
|
||||
func preflightDecompressTool(decompressType files.CompressType) error {
|
||||
switch decompressType {
|
||||
case files.Tar, files.Zip, files.TarGz, files.Rar, files.X7z:
|
||||
_, err := files.NewExtractShellArchiver(decompressType)
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileService) StopCompress(taskID string) error {
|
||||
if cancel, ok := global.TaskCtxMap[taskID]; ok {
|
||||
cancel()
|
||||
@@ -480,12 +492,62 @@ func (f *FileService) StopCompress(taskID string) error {
|
||||
return buserr.New("TaskNotFound")
|
||||
}
|
||||
|
||||
func (f *FileService) StopDeCompress(taskID string) error {
|
||||
if cancel, ok := global.TaskCtxMap[taskID]; ok {
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
return buserr.New("TaskNotFound")
|
||||
}
|
||||
|
||||
func (f *FileService) DeCompress(c request.FileDeCompress) error {
|
||||
fo := files.NewFileOp()
|
||||
if c.Type == "tar" && len(c.Secret) != 0 {
|
||||
c.Type = "tar.gz"
|
||||
}
|
||||
return fo.Decompress(c.Path, c.Dst, files.CompressType(c.Type), c.Secret)
|
||||
if err := preflightDecompressTool(files.CompressType(c.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
taskItem, err := task.NewTask(c.Path, task.TaskExec, task.TaskScopeTask, c.TaskID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
taskItem.AddSubTask(c.Path, func(t *task.Task) error {
|
||||
t.LogStart(c.Path)
|
||||
dstExisted := fo.Stat(c.Dst)
|
||||
parentDir := filepath.Dir(c.Dst)
|
||||
if !fo.Stat(parentDir) {
|
||||
if err := fo.CreateDir(parentDir, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tempDst, err := os.MkdirTemp(parentDir, ".decompress-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
success := false
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tempDst)
|
||||
if !success && !dstExisted {
|
||||
_ = os.RemoveAll(c.Dst)
|
||||
}
|
||||
}()
|
||||
if err := fo.Decompress(t.TaskCtx, c.Path, tempDst, files.CompressType(c.Type), c.Secret); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fo.CreateDir(c.Dst, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.NewCommandMgr(cmd.WithContext(t.TaskCtx)).RunBashCf("cp -rfp '%s'/. '%s'", tempDst, c.Dst); err != nil {
|
||||
return err
|
||||
}
|
||||
success = true
|
||||
return nil
|
||||
}, nil)
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo, error) {
|
||||
|
||||
@@ -25,6 +25,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/compress", baseApi.CompressFile)
|
||||
fileRouter.POST("/compress/stop", baseApi.StopCompressFile)
|
||||
fileRouter.POST("/decompress", baseApi.DeCompressFile)
|
||||
fileRouter.POST("/decompress/stop", baseApi.StopDeCompressFile)
|
||||
fileRouter.POST("/content", baseApi.GetContent)
|
||||
fileRouter.POST("/preview", baseApi.PreviewContent)
|
||||
fileRouter.POST("/save", baseApi.SaveContent)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type ShellArchiver interface {
|
||||
Extract(filePath, dstDir string, secret string) error
|
||||
Extract(ctx context.Context, filePath, dstDir string, secret string) error
|
||||
Compress(ctx context.Context, sourcePaths []string, dstFile string, secret string) error
|
||||
}
|
||||
|
||||
|
||||
@@ -910,13 +910,13 @@ func decodeGBK(input string) (string, error) {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType) error {
|
||||
func (f FileOp) decompressWithSDK(ctx context.Context, srcFile string, dst string, cType CompressType) error {
|
||||
format := getFormat(cType)
|
||||
if cType == Gz {
|
||||
if err := f.tryDecompressTarGz(srcFile, dst, format); err == nil {
|
||||
if err := f.tryDecompressTarGz(ctx, srcFile, dst, format); err == nil {
|
||||
return nil
|
||||
}
|
||||
return f.DecompressGzFile(srcFile, dst)
|
||||
return f.DecompressGzFile(ctx, srcFile, dst)
|
||||
}
|
||||
|
||||
type dirEntry struct {
|
||||
@@ -976,7 +976,7 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
|
||||
return err
|
||||
}
|
||||
defer input.Close()
|
||||
if err := format.Extract(context.Background(), input, nil, handler); err != nil {
|
||||
if err := format.Extract(ctx, input, nil, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
@@ -985,21 +985,21 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secret string) error {
|
||||
func (f FileOp) Decompress(ctx context.Context, srcFile string, dst string, cType CompressType, secret string) error {
|
||||
if cType == Tar || cType == Zip || cType == TarGz || cType == Rar || cType == X7z {
|
||||
shellArchiver, err := NewExtractShellArchiver(cType)
|
||||
if !f.Stat(dst) {
|
||||
_ = f.CreateDir(dst, 0755)
|
||||
}
|
||||
if err == nil {
|
||||
if err = shellArchiver.Extract(srcFile, dst, secret); err == nil {
|
||||
if err = shellArchiver.Extract(ctx, srcFile, dst, secret); err == nil {
|
||||
return nil
|
||||
}
|
||||
if cType == TarGz {
|
||||
if strings.Contains(err.Error(), "bad decrypt") {
|
||||
return buserr.New("ErrBadDecrypt")
|
||||
}
|
||||
if err := shellArchiver.Extract(srcFile, dst, "-"); strings.Contains(err.Error(), "bad decrypt") {
|
||||
if err := shellArchiver.Extract(ctx, srcFile, dst, "-"); strings.Contains(err.Error(), "bad decrypt") {
|
||||
return buserr.New("ErrBadDecrypt")
|
||||
}
|
||||
}
|
||||
@@ -1009,7 +1009,7 @@ func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secre
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.decompressWithSDK(srcFile, dst, cType)
|
||||
return f.decompressWithSDK(ctx, srcFile, dst, cType)
|
||||
}
|
||||
|
||||
func ZipFile(ctx context.Context, files []archiver.File, dst afero.File, progress func(current, total int, message string)) error {
|
||||
@@ -1088,7 +1088,7 @@ func (r *contextReader) Read(p []byte) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f FileOp) tryDecompressTarGz(srcFile string, dst string, format archiver.CompressedArchive) error {
|
||||
func (f FileOp) tryDecompressTarGz(ctx context.Context, srcFile string, dst string, format archiver.CompressedArchive) error {
|
||||
input, err := f.Fs.Open(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1139,7 +1139,7 @@ func (f FileOp) tryDecompressTarGz(srcFile string, dst string, format archiver.C
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := format.Extract(context.Background(), input, nil, handler); err != nil {
|
||||
if err := format.Extract(ctx, input, nil, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
if !extracted {
|
||||
@@ -1151,7 +1151,7 @@ func (f FileOp) tryDecompressTarGz(srcFile string, dst string, format archiver.C
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) DecompressGzFile(srcFile, dst string) error {
|
||||
func (f FileOp) DecompressGzFile(ctx context.Context, srcFile, dst string) error {
|
||||
var archiveModTime time.Time
|
||||
if st, err := f.Fs.Stat(srcFile); err == nil {
|
||||
archiveModTime = st.ModTime()
|
||||
@@ -1163,7 +1163,7 @@ func (f FileOp) DecompressGzFile(srcFile, dst string) error {
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
gr, err := gzip.NewReader(in)
|
||||
gr, err := gzip.NewReader(&contextReader{ctx: ctx, r: in})
|
||||
if err != nil {
|
||||
return fmt.Errorf("gzip reader creation failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ func NewRarArchiver() ShellArchiver {
|
||||
return &RarArchiver{}
|
||||
}
|
||||
|
||||
func (z RarArchiver) Extract(filePath, dstDir string, _ string) error {
|
||||
func (z RarArchiver) Extract(ctx context.Context, filePath, dstDir string, _ string) error {
|
||||
if err := checkCmdAvailability("unrar"); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.RunDefaultBashCf("unrar x -y -o+ %q %q", filePath, dstDir)
|
||||
return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("unrar x -y -o+ %q %q", filePath, dstDir)
|
||||
}
|
||||
|
||||
func (z RarArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, _ string) (err error) {
|
||||
|
||||
@@ -18,8 +18,8 @@ func NewTarArchiver(compressType CompressType) ShellArchiver {
|
||||
}
|
||||
}
|
||||
|
||||
func (t TarArchiver) Extract(FilePath string, dstDir string, secret string) error {
|
||||
return cmd.RunDefaultBashCf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir)
|
||||
func (t TarArchiver) Extract(ctx context.Context, FilePath string, dstDir string, secret string) error {
|
||||
return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir)
|
||||
}
|
||||
|
||||
func (t TarArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, secret string) error {
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewTarGzArchiver() ShellArchiver {
|
||||
return &TarGzArchiver{}
|
||||
}
|
||||
|
||||
func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
func (t TarGzArchiver) Extract(ctx context.Context, filePath, dstDir string, secret string) error {
|
||||
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination dir: %w", err)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
commands = fmt.Sprintf("tar -zxvf '%s' -C '%s' > /dev/null 2>&1", filePath, dstDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
if err = cmd.RunDefaultBashC(commands); err != nil {
|
||||
if err = cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashC(commands); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -19,11 +19,11 @@ func NewX7zArchiver() ShellArchiver {
|
||||
return &X7zArchiver{}
|
||||
}
|
||||
|
||||
func (z X7zArchiver) Extract(filePath, dstDir string, _ string) error {
|
||||
func (z X7zArchiver) Extract(ctx context.Context, filePath, dstDir string, _ string) error {
|
||||
if err := checkCmdAvailability("7z"); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.RunDefaultBashCf("7z x -y -o%q %q", dstDir, filePath)
|
||||
return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("7z x -y -o%q %q", dstDir, filePath)
|
||||
}
|
||||
|
||||
func (z X7zArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, _ string) (err error) {
|
||||
|
||||
@@ -20,11 +20,11 @@ func NewZipArchiver() ShellArchiver {
|
||||
return &ZipArchiver{}
|
||||
}
|
||||
|
||||
func (z ZipArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
func (z ZipArchiver) Extract(ctx context.Context, filePath, dstDir string, secret string) error {
|
||||
if err := checkCmdAvailability("unzip"); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.RunDefaultBashCf("unzip -qo %s -d %s", filePath, dstDir)
|
||||
return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("unzip -qo %s -d %s", filePath, dstDir)
|
||||
}
|
||||
|
||||
func (z ZipArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, _ string) error {
|
||||
|
||||
@@ -149,6 +149,11 @@ export namespace File {
|
||||
dst: string;
|
||||
type: string;
|
||||
secret: string;
|
||||
taskID?: string;
|
||||
}
|
||||
|
||||
export interface FileDeCompressStopReq {
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface FileEdit {
|
||||
|
||||
@@ -57,8 +57,15 @@ export const stopCompressFile = (taskID: string) => {
|
||||
return http.post('files/compress/stop', { taskID } as File.FileCompressStopReq);
|
||||
};
|
||||
|
||||
export const deCompressFile = (form: File.FileDeCompress) => {
|
||||
return http.post<File.File>('files/decompress', form, TimeoutEnum.T_10M);
|
||||
export const deCompressFile = (form: File.FileDeCompress, config?: AxiosRequestConfig) => {
|
||||
return http.service.post<File.File>('files/decompress', form, {
|
||||
timeout: TimeoutEnum.T_10M,
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const stopDeCompressFile = (taskID: string) => {
|
||||
return http.post('files/decompress/stop', { taskID } as File.FileDeCompressStopReq);
|
||||
};
|
||||
|
||||
export const getFileContent = (params: File.ReqFile) => {
|
||||
|
||||
@@ -1,49 +1,109 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('file.deCompress')" :resource="name" @close="handleClose" size="normal">
|
||||
<el-form
|
||||
ref="fileForm"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
label-width="100px"
|
||||
:rules="rules"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.name')">
|
||||
<el-input v-model="name" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('file.deCompressDst')" prop="dst">
|
||||
<el-input v-model="form.dst">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="fileRef.acceptParams({ path: form.dst, dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.compressPassword')" prop="secret" v-if="name.includes('tar.gz')">
|
||||
<el-input v-model="form.secret"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<DrawerPro v-model="open" :header="title" @close="handleClose" size="large">
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-if="showTaskStatus"
|
||||
class="rounded-lg border border-[var(--el-border-color-light)] bg-[var(--el-fill-color-light)] px-4 py-3"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="space-y-1">
|
||||
<div class="text-sm font-medium">
|
||||
{{ $t('commons.status.executing') }}
|
||||
</div>
|
||||
<div class="text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ taskInfo?.name || $t('file.deCompress') }}
|
||||
</div>
|
||||
</div>
|
||||
<el-button link type="primary" @click="openTaskLog" :disabled="!currentTaskID">
|
||||
{{ $t('commons.button.log') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-progress
|
||||
class="mt-3"
|
||||
:percentage="100"
|
||||
:indeterminate="true"
|
||||
:duration="1"
|
||||
:stroke-width="8"
|
||||
:show-text="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
v-if="!currentTaskID"
|
||||
ref="fileForm"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
label-width="100px"
|
||||
:rules="rules"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.name')">
|
||||
<el-input v-model="name" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('file.deCompressDst')" prop="dst">
|
||||
<el-input v-model="form.dst">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="fileRef.acceptParams({ path: form.dst, dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.compressPassword')" prop="secret" v-if="name.includes('tar.gz')">
|
||||
<el-input v-model="form.secret"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(fileForm)">{{ $t('commons.button.confirm') }}</el-button>
|
||||
<template v-if="!currentTaskID">
|
||||
<el-button :type="loading ? 'danger' : 'default'" :loading="canceling" @click="handleClose">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button v-if="!loading" type="primary" @click="submit(fileForm)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button
|
||||
v-if="isTaskExecuting || loading"
|
||||
type="danger"
|
||||
:loading="stopping"
|
||||
@click="stopCurrentTask"
|
||||
>
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button v-if="currentTaskID" @click="openTaskLog">
|
||||
{{ $t('commons.button.log') }}
|
||||
</el-button>
|
||||
<el-button type="default" @click="closeDrawer">
|
||||
{{ $t('commons.button.close') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<FileList ref="fileRef" @choose="getLinkPath" />
|
||||
<TaskLog ref="taskLogRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import i18n from '@/lang';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { File } from '@/api/interface/file';
|
||||
import { Log } from '@/api/interface/log';
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { deCompressFile } from '@/api/modules/files';
|
||||
import { deCompressFile, stopDeCompressFile } from '@/api/modules/files';
|
||||
import { searchTasks } from '@/api/modules/log';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { getErrorMessage } from '@/utils/misc';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
interface CompressProps {
|
||||
files: Array<any>;
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
interface DecompressProps {
|
||||
dst: string;
|
||||
name: string;
|
||||
path: string;
|
||||
@@ -55,20 +115,166 @@ const rules = reactive<FormRules>({
|
||||
});
|
||||
|
||||
const fileForm = ref<FormInstance>();
|
||||
let loading = ref(false);
|
||||
let form = ref<File.FileDeCompress>({ type: 'zip', dst: '', path: '', secret: '' });
|
||||
let open = ref(false);
|
||||
let name = ref('');
|
||||
const loading = ref(false);
|
||||
const canceling = ref(false);
|
||||
const stopping = ref(false);
|
||||
const abortController = ref<AbortController | null>(null);
|
||||
const form = ref<File.FileDeCompress>({ type: 'zip', dst: '', path: '', secret: '' });
|
||||
const open = ref(false);
|
||||
const title = ref('');
|
||||
const fileRef = ref();
|
||||
const taskLogRef = ref<InstanceType<typeof TaskLog> | null>(null);
|
||||
const currentTaskID = ref('');
|
||||
const taskInfo = ref<Log.Task | null>(null);
|
||||
let taskTimer: ReturnType<typeof setInterval> | null = null;
|
||||
const decompressTaskKey = 'file-management-decompress-task';
|
||||
const name = ref('');
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
const em = defineEmits<{
|
||||
(e: 'close', value: boolean): void;
|
||||
(
|
||||
e: 'task-change',
|
||||
value: {
|
||||
taskID: string;
|
||||
status: string;
|
||||
},
|
||||
): void;
|
||||
}>();
|
||||
|
||||
const handleClose = () => {
|
||||
const isTaskExecuting = computed(() => taskInfo.value?.status === 'Executing');
|
||||
const showTaskStatus = computed(() =>
|
||||
Boolean(currentTaskID.value || loading.value || canceling.value || stopping.value),
|
||||
);
|
||||
|
||||
const emitTaskChange = () => {
|
||||
const payload = {
|
||||
taskID: currentTaskID.value,
|
||||
status: taskInfo.value?.status || (currentTaskID.value || loading.value ? 'Executing' : ''),
|
||||
};
|
||||
if (payload.taskID && payload.status) {
|
||||
localStorage.setItem(decompressTaskKey, JSON.stringify(payload));
|
||||
} else {
|
||||
localStorage.removeItem(decompressTaskKey);
|
||||
}
|
||||
em('task-change', payload);
|
||||
};
|
||||
|
||||
const stopTaskPolling = () => {
|
||||
if (taskTimer) {
|
||||
clearInterval(taskTimer);
|
||||
taskTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const loadTaskInfo = async () => {
|
||||
if (!currentTaskID.value) {
|
||||
taskInfo.value = null;
|
||||
emitTaskChange();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await searchTasks(
|
||||
{
|
||||
taskID: currentTaskID.value,
|
||||
type: '',
|
||||
status: '',
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
},
|
||||
globalStore.currentNode,
|
||||
);
|
||||
taskInfo.value = res.data.items?.[0] || null;
|
||||
emitTaskChange();
|
||||
if (!taskInfo.value || taskInfo.value.status !== 'Executing') {
|
||||
stopTaskPolling();
|
||||
currentTaskID.value = '';
|
||||
taskInfo.value = null;
|
||||
emitTaskChange();
|
||||
}
|
||||
} catch {
|
||||
stopTaskPolling();
|
||||
currentTaskID.value = '';
|
||||
taskInfo.value = null;
|
||||
emitTaskChange();
|
||||
}
|
||||
};
|
||||
|
||||
const startTaskPolling = () => {
|
||||
stopTaskPolling();
|
||||
void loadTaskInfo();
|
||||
taskTimer = setInterval(() => {
|
||||
void loadTaskInfo();
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const resetDrawerState = () => {
|
||||
if (fileForm.value) {
|
||||
fileForm.value.resetFields();
|
||||
}
|
||||
abortController.value = null;
|
||||
canceling.value = false;
|
||||
loading.value = false;
|
||||
taskInfo.value = null;
|
||||
currentTaskID.value = '';
|
||||
stopTaskPolling();
|
||||
emitTaskChange();
|
||||
open.value = false;
|
||||
em('close', open.value);
|
||||
};
|
||||
|
||||
const closeDrawer = () => {
|
||||
if (currentTaskID.value && isTaskExecuting.value) {
|
||||
stopTaskPolling();
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
return;
|
||||
}
|
||||
resetDrawerState();
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const resumeTask = () => {
|
||||
if (!currentTaskID.value) {
|
||||
return;
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const stopCurrentTask = async () => {
|
||||
if (stopping.value || (!loading.value && !currentTaskID.value)) {
|
||||
return;
|
||||
}
|
||||
stopping.value = true;
|
||||
try {
|
||||
if (loading.value) {
|
||||
if (abortController.value) {
|
||||
canceling.value = true;
|
||||
abortController.value.abort();
|
||||
}
|
||||
} else if (currentTaskID.value) {
|
||||
await stopDeCompressFile(currentTaskID.value);
|
||||
}
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
} catch (err) {
|
||||
MsgError(getErrorMessage(err));
|
||||
} finally {
|
||||
stopping.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (loading.value) {
|
||||
stopCurrentTask();
|
||||
return;
|
||||
}
|
||||
closeDrawer();
|
||||
};
|
||||
|
||||
const openTaskLog = () => {
|
||||
if (!currentTaskID.value) {
|
||||
return;
|
||||
}
|
||||
taskLogRef.value?.openWithTaskID(currentTaskID.value, true, globalStore.currentNode);
|
||||
};
|
||||
|
||||
const getLinkPath = (path: string) => {
|
||||
@@ -81,25 +287,111 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const taskID = newUUID();
|
||||
form.value.taskID = taskID;
|
||||
canceling.value = false;
|
||||
abortController.value = new AbortController();
|
||||
loading.value = true;
|
||||
deCompressFile(form.value)
|
||||
deCompressFile(form.value, {
|
||||
signal: abortController.value?.signal,
|
||||
})
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('file.deCompressSuccess'));
|
||||
handleClose();
|
||||
currentTaskID.value = taskID;
|
||||
loading.value = false;
|
||||
taskInfo.value = null;
|
||||
emitTaskChange();
|
||||
startTaskPolling();
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = getErrorMessage(error).toLowerCase();
|
||||
if (canceling.value || message.includes('canceled') || message.includes('errshutdown')) {
|
||||
currentTaskID.value = '';
|
||||
emitTaskChange();
|
||||
return;
|
||||
}
|
||||
currentTaskID.value = '';
|
||||
emitTaskChange();
|
||||
MsgError(getErrorMessage(error));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
const wasCanceling = canceling.value;
|
||||
abortController.value = null;
|
||||
canceling.value = false;
|
||||
if (wasCanceling) {
|
||||
closeDrawer();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const acceptParams = (props: CompressProps) => {
|
||||
const acceptParams = (props: DecompressProps) => {
|
||||
if (currentTaskID.value) {
|
||||
open.value = true;
|
||||
if (!taskTimer) {
|
||||
startTaskPolling();
|
||||
}
|
||||
emitTaskChange();
|
||||
return;
|
||||
}
|
||||
|
||||
form.value.type = props.type;
|
||||
form.value.dst = props.dst;
|
||||
form.value.path = props.path;
|
||||
form.value.secret = '';
|
||||
name.value = props.name;
|
||||
abortController.value = null;
|
||||
canceling.value = false;
|
||||
stopping.value = false;
|
||||
taskInfo.value = null;
|
||||
currentTaskID.value = '';
|
||||
emitTaskChange();
|
||||
stopTaskPolling();
|
||||
open.value = true;
|
||||
|
||||
title.value = i18n.global.t('file.deCompress');
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
const restoreRunningTask = () => {
|
||||
const taskText = localStorage.getItem(decompressTaskKey);
|
||||
if (!taskText) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const task = JSON.parse(taskText) as {
|
||||
taskID?: string;
|
||||
status?: string;
|
||||
};
|
||||
if (!task.taskID) {
|
||||
localStorage.removeItem(decompressTaskKey);
|
||||
return;
|
||||
}
|
||||
currentTaskID.value = task.taskID;
|
||||
if (task.status === 'Executing') {
|
||||
emitTaskChange();
|
||||
} else {
|
||||
localStorage.removeItem(decompressTaskKey);
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(decompressTaskKey);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
restoreRunningTask();
|
||||
});
|
||||
|
||||
watch(open, (val) => {
|
||||
if (val && currentTaskID.value) {
|
||||
startTaskPolling();
|
||||
} else {
|
||||
stopTaskPolling();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTaskPolling();
|
||||
});
|
||||
|
||||
defineExpose({ acceptParams, resumeTask });
|
||||
</script>
|
||||
|
||||
@@ -660,7 +660,7 @@
|
||||
|
||||
<CreateFile ref="createRef" @close="search" />
|
||||
<ChangeRole ref="roleRef" @close="search" />
|
||||
<Compress ref="compressRef" @close="search" @task-change="onCompressTaskChange" />
|
||||
<Compress ref="compressRef" @close="search" />
|
||||
<Decompress ref="deCompressRef" @close="search" />
|
||||
<CodeEditor ref="codeEditorRef" @close="search" />
|
||||
<FileRename ref="renameRef" @close="search" />
|
||||
@@ -883,10 +883,6 @@ const recycleBinRef = ref();
|
||||
const favoriteRef = ref();
|
||||
const shareListRef = ref();
|
||||
const historyDrawerRef = ref<InstanceType<typeof FileHistoryDrawer> | null>(null);
|
||||
const compressTaskState = ref<{
|
||||
taskID: string;
|
||||
status: string;
|
||||
} | null>(null);
|
||||
const hoveredRowPath = ref(null);
|
||||
const favorites = ref([]);
|
||||
const batchRoleRef = ref();
|
||||
@@ -926,14 +922,6 @@ const setPathRef = (key: string, el: any) => {
|
||||
};
|
||||
const getCurrentPath = () => pathRefs.value[editableTabsKey.value];
|
||||
|
||||
const onCompressTaskChange = (task: { taskID: string; status: string }) => {
|
||||
if (!task.taskID) {
|
||||
compressTaskState.value = null;
|
||||
return;
|
||||
}
|
||||
compressTaskState.value = task;
|
||||
};
|
||||
|
||||
const { searchableStatus, searchablePath, setSearchableInputRef, searchableInputBlur } = useMultipleSearchable(paths);
|
||||
|
||||
const paginationConfig = reactive({
|
||||
|
||||
Reference in New Issue
Block a user