diff --git a/web/public/locales/en/translation.json b/web/public/locales/en/translation.json index 1c42584cc9..c57b49722c 100644 --- a/web/public/locales/en/translation.json +++ b/web/public/locales/en/translation.json @@ -139,7 +139,9 @@ "DeleteApp": "delete apps", "DeleteTip": "The current operation will permanently delete applications, including functions, data, cloud storage, etc. related data related to the application. After deleting, will not be able to cancel ,please enter {{appId}} confirm", "Duration": "Duration", - "Spec": "Spec" + "Spec": "Spec", + "WelcomeTo": "Welcome to", + "LAF": "the LAF cloud development platform!" }, "SpecItem": { "cpu": "CPU", @@ -204,7 +206,9 @@ "CostTrend": "Cost Trend", "Balance": "Balance", "Pause": "Pause", - "AppMonitor": "Application Monitor" + "AppMonitor": "App Monitoring", + "PauseTips": "Are you sure you want to pause this application?", + "RestartTips": "Are you sure you want to restart this application?" }, "StoragePanel": { "All": "Total Capacity", @@ -558,7 +562,12 @@ "Email": "Email", "ChangeEmail": "Change Email", "SmsNumber": "Verification Code", - "ChangeEmailSuccess": "Change Email Success" + "ChangeEmailSuccess": "Change Email Success", + "GotoAuth": "Go to Authenticate", + "NoAuth": "Not Real-name Verified", + "WarnTips": "You have not yet completed real-name authentication.", + "PleaseBindPhone": "Please bind your mobile number first", + "VerifiedIdentity": "Verified Identity" }, "Reset": "Reset", "SettingModal": { @@ -612,6 +621,7 @@ "Quit": "Quit" }, "Collaborative": "Collaborative", + "CreateApp": "New Application", "Used": "Used", "Remaining": "Remaining", "Pod": "Pod" diff --git a/web/public/locales/zh-CN/translation.json b/web/public/locales/zh-CN/translation.json index c8ac5fced8..08a8f4e92c 100644 --- a/web/public/locales/zh-CN/translation.json +++ b/web/public/locales/zh-CN/translation.json @@ -139,7 +139,9 @@ "DeleteApp": "删除 APP", "DeleteTip": "当前操作将会永久删除应用, 包括函数、数据、云存储等与应用相关的数据, 删除后将无法撤消, 请输入 {{appId}} 后确认", "Duration": "购买时长", - "Spec": "规格" + "Spec": "规格", + "WelcomeTo": "欢迎来到 ", + "LAF": " LAF 云开发平台!" }, "SpecItem": { "cpu": "CPU", @@ -204,7 +206,9 @@ "CostTrend": "成本趋势", "Balance": "余额", "Pause": "暂停应用", - "AppMonitor": "资源监控" + "AppMonitor": "资源监控", + "PauseTips": "确定要暂停此应用吗?", + "RestartTips": "确定要重启此应用吗?" }, "StoragePanel": { "All": "总容量", @@ -558,7 +562,12 @@ "Email": "邮箱", "ChangeEmail": "更改邮箱", "SmsNumber": "验证码", - "ChangeEmailSuccess": "修改邮箱成功" + "ChangeEmailSuccess": "修改邮箱成功", + "GotoAuth": "去认证", + "NoAuth": "未实名", + "WarnTips": "您尚未实名认证", + "PleaseBindPhone": "请先绑定手机号", + "VerifiedIdentity": "已实名" }, "Reset": "重置", "SettingModal": { @@ -612,6 +621,7 @@ "Quit": "退出" }, "Collaborative": "协作", + "CreateApp": "新建应用", "Used": "已使用", "Remaining": "剩余", "Pod": "实例" diff --git a/web/public/locales/zh/translation.json b/web/public/locales/zh/translation.json index 9fbc344c29..7b71a99df4 100644 --- a/web/public/locales/zh/translation.json +++ b/web/public/locales/zh/translation.json @@ -139,7 +139,9 @@ "DeleteApp": "删除 APP", "DeleteTip": "当前操作将会永久删除应用, 包括函数、数据、云存储等与应用相关的数据, 删除后将无法撤消, 请输入 {{appId}} 后确认", "Duration": "购买时长", - "Spec": "规格" + "Spec": "规格", + "WelcomeTo": "欢迎来到 ", + "LAF": " LAF 云开发平台!" }, "SpecItem": { "cpu": "CPU", @@ -204,7 +206,9 @@ "CostTrend": "成本趋势", "Balance": "余额", "Pause": "暂停应用", - "AppMonitor": "资源监控" + "AppMonitor": "资源监控", + "PauseTips": "确定要暂停此应用吗?", + "RestartTips": "确定要重启此应用吗?" }, "StoragePanel": { "All": "总容量", @@ -558,7 +562,12 @@ "Email": "邮箱", "ChangeEmail": "更改邮箱", "SmsNumber": "验证码", - "ChangeEmailSuccess": "修改邮箱成功" + "ChangeEmailSuccess": "修改邮箱成功", + "GotoAuth": "去认证", + "NoAuth": "未实名", + "WarnTips": "您尚未实名认证", + "PleaseBindPhone": "请先绑定手机号", + "VerifiedIdentity": "已实名" }, "Reset": "重置", "SettingModal": { @@ -612,6 +621,7 @@ "Quit": "退出" }, "Collaborative": "协作", + "CreateApp": "新建应用", "Used": "已使用", "Remaining": "剩余", "Pod": "实例" diff --git a/web/src/apis/v1/api-auto.d.ts b/web/src/apis/v1/api-auto.d.ts index 701d58eea6..d791aaf727 100644 --- a/web/src/apis/v1/api-auto.d.ts +++ b/web/src/apis/v1/api-auto.d.ts @@ -507,6 +507,7 @@ declare namespace Definitions { name?: string; createdAt?: string; updatedAt?: string; + idVerified?: { isVerified?: boolean }; }; export type CreateDependencyDto = { diff --git a/web/src/layouts/Basic/RealNameWarn.tsx b/web/src/layouts/Basic/RealNameWarn.tsx new file mode 100644 index 0000000000..20ee4a155a --- /dev/null +++ b/web/src/layouts/Basic/RealNameWarn.tsx @@ -0,0 +1,48 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { WarningTwoIcon } from "@chakra-ui/icons"; + +import SettingModal from "@/pages/app/setting"; +import useTabMatch from "@/pages/app/setting/UserSetting/useTabMatch"; +import useGlobalStore from "@/pages/globalStore"; +import useSiteSettingStore from "@/pages/siteSetting"; + +export default function Warn() { + const { t } = useTranslation(); + const { userInfo, showError } = useGlobalStore((state) => state); + const [openModal, setOpenModal] = useState(false); + const { siteSettings } = useSiteSettingStore((state) => state); + + return ( +
+ + +

{siteSettings.id_verify?.metadata.message}

+
+

{ + if (userInfo?.phone) { + const w = window.open("about:blank"); + w!.location.href = `${ + siteSettings.id_verify?.metadata.authenticateSite + }?token=${localStorage.getItem("token")}`; + } else { + showError(t("UserInfo.PleaseBindPhone")); + setOpenModal(true); + } + }} + > + {t("UserInfo.GotoAuth")} +

+ + +
+ ); +} diff --git a/web/src/layouts/Basic.tsx b/web/src/layouts/Basic/index.tsx similarity index 61% rename from web/src/layouts/Basic.tsx rename to web/src/layouts/Basic/index.tsx index 689aec1f57..0ee353794e 100644 --- a/web/src/layouts/Basic.tsx +++ b/web/src/layouts/Basic/index.tsx @@ -3,25 +3,33 @@ import { AiFillHeart } from "react-icons/ai"; import { Outlet } from "react-router-dom"; import { Center, Spinner } from "@chakra-ui/react"; +import Warn from "./RealNameWarn"; + import Header from "@/layouts/Header"; import useGlobalStore from "@/pages/globalStore"; +import useSiteSettingStore from "@/pages/siteSetting"; export default function BasicLayout() { - const { init, loading } = useGlobalStore((state) => state); + const { init, loading, userInfo } = useGlobalStore((state) => state); + const { siteSettings } = useSiteSettingStore((state) => state); + useEffect(() => { init(); }, [init]); - return (
{loading ? ( -
+
) : ( - + <> + {siteSettings.id_verify?.value === "on" && + !userInfo?.profile?.idVerified?.isVerified && } + + )}
diff --git a/web/src/pages/app/setting/UserInfo/AuthDetail/index.tsx b/web/src/pages/app/setting/UserInfo/AuthDetail/index.tsx deleted file mode 100644 index aa08bf9dd2..0000000000 --- a/web/src/pages/app/setting/UserInfo/AuthDetail/index.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { Controller, useForm } from "react-hook-form"; -import { ChevronLeftIcon } from "@chakra-ui/icons"; -import { - Box, - Button, - FormControl, - FormErrorMessage, - Input, - InputGroup, - InputRightElement, - VStack, -} from "@chakra-ui/react"; -import { t } from "i18next"; - -import { SendSmsCodeButton } from "@/components/SendSmsCodeButton"; -import SmsCodeInput from "@/components/SmsCodeInput"; - -export default function AuthDetail(props: { handleBack: () => void }) { - type FormData = { - tel: string; - code: string; - name: string; - id: string; - }; - const { - register, - handleSubmit, - getValues, - control, - formState: { errors }, - } = useForm(); - - const { handleBack } = props; - const onSubmit = async (data: any) => {}; - - return ( - <> - handleBack()} - className="absolute left-[280px] flex cursor-pointer items-center" - > - {t("Back")} - - - {t("SettingPanel.Auth")} - - -
{t("SettingPanel.Tel")}
- - - - - - - {/* - {errors?.tel && errors?.tel?.message} - */} -
- -
{t("SettingPanel.Code")}:
- ( -
- -
- )} - /> - {/* - {errors?.code && errors?.code?.message} - */} -
- -
{t("SettingPanel.Name")}:
- - {/* - {errors?.name && errors?.name?.message} - */} -
- -
{t("SettingPanel.ID")}:
- - - {errors?.id && errors?.id?.message} - -
-
- -
- - ); -} diff --git a/web/src/pages/app/setting/UserInfo/index.tsx b/web/src/pages/app/setting/UserInfo/index.tsx index fbe12f821a..c4b4e9be8b 100644 --- a/web/src/pages/app/setting/UserInfo/index.tsx +++ b/web/src/pages/app/setting/UserInfo/index.tsx @@ -1,5 +1,6 @@ import { useRef, useState } from "react"; -import { ChevronRightIcon, EditIcon } from "@chakra-ui/icons"; +import { useNavigate } from "react-router-dom"; +import { CheckCircleIcon, ChevronRightIcon, EditIcon, InfoOutlineIcon } from "@chakra-ui/icons"; import { Avatar, Box, Divider, useColorMode } from "@chakra-ui/react"; import clsx from "clsx"; import { t } from "i18next"; @@ -12,20 +13,21 @@ import EmailEditor from "./Mods/EmailEditor"; import PasswordEditor from "./Mods/PasswordEditor"; import PhoneEditor from "./Mods/PhoneEditor"; import UsernameEditor from "./Mods/UsernameEditor"; -import AuthDetail from "./AuthDetail"; import "react-image-crop/dist/ReactCrop.css"; import useGlobalStore from "@/pages/globalStore"; +import useSiteSettingStore from "@/pages/siteSetting"; export default function UserInfo() { const [showItem, setShowItem] = useState(""); const [selectedImage, setSelectedImage] = useState(null); - const { userInfo, avatarUpdatedAt } = useGlobalStore((state) => state); - + const { userInfo, avatarUpdatedAt, showError } = useGlobalStore((state) => state); const fileInputRef = useRef(null); const { colorMode } = useColorMode(); const darkMode = colorMode === "dark"; + const navigate = useNavigate(); + const { siteSettings } = useSiteSettingStore((state) => state); const handleClick = () => { if (fileInputRef.current) { @@ -112,8 +114,50 @@ export default function UserInfo() {
- -
+ + {siteSettings.id_verify?.value === "on" && ( +
+ + {t("SettingPanel.Auth")} + {!userInfo?.profile?.idVerified?.isVerified && ( + + )} + + + + {userInfo?.profile?.idVerified?.isVerified + ? userInfo?.profile?.name + : t("UserInfo.NoAuth")} + + {!userInfo?.profile?.idVerified?.isVerified ? ( + { + if (userInfo?.phone) { + navigate("/auth/real-name"); + } else { + showError(t("UserInfo.PleaseBindPhone")); + setShowItem("phone"); + } + }} + > + {t("UserInfo.GotoAuth")} + + ) : ( + + {t("UserInfo.VerifiedIdentity")} + + + )} + +
+ )} +
{t("SettingPanel.Tel")} @@ -155,7 +199,6 @@ export default function UserInfo() { {showItem === "avatar" && } {showItem === "username" && } {showItem === "password" && } - {showItem === "auth" && } {showItem === "phone" && } {showItem === "email" && } diff --git a/web/src/pages/app/setting/UserSetting/index.tsx b/web/src/pages/app/setting/UserSetting/index.tsx index 356a608772..70064b3c41 100644 --- a/web/src/pages/app/setting/UserSetting/index.tsx +++ b/web/src/pages/app/setting/UserSetting/index.tsx @@ -1,4 +1,3 @@ -import { useTranslation } from "react-i18next"; import { ChevronRightIcon } from "@chakra-ui/icons"; import { Avatar, @@ -11,36 +10,24 @@ import { VStack, } from "@chakra-ui/react"; import clsx from "clsx"; +import { t } from "i18next"; import { BillingIcon, - CardIcon, - ChargeIcon, ContactIcon, - CostIcon, DiscordIcon, ExitIcon, GroupIcon, - InviteIcon, - PATIcon, - StandardIcon, UserIcon, WechatIcon, } from "@/components/CommonIcon"; +import useTabMatch from "./useTabMatch"; + import UserBalance from "@/layouts/Header/UserBalance"; import SettingModal, { TabKeys } from "@/pages/app/setting"; -import BillingDetails from "@/pages/app/setting/BillingDetails"; -import CardRedemption from "@/pages/app/setting/CardRedemption"; -import PATList from "@/pages/app/setting/PATList"; -import PricingStandards from "@/pages/app/setting/PricingStandards"; -import RechargeHistory from "@/pages/app/setting/RechargeHistory"; -import Usage from "@/pages/app/setting/Usage"; -import UserInfo from "@/pages/app/setting/UserInfo"; -import UserInvite from "@/pages/app/setting/UserInvite"; export default function UserSetting(props: { name: string; avatar?: string; width: string }) { - const { t } = useTranslation(); const darkMode = useColorMode().colorMode === "dark"; return ( @@ -97,26 +84,7 @@ export default function UserSetting(props: { name: string; avatar?: string; widt >
, - icon: , - }, - { - key: TabKeys.UserInvite, - name: String(t("SettingPanel.UserInvite")), - component: , - icon: , - }, - { - key: TabKeys.PAT, - name: t("Personal Access Token"), - component: , - icon: , - }, - ]} + tabMatch={useTabMatch("user")} headerTitle={t("SettingPanel.UserCenter")} currentTab={TabKeys.UserInfo} > @@ -136,38 +104,7 @@ export default function UserSetting(props: { name: string; avatar?: string; widt
, - icon: , - }, - { - key: TabKeys.CardRedemption, - name: String(t("SettingPanel.CardRedemption")), - component: , - icon: , - }, - { - key: TabKeys.BillingDetails, - name: String(t("SettingPanel.BillingDetails")), - component: , - icon: , - }, - { - key: TabKeys.RechargeHistory, - name: String(t("SettingPanel.RechargeHistory")), - component: , - icon: , - }, - { - key: TabKeys.PricingStandards, - name: String(t("SettingPanel.PricingStandards")), - component: , - icon: , - }, - ]} + tabMatch={useTabMatch("usage")} headerTitle={t("SettingPanel.Usage")} currentTab={TabKeys.CostOverview} > diff --git a/web/src/pages/app/setting/UserSetting/useTabMatch.tsx b/web/src/pages/app/setting/UserSetting/useTabMatch.tsx new file mode 100644 index 0000000000..a80777b214 --- /dev/null +++ b/web/src/pages/app/setting/UserSetting/useTabMatch.tsx @@ -0,0 +1,86 @@ +import { useTranslation } from "react-i18next"; + +import { + BillingIcon, + CardIcon, + ChargeIcon, + CostIcon, + InviteIcon, + PATIcon, + StandardIcon, + UserIcon, +} from "@/components/CommonIcon"; + +import { TabKeys } from "@/pages/app/setting"; +import BillingDetails from "@/pages/app/setting/BillingDetails"; +import CardRedemption from "@/pages/app/setting/CardRedemption"; +import PATList from "@/pages/app/setting/PATList"; +import PricingStandards from "@/pages/app/setting/PricingStandards"; +import RechargeHistory from "@/pages/app/setting/RechargeHistory"; +import Usage from "@/pages/app/setting/Usage"; +import UserInfo from "@/pages/app/setting/UserInfo"; +import UserInvite from "@/pages/app/setting/UserInvite"; + +export default function useTabMatch(type: string) { + const { t } = useTranslation(); + + const User_TabMatch = [ + { + key: TabKeys.UserInfo, + name: t("SettingPanel.UserInfo"), + component: , + icon: , + }, + { + key: TabKeys.UserInvite, + name: t("SettingPanel.UserInvite"), + component: , + icon: , + }, + { + key: TabKeys.PAT, + name: t("Personal Access Token"), + component: , + icon: , + }, + ]; + + const Usage_TabMatch = [ + { + key: TabKeys.CostOverview, + name: String(t("SettingPanel.CostOverview")), + component: , + icon: , + }, + { + key: TabKeys.CardRedemption, + name: String(t("SettingPanel.CardRedemption")), + component: , + icon: , + }, + { + key: TabKeys.BillingDetails, + name: String(t("SettingPanel.BillingDetails")), + component: , + icon: , + }, + { + key: TabKeys.RechargeHistory, + name: String(t("SettingPanel.RechargeHistory")), + component: , + icon: , + }, + { + key: TabKeys.PricingStandards, + name: String(t("SettingPanel.PricingStandards")), + component: , + icon: , + }, + ]; + + if (type === "user") { + return User_TabMatch; + } else if (type === "usage") { + return Usage_TabMatch; + } +} diff --git a/web/src/pages/app/setting/index.tsx b/web/src/pages/app/setting/index.tsx index 50d355913a..fc13ba1b57 100644 --- a/web/src/pages/app/setting/index.tsx +++ b/web/src/pages/app/setting/index.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import React from "react"; import { Box, @@ -38,28 +38,40 @@ export const TabKeys = { const SettingModal = (props: { headerTitle: string; - children: React.ReactElement; + children?: React.ReactElement; setApp?: TApplicationDetail; tabMatch?: TTabItem[]; currentTab: string; + openModal?: boolean; + setOpenModal?: (open: boolean) => void; }) => { - const { headerTitle, children, setApp, tabMatch = [] } = props; + const { headerTitle, children, setApp, tabMatch = [], openModal, setOpenModal } = props; const { isOpen, onOpen, onClose } = useDisclosure(); const currentIndex = tabMatch.findIndex((tab) => tab.key === props.currentTab); const [item, setItem] = useState(tabMatch[currentIndex]); const { setCurrentApp } = useGlobalStore((state) => state); const borderColor = useColorModeValue("lafWhite.600", "lafDark.600"); const darkMode = useColorMode().colorMode === "dark"; + + useEffect(() => { + if (!children && openModal && setOpenModal) { + onOpen(); + setOpenModal(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [openModal]); + return ( <> - {React.cloneElement(children, { - onClick: () => { - if (setApp) { - setCurrentApp(setApp); - } - onOpen(); - }, - })} + {children && + React.cloneElement(children, { + onClick: () => { + if (setApp) { + setCurrentApp(setApp); + } + onOpen(); + }, + })} diff --git a/web/src/pages/home/mods/CreateAppModal/index.tsx b/web/src/pages/home/mods/CreateAppModal/index.tsx index 18b7fb5dbe..4e4bb0c43e 100644 --- a/web/src/pages/home/mods/CreateAppModal/index.tsx +++ b/web/src/pages/home/mods/CreateAppModal/index.tsx @@ -52,7 +52,7 @@ const CreateAppModal = (props: { const { application, type, isCurrentApp } = props; - const title = type === "edit" ? t("Edit") : type === "change" ? t("Change") : t("Create"); + const title = type === "edit" ? t("Edit") : type === "change" ? t("Change") : t("CreateApp"); const { runtimes = [], regions = [] } = useGlobalStore(); diff --git a/web/src/pages/home/mods/Empty/index.tsx b/web/src/pages/home/mods/Empty/index.tsx index e6634726cf..428d68738f 100644 --- a/web/src/pages/home/mods/Empty/index.tsx +++ b/web/src/pages/home/mods/Empty/index.tsx @@ -31,12 +31,12 @@ function Empty() { return (
-

- {t("HomePanel.Hello")} 👋 ,{" "} - {hidePhoneNumber(userInfo?.profile?.name || userInfo?.username || "")} , - {t("HomePanel.Welcome")} +

+ {t("HomePanel.Hello")} 👋 , {hidePhoneNumber(userInfo?.username || "")} , + {t("HomePanel.WelcomeTo")} + {t("HomePanel.LAF")}

-

{t("HomePanel.Introduction")}

+

{t("HomePanel.Introduction")}

{messageList.map((item, index) => { return ( @@ -44,6 +44,7 @@ function Empty() { key={index} className={clsx("flex items-center pl-9 text-xl font-medium", styles.emptyItem, { "bg-lafDark-300": darkMode, + "bg-lafWhite-100": !darkMode, })} > {item} @@ -51,7 +52,7 @@ function Empty() { ); })}
-

{t("HomePanel.Use")}

+

{t("HomePanel.Use")}

diff --git a/web/src/pages/home/mods/List/index.tsx b/web/src/pages/home/mods/List/index.tsx index 8be71c3532..922f5c2bbf 100644 --- a/web/src/pages/home/mods/List/index.tsx +++ b/web/src/pages/home/mods/List/index.tsx @@ -18,6 +18,7 @@ import { } from "@chakra-ui/react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import ConfirmButton from "@/components/ConfirmButton"; import CopyText from "@/components/CopyText"; import FileTypeIcon from "@/components/FileTypeIcon"; import IconWrap from "@/components/IconWrap"; @@ -77,7 +78,7 @@ function List(props: { appList: TApplicationItem[] }) {
@@ -169,51 +170,79 @@ function List(props: { appList: TApplicationItem[] }) { - { - event?.preventDefault(); - const res = await updateAppStateMutation.mutateAsync({ - appid: item.appid, - state: - item.phase === APP_STATUS.Stopped - ? APP_STATUS.Running - : APP_STATUS.Restarting, - }); - if (!res.error) { - queryClient.setQueryData(APP_LIST_QUERY_KEY, (old: any) => { - return { - ...old, - data: old.data.map((app: any) => { - if (app.appid === item.appid) { - return { - ...app, - phase: - item.phase === APP_STATUS.Stopped - ? APP_PHASE_STATUS.Starting - : APP_STATUS.Restarting, - }; - } - return app; - }), - }; - }); - } - }} - > - - {item.phase === APP_STATUS.Stopped - ? t("SettingPanel.Start") - : t("SettingPanel.Restart")} - - - - {item.phase === APP_PHASE_STATUS.Started && ( + {item.phase === APP_STATUS.Stopped ? ( { + onClick={async (event) => { + event?.preventDefault(); + const res = await updateAppStateMutation.mutateAsync({ + appid: item.appid, + state: APP_STATUS.Running, + }); + if (!res.error) { + queryClient.setQueryData(APP_LIST_QUERY_KEY, (old: any) => { + return { + ...old, + data: old.data.map((app: any) => { + if (app.appid === item.appid) { + return { + ...app, + phase: APP_PHASE_STATUS.Starting, + }; + } + return app; + }), + }; + }); + } + }} + > + {t("SettingPanel.Start")} + + ) : ( + { + event?.preventDefault(); + const res = await updateAppStateMutation.mutateAsync({ + appid: item.appid, + state: APP_STATUS.Restarting, + }); + if (!res.error) { + queryClient.setQueryData(APP_LIST_QUERY_KEY, (old: any) => { + return { + ...old, + data: old.data.map((app: any) => { + if (app.appid === item.appid) { + return { + ...app, + phase: APP_STATUS.Restarting, + }; + } + return app; + }), + }; + }); + } + }} + > + + + {t("SettingPanel.Restart")} + + + + )} + + {item.phase === APP_PHASE_STATUS.Started && ( + { event?.preventDefault(); const res = await updateAppStateMutation.mutateAsync({ appid: item.appid, @@ -237,10 +266,10 @@ function List(props: { appList: TApplicationItem[] }) { } }} > - + {t("SettingPanel.Pause")} - - + + )}