diff --git a/frontend/desktop/public/icons/inviter.svg b/frontend/desktop/public/icons/inviter.svg new file mode 100644 index 00000000000..f7c0fee038e --- /dev/null +++ b/frontend/desktop/public/icons/inviter.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/desktop/public/locales/en/common.json b/frontend/desktop/public/locales/en/common.json index 16cfb810c70..065f1ce0cd9 100644 --- a/frontend/desktop/public/locales/en/common.json +++ b/frontend/desktop/public/locales/en/common.json @@ -135,6 +135,7 @@ "invitation_reminder": "Invitation reminder", "invite_member": "Invite Member", "invite_members_to_workspace_": "Invite members to workspace {{workspace}}", + "inviter_id_tips": "Invitation code (optional)", "irreversibleactiontips": "This action is irreversible, please proceed with caution.", "jump_over": "Jump Over", "laf_on_sealos": "Laf on Sealos", @@ -302,4 +303,4 @@ "you_can_view_fees_through_the_fee_center": "You can view fees through the fee center", "you_have_not_purchased_the_license": "You have not purchased the License", "yuan": "Yuan" -} +} \ No newline at end of file diff --git a/frontend/desktop/public/locales/zh/common.json b/frontend/desktop/public/locales/zh/common.json index 1e56f9d94f3..e901112dbb2 100644 --- a/frontend/desktop/public/locales/zh/common.json +++ b/frontend/desktop/public/locales/zh/common.json @@ -130,6 +130,7 @@ "invitation_reminder": "受邀提醒", "invite_member": "邀请成员", "invite_members_to_workspace": "邀请成员至工作空间 {{workspace}}", + "inviter_id_tips": "邀请码(选填)", "irreversibleactiontips": "此操作不可逆转,请谨慎操作", "jump_over": "跳过", "language": "语言", @@ -293,4 +294,4 @@ "you_can_view_fees_through_the_fee_center": "您可通过费用中心查看费用", "you_have_not_purchased_the_license": "您还没有购买 License", "yuan": "元" -} +} \ No newline at end of file diff --git a/frontend/desktop/src/components/signin/auth/useSms.tsx b/frontend/desktop/src/components/signin/auth/useSms.tsx index 81a4c27fa96..aa63cc56ef6 100644 --- a/frontend/desktop/src/components/signin/auth/useSms.tsx +++ b/frontend/desktop/src/components/signin/auth/useSms.tsx @@ -34,6 +34,7 @@ export default function useSms({ const { register, handleSubmit, trigger, getValues, watch } = useForm<{ phoneNumber: string; verifyCode: string; + inviterId?: string; }>(); const login = async () => { @@ -54,7 +55,7 @@ export default function useSms({ { id: data.phoneNumber, code: data.verifyCode, - inviterId: getInviterId(), + inviterId: data?.inviterId || getInviterId(), semData: getUserSemData(), bdVid: getBaiduId() } @@ -225,6 +226,36 @@ export default function useSms({ )} + {!!authConfig?.invite.enabled && ( + + + inviter + + + + )} ); }; diff --git a/frontend/providers/template/src/pages/api/getTemplateSource.ts b/frontend/providers/template/src/pages/api/getTemplateSource.ts index 8343a8187c8..fa856619074 100644 --- a/frontend/providers/template/src/pages/api/getTemplateSource.ts +++ b/frontend/providers/template/src/pages/api/getTemplateSource.ts @@ -93,7 +93,18 @@ export async function GetTemplateByName({ if (cdnUrl) { templateYaml.spec.readme = replaceRawWithCDN(templateYaml.spec.readme, cdnUrl); templateYaml.spec.icon = replaceRawWithCDN(templateYaml.spec.icon, cdnUrl); + if (templateYaml?.spec?.i18n) { + Object.keys(templateYaml?.spec?.i18n || {}).forEach((lang) => { + const i18nLang = templateYaml?.spec?.i18n?.[lang]; + ['readme', 'icon'].forEach((field) => { + if (i18nLang?.[field]) { + i18nLang[field] = replaceRawWithCDN(i18nLang[field], cdnUrl); + } + }); + }); + } } + templateYaml = parseTemplateVariable(templateYaml, TemplateEnvs); const dataSource = getTemplateDataSource(templateYaml); diff --git a/frontend/providers/template/src/pages/deploy/index.tsx b/frontend/providers/template/src/pages/deploy/index.tsx index b2e27c25bce..2c293d034dc 100644 --- a/frontend/providers/template/src/pages/deploy/index.tsx +++ b/frontend/providers/template/src/pages/deploy/index.tsx @@ -405,54 +405,91 @@ export default function EditApp({ ); } +async function fetchReadmeContent(url: string): Promise { + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + const response = await fetch(url, { + headers: { + Accept: 'text/markdown,text/plain,*/*', + 'Content-Type': 'text/markdown; charset=UTF-8', + 'Cache-Control': 'no-cache', + Pragma: 'no-cache', + 'User-Agent': 'Mozilla/5.0' + }, + credentials: 'omit' + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.text(); + } catch (err) { + retryCount++; + + if (retryCount === maxRetries) { + return ''; + } + await new Promise((resolve) => setTimeout(resolve, retryCount * 1000)); + } + } + return ''; +} + export async function getServerSideProps(content: any) { - const brandName = process.env.NEXT_PUBLIC_BRAND_NAME; + const brandName = process.env.NEXT_PUBLIC_BRAND_NAME || 'Sealos'; const local = content?.req?.cookies?.NEXT_LOCALE || compareFirstLanguages(content?.req?.headers?.['accept-language'] || 'zh'); + const appName = content?.query?.templateName || ''; + const baseurl = `http://${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}`; content?.res.setHeader( 'Set-Cookie', `NEXT_LOCALE=${local}; Max-Age=2592000; Secure; SameSite=None` ); - const appName = content?.query?.templateName || ''; - - const baseurl = `http://${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}`; - - let metaData = { - title: `${appName}部署和安装教程 - Sealos`, - keywords: '', - description: '' - }; - let readmeContent = ''; - let readUrl = ''; - try { - const templateSource: { data: TemplateSourceType } = await ( + const { data: templateSource } = await ( await fetch(`${baseurl}/api/getTemplateSource?templateName=${appName}`) ).json(); - const templateDetail = templateSource?.data.templateYaml; - metaData = { - title: templateDetail?.spec?.title, - keywords: templateDetail?.spec?.description, - description: templateDetail?.spec?.description + const templateDetail = templateSource?.templateYaml; + const metaData = { + title: templateDetail?.spec?.title || '', + keywords: templateDetail?.spec?.description || '', + description: templateDetail?.spec?.description || '' }; - const readme = templateDetail?.spec?.i18n?.[local]?.readme ?? templateDetail?.spec?.readme; - readUrl = readme; - readmeContent = await (await fetch(readme)).text(); - } catch (error) {} - - return { - props: { - appName, - metaData, - brandName, - readmeContent, - readUrl, - ...(await serviceSideProps(content)) - } - }; + const readUrl = + templateDetail?.spec?.i18n?.[local]?.readme || templateDetail?.spec?.readme || ''; + const readmeContent = readUrl ? await fetchReadmeContent(readUrl) : ''; + + return { + props: { + appName, + metaData, + brandName, + readmeContent, + readUrl, + ...(await serviceSideProps(content)) + } + }; + } catch (error) { + console.log('Error in getServerSideProps:', error); + + return { + props: { + appName, + metaData: { title: appName, keywords: '', description: '' }, + brandName, + readmeContent: '', + readUrl: '', + ...(await serviceSideProps(content)) + } + }; + } } diff --git a/frontend/providers/template/src/pages/index.tsx b/frontend/providers/template/src/pages/index.tsx index 7484088f1d8..00c1f5b80c4 100644 --- a/frontend/providers/template/src/pages/index.tsx +++ b/frontend/providers/template/src/pages/index.tsx @@ -276,7 +276,7 @@ export default function AppList({ // https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props#context-parameter export async function getServerSideProps(content: any) { const forcedLanguage = process.env.FORCED_LANGUAGE; - const brandName = process.env.NEXT_PUBLIC_BRAND_NAME; + const brandName = process.env.NEXT_PUBLIC_BRAND_NAME || 'Sealos'; const local = forcedLanguage || content?.req?.cookies?.NEXT_LOCALE ||