diff --git a/web/public/locales/en/translation.json b/web/public/locales/en/translation.json index f52991466e..c61b2be6f0 100644 --- a/web/public/locales/en/translation.json +++ b/web/public/locales/en/translation.json @@ -279,7 +279,7 @@ "Inaccessible": "Inaccessible", "isResolved": "Effective", "cnameTip": "Add the \"CNAME\" record of the domain to resolve to {{cnameDomain}}, After the resolution takes effect, the custom domain name can be bound.", - "editHostTip": "oss-bucket needs to be in read mode to enable website hosting", + "editHostTip": "oss-bucket needs to be in readOnly mode to enable website hosting", "CreateWebHosting": "Bind custom domain name", "CustomDomain": "Custom Domain", "CurrentDomain": "Current domain: ", @@ -292,7 +292,12 @@ "CustomApplicationDomain": "Custom Application Domain", "BucketNameisRequired": "Bucket name is required", "Fail": "upload failed", - "UploadFailTip": "{{number}} files upload failed" + "UploadFailTip": "{{number}} files upload failed", + "StartAppTips": " Please start the application first", + "SuccessfullyHosted": "Successfully hosted", + "SuccessfullyHostedTips": "HTTP is active, HTTPS activation may take some time. To check the hosting status, use HTTP for now.", + "BucketTips": "Newly created buckets or buckets after app restart need to wait for 15 ~ 30 s to take effect", + "DomainNotResolved": "Try using ping or nslookup to check if domain name resolution is effective" }, "TriggerPanel": { "AddTrigger": "Add Trigger", @@ -313,7 +318,8 @@ "EmptyTriggerTip": "You have not created a trigger,", "Every8AM": "8 am every day", "EveryHour": "per hour", - "EveryFiveMinutes": "every five minutes" + "EveryFiveMinutes": "every five minutes", + "createFail": "Trigger limit reached. Upgrade app configuration." }, "StatusBar": { "CurrentApplication": "Current App" @@ -393,7 +399,8 @@ "LoginOrRegister": "Login / Register", "BindGitHub": "Bind GitHub", "BindSuccess": "Bind Successfully", - "GitHubLogin": "GitHub Login" + "GitHubLogin": "GitHub Login", + "PleaseBindUser": "Please bind your user" }, "Time": "Time", "CreateTime": "Created", diff --git a/web/public/locales/zh-CN/translation.json b/web/public/locales/zh-CN/translation.json index 4b483f3b6a..07e88cf19f 100644 --- a/web/public/locales/zh-CN/translation.json +++ b/web/public/locales/zh-CN/translation.json @@ -292,7 +292,12 @@ "CustomApplicationDomain": "自定义应用域名", "BucketNameisRequired": "请输入 Bucket 名称", "Fail": "上传失败", - "UploadFailTip": "{{number}} 个文件上传失败" + "UploadFailTip": "{{number}} 个文件上传失败", + "StartAppTips": "请先启动应用", + "SuccessfullyHosted": "托管成功", + "SuccessfullyHostedTips": "HTTP已生效,HTTPS需要一些时间生效。如需查看托管状态,请暂时使用HTTP访问。", + "BucketTips": "新创建的 bucket,或应用重启后的 bucket ,需要等待 15 ~ 30s 才能生效", + "DomainNotResolved": "尝试使用 ping 或 nslookup 来查看域名解析是否生效" }, "TriggerPanel": { "AddTrigger": "新建触发器", @@ -313,7 +318,8 @@ "EmptyTriggerTip": "您还没有创建触发器,", "Every8AM": "每天上午8点", "EveryHour": "每小时", - "EveryFiveMinutes": "每五分钟" + "EveryFiveMinutes": "每五分钟", + "createFail": "触发器数量已达上限,请升级应用配置" }, "StatusBar": { "CurrentApplication": "当前应用" @@ -393,7 +399,8 @@ "LoginOrRegister": "登录 / 注册", "BindGitHub": "绑定 GitHub", "BindSuccess": "绑定成功", - "GitHubLogin": "GitHub 登录" + "GitHubLogin": "GitHub 登录", + "PleaseBindUser": "请绑定对应用户" }, "Time": "时间", "CreateTime": "创建时间", diff --git a/web/public/locales/zh/translation.json b/web/public/locales/zh/translation.json index f6a50536d0..71840ae8be 100644 --- a/web/public/locales/zh/translation.json +++ b/web/public/locales/zh/translation.json @@ -292,7 +292,12 @@ "CustomApplicationDomain": "自定义应用域名", "BucketNameisRequired": "请输入 Bucket 名称", "Fail": "上传失败", - "UploadFailTip": "{{number}} 个文件上传失败" + "UploadFailTip": "{{number}} 个文件上传失败", + "StartAppTips": "请先启动应用", + "SuccessfullyHosted": "托管成功", + "SuccessfullyHostedTips": "HTTP已生效,HTTPS需要一些时间生效。如需查看托管状态,请暂时使用HTTP访问。", + "BucketTips": "新创建的 bucket,或应用重启后的 bucket ,需要等待 15 ~ 30s 才能生效", + "DomainNotResolved": "尝试使用 ping 或 nslookup 来查看域名解析是否生效" }, "TriggerPanel": { "AddTrigger": "新建触发器", @@ -313,7 +318,8 @@ "EmptyTriggerTip": "您还没有创建触发器,", "Every8AM": "每天上午8点", "EveryHour": "每小时", - "EveryFiveMinutes": "每五分钟" + "EveryFiveMinutes": "每五分钟", + "createFail": "触发器数量已达上限,请升级应用配置" }, "StatusBar": { "CurrentApplication": "当前应用" @@ -393,7 +399,8 @@ "LoginOrRegister": "登录 / 注册", "BindGitHub": "绑定 GitHub", "BindSuccess": "绑定成功", - "GitHubLogin": "GitHub 登录" + "GitHubLogin": "GitHub 登录", + "PleaseBindUser": "请绑定对应用户" }, "Time": "时间", "CreateTime": "创建时间", diff --git a/web/src/components/ChargeButton/index.tsx b/web/src/components/ChargeButton/index.tsx index b08e1be2b7..56dc3b192b 100644 --- a/web/src/components/ChargeButton/index.tsx +++ b/web/src/components/ChargeButton/index.tsx @@ -112,10 +112,11 @@ export default function ChargeButton(props: { amount?: number; children: React.R )} @@ -336,21 +366,18 @@ export default function DebugPanel(props: { containerRef: any }) { pageId="functionPage" panelId="RunningPanel" /> -
+
{isLoading ? ( -
-
- -
-
- ) : null} - {runningResData !== undefined ? ( +
+ +
+ ) : runningResData !== undefined ? ( ) : ( -
+
{t("FunctionPanel.EmptyDebugTip")}
)} diff --git a/web/src/pages/app/functions/mods/FunctionPanel/index.tsx b/web/src/pages/app/functions/mods/FunctionPanel/index.tsx index 325eac45d2..39ed510756 100644 --- a/web/src/pages/app/functions/mods/FunctionPanel/index.tsx +++ b/web/src/pages/app/functions/mods/FunctionPanel/index.tsx @@ -335,7 +335,7 @@ export default function FunctionList() { } return ( - + diff --git a/web/src/pages/app/functions/mods/TriggerModal/AddTriggerModal/index.tsx b/web/src/pages/app/functions/mods/TriggerModal/AddTriggerModal/index.tsx index 45d462c423..df58abe760 100644 --- a/web/src/pages/app/functions/mods/TriggerModal/AddTriggerModal/index.tsx +++ b/web/src/pages/app/functions/mods/TriggerModal/AddTriggerModal/index.tsx @@ -25,6 +25,7 @@ import { useCreateTriggerMutation } from "../service"; import {} from "../service"; import { useFunctionListQuery } from "@/pages/app/functions/service"; +import useGlobalStore from "@/pages/globalStore"; const CRON_TEMPLATE = [ { @@ -58,13 +59,19 @@ const AddTriggerModal = (props: { children: React.ReactElement; targetFunc?: str const { targetFunc, children } = props; const { isOpen, onOpen, onClose } = useDisclosure(); + const { showSuccess, showError } = useGlobalStore(); const functionListQuery = useFunctionListQuery({ onSuccess: (data: any) => {} }); const addTriggerMutation = useCreateTriggerMutation(() => { onClose(); }); - const onSubmit = (data: any) => { - addTriggerMutation.mutate(data); - onClose(); + const onSubmit = async (data: any) => { + const res = await addTriggerMutation.mutateAsync(data); + if (!res.error) { + showSuccess(t("CreateSuccess")); + onClose(); + } else if (res.error === "Trigger count limit exceeded") { + showError(t("TriggerPanel.createFail")); + } }; const initFormData = () => { reset({ diff --git a/web/src/pages/app/functions/mods/TriggerModal/index.tsx b/web/src/pages/app/functions/mods/TriggerModal/index.tsx index c1b45b2d98..fd28cd01b0 100644 --- a/web/src/pages/app/functions/mods/TriggerModal/index.tsx +++ b/web/src/pages/app/functions/mods/TriggerModal/index.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import React from "react"; -import { AddIcon, DeleteIcon, Search2Icon } from "@chakra-ui/icons"; +import { AddIcon, Search2Icon } from "@chakra-ui/icons"; import { Button, Center, @@ -27,6 +27,7 @@ import { } from "@chakra-ui/react"; import { t } from "i18next"; +import { RecycleDeleteIcon } from "@/components/CommonIcon"; import ConfirmButton from "@/components/ConfirmButton"; import EmptyBox from "@/components/EmptyBox"; import IconWrap from "@/components/IconWrap"; @@ -133,7 +134,7 @@ export default function TriggerModal(props: { children: React.ReactElement }) { bodyText={t("TriggerPanel.DeleteConfirm")} > - + diff --git a/web/src/pages/app/functions/mods/TriggerModal/service.ts b/web/src/pages/app/functions/mods/TriggerModal/service.ts index d27790b133..c79519e814 100644 --- a/web/src/pages/app/functions/mods/TriggerModal/service.ts +++ b/web/src/pages/app/functions/mods/TriggerModal/service.ts @@ -24,7 +24,6 @@ export const useTriggerListQuery = (onSuccess: (data: any) => void) => { }; export const useCreateTriggerMutation = (onSuccess: (data: any) => void) => { - const globalStore = useGlobalStore(); const queryClient = useQueryClient(); return useMutation( (values: any) => { @@ -32,11 +31,8 @@ export const useCreateTriggerMutation = (onSuccess: (data: any) => void) => { }, { onSuccess: async (data) => { - if (data.error) { - globalStore.showError(data.error); - } else { + if (!data.error) { await queryClient.invalidateQueries(queryKeys.useTriggerQuery); - onSuccess && onSuccess(data); } }, }, diff --git a/web/src/pages/app/mods/StatusBar/LogsModal/index.tsx b/web/src/pages/app/mods/StatusBar/LogsModal/index.tsx index f52531a9ff..589fea0403 100644 --- a/web/src/pages/app/mods/StatusBar/LogsModal/index.tsx +++ b/web/src/pages/app/mods/StatusBar/LogsModal/index.tsx @@ -186,7 +186,7 @@ export default function LogsModal(props: { children: React.ReactElement }) { style={{ height: "98%", fontSize: settingStore.commonSettings.fontSize - 1 }} >

{entry.value}

-

{(pieData[index]?.value).toFixed(3)} MB

+

{formatSize(pieData[index]?.value * 1024 * 1024)}

))} diff --git a/web/src/pages/app/setting/SysSetting/CustomDomain/index.tsx b/web/src/pages/app/setting/SysSetting/CustomDomain/index.tsx index 2276e76814..ab91c83227 100644 --- a/web/src/pages/app/setting/SysSetting/CustomDomain/index.tsx +++ b/web/src/pages/app/setting/SysSetting/CustomDomain/index.tsx @@ -19,7 +19,7 @@ import { useBindDomainMutation, useRemoveApplicationMutation } from "./service"; import useGlobalStore from "@/pages/globalStore"; export default function CustomDomain() { - const { currentApp, showSuccess, setCurrentApp } = useGlobalStore(); + const { currentApp, showSuccess, setCurrentApp, showWarning } = useGlobalStore(); const { register, handleSubmit } = useForm<{ domain: string }>({ defaultValues: { domain: currentApp?.domain?.customDomain || "" }, @@ -94,6 +94,8 @@ export default function CustomDomain() { if (res.data) { setCurrentApp({ ...currentApp, domain: res.data }); showSuccess(t("StoragePanel.DomainUpdateSuccess")); + } else if (res.error === "domain not resolved") { + showWarning(t("StoragePanel.DomainNotResolved")); } })} > diff --git a/web/src/pages/app/setting/UserSetting/BillingDetails/index.tsx b/web/src/pages/app/setting/UserSetting/BillingDetails/index.tsx index bee5b8cf05..444d7722f6 100644 --- a/web/src/pages/app/setting/UserSetting/BillingDetails/index.tsx +++ b/web/src/pages/app/setting/UserSetting/BillingDetails/index.tsx @@ -94,7 +94,11 @@ export default function BillingDetails() { - AppId + + AppId + @@ -135,7 +139,12 @@ export default function BillingDetails() { - + {t("Duration")} @@ -165,28 +174,53 @@ export default function BillingDetails() { - + {t("SpecItem.cpu")} - + {t("SpecItem.memory")} - + {t("Spec.Database")} - + {t("Spec.Storage")} - + {t("State")} @@ -219,7 +253,12 @@ export default function BillingDetails() { - + {t("TotalAmount")} @@ -239,11 +278,51 @@ export default function BillingDetails() { > {item.appid} - {formatDate(item.endAt)} - {item.detail?.cpu?.amount} - {item.detail?.memory?.amount} - {item.detail?.databaseCapacity?.amount} - {item.detail?.storageCapacity?.amount} + + {formatDate(item.endAt)} + + + {item.detail?.cpu?.amount} + + + {item.detail?.memory?.amount} + + + {item.detail?.databaseCapacity?.amount} + + + {item.detail?.storageCapacity?.amount} + - {t("OrderNumber")} + + {t("OrderNumber")} + - + {t("Duration")} @@ -119,7 +126,12 @@ export default function RechargeHistory() { - + {t("State")} @@ -152,17 +164,32 @@ export default function RechargeHistory() { - + {t("Recharge amount")} - + {t("Bonus amount")} - + {t("Total payment amount")} @@ -180,10 +207,18 @@ export default function RechargeHistory() { > {item._id} - {formatDate(item.createdAt)} + + {formatDate(item.createdAt)} + diff --git a/web/src/pages/app/setting/index.tsx b/web/src/pages/app/setting/index.tsx index 97b63cbf24..910ed69e87 100644 --- a/web/src/pages/app/setting/index.tsx +++ b/web/src/pages/app/setting/index.tsx @@ -117,10 +117,10 @@ const SettingModal = (props: { }} > - {tab.icon} - {tab.name} + {tab.icon} +
{tab.name}
{tab?.status && ( - + {tab.status} )} diff --git a/web/src/pages/app/storages/index.tsx b/web/src/pages/app/storages/index.tsx index ab7c8d8819..b03548a092 100644 --- a/web/src/pages/app/storages/index.tsx +++ b/web/src/pages/app/storages/index.tsx @@ -1,8 +1,9 @@ /**************************** * cloud functions storage page ***************************/ -import { useRef } from "react"; +import { useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; +import { Center } from "@chakra-ui/react"; import Content from "@/components/Content"; import EmptyBox from "@/components/EmptyBox"; @@ -19,34 +20,57 @@ import StorageListPanel from "./mods/StorageListPanel"; import useStorageStore from "./store"; import useCustomSettingStore from "@/pages/customSetting"; +import useGlobalStore from "@/pages/globalStore"; export default function StoragePage() { const { t } = useTranslation(); const containerRef = useRef(null); const settingStore = useCustomSettingStore(); const { currentStorage } = useStorageStore(); + + const { currentApp, showWarning } = useGlobalStore((state) => state); + + useEffect(() => { + if (currentApp.state !== "Running") { + showWarning(t("StoragePanel.StartAppTips")); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( - + - {currentStorage === undefined ? ( + {currentApp.state === "Running" ? ( + currentStorage === undefined ? ( + + +
+ {t("StoragePanel.EmptyText")} + + + {t("CreateNow")} + + +
+
+
+ ) : ( + + ) + ) : ( - -
- {t("StoragePanel.EmptyText")} - - - {t("CreateNow")} - - -
-
+
+ {t("StoragePanel.StartAppTips")} +
- ) : ( - )}
diff --git a/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx b/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx index 097eee3253..6add1fed8b 100644 --- a/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx +++ b/web/src/pages/app/storages/mods/CreateBucketModal/index.tsx @@ -1,5 +1,6 @@ import React from "react"; import { useForm } from "react-hook-form"; +import { InfoOutlineIcon } from "@chakra-ui/icons"; import { Button, FormControl, @@ -145,6 +146,11 @@ function CreateBucketModal(props: { storage?: TBucket; children: React.ReactElem + + + + {t("StoragePanel.BucketTips")} + diff --git a/web/src/pages/app/storages/mods/CreateWebsiteModal/index.tsx b/web/src/pages/app/storages/mods/CreateWebsiteModal/index.tsx index 8f3281277b..d6f02e2402 100644 --- a/web/src/pages/app/storages/mods/CreateWebsiteModal/index.tsx +++ b/web/src/pages/app/storages/mods/CreateWebsiteModal/index.tsx @@ -37,9 +37,12 @@ import useStorageStore from "../../store"; import SiteStatus from "./SiteStatus"; +import useGlobalStore from "@/pages/globalStore"; + function CreateWebsiteModal() { const { isOpen, onOpen, onClose } = useDisclosure(); const { currentStorage, getOrigin } = useStorageStore(); + const { showSuccess, showInfo, showWarning } = useGlobalStore(); const { register, setFocus, handleSubmit, reset } = useForm<{ domain: string }>(); const { t } = useTranslation(); const createWebsiteMutation = useWebsiteCreateMutation(); @@ -113,7 +116,7 @@ function CreateWebsiteModal() { variant={"secondary"} style={{ borderRadius: "1rem" }} disabled={currentStorage === undefined} - onClick={() => { + onClick={async () => { if (!(currentStorage?.policy === BUCKET_POLICY_TYPE.readonly)) { toast({ status: "warning", @@ -122,10 +125,14 @@ function CreateWebsiteModal() { }); return; } - createWebsiteMutation.mutateAsync({ + const res = await createWebsiteMutation.mutateAsync({ bucketName: currentStorage && currentStorage.name, state: BUCKET_STATUS.Active, }); + if (!res.error) { + showSuccess(t("StoragePanel.SuccessfullyHosted")); + showInfo(t("StoragePanel.SuccessfullyHostedTips"), 5000, true); + } }} > {t("StoragePanel.websiteHost")} @@ -175,12 +182,14 @@ function CreateWebsiteModal() { type="submit" isLoading={updateWebsiteMutation.isLoading} onClick={handleSubmit(async (value) => { - const res: any = await updateWebsiteMutation.mutateAsync({ + const res = await updateWebsiteMutation.mutateAsync({ id: currentStorage?.websiteHosting._id, domain: value.domain, }); if (res.data) { onClose(); + } else if (res.error === "domain not resolved") { + showWarning(t("StoragePanel.DomainNotResolved")); } })} > diff --git a/web/src/pages/app/storages/mods/StorageListPanel/index.tsx b/web/src/pages/app/storages/mods/StorageListPanel/index.tsx index 5f7b742b1b..5dcea89596 100644 --- a/web/src/pages/app/storages/mods/StorageListPanel/index.tsx +++ b/web/src/pages/app/storages/mods/StorageListPanel/index.tsx @@ -115,9 +115,9 @@ export default function StorageListPanel() { }} > <> -
+
- {storage.name} +
{storage.name}
diff --git a/web/src/pages/auth/signin/index.tsx b/web/src/pages/auth/signin/index.tsx index 3d1749454d..c2fd394f0f 100644 --- a/web/src/pages/auth/signin/index.tsx +++ b/web/src/pages/auth/signin/index.tsx @@ -11,6 +11,7 @@ import LoginByPasswordPanel from "./mods/LoginByPasswordPanel"; import LoginByPhonePanel from "./mods/LoginByPhonePanel"; import useAuthStore from "@/pages/auth/store"; +import useGlobalStore from "@/pages/globalStore"; type providersTypes = "user-password" | "phone" | "github" | "wechat"; @@ -22,11 +23,18 @@ export default function SignIn() { const [currentProvider, setCurrentProvider] = useState(); const isBindGithub = !!sessionStorage.getItem("githubToken"); + const { showInfo } = useGlobalStore(); useEffect(() => { setCurrentProvider(defaultProvider.name); }, [defaultProvider]); + useEffect(() => { + if (isBindGithub) { + showInfo(t("AuthPanel.PleaseBindUser"), 5000, true); + } + }, [isBindGithub, showInfo]); + return (
()( SideBar: { id: "SideBar", style: { - width: 218, - minWidth: 0, + width: 240, + minWidth: 220, }, }, @@ -71,7 +71,7 @@ const useCustomSettingStore = create()( id: "RightPanel", style: { width: 270, - minWidth: 0, + minWidth: 232, }, }, @@ -113,7 +113,7 @@ const useCustomSettingStore = create()( id: "SideBar", style: { width: 300, - minWidth: 0, + minWidth: 206, }, }, CollectionPanel: { @@ -142,7 +142,7 @@ const useCustomSettingStore = create()( id: "SideBar", style: { width: 300, - minWidth: 0, + minWidth: 230, maxWidth: 800, }, }, @@ -184,7 +184,7 @@ const useCustomSettingStore = create()( { name: "laf_custom_setting", - version: 2, + version: 3, }, ), ), diff --git a/web/src/pages/globalStore.ts b/web/src/pages/globalStore.ts index f29c122798..421dca9e6c 100644 --- a/web/src/pages/globalStore.ts +++ b/web/src/pages/globalStore.ts @@ -32,7 +32,8 @@ type State = { visitedViews: string[]; showSuccess: (text: string | React.ReactNode) => void; - showInfo: (text: string | React.ReactNode) => void; + showInfo: (text: string | React.ReactNode, duration?: number, isClosable?: boolean) => void; + showWarning: (text: string | React.ReactNode) => void; showError: (text: string | React.ReactNode) => void; }; @@ -161,16 +162,35 @@ const useGlobalStore = create()( }); }, - showInfo: (text: string | React.ReactNode) => { + showWarning: (text: string | React.ReactNode) => { toast({ position: "top", title: text, + status: "warning", variant: localStorage.getItem(CHAKRA_UI_COLOR_MODE_KEY) ? "subtle" : "solid", - duration: 1000, + duration: 3000, + containerStyle: { + maxWidth: "100%", + minWidth: "100px", + }, + }); + }, + + showInfo: ( + text: string | React.ReactNode, + duration: number = 1000, + isClosable: boolean = false, + ) => { + toast({ + position: "top", + title: text, + variant: localStorage.getItem(CHAKRA_UI_COLOR_MODE_KEY) ? "subtle" : "solid", + duration: duration, containerStyle: { maxWidth: "100%", minWidth: "100px", }, + isClosable: isClosable, }); }, })), diff --git a/web/src/pages/home/mods/List/index.tsx b/web/src/pages/home/mods/List/index.tsx index d5249661ca..bb0c95c292 100644 --- a/web/src/pages/home/mods/List/index.tsx +++ b/web/src/pages/home/mods/List/index.tsx @@ -111,13 +111,13 @@ function List(props: { appList: TApplicationItem[] }) { bg={bg} className="group mb-3 flex h-[68px] items-center rounded-[10px] px-6" > -
-
- {item?.name} +
+
+
{item?.name}
{item?.createdBy !== userInfo?._id && (