diff --git a/packages/shared/src/components/Apps/AppCreate/CreateHelmApp/index.tsx b/packages/shared/src/components/Apps/AppCreate/CreateHelmApp/index.tsx index 2972f866b0f..ff117f4fe8e 100644 --- a/packages/shared/src/components/Apps/AppCreate/CreateHelmApp/index.tsx +++ b/packages/shared/src/components/Apps/AppCreate/CreateHelmApp/index.tsx @@ -8,7 +8,7 @@ import { Templet } from '@kubed/icons'; import { notify } from '@kubed/components'; import { useParams } from 'react-router-dom'; import { PackageUpload } from '../../PackageUpload'; -import { getCreateAppParams } from '../../../../utils'; +import { getCreateAppParams, getCreateAppParamsFormData } from '../../../../utils'; import { openpitrixStore } from '../../../../stores'; import CheckFiles from './CheckFiles'; import CreateInfo from './CreateInfo'; @@ -22,6 +22,13 @@ type FormattedFileInfo = { icon?: string; }; +type FormattedFileInfoFormData = { + status?: string; + name?: string; + formData?: FormData; + icon?: string; +}; + type Props = { visible: boolean; title?: ReactNode; @@ -30,6 +37,7 @@ type Props = { workspace?: string; type?: string; onOk?: (data: Record) => void; + onOkFormData?: (data: Record, formData: FormData) => void; onCancel?: () => void; }; @@ -42,25 +50,39 @@ export function CreateHelmApp({ description, onCancel, onOk, + onOkFormData, }: Props): JSX.Element { const { appName = '', workspace = ws } = useParams(); const [appIcon, setIconStr] = useState(); const [canCreate, setCanCreate] = useState(false); const [checkedFileInfo, setCheckedFileInfo] = useState(); + const [checkedFileInfoFormData, setCheckedFileInfoFormData] = + useState(); + // const htmlDesc = t('APP_CREATE_GUIDE', { docUrl: getWebsiteUrl() }); const checkedUnSuccess = useMemo( () => checkedFileInfo?.status !== 'success', [checkedFileInfo?.status], ); + const checkedUnSuccessFormData = useMemo( + () => checkedFileInfoFormData?.status !== 'success', + [checkedFileInfoFormData?.status], + ); + function initCheckedStatus(): void { setCheckedFileInfo(prevChecked => ({ ...prevChecked, status: 'init' })); } + function initCheckedStatusFormData(): void { + setCheckedFileInfoFormData(prevChecked => ({ ...prevChecked, status: 'init' })); + } + function handleCancel(): void { onCancel?.(); setCanCreate(false); initCheckedStatus(); + initCheckedStatusFormData(); } async function submitData(): Promise { @@ -74,6 +96,22 @@ export function CreateHelmApp({ await fileStore.uploadPackage('CREATE_APP', data, onOk); } + async function submitDataFormData(): Promise { + if (!checkedFileInfoFormData?.formData) { + return notify.error(t('UPLOAD_PACKAGE_OK_NOTE')); + } + const { formData, ...restInfo } = checkedFileInfoFormData; + + const data: Record = getCreateAppParamsFormData({ + appType: 'helm', + workspace, + // package: checkedFileInfo?.base64Str, + ...restInfo, + icon: appIcon, + }); + await fileStore.uploadPackageFormData('CREATE_APP', data, formData, onOkFormData); + } + async function handleSubmit() { if (checkedUnSuccess) { return notify.error(t('UPLOAD_PACKAGE_OK_NOTE')); @@ -87,6 +125,19 @@ export function CreateHelmApp({ initCheckedStatus(); } + async function handleSubmitFormData() { + if (checkedUnSuccessFormData) { + return notify.error(t('UPLOAD_PACKAGE_OK_NOTE')); + } + if (!canCreate) { + return setCanCreate(!canCreate); + } + + await submitDataFormData(); + setCanCreate(false); + initCheckedStatusFormData(); + } + return ( {!canCreate ? ( <> - + {/*{showOutSiteLink() && (*/} {/*
*/} {/* 💁‍♂️ */} @@ -116,7 +170,10 @@ export function CreateHelmApp({ {/*)}*/} ) : ( - + )} ); diff --git a/packages/shared/src/components/Apps/AppCreate/index.tsx b/packages/shared/src/components/Apps/AppCreate/index.tsx index f6f0ea0b9d8..1a355f4ddaa 100644 --- a/packages/shared/src/components/Apps/AppCreate/index.tsx +++ b/packages/shared/src/components/Apps/AppCreate/index.tsx @@ -9,7 +9,7 @@ import { Button, Modal, notify } from '@kubed/components'; import Icon from '../../Icon'; import { useV3action } from '../../../hooks'; import { openpitrixStore, workspaceStore } from '../../../stores'; -import { getCreateAppParams } from '../../../utils'; +import { getCreateAppParams, getCreateAppParamsFormData } from '../../../utils'; import { CreateHelmApp } from './CreateHelmApp'; import { CreateYamlApp } from './CreateYamlApp'; import { Header, HeaderFieldItem, Logo, FieldItem } from './styles'; @@ -17,6 +17,7 @@ import { Header, HeaderFieldItem, Logo, FieldItem } from './styles'; type Props = { visible?: boolean; onOk?: (data: any, params: any) => void; + onOkFormData?: (data: any, formData: FormData, params: any) => void; onCancel?: () => void; tableRef?: any; workspace?: string; @@ -27,7 +28,7 @@ type Props = { type ModalType = 'create_helm' | 'create_yaml' | 'create_edge'; -const { createApp } = openpitrixStore; +const { createApp, createAppFormData } = openpitrixStore; const { useFetchWorkspaceQuery } = workspaceStore; export function CreateApp({ @@ -35,6 +36,7 @@ export function CreateApp({ onCancel, tableRef, onOk, + onOkFormData, workspace = '', isDetail, appName, @@ -104,6 +106,27 @@ export function CreateApp({ onCancel?.(); tableRef?.current?.refetch(); } + + // todo When using formData and an external operation function is passed, use onOkFormData + async function handleCreateFormData(fileData: any, formData: FormData): Promise { + fileData.maintainers = [{ name: globals.user.username }]; + fileData.workspace = workspace; + if (onOkFormData) { + onOkFormData(getCreateAppParamsFormData(fileData), formData, { workspace }); + notify.success(t('UPLOAD_SUCCESSFUL')); + + setModalVisible(false); + onCancel?.(); + tableRef?.current?.refetch(); + return; + } + sessionStorage.removeItem('appType'); + await createAppFormData({ workspace }, fileData, formData); + notify.success(t('UPLOAD_SUCCESSFUL')); + setModalVisible(false); + onCancel?.(); + tableRef?.current?.refetch(); + } function renderModal() { if (modalType === 'create_helm') { return ( @@ -111,6 +134,7 @@ export function CreateApp({ visible={modalVisible} onCancel={() => setModalVisible(false)} onOk={handleCreate} + onOkFormData={!onOk || onOkFormData ? handleCreateFormData : undefined} /> ); } diff --git a/packages/shared/src/components/Apps/PackageUpload/index.tsx b/packages/shared/src/components/Apps/PackageUpload/index.tsx index e9eead436ed..cd9e9d756ce 100644 --- a/packages/shared/src/components/Apps/PackageUpload/index.tsx +++ b/packages/shared/src/components/Apps/PackageUpload/index.tsx @@ -29,7 +29,11 @@ type Props = { appName?: string; type?: string; onOk?: (data: PackageInfo) => void; + onOkFormData?: (data: any, formData: FormData) => void; onCheckStatusChange?: (status: any) => void; + onCheckStatusChangeFormData?: (status: any) => void; + initCheckedStatus?: () => void; + initCheckedStatusFormData?: () => void; hasPackage?: boolean; canCreate?: boolean; canEdit?: boolean; @@ -56,7 +60,11 @@ export function PackageUpload({ versionID, type = 'CREATE_APP', onOk, + onOkFormData, onCheckStatusChange, + onCheckStatusChangeFormData, + initCheckedStatus, + initCheckedStatusFormData, updateTime = '', packageName, className, @@ -64,7 +72,16 @@ export function PackageUpload({ appType, disabledUpload, }: Props): JSX.Element { - const { checkFile, handleFileByBase64Str, validatePackage, uploadPackage } = fileStore; + const { + checkFile, + checkFileFormData, + handleFileByBase64Str, + handleFileFormData, + validatePackage, + validatePackageFormData, + uploadPackage, + uploadPackageFormData, + } = fileStore; const state: Record = {}; const { workspace = ws } = useParams(); const [errorInfo, setErrorInfo] = useState(''); @@ -108,16 +125,58 @@ export function PackageUpload({ } } + async function packageValidatorFormData(formData: FormData): Promise { + const result = await validatePackageFormData({ + formData, + appName, + workspace, + }); + const status = result.error ? 'error' : 'success'; + setMissFile(result.missFile); + setErrorInfo(result.error); + setUploadStatus(status); + onCheckStatusChangeFormData?.({ status, formData, ...result }); + + // todo when using formdata + // todo type === MODIFY_VERSION and an external operation function is passed,use onOkFormData + if (type === 'MODIFY_VERSION' && status === 'success') { + const uploadData = { + versionID: versionID || result.versionID, + appName: appName, + workspace, + name: result.versionName, + // package: result.base64Str || base64Str, + }; + uploadPackageFormData(type, uploadData, formData, (data: any, form: FormData) => { + onOkFormData?.(data, form); + }); + setUploadStatus('init'); + } + } + async function checkPackage(file: File): Promise { + initCheckedStatusFormData?.(); + initCheckedStatus?.(); + setUploadStatus('uploading'); setFileName(file.name); - const result = checkFile?.(file, 'package'); - if (!result) { - return handleFileByBase64Str?.(file, packageValidator); + if (onCheckStatusChangeFormData || onOkFormData) { + const result = checkFileFormData?.(file, 'package'); + if (!result) { + return handleFileFormData?.(file, packageValidatorFormData); + } + + setErrorInfo(result); + } else { + const result = checkFile?.(file, 'package'); + if (!result) { + return handleFileByBase64Str?.(file, packageValidator); + } + + setErrorInfo(result); } - setErrorInfo(result); setUploadStatus('error'); return Promise.reject(); } diff --git a/packages/shared/src/stores/openpitrix/app.ts b/packages/shared/src/stores/openpitrix/app.ts index 4c9da68cc09..2061dcfeaec 100644 --- a/packages/shared/src/stores/openpitrix/app.ts +++ b/packages/shared/src/stores/openpitrix/app.ts @@ -14,6 +14,7 @@ import { request, addCreateAppUrl, getCreateAppParams, + getCreateAppParamsFormData, } from '../../utils'; import { BaseUrlParams, defaultUrl, getBaseUrl, useBaseList } from './base'; @@ -94,6 +95,19 @@ export function createApp({ workspace }: BaseUrlParams, data: any): Promise return request.post(addCreateAppUrl(url), getCreateAppParams(data)); } +export function createAppFormData({ workspace }: BaseUrlParams, data: any, formData: FormData) { + const url = getBaseUrl({ workspace }, resourceName); + const requestData = getCreateAppParamsFormData(data); + const jsonData = JSON.stringify(requestData); + formData.append('jsonData', jsonData); + + return request.post(addCreateAppUrl(url), formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +} + export function EditApp({ workspace, appName }: BaseUrlParams, params: any): Promise { const url = getBaseUrl({ workspace, appName }, resourceName); diff --git a/packages/shared/src/stores/openpitrix/files.ts b/packages/shared/src/stores/openpitrix/files.ts index 87d0e177a12..3b235c703e7 100644 --- a/packages/shared/src/stores/openpitrix/files.ts +++ b/packages/shared/src/stores/openpitrix/files.ts @@ -7,7 +7,13 @@ import { isArray, isString, keys } from 'lodash'; import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; import { Constants } from '../../constants'; -import { downloadFileFromBase64, request, safeAtob, getCreateAppParams } from '../../utils'; +import { + downloadFileFromBase64, + request, + safeAtob, + getCreateAppParams, + getCreateAppParamsFormData, +} from '../../utils'; import { BaseUrlParams, getBaseUrl } from './base'; @@ -48,6 +54,12 @@ export function handleFileByBase64Str(file: File, callBack: (base64Str: string) }); } +export function handleFileFormData(file: File, callBack: (formData: FormData) => void) { + const formData = new FormData(); + formData.append('file', file); + callBack(formData); +} + type ValidateResponse = { appName: string; versionName: string; @@ -101,6 +113,55 @@ export async function validatePackage({ return response; } +export async function validatePackageFormData({ + formData, + appName, + versionName, + workspace, +}: { + formData: FormData; + appName?: string; + name?: string; + versionName?: string; + workspace?: string; +}): Promise> { + const data: Record = getCreateAppParamsFormData({ + appType: 'helm', + appName, + versionName, + workspace, + }); + + const jsonData = JSON.stringify(data); + formData.append('jsonData', jsonData); + + const result: ValidateResponse | undefined = await request.post( + `${getBaseUrl( + { workspace, appName: appName, name: appName ? 'versions' : 'apps' }, + resourceName, + )}?validate=true`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }, + ); + + const response: Omit = result ? { ...result } : {}; + + // if (result?.versionName) { + // response.base64Str = base64Str; + // } + + if (result?.errorDetails) { + response.error = 'MISS_FILE_NOTE'; + response.missFile = keys(result.errorDetails); + } + + return response; +} + export type UploadPackageParams = { base64Str?: string; version_type?: string; @@ -113,6 +174,8 @@ export type UploadPackageParams = { package?: string; }; +export type UploadPackageParamsFormData = Omit, 'package'>; + /** * * @param type CREATE_APP, CREATE_VERSION, MODIFY_VERSION @@ -126,6 +189,15 @@ export async function uploadPackage( return callFun?.(params); } +export async function uploadPackageFormData( + type: string, + params: UploadPackageParamsFormData, + formData: FormData, + callFun?: (data: UploadPackageParamsFormData, form: FormData) => any, +) { + return callFun?.(params, formData); +} + export type FileError = string; export function checkFile(uploadFile: File, type: string): FileError { @@ -142,6 +214,16 @@ export function checkFile(uploadFile: File, type: string): FileError { return ''; } +export function checkFileFormData(uploadFile: File, type: string): FileError { + const rule = Constants.UPLOAD_CHECK_RULES[type]; + + if (!rule.format.test(uploadFile.name.toLocaleLowerCase())) { + return `FILE_FORMAT_${type.toLocaleUpperCase()}`; + } + + return ''; +} + export function validateImageSize(base64Str: string): Promise { const image = new Image(); image.src = base64Str; diff --git a/packages/shared/src/stores/openpitrix/version.ts b/packages/shared/src/stores/openpitrix/version.ts index 7af0083715a..84f10df5b19 100644 --- a/packages/shared/src/stores/openpitrix/version.ts +++ b/packages/shared/src/stores/openpitrix/version.ts @@ -93,6 +93,23 @@ export async function updateVersion( return request.post(url, data); } +export async function updateVersionFormData( + { appName, versionID, workspace }: BaseUrlParams, + data: T, + formData: FormData, +): Promise { + const url = getBaseUrl({ appName, versionID, workspace }, resourceName); + + const jsonData = JSON.stringify(data); + formData.append('jsonData', jsonData); + + return request.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +} + export function handleVersion({ appName, versionID }: BaseUrlParams, data: any): Promise { const url = getBaseUrl({ appName, versionID, name: 'action' }, resourceName); diff --git a/packages/shared/src/utils/apps.ts b/packages/shared/src/utils/apps.ts index 5cef9cf6ce3..0be3e4d84e4 100644 --- a/packages/shared/src/utils/apps.ts +++ b/packages/shared/src/utils/apps.ts @@ -66,6 +66,19 @@ export function getCreateAppParams(data: Record) { }; } +export function getCreateAppParamsFormData(data: Record) { + const { appName, versionName, workspace, ...otherData } = data; + return { + repoName: 'upload', + originalName: appName, + categoryName: getUncategorizedApp, + appName: appName || undefined, + versionName, + workspace, + ...otherData, + }; +} + export function getCategoryDisplayName(name: string): string { if (!name) return '-'; if (name === getUncategorizedApp) {