Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(portal): 接入 scowd 文件分片上传 #1346

Merged
merged 61 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
53ceaf8
feat: scowd proto 新增 mergeFileChunks 接口
Miracle575 Jul 8, 2024
43cae2d
feat: MergeFileChunksRequest 增加 user_id
Miracle575 Jul 8, 2024
43ee543
feat: 初步完成断点续传逻辑
Miracle575 Jul 9, 2024
1e66467
style: lint
Miracle575 Jul 9, 2024
1d9c1b7
Merge branch 'master' into feat-breakpoint-upload
Miracle575 Jul 9, 2024
807ab50
feat: 获取scowd 是否启动
Miracle575 Jul 9, 2024
ea590d8
fix: pnpm-lock.yaml
Miracle575 Jul 9, 2024
c929200
fix: pnpm-lock
Miracle575 Jul 9, 2024
721eac5
fix: 删除多余接口定义
Miracle575 Jul 9, 2024
929fa8c
feat: 将mergefilechunks正确返回状态码改为 204
Miracle575 Jul 9, 2024
93e8d04
fix: mis-web operationlog i18n
Miracle575 Jul 9, 2024
67e42da
fix: 添加 ai 操作日志类型
Miracle575 Jul 9, 2024
d638c74
feat: 分片上传判断放到 getServerSideProps
Miracle575 Jul 10, 2024
7ebd941
Merge branch 'master' into feat-breakpoint-upload
Miracle575 Jul 10, 2024
68b593d
fix: lock file
Miracle575 Jul 10, 2024
4cbe7d0
feat: delete complier.styledComponents
Miracle575 Jul 10, 2024
7a50df2
feat: update pnpm
Miracle575 Jul 10, 2024
394d0fc
fix: getServerSideProps pages
Miracle575 Jul 10, 2024
41ca7fe
refactor: rename startBreakpointUpload to startMultipartUpload
Miracle575 Jul 10, 2024
b98263a
fix: getConfigFiles 不反悔 scowd.enabled 的问题
Miracle575 Jul 10, 2024
b78e2c4
feat: 提取 ip 函数未匹配则直接返回 address
Miracle575 Jul 11, 2024
c7986ae
fix: 修改测试用例
Miracle575 Jul 11, 2024
8169fa7
fix: test
Miracle575 Jul 11, 2024
c89b596
fix: 忽略文件分片上传时目录检查不存在的异常
Miracle575 Jul 11, 2024
dd5ad1a
fix: lisFile 增加 checkFileChunks 参数
Miracle575 Jul 11, 2024
b995dfa
fix: 修改分片上传 path
Miracle575 Jul 11, 2024
ec8f104
fix: list return
Miracle575 Jul 12, 2024
430e9b6
fix: mergeFileChunks 缺少 name 参数
Miracle575 Jul 12, 2024
8d28f8d
feat: 新增 initMultipartUpload 接口定义
Miracle575 Jul 12, 2024
96485b7
feat: 新增初始化分片上传的接口
Miracle575 Jul 15, 2024
178e324
Merge branch 'master' into feat-breakpoint-upload
Miracle575 Jul 15, 2024
7590b65
fix: 修复合并操作日志后的问题
Miracle575 Jul 15, 2024
84657c5
feat: 新增错误处理和 api mock
Miracle575 Jul 15, 2024
d34fb20
fix: hash 生成
Miracle575 Jul 15, 2024
1d31783
refactor: 删除冗余代码
Miracle575 Jul 15, 2024
134f930
feat: 新增自定义上传的取消上传功能
Miracle575 Jul 15, 2024
ab4989f
fix: extractIP 改为 removePort
Miracle575 Jul 15, 2024
5509a60
fix: 取消上传只有部分取消的问题
Miracle575 Jul 15, 2024
09b4da5
feat: 优化国际化和错误提示
Miracle575 Jul 15, 2024
245c67b
feat: 优化文件分片上传方法
Miracle575 Jul 16, 2024
353670b
Merge branch 'master' into feat-breakpoint-upload
Miracle575 Jul 16, 2024
4cb7419
chore: 添加 changeset
Miracle575 Jul 16, 2024
1114252
fix: 修复无法取消上传的问题
Miracle575 Jul 16, 2024
729d1ee
fix: 修复无法取消上传的问题
Miracle575 Jul 16, 2024
3d9540b
fix: 取消上传的错误
Miracle575 Jul 16, 2024
f034529
temp: 新增临时日志
Miracle575 Jul 16, 2024
c8c38bc
temp: 删除临时日志
Miracle575 Jul 16, 2024
fd0873d
feat: 增加错误提示
Miracle575 Jul 16, 2024
2b0bbac
refactor: 删除 console
Miracle575 Jul 16, 2024
eeed28c
fix: 修复 eslint 错误
Miracle575 Jul 16, 2024
6b3ded9
Merge branch 'master' into feat-breakpoint-upload
Miracle575 Jul 18, 2024
d3f9677
Merge branch 'master' into feat-breakpoint-upload
Miracle575 Aug 5, 2024
18f7342
chore: 修改 api 修改为 minor
Miracle575 Aug 7, 2024
fdc26cf
refactor: 完善代码逻辑,添加注释
Miracle575 Aug 9, 2024
4593151
fix: proto size 添加单位
Miracle575 Aug 9, 2024
306fbed
fix: delete error
Miracle575 Aug 12, 2024
3026492
feat: 新增接口未实现提示
Miracle575 Aug 12, 2024
12fdfc6
chore: 删除冗余log
Miracle575 Aug 12, 2024
9e72350
feat: 优化上传效率
Miracle575 Aug 13, 2024
4094578
fix: merge error
Miracle575 Aug 13, 2024
a535fca
fix: 关闭modal框是 file list 不消失的问题
Miracle575 Aug 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/warm-gifts-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@scow/portal-server": patch
"@scow/lib-operation-log": patch
"@scow/scowd-protos": patch
"@scow/portal-web": patch
"@scow/mis-web": patch
"@scow/lib-server": patch
"@scow/grpc-api": minor
---

