mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-09 23:17:21 +08:00
feat: Add download task button to view current ongoing download tasks (#7995)
This commit is contained in:
parent
f18a37ad47
commit
51bb42c493
13 changed files with 125 additions and 38 deletions
|
@ -748,7 +748,7 @@ var wsUpgrade = websocket.Upgrader{
|
|||
},
|
||||
}
|
||||
|
||||
func (b *BaseApi) Ws(c *gin.Context) {
|
||||
func (b *BaseApi) WgetProcess(c *gin.Context) {
|
||||
ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -758,7 +758,7 @@ func (b *BaseApi) Ws(c *gin.Context) {
|
|||
go wsClient.Write()
|
||||
}
|
||||
|
||||
func (b *BaseApi) Keys(c *gin.Context) {
|
||||
func (b *BaseApi) ProcessKeys(c *gin.Context) {
|
||||
res := &response.FileProcessKeys{}
|
||||
keys := global.CACHE.PrefixScanKey("file-wget-")
|
||||
res.Keys = keys
|
||||
|
|
|
@ -33,8 +33,8 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
fileRouter.GET("/download", baseApi.Download)
|
||||
fileRouter.POST("/chunkdownload", baseApi.DownloadChunkFiles)
|
||||
fileRouter.POST("/size", baseApi.Size)
|
||||
fileRouter.GET("/ws", baseApi.Ws)
|
||||
fileRouter.GET("/keys", baseApi.Keys)
|
||||
fileRouter.GET("/wget/process", baseApi.WgetProcess)
|
||||
fileRouter.GET("/wget/process/keys", baseApi.ProcessKeys)
|
||||
fileRouter.POST("/read", baseApi.ReadFileByLine)
|
||||
fileRouter.POST("/batch/role", baseApi.BatchChangeModeAndOwner)
|
||||
|
||||
|
|
|
@ -85,8 +85,8 @@ export const computeDirSize = (params: File.DirSizeReq) => {
|
|||
return http.post<File.DirSizeRes>('files/size', params, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const fileKeys = () => {
|
||||
return http.get<File.FileKeys>('files/keys');
|
||||
export const fileWgetKeys = () => {
|
||||
return http.get<File.FileKeys>('files//wget/process/keys');
|
||||
};
|
||||
|
||||
export const getRecycleList = (params: ReqPage) => {
|
||||
|
|
|
@ -1392,6 +1392,7 @@ const message = {
|
|||
minimap: 'Code Mini Map',
|
||||
fileCanNotRead: 'File can not read',
|
||||
panelInstallDir: '1Panel installation directory cannot be deleted',
|
||||
wgetTask: 'Download Task',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
|
|
|
@ -1334,6 +1334,7 @@ const message = {
|
|||
minimap: 'コードミニマップ',
|
||||
fileCanNotRead: 'ファイルは読み取れません',
|
||||
panelInstallDir: `1Panelインストールディレクトリは削除できません`,
|
||||
wgetTask: 'ダウンロードタスク',
|
||||
},
|
||||
ssh: {
|
||||
setting: '設定',
|
||||
|
|
|
@ -1320,6 +1320,7 @@ const message = {
|
|||
minimap: '코드 미니맵',
|
||||
fileCanNotRead: '파일을 읽을 수 없습니다.',
|
||||
panelInstallDir: `1Panel 설치 디렉터리는 삭제할 수 없습니다.`,
|
||||
wgetTask: '다운로드 작업',
|
||||
},
|
||||
ssh: {
|
||||
setting: '설정',
|
||||
|
|
|
@ -1377,6 +1377,7 @@ const message = {
|
|||
minimap: 'Peta mini kod',
|
||||
fileCanNotRead: 'Fail tidak dapat dibaca',
|
||||
panelInstallDir: 'Direktori pemasangan 1Panel tidak boleh dipadamkan',
|
||||
wgetTask: 'Tugas Muat Turun',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'tetapan',
|
||||
|
|
|
@ -1362,6 +1362,7 @@ const message = {
|
|||
minimap: 'Mini mapa de código',
|
||||
fileCanNotRead: 'O arquivo não pode ser lido',
|
||||
panelInstallDir: 'O diretório de instalação do 1Panel não pode ser excluído',
|
||||
wgetTask: 'Tarefa de Download',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'configuração',
|
||||
|
|
|
@ -1366,6 +1366,7 @@ const message = {
|
|||
minimap: 'Мини-карта кода',
|
||||
fileCanNotRead: 'Файл не может быть прочитан',
|
||||
panelInstallDir: 'Директорию установки 1Panel нельзя удалить',
|
||||
wgetTask: 'Задача загрузки',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'настройка',
|
||||
|
|
|
@ -1344,6 +1344,7 @@ const message = {
|
|||
minimap: '縮略圖',
|
||||
fileCanNotRead: '此文件不支持預覽',
|
||||
panelInstallDir: '1Panel 安裝目錄不能删除',
|
||||
wgetTask: '下載任務',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
|
|
|
@ -1317,6 +1317,7 @@ const message = {
|
|||
minimap: '缩略图',
|
||||
fileCanNotRead: '此文件不支持预览',
|
||||
panelInstallDir: '1Panel 安装目录不能删除',
|
||||
wgetTask: '下载任务',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
|
|
|
@ -102,6 +102,12 @@
|
|||
{{ $t('menu.terminal') }}
|
||||
</el-button>
|
||||
|
||||
<el-badge :value="processCount" class="btn" v-if="processCount > 0">
|
||||
<el-button class="btn" @click="openProcess">
|
||||
{{ $t('file.wgetTask') }}
|
||||
</el-button>
|
||||
</el-badge>
|
||||
|
||||
<el-button-group class="copy-button" v-if="moveOpen">
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
||||
<el-button plain @click="openPaste">{{ $t('file.paste') }}({{ fileMove.count }})</el-button>
|
||||
|
@ -291,7 +297,7 @@
|
|||
<Wget ref="wgetRef" @close="closeWget" />
|
||||
<Move ref="moveRef" @close="closeMovePage" />
|
||||
<Download ref="downloadRef" @close="search" />
|
||||
<Process :open="processPage.open" @close="closeProcess" />
|
||||
<Process ref="processRef" @close="getWgetProcess" />
|
||||
<Owner ref="chownRef" @close="search"></Owner>
|
||||
<Detail ref="detailRef" />
|
||||
<DeleteFile ref="deleteRef" @close="search" />
|
||||
|
@ -313,6 +319,7 @@ import {
|
|||
addFavorite,
|
||||
removeFavorite,
|
||||
searchFavorite,
|
||||
fileWgetKeys,
|
||||
} from '@/api/modules/files';
|
||||
import { computeSize, copyText, dateFormat, getFileType, getIcon, getRandomStr, downloadFile } from '@/utils/util';
|
||||
import { File } from '@/api/interface/file';
|
||||
|
@ -386,7 +393,6 @@ const fileUpload = reactive({ path: '' });
|
|||
const fileRename = reactive({ path: '', oldName: '' });
|
||||
const fileWget = reactive({ path: '' });
|
||||
const fileMove = reactive({ oldPaths: [''], type: '', path: '', name: '', count: 0 });
|
||||
const processPage = reactive({ open: false });
|
||||
|
||||
const createRef = ref();
|
||||
const roleRef = ref();
|
||||
|
@ -411,6 +417,7 @@ const favorites = ref([]);
|
|||
const batchRoleRef = ref();
|
||||
const dialogVscodeOpenRef = ref();
|
||||
const previewRef = ref();
|
||||
const processRef = ref();
|
||||
|
||||
// editablePath
|
||||
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths);
|
||||
|
@ -754,11 +761,17 @@ const closeMovePage = (submit: Boolean) => {
|
|||
};
|
||||
|
||||
const openProcess = () => {
|
||||
processPage.open = true;
|
||||
processRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const closeProcess = () => {
|
||||
processPage.open = false;
|
||||
const processCount = ref(0);
|
||||
const getWgetProcess = async () => {
|
||||
try {
|
||||
const res = await fileWgetKeys();
|
||||
if (res.data && res.data.keys.length > 0) {
|
||||
processCount.value = res.data.keys.length;
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const openRename = (item: File.File) => {
|
||||
|
@ -965,6 +978,7 @@ onMounted(() => {
|
|||
nextTick(function () {
|
||||
handlePath();
|
||||
});
|
||||
getWgetProcess();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,39 +1,68 @@
|
|||
<template>
|
||||
<DialogPro v-model="open" :title="$t('file.downloadProcess')" size="small" @open="onOpen" @close="handleClose">
|
||||
<div v-for="(value, index) in res" :key="index">
|
||||
<span>{{ value['percent'] === 100 ? $t('file.downloadSuccess') : $t('file.downloading') }}</span>
|
||||
<MsgInfo :info="value['name']" width="250" />
|
||||
<el-progress v-if="value['total'] == 0" :percentage="100" :indeterminate="true" :duration="1" />
|
||||
<el-progress v-else :text-inside="true" :stroke-width="15" :percentage="value['percent']"></el-progress>
|
||||
<span>
|
||||
{{ getFileSize(value['written']) }}/
|
||||
<span v-if="value['total'] > 0">{{ getFileSize(value['total']) }}</span>
|
||||
</span>
|
||||
<DialogPro v-model="open" :title="$t('file.downloadProcess')" size="small" @close="handleClose">
|
||||
<template #content>
|
||||
<div class="space-y-4 p-4" :loading="loading">
|
||||
<div
|
||||
v-for="(value, index) in res"
|
||||
:key="index"
|
||||
class="bg-white rounded-lg p-4 shadow-sm border border-gray-100 transition-all duration-200 hover:shadow-md"
|
||||
:class="{ completed: value.percent === 100 }"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-1">
|
||||
<MsgInfo :info="value.name" class="text-gray-700" />
|
||||
<div class="text-gray-500">
|
||||
{{ value.percent === 100 ? $t('file.downloadSuccess') : $t('file.downloading') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-end text-gray-500 mb-1">
|
||||
<span>{{ getFileSize(value.written) }}</span>
|
||||
<span v-if="value.total > 0" class="text-gray-400">/{{ getFileSize(value.total) }}</span>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<el-progress
|
||||
v-if="value.total === 0"
|
||||
:percentage="100"
|
||||
:indeterminate="true"
|
||||
:duration="1"
|
||||
class="progress-bar"
|
||||
:stroke-width="8"
|
||||
:show-text="false"
|
||||
/>
|
||||
<el-progress
|
||||
v-else
|
||||
:percentage="value.percent"
|
||||
:stroke-width="8"
|
||||
class="progress-bar"
|
||||
:status="value.percent === 100 ? 'success' : ''"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { fileKeys } from '@/api/modules/files';
|
||||
import { fileWgetKeys } from '@/api/modules/files';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { onBeforeUnmount, ref, toRefs } from 'vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import MsgInfo from '@/components/msg-info/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const { open } = toRefs(props);
|
||||
let processSocket = ref(null) as unknown as WebSocket;
|
||||
const res = ref([]);
|
||||
const keys = ref(['']);
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
const handleClose = () => {
|
||||
closeSocket();
|
||||
open.value = false;
|
||||
em('close', open);
|
||||
};
|
||||
|
||||
|
@ -58,7 +87,7 @@ const initProcess = () => {
|
|||
let href = window.location.href;
|
||||
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
|
||||
let ipLocal = href.split('//')[1].split('/')[0];
|
||||
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/files/ws`);
|
||||
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/files/wget/process`);
|
||||
processSocket.onopen = onOpenProcess;
|
||||
processSocket.onmessage = onMessage;
|
||||
processSocket.onerror = onerror;
|
||||
|
@ -66,15 +95,20 @@ const initProcess = () => {
|
|||
sendMsg();
|
||||
};
|
||||
|
||||
const getKeys = () => {
|
||||
const getKeys = async () => {
|
||||
keys.value = [];
|
||||
res.value = [];
|
||||
fileKeys().then((res) => {
|
||||
if (res.data.keys.length > 0) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await fileWgetKeys();
|
||||
if (res.data && res.data.keys.length > 0) {
|
||||
keys.value = res.data.keys;
|
||||
initProcess();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const sendMsg = () => {
|
||||
|
@ -98,7 +132,38 @@ onBeforeUnmount(() => {
|
|||
closeSocket();
|
||||
});
|
||||
|
||||
const onOpen = () => {
|
||||
const acceptParams = () => {
|
||||
open.value = true;
|
||||
getKeys();
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
||||
|
||||
<style type="scss" scoped>
|
||||
.download-item.completed {
|
||||
@apply bg-green-50/50;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
:deep(.el-progress-bar__outer) {
|
||||
@apply rounded-full bg-gray-100;
|
||||
}
|
||||
|
||||
:deep(.el-progress-bar__inner) {
|
||||
@apply rounded-full transition-all duration-300;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-10%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(0);
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Add table
Reference in a new issue