接入 scowd 文件分片上传
2 changes: 2 additions & 0 deletions apps/mis-web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,8 @@ export default {
customEvent: "Custom Operation Event",
activateCluster: "Activate Cluster",
deactivateCluster: "Deactivate Cluster",
mergeFileChunks: "Merge and upload temporary file blocks",
initMultipartUpload: "Initial multipart upload file",
},
operationDetails: {
login: "User Login",
Expand Down
2 changes: 2 additions & 0 deletions apps/mis-web/src/i18n/zh_cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,8 @@ export default {
customEvent: "自定义操作行为",
activateCluster: "启用集群",
deactivateCluster: "停用集群",
mergeFileChunks: "合并临时文件块",
initMultipartUpload: "初始化分片上传",
},
operationDetails: {
login: "用户登录",
Expand Down
4 changes: 4 additions & 0 deletions apps/mis-web/src/models/operationLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export const getOperationTypeTexts = (t: OperationTextsTransType): {[key in LibO
activateCluster: t(pTypes("activateCluster")),
deactivateCluster: t(pTypes("deactivateCluster")),
customEvent: t(pTypes("customEvent")),
mergeFileChunks: t(pTypes("mergeFileChunks")),
initMultipartUpload: t(pTypes("initMultipartUpload")),
};

};
Expand Down Expand Up @@ -202,6 +204,8 @@ export const OperationCodeMap: {[key in LibOperationType]: string } = {
moveFileItem: "010506",
copyFileItem: "010507",
submitFileItemAsJob: "010508",
mergeFileChunks: "010509",
initMultipartUpload: "010510",
setJobTimeLimit: "010601",
createImage:"010701",
updateImage:"010702",
Expand Down
6 changes: 3 additions & 3 deletions apps/portal-server/src/clusterops/file/scowdFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const scowdFileServices = (client: ScowdClient): FileOps => ({
type: fileInfo_FileTypeFromJSON(info.fileType),
mtime: info.modTime,
mode: info.mode,
size: Number(info.size),
size: Number(info.sizeByte),
};
});
return { results };
Expand All @@ -124,7 +124,7 @@ export const scowdFileServices = (client: ScowdClient): FileOps => ({

try {
const readStream = client.file.download({
userId, path, chunkSize: config.DOWNLOAD_CHUNK_SIZE,
userId, path, chunkSizeByte: config.DOWNLOAD_CHUNK_SIZE,
});

for await (const response of readStream) {
Expand Down Expand Up @@ -183,7 +183,7 @@ export const scowdFileServices = (client: ScowdClient): FileOps => ({
try {
const res = await client.file.getFileMetadata({ userId, filePath: path });

return { size: Number(res.size), type: res.type === FileType.DIR ? "dir" : "file" };
return { size: Number(res.sizeByte), type: res.type === FileType.DIR ? "dir" : "file" };

} catch (err) {
throw mapTRPCExceptionToGRPC(err);
Expand Down
78 changes: 77 additions & 1 deletion apps/portal-server/src/services/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@

import { plugin } from "@ddadaal/tsgrpc-server";
import { ServiceError, status } from "@grpc/grpc-js";
import { Status } from "@grpc/grpc-js/build/src/constants";
import { loggedExec, sftpAppendFile, sftpExists, sftpMkdir,
sftpReadFile, sftpRealPath, sshRmrf } from "@scow/lib-ssh";
import { FileServiceServer, FileServiceService, TransferInfo } from "@scow/protos/build/portal/file";
import {
FileInfo, fileInfo_FileTypeFromJSON, FileServiceServer, FileServiceService, TransferInfo,
} from "@scow/protos/build/portal/file";
import { getClusterOps } from "src/clusterops";
import { configClusters } from "src/config/clusters";
import { checkActivatedClusters } from "src/utils/clusters";
import { clusterNotFound } from "src/utils/errors";
import { getScowdClient, mapTRPCExceptionToGRPC } from "src/utils/scowd";
import { getClusterLoginNode, getClusterTransferNode, sshConnect, tryGetClusterTransferNode } from "src/utils/ssh";

export const fileServiceServer = plugin((server) => {
Expand Down Expand Up @@ -193,6 +197,78 @@ export const fileServiceServer = plugin((server) => {

},

initMultipartUpload: async ({ request }) => {

const { cluster, userId, path, name } = request;
await checkActivatedClusters({ clusterIds: cluster });

const host = getClusterLoginNode(cluster);

if (!host) { throw clusterNotFound(cluster); }

const clusterInfo = configClusters[cluster];

if (!clusterInfo.scowd?.enabled) {
throw {
code: Status.UNIMPLEMENTED,
message: "To use this interface, you need to enable scowd.",
} as ServiceError;
}

const client = getScowdClient(cluster);

try {
const initData = await client.file.initMultipartUpload({ userId, path, name });

return [{
...initData,
chunkSizeByte: Number(initData.chunkSizeByte),
filesInfo: initData.filesInfo.map((info): FileInfo => {
return {
name: info.name,
type: fileInfo_FileTypeFromJSON(info.fileType),
mtime: info.modTime,
mode: info.mode,
size: Number(info.sizeByte),
};
}),
}];

} catch (err) {
throw mapTRPCExceptionToGRPC(err);
}
},

mergeFileChunks: async ({ request }) => {
const { cluster, userId, path, name, sizeByte } = request;
await checkActivatedClusters({ clusterIds: cluster });

const host = getClusterLoginNode(cluster);

if (!host) { throw clusterNotFound(cluster); }

const clusterInfo = configClusters[cluster];
if (clusterInfo.scowd?.enabled) {
const client = getScowdClient(cluster);

try {
await client.file.mergeFileChunks({ userId, path, name, sizeByte: BigInt(sizeByte) });

return [{}];

} catch (err) {
throw mapTRPCExceptionToGRPC(err);
}

} else {
throw {
code: Status.UNIMPLEMENTED,
message: "The mergeFileChunks interface is not implemented",
} as ServiceError;
}
},


getFileMetadata: async ({ request, logger }) => {
const { userId, cluster, path } = request;
await checkActivatedClusters({ clusterIds: cluster });
Expand Down
1 change: 1 addition & 0 deletions apps/portal-server/src/utils/scowd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function getLoginNodeScowdUrl(cluster: string, host: string): string | un
if (!loginNode) return undefined;

const { address, scowdPort } = loginNode;

return config.SCOWD_SSL_ENABLED
? `https://${removePort(address)}:${scowdPort}` : `http://${removePort(address)}:${scowdPort}`;
}
Expand Down
3 changes: 0 additions & 3 deletions apps/portal-web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ module.exports = async (phase) => {

return config;
},
compiler: {
styledComponents: true,
},
skipTrailingSlashRedirect: true,
transpilePackages: ["antd", "@ant-design/icons"],
};
Expand Down
8 changes: 5 additions & 3 deletions apps/portal-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@
"@sinclair/typebox": "0.32.34",
"@uiw/codemirror-theme-github": "4.22.2",
"@uiw/react-codemirror": "4.21.20",
"@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "5.5.0",
"antd": "5.18.3",
"busboy": "1.6.0",
"crypto-js": "4.2.0",
"dayjs": "1.11.11",
"google-protobuf": "3.21.2",
"http-proxy": "1.18.1",
Expand All @@ -63,6 +66,7 @@
"next-compose-plugins": "2.2.1",
"nookies": "2.5.2",
"nprogress": "0.2.0",
"p-limit": "6.1.0",
"react": "18.3.1",
"react-async": "10.0.1",
"react-dom": "18.3.1",
Expand All @@ -73,9 +77,7 @@
"styled-components": "6.1.11",
"tslib": "2.6.3",
"typescript": "5.5.2",
"ws": "8.17.1",
"@xterm/xterm": "5.5.0",
"@xterm/addon-fit": "0.10.0"
"ws": "8.17.1"
},
"devDependencies": {
"@ddadaal/next-typed-api-routes-cli": "0.9.1",
Expand Down
6 changes: 6 additions & 0 deletions apps/portal-web/src/apis/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,12 @@ export const mockApi: MockApi<typeof api> = {
},
}),

mergeFileChunks: null,
initMultipartUpload: async () => ({
tempFileDir: "home/user/scow/tempDir",
chunkSizeByte: 5 * 1024 * 1024,
filesInfo: [],
}),
getClustersRuntimeInfo: async () => ({
results: [{
clusterId: "hpc01",
Expand Down
4 changes: 4 additions & 0 deletions apps/portal-web/src/apis/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ import type { DownloadFileSchema } from "src/pages/api/file/download";
import type { FileExistSchema } from "src/pages/api/file/fileExist";
import type { GetFileTypeSchema } from "src/pages/api/file/getFileType";
import type { GetHomeDirectorySchema } from "src/pages/api/file/getHome";
import type { InitMultipartUploadSchema } from "src/pages/api/file/initMultipartUpload";
import type { ListFileSchema } from "src/pages/api/file/list";
import type { ListAvailableTransferClustersSchema } from "src/pages/api/file/listAvailableTransferClusters";
import type { MergeFileChunksSchema } from "src/pages/api/file/mergeFileChunks";
import type { MkdirSchema } from "src/pages/api/file/mkdir";
import type { MoveFileItemSchema } from "src/pages/api/file/move";
import type { QueryFileTransferProgressSchema } from "src/pages/api/file/queryFileTransferProgress";
Expand Down Expand Up @@ -97,8 +99,10 @@ export const api = {
fileExist: apiClient.fromTypeboxRoute<typeof FileExistSchema>("GET", "/api/file/fileExist"),
getFileType: apiClient.fromTypeboxRoute<typeof GetFileTypeSchema>("GET", "/api/file/getFileType"),
getHomeDirectory: apiClient.fromTypeboxRoute<typeof GetHomeDirectorySchema>("GET", "/api/file/getHome"),
initMultipartUpload: apiClient.fromTypeboxRoute<typeof InitMultipartUploadSchema>("POST", "/api/file/initMultipartUpload"),
listFile: apiClient.fromTypeboxRoute<typeof ListFileSchema>("GET", "/api/file/list"),
listAvailableTransferClusters: apiClient.fromTypeboxRoute<typeof ListAvailableTransferClustersSchema>("GET", "/api/file/listAvailableTransferClusters"),
mergeFileChunks: apiClient.fromTypeboxRoute<typeof MergeFileChunksSchema>("POST", "/api/file/mergeFileChunks"),
mkdir: apiClient.fromTypeboxRoute<typeof MkdirSchema>("POST", "/api/file/mkdir"),
moveFileItem: apiClient.fromTypeboxRoute<typeof MoveFileItemSchema>("PATCH", "/api/file/move"),
queryFileTransferProgress: apiClient.fromTypeboxRoute<typeof QueryFileTransferProgressSchema>("GET", "/api/file/queryFileTransferProgress"),
Expand Down
4 changes: 4 additions & 0 deletions apps/portal-web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ export default {
existedModalOk: "Confirm",
dragText: "Click or drag files here",
hintText: "Supports uploading single or multiple files",
multipartUploadError: "Upload file failed: {}",
calculateHashError: "Error calculating hash: {}",
uploadFileListNotExist: "The uploaded file list does not exist: {}",
mergeFileChunksErrorText: "Failed to merge file {}, please try again",
},
},
// desktop
Expand Down
4 changes: 4 additions & 0 deletions apps/portal-web/src/i18n/zh_cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ export default {
existedModalOk: "确认",
dragText: "点击或者将文件拖动到这里",
hintText: "支持上传单个或者多个文件",
multipartUploadError: "文件上传失败: {}",
calculateHashError: "计算哈希值错误: {}",
uploadFileListNotExist: "上传文件列表中不存在: {}",
mergeFileChunksErrorText: "合并文件 {} 失败,请重试",
},
},
// desktop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface Props {
cluster: Cluster;
path: string;
urlPrefix: string;
scowdEnabled: boolean;
}

interface PromiseSettledResult {
Expand Down Expand Up @@ -93,7 +94,7 @@ interface Operation {

const p = prefix("pageComp.fileManagerComp.fileManager.");

export const FileManager: React.FC<Props> = ({ cluster, path, urlPrefix }) => {
export const FileManager: React.FC<Props> = ({ cluster, path, urlPrefix, scowdEnabled }) => {

const router = useRouter();

Expand Down Expand Up @@ -411,6 +412,7 @@ export const FileManager: React.FC<Props> = ({ cluster, path, urlPrefix }) => {
cluster={cluster.id}
path={path}
reload={reload}
scowdEnabled={scowdEnabled}
>
{t(p("tableInfo.uploadButton"))}
</UploadButton>
Expand Down
Loading
Loading