eat(frontend/desktop):change password (#4149)

This commit is contained in:
xudaotutou
2023-10-24 12:17:21 +08:00
committed by GitHub
parent 099f0f8258
commit c8dfbd0523
41 changed files with 743 additions and 153 deletions
+3 -3
View File
@@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM node:18.15-alpine As base
FROM node:20.4.0-alpine As base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
@@ -60,7 +60,7 @@ ARG path
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --offline --filter=$name install && pnpm --filter=$name run build
# Production image, copy all the files and run next
FROM node:current-alpine AS runner
FROM node:20.4.0-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
@@ -84,7 +84,7 @@ fi
USER nextjs
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/desktop/next.config.js ./
COPY --from=builder /app/$path/next.config.js ./$path/next.config.js
COPY --from=builder /app/$path/public ./$path/public
COPY --from=builder --chown=nextjs:nodejs /app/$path/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/$path/.next/static ./$path/.next/static
+1 -1
View File
@@ -13,7 +13,7 @@
# limitations under the License.
# Install dependencies only when needed
FROM node:current-alpine AS deps
FROM node:20.4.0-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat && npm install -g pnpm
WORKDIR /app
+1
View File
@@ -15,6 +15,7 @@ const nextConfig = withPWA({
reactStrictMode: false,
swcMinify: isProduction,
output: 'standalone',
transpilePackages: ['@sealos/ui'],
experimental: {
outputFileTracingRoot: path.join(__dirname, '../')
}
+7
View File
@@ -8,12 +8,18 @@
"start": "next start",
"lint": "next lint",
"test:e2e": "jest --testPathPattern=/e2e/ --runInBand",
"test:e2e-namespace": "jest --testPathPattern=/e2e/namespace/ --runInBand",
"test:e2e-auth": "jest --testPathPattern=/e2e/auth/ --runInBand",
"test:ci": "jest --runInBand"
},
"dependencies": {
"@alicloud/dysmsapi20170525": "^2.0.24",
"@alicloud/openapi-client": "^0.4.6",
"@alicloud/tea-util": "^1.4.7",
"@chakra-ui/next-js": "^2.1.5",
"@chakra-ui/react": "^2.8.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@kubernetes/client-node": "^0.18.1",
"@sealos/ui": "workspace:^",
"@tanstack/react-query": "^4.35.3",
@@ -23,6 +29,7 @@
"dayjs": "^1.11.10",
"eslint": "8.38.0",
"eslint-config-next": "13.3.0",
"framer-motion": "^10.16.4",
"i18next": "^22.5.1",
"immer": "^10.0.2",
"js-cookie": "^3.0.5",
@@ -21,6 +21,14 @@
"password tips": "Password must be 8 characters or more",
"username tips": "Username must be 3-16 characters, including letters, numbers",
"Password": "Password",
"change": "change",
"changePassword": "Change Password",
"currentPassword": "Current Password",
"newPassword": "New Password",
"confirmNewPassword": "Confirm New Password",
"passwordMismatch": "Passwords do not match",
"passwordChangeSuccess": "Password changed successfully",
"currentPasswordRequired": "Current password cannot be empty",
"Verify password": "Verify password",
"verify code tips": "6-digit Verification Code",
"phone number tips": "Phone Number",
@@ -17,7 +17,17 @@
"phone number tips": "手机号码",
"password tips": "密码为8位以上字符",
"username tips": "用户名为3-16位的英文或数字的字符",
"currentPasswordRequired": "当前密码不能为空",
"changePassword": "修改密码",
"currentPassword": "当前密码",
"newPassword": "新密码",
"confirmNewPassword": "确认新密码",
"passwordMismatch": "密码不一致",
"passwordChangeSuccess": "密码修改成功",
"passwordMismatch": "密码不一致",
"newPassword": "新密码",
"Verify password": "确认密码",
"change": "修改",
"Invalid username or password": "用户名或密码错误",
"Invalid phone number": "无效的手机号码",
"Invalid verification code": "无效的验证码",
@@ -0,0 +1,67 @@
import { Session } from 'sealos-desktop-sdk/*';
import * as k8s from '@kubernetes/client-node';
import { _passwordLoginRequest, _passwordModifyRequest } from '@/api/auth';
import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools';
import { _createRequest } from '@/api/namespace';
import request from '@/__tests__/api/request';
import { Db, MongoClient } from 'mongodb';
describe('Password', () => {
let session: Session;
const createRequest = _createRequest(request);
const modifyRequest = _passwordModifyRequest(request);
let db: Db;
let connection: MongoClient;
const setAuth = _setAuth(request);
beforeAll(async () => {
//@ts-ignore
const uri = process.env.MONGODB_URI as string;
// console.log('MONGODB_URI', uri)
connection = new MongoClient(uri);
await connection.connect();
db = connection.db();
const kc = new k8s.KubeConfig();
await cleanK8s(kc, db);
await cleanDb(db);
const res = await _passwordLoginRequest(request)({ user: 'createTest', password: 'testtest' });
expect(res.data?.user).toBeDefined();
session = res.data!;
setAuth(session);
console.log('create,', session.user);
}, 100000);
afterAll(async () => {
await connection.close();
});
it('modify: null param', async () => {
// @ts-ignore
const res = await modifyRequest({});
// 没参数
expect(res.code).toBe(400);
/** @ts-ignore **/
const res2 = await modifyRequest({ oldPassword: 'testtest' });
// 没参数
expect(res2.code).toBe(400);
/** @ts-ignore **/
const res3 = await modifyRequest({ newPassword: 'zxafafa' });
// 没参数
expect(res3.code).toBe(400);
});
it('error oldPassword', async () => {
const res = await modifyRequest({ oldPassword: 'test', newPassword: 'testtest' });
expect(res.code).toBe(409);
});
it('same password', async () => {
const res = await modifyRequest({ oldPassword: 'testtest', newPassword: 'testtest' });
expect(res.code).toBe(409);
});
it('right password', async () => {
const res = await modifyRequest({ oldPassword: 'testtest', newPassword: 'testtest2' });
expect(res.code).toBe(200);
const res2 = await _passwordLoginRequest(request)({
user: 'createTest',
password: 'testtest2'
});
expect(res2.code).toBe(200);
});
});
+6 -4
View File
@@ -4,13 +4,15 @@ import { TUserExist } from '@/types/user';
import { ApiResp } from '@/types';
import { AxiosInstance } from 'axios';
export const _passwordExistRequest = (request: AxiosInstance) => (user: string) =>
request.post<any, ApiResp<TUserExist>, { user: string }>('/api/auth/password/exist', {
user
});
export const _passwordExistRequest = (request: AxiosInstance) => (data: { user: string }) =>
request.post<any, ApiResp<TUserExist>>('/api/auth/password/exist', data);
export const _passwordLoginRequest =
(request: AxiosInstance) => (data: { user: string; password: string }) =>
request.post<any, ApiResp<Session>>('/api/auth/password', data);
export const _passwordModifyRequest =
(request: AxiosInstance) => (data: { oldPassword: string; newPassword: string }) =>
request.post<any, ApiResp<{ message: string }>>('/api/auth/password/modify', data);
export const passwordExistRequest = _passwordExistRequest(request);
export const passwordLoginRequest = _passwordLoginRequest(request);
export const passwordModifyRequest = _passwordModifyRequest(request);
@@ -0,0 +1,190 @@
import {
Button,
Flex,
FlexProps,
Image,
Input,
InputGroup,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Spinner,
Stack,
useDisclosure
} from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import { useCustomToast } from '@/hooks/useCustomToast';
import { ApiResp } from '@/types';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
import { strongPassword } from '@/utils/crypto';
import { passwordModifyRequest } from '@/api/auth';
export default function PasswordModify(props: FlexProps) {
const { onOpen, isOpen, onClose } = useDisclosure();
const { t } = useTranslation();
const { toast } = useCustomToast({ status: 'error' });
const mutation = useMutation(passwordModifyRequest, {
onSuccess(data) {
if (data.code === 200) {
toast({
status: 'success',
title: t('passwordChangeSuccess')
});
onClose();
}
},
onError(error) {
toast({ title: (error as ApiResp).message });
}
});
const { register, handleSubmit, reset } = useForm<{
oldPassword: string;
newPassword: string;
againPassword: string;
}>();
return (
<>
<Flex
_hover={{
bgColor: 'rgba(0, 0, 0, 0.03)'
}}
h="28px"
p="4px"
transition={'all 0.3s'}
justify={'center'}
fontSize={'12px'}
color="#219BF4"
align={'center'}
cursor={'pointer'}
{...props}
onClick={() => {
reset();
onOpen();
}}
>
{t('change')}
</Flex>
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay />
<ModalContent
borderRadius={'4px'}
maxW={'380px'}
bgColor={'#FFF'}
backdropFilter="blur(150px)"
p="24px"
>
<ModalCloseButton right={'24px'} top="24px" p="0" />
<ModalHeader p="0">{t('changePassword')}</ModalHeader>
{mutation.isLoading ? (
<Spinner mx={'auto'} />
) : (
<>
<ModalBody h="100%" w="100%" p="0" mt="22px">
<form
onSubmit={handleSubmit(
({ oldPassword, newPassword }) => {
mutation.mutate({ oldPassword, newPassword });
},
(errors) => {
if (errors.oldPassword) return toast({ title: t('currentPasswordRequired') });
if (errors.newPassword) return toast({ title: t('password tips') });
if (errors.againPassword) return toast({ title: t('passwordMismatch') });
}
)}
>
<Stack gap="10px">
<Flex
w="100%"
borderRadius="2px"
border="1px solid #DEE0E2"
bg="#FBFBFC"
align={'center'}
py={'11px'}
pl={'12px'}
>
<Input
{...register('oldPassword', {
required: true
})}
type="password"
autoComplete="current-password"
_autofill={{
bgColor: '#FBFBFC'
}}
placeholder={t('currentPassword') || ''}
h="20px"
fontSize={'14px'}
variant={'unstyled'}
borderRadius={'unset'}
></Input>
</Flex>
<Flex
w="100%"
borderRadius="2px"
border="1px solid #DEE0E2"
bg="#FBFBFC"
align={'center'}
py={'11px'}
pl={'12px'}
>
<Input
{...register('newPassword', {
required: true,
validate: strongPassword
})}
type="password"
placeholder={t('newPassword') || ''}
autoComplete="new-password"
_autofill={{
bgColor: '#FBFBFC'
}}
h="20px"
fontSize={'14px'}
variant={'unstyled'}
borderRadius={'unset'}
></Input>
</Flex>
<Flex
w="100%"
borderRadius="2px"
border="1px solid #DEE0E2"
bg="#FBFBFC"
align={'center'}
py={'11px'}
pl={'12px'}
>
<Input
{...register('againPassword', {
required: true,
validate(value, formValues) {
return value === formValues.newPassword;
}
})}
type="password"
_autofill={{
bgColor: '#FBFBFC'
}}
autoComplete="new-password"
placeholder={t('confirmNewPassword') || ''}
h="20px"
fontSize={'14px'}
variant={'unstyled'}
borderRadius={'unset'}
></Input>
</Flex>
</Stack>
<Button mt="24px" variant={'primary'} w="full" type="submit">
{t('Confirm')}
</Button>
</form>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
</>
);
}
@@ -17,24 +17,25 @@ import {
import { useMutation, useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { useContext, useEffect, useMemo } from 'react';
import { useMemo } from 'react';
import Iconfont from '../iconfont';
import useAppStore from '@/stores/app';
import { ApiResp } from '@/types';
import { formatMoney } from '@/utils/format';
import { RechargeEnabledContext } from '@/pages';
import TeamCenter from '@/components/team/TeamCenter';
import NsList from '@/components/team/NsList';
import { nsListRequest, switchRequest } from '@/api/namespace';
import RightIcon from '../icons/RightArrow';
import { NSType } from '@/types/team';
import PasswordModify from './PasswordModify';
import { useGlobalStore } from '@/stores/global';
const NsMenu = () => {
const { t } = useTranslation();
const session = useSessionStore((s) => s.session);
const setSession = useSessionStore((s) => s.setSession);
const { ns_uid, k8s_username } = session.user;
const { ns_uid } = session.user;
const router = useRouter();
const mutation = useMutation({
mutationFn: switchRequest,
@@ -83,7 +84,7 @@ const NsMenu = () => {
transition={'0.3s'}
p="4px"
>
<Text fontSize="14px" fontWeight={'500'} color={'#363C42'}>
<Text fontSize="13px" fontWeight={'500'} color={'#363C42'}>
{namespace?.nstype === NSType.Private ? t('Default Team') : namespace?.teamName}
</Text>
<RightIcon w="16px" h="16px" />
@@ -112,7 +113,6 @@ const NsMenu = () => {
};
export default function Index({ disclosure }: { disclosure: UseDisclosureReturn }) {
const router = useRouter();
const rechargeEnabled = useContext(RechargeEnabledContext);
const { t } = useTranslation();
const { delSession, getSession } = useSessionStore();
const { user, kubeconfig } = getSession();
@@ -133,7 +133,6 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
'/api/account/getAmount'
)
});
const balance = useMemo(() => {
let real_balance = data?.data?.balance || 0;
if (data?.data?.deductionBalance) {
@@ -146,9 +145,17 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
delSession();
router.replace('/signin');
};
const needPassword = useGlobalStore((s) => s.needPassword);
const rechargeEnabled = useGlobalStore((s) => s.rechargeEnabled);
return disclosure.isOpen ? (
<>
<Box position={'fixed'} inset={0} zIndex={'998'} onClick={disclosure.onClose}></Box>
<Box
position={'fixed'}
inset={0}
zIndex={'998'}
cursor={'initial'}
onClick={disclosure.onClose}
></Box>
<Box
w="297px"
bg="rgba(255, 255, 255, 0.6)"
@@ -156,6 +163,7 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
position={'absolute'}
top="48px"
right={0}
cursor={'initial'}
zIndex={'999'}
borderRadius={'8px'}
p="20px"
@@ -186,13 +194,26 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
mt="12px"
bg="rgba(255, 255, 255, 0.6)"
borderRadius={'8px'}
fontSize={'13px'}
gap={'0px'}
>
<Flex alignItems={'center'} borderBottom={'1px solid #0000001A'} p="16px">
<Flex alignItems={'center'} borderBottom={'1px solid #0000001A'} px="16px" py="11px">
<Text>{t('Manage Team')}</Text>
<TeamCenter mr="0" />
</Flex>
<Flex alignItems={'center'} borderBottom={'1px solid #0000001A'} p="16px">
{needPassword && (
<Flex
justify={'space-between'}
alignItems={'center'}
borderBottom={'1px solid #0000001A'}
px="16px"
py="11px"
>
<Text>{t('changePassword')}</Text>
<PasswordModify mr="0" />
</Flex>
)}
<Flex px="16px" py="11px" alignItems={'center'} borderBottom={'1px solid #0000001A'}>
<Text>
{t('Balance')}: {formatMoney(balance).toFixed(2)}
</Text>
@@ -217,14 +238,15 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
color={'#219BF4'}
fontWeight="500"
fontSize="12px"
cursor={'pointer'}
>
{t('Charge')}
</Box>
)}
</Flex>
{
<Flex alignItems={'center'} py="16px">
<Text ml="16px">kubeconfig</Text>
<Flex alignItems={'center'} px="16px" py="11px">
<Text>kubeconfig</Text>
<Box
_hover={{
bgColor: 'rgba(0, 0, 0, 0.03)'
@@ -232,6 +254,7 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
transition={'0.3s'}
p="4px"
ml="auto"
cursor={'pointer'}
onClick={() => download('kubeconfig.yaml', kubeconfig)}
>
<Iconfont
@@ -247,7 +270,6 @@ export default function Index({ disclosure }: { disclosure: UseDisclosureReturn
}}
p="4px"
ml="8px"
mr="20px"
onClick={() => copyData(kubeconfig)}
cursor={'pointer'}
>
@@ -121,7 +121,7 @@ export default function DesktopContent(props: any) {
textAlign={'center'}
mt="8px"
color={'#FFFFFF'}
fontSize={'10px'}
fontSize={'13px'}
lineHeight={'16px'}
>
{item?.i18n?.[i18n?.language]?.name
@@ -1,3 +1,4 @@
import { passwordExistRequest, passwordLoginRequest } from '@/api/auth';
import request from '@/services/request';
import useSessionStore from '@/stores/session';
import { ApiResp, Session } from '@/types';
@@ -43,15 +44,9 @@ export default function usePassword({
if (data?.username && data?.password) {
try {
setIsLoading(true);
const result = await request.post<any, ApiResp<TUserExist>, { user: string }>(
'/api/auth/password/exist',
{
user: data.username
}
);
const result = await passwordExistRequest({ user: data.username });
if (result?.code === 200) {
const result = await request.post<any, ApiResp<Session>>('/api/auth/password', {
const result = await passwordLoginRequest({
user: data.username,
password: data.password
});
@@ -16,7 +16,6 @@ export default function Index(props: { userMenuStyleProps?: FlexProps }) {
const [notificationAmount, setNotificationAmount] = useState(0);
const accountDisclosure = useDisclosure();
const showDisclosure = useDisclosure();
const switchLangDisclosure = useDisclosure();
const userInfo = useSessionStore((state) => state.getSession());
if (!userInfo) return null;
@@ -75,7 +74,7 @@ export default function Index(props: { userMenuStyleProps?: FlexProps }) {
background={'rgba(244, 246, 248, 0.7)'}
boxShadow={'0px 1.2px 2.3px rgba(0, 0, 0, 0.2)'}
/>
{buttonList.map((item, index) => (
{buttonList.map((item) => (
<Flex
w="36px"
h="36px"
+4 -1
View File
@@ -1,7 +1,10 @@
import request from '@/services/request';
import { useGlobalStore } from '@/stores/global';
import { theme } from '@/styles/chakraTheme';
import '@/styles/globals.scss';
import { ApiResp, SystemEnv } from '@/types';
import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { appWithTranslation } from 'next-i18next';
import type { AppProps } from 'next/app';
import Router from 'next/router';
@@ -1,6 +1,5 @@
import { authSession, generateJWT } from '@/services/backend/auth';
import { queryUsersByNamespace } from '@/services/backend/db/userToNamespace';
import { switchNamespace } from '@/services/backend/kubernetes/user';
import { jsonRes } from '@/services/backend/response';
import * as jsYaml from 'js-yaml';
import { Session } from 'sealos-desktop-sdk';
@@ -14,11 +13,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const nsUsers = await queryUsersByNamespace({ namespaceId: ns_uid });
const userInNs = nsUsers.find(
(item) => item.userId === payload.user.uid && payload.user.k8s_username === item.k8s_username
); // if (
if (!userInNs) return jsonRes(res, { code: 403, message: 'you are not in this namespace' });
const kubeconfig = jsYaml.dump(
JSON.parse(switchNamespace(payload.kc, userInNs?.namespace?.id || '').exportConfig())
);
if (!userInNs) return jsonRes(res, { code: 403, message: 'you are not in this namespace' });
const oldKc = jsYaml.load(payload.kcRaw);
// @ts-ignore
oldKc.contexts[0].context.namespace = userInNs?.namespace?.id || '';
const kubeconfig = jsYaml.dump(oldKc);
const user = {
ns_uid: userInNs.namespaceId,
nsid: userInNs.namespace.id,
@@ -48,6 +49,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
message: 'Successfully'
});
} catch (e) {
console.log(e);
jsonRes(res, { code: 500, message: 'fail to switch namespace' });
}
}
@@ -0,0 +1,64 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/services/backend/response';
import { Session } from '@/types/session';
import { hashPassword, strongPassword, verifyPassword } from '@/utils/crypto';
import { enablePassword } from '@/services/enable';
import { authSession } from '@/services/backend/auth';
import { queryUser, updateUser } from '@/services/backend/db/user';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (!enablePassword()) {
throw new Error('PASSWORD_SALT is not defined');
}
const payload = await authSession(req.headers);
if (!payload) return jsonRes(res, { code: 401, message: 'token verify error' });
const uid = payload.user.uid;
const { oldPassword, newPassword } = req.body;
if (!oldPassword) return jsonRes(res, { code: 400, message: 'oldPassword is required' });
if (!newPassword) return jsonRes(res, { code: 400, message: 'newPassword is required' });
const _user = await queryUser({ id: uid, provider: 'uid' });
if (!_user) return jsonRes(res, { code: 404, message: 'user is not founded' });
if (!_user.password) {
return jsonRes(res, { code: 409, message: 'Please login by password' });
}
if (
!oldPassword ||
!verifyPassword(oldPassword, _user.password) ||
oldPassword === newPassword
) {
return jsonRes(res, { code: 409, message: 'password error' });
}
if (!strongPassword(newPassword)) {
return jsonRes(res, {
message:
'Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character',
code: 400
});
}
const updateRes = await updateUser({
id: uid,
provider: 'uid',
data: { password: hashPassword(newPassword) }
});
if (!updateRes.acknowledged) throw new Error('modify password error');
console.log(
'modify',
updateRes,
oldPassword,
newPassword,
'hash',
hashPassword(oldPassword),
hashPassword(newPassword)
);
return jsonRes<Session>(res, {
code: 200,
message: 'Successfully!'
});
} catch (err) {
console.log(err);
return jsonRes(res, {
message: 'Failed to authenticate with password',
code: 500
});
}
}
@@ -8,7 +8,8 @@ import {
enableGoogle,
enableStripe,
enableWechatRecharge,
enableLicense
enableLicense,
enableRecharge
} from '@/services/enable';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -26,7 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const stripeEnabled = enableStripe();
const wechatEnabledRecharge = enableWechatRecharge();
const licenseEnabled = enableLicense();
const rechargeEnabled = enableRecharge();
jsonRes(res, {
data: {
SEALOS_CLOUD_DOMAIN: process.env.SEALOS_CLOUD_DOMAIN || 'cloud.sealos.io',
@@ -43,6 +44,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
needGoogle,
stripeEnabled,
wechatEnabledRecharge,
rechargeEnabled,
licenseEnabled
}
});
+12 -19
View File
@@ -4,9 +4,10 @@ import MoreApps from '@/components/more_apps';
import { enableRecharge } from '@/services/enable';
import request from '@/services/request';
import useAppStore from '@/stores/app';
import { useGlobalStore } from '@/stores/global';
import useSessionStore from '@/stores/session';
import { ApiResp } from '@/types';
import { SystemConfigType } from '@/types/system';
import { SystemConfigType, SystemEnv } from '@/types/system';
import { parseOpenappQuery } from '@/utils/format';
import { compareFirstLanguages } from '@/utils/tools';
import { Box, useColorMode } from '@chakra-ui/react';
@@ -23,26 +24,23 @@ interface IMoreAppsContext {
setShowMoreApps: (value: boolean) => void;
}
export const MoreAppsContext = createContext<IMoreAppsContext | null>(null);
export const RechargeEnabledContext = createContext<boolean>(false);
export default function Home({
rechargeEnabled,
sealos_cloud_domain
}: {
rechargeEnabled: boolean;
sealos_cloud_domain: string;
}) {
export default function Home({ sealos_cloud_domain }: { sealos_cloud_domain: string }) {
const router = useRouter();
const { isUserLogin, setSession } = useSessionStore();
const { colorMode, toggleColorMode } = useColorMode();
const init = useAppStore((state) => state.init);
const setAutoLaunch = useAppStore((state) => state.setAutoLaunch);
const cancelAutoLaunch = useAppStore((state) => state.cancelAutoLaunch);
const { data: systemConfig, refetch } = useQuery(['getSystemConfig'], () =>
request<any, ApiResp<SystemConfigType>>('/api/system/getSystemConfig')
);
const { data: platformEnv, isSuccess } = useQuery(['getPlatformEnv'], () =>
request<any, ApiResp<SystemEnv>>('/api/platform/getEnv')
);
const setEnv = useGlobalStore((s) => s.setEnv);
// @ts-ignore
if (isSuccess) Object.entries(platformEnv?.data!).forEach(([k, v]) => setEnv(k, v));
useEffect(() => {
colorMode === 'dark' ? toggleColorMode() : null;
}, [colorMode, toggleColorMode]);
@@ -52,7 +50,6 @@ export default function Home({
const { query } = router;
const is_login = isUserLogin();
const whitelistApps = ['system-fastdeploy'];
console.log('index');
if (!is_login) {
const { appkey, appQuery } = parseOpenappQuery((query?.openapp as string) || '');
// sealos_inside=true internal call
@@ -95,11 +92,9 @@ export default function Home({
return <Script key={i} {...item} />;
})}
<MoreAppsContext.Provider value={{ showMoreApps, setShowMoreApps }}>
<RechargeEnabledContext.Provider value={rechargeEnabled}>
<DesktopContent />
<FloatButton />
<MoreApps />
</RechargeEnabledContext.Provider>
<DesktopContent />
<FloatButton />
<MoreApps />
</MoreAppsContext.Provider>
</Box>
);
@@ -110,11 +105,9 @@ export async function getServerSideProps({ req, res, locales }: any) {
res.setHeader('Set-Cookie', `NEXT_LOCALE=${local}; Max-Age=2592000; Secure; SameSite=None`);
const sealos_cloud_domain = process.env.SEALOS_CLOUD_DOMAIN || 'cloud.sealos.io';
return {
props: {
...(await serverSideTranslations(local, undefined, null, locales || [])),
rechargeEnabled: enableRecharge(),
sealos_cloud_domain
}
};
@@ -2,7 +2,6 @@ import { IncomingHttpHeaders } from 'http';
import { sign, verify } from 'jsonwebtoken';
import { K8sApi } from './kubernetes/user';
import { JWTPayload } from '@/types';
import { queryUTN } from './db/userToNamespace';
const jwtSecret = (process.env.JWT_SECRET as string) || '123456789';
export const authSession = async (header: IncomingHttpHeaders) => {
@@ -26,7 +25,7 @@ export const authSession = async (header: IncomingHttpHeaders) => {
const username = kc.getCurrentUser()?.name;
const user = payload.user;
if (!username || user.k8s_username !== username) throw new Error('user is invaild');
return Promise.resolve({ kc, user });
return Promise.resolve({ kc, user, kcRaw: payload.kubeconfig });
} catch (err) {
console.error(err);
return Promise.resolve(null);
@@ -47,7 +46,6 @@ export const verifyJWT: (token: string) => Promise<JWTPayload | null> = (token:
});
});
export const generateJWT = (props: JWTPayload) => {
console.log('jwt: ', props);
return sign(props, jwtSecret, {
expiresIn: '7d'
});
@@ -28,15 +28,17 @@ export async function createUser({
id,
provider,
name,
avatar_url
avatar_url,
password
}: {
id: string;
provider: Provider;
name: string;
avatar_url: string;
password?: string;
}) {
if (!verifyProvider(provider)) return Promise.reject('provider error');
const users = await connectToUserCollection();
let uid = uuid();
const k8s_username = await get_k8s_username();
let user: User = {
@@ -50,10 +52,10 @@ export async function createUser({
}
]
};
if (!verifyProvider(provider)) return Promise.reject('provider error');
user[provider] = id;
await users.insertOne(user);
return user;
if (password && provider === 'password_user') user.password = password;
const res = await users.insertOne(user);
return res.acknowledged ? user : null;
}
export async function updateUser({
id,
@@ -37,18 +37,6 @@ export function K8sApi(config: string): k8s.KubeConfig {
}
return kc;
}
export function switchNamespace(oldkc: k8s.KubeConfig, namespace: string) {
console.log('old kc :', oldkc);
const kc = new k8s.KubeConfig();
// console.log(kc)
const oldCtx = oldkc.contexts[0];
// console.log(oldCtx)
kc.loadFromOptions({
...oldkc,
contexts: [{ ...oldCtx, namespace }]
});
return kc;
}
export async function ListPods(
kc: k8s.KubeConfig,
ns: string
@@ -154,17 +154,16 @@ async function signUp({
password?: string;
}) {
const ns_uid = uuid();
const user = await createUser({ id, provider, name, avatar_url });
if (provider === 'password_user') {
await updateUser({
id,
provider: 'password_user',
data: { password: hashPassword(password!) }
}).catch(async (_) => {
await removeUser({ id: '' + id, provider: 'password_user' });
throw new Error('Failed to create user by password');
});
}
let user: User | null = null;
if (provider === 'password_user') {
if (!password) return null;
user = await createUser({ id, provider, name, avatar_url, password: hashPassword(password) });
} else {
user = await createUser({ id, provider, name, avatar_url });
}
if (!user) return null;
const k8s_users = user.k8s_users || [];
const userId = user.uid;
if (!k8s_users) return null;
+37
View File
@@ -0,0 +1,37 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { Session, SystemEnv, sessionKey } from '@/types';
type GlobalState = SystemEnv & {
setEnv: <T extends keyof SystemEnv>(env: T, val: SystemEnv[T]) => void;
};
export const useGlobalStore = create<GlobalState>()(
persist(
immer((set) => ({
callback_url: '',
github_client_id: '',
google_client_id: '',
wechat_client_id: '',
licenseEnabled: false,
needGithub: false,
needGoogle: false,
needPassword: false,
needSms: false,
needWechat: false,
private_protocol: '',
service_protocol: '',
stripeEnabled: false,
wechatEnabledRecharge: false,
SEALOS_CLOUD_DOMAIN: 'cloud.sealos.io',
rechargeEnabled: false,
setEnv(env, val) {
set({ [env]: val });
}
})),
{
name: 'sealos-desktop-global'
}
)
);
+7 -1
View File
@@ -15,22 +15,26 @@ body,
iframe {
border: none;
}
::-webkit-scrollbar,
::-webkit-scrollbar {
width: 8px;
height: 8px;
border-radius: 8px;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track {
background: transparent;
border-radius: 6px;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-thumb {
background: #6a6969;
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover,
::-webkit-scrollbar-thumb:hover {
background: #999;
@@ -47,8 +51,8 @@ iframe {
fill: currentColor;
overflow: hidden;
}
input:-webkit-autofill {
background-color: rgba($color: #000000, $alpha: 0.2) !important;
background-image: none;
color: #000;
}
@@ -60,11 +64,13 @@ div {
border-radius: 10px;
transition: 1s;
}
&:hover {
&::-webkit-scrollbar-thumb,
&::-webkit-scrollbar-thumb {
background: rgba(189, 193, 197, 0.5) !important;
}
&::-webkit-scrollbar-thumb:hover,
&::-webkit-scrollbar-thumb:hover {
background: rgba(189, 193, 197, 1) !important;
+1
View File
@@ -25,5 +25,6 @@ export type SystemEnv = {
SEALOS_CLOUD_DOMAIN: string;
stripeEnabled: boolean;
wechatEnabledRecharge: boolean;
rechargeEnabled: boolean;
licenseEnabled: boolean;
} & LoginProps;
+175 -20
View File
@@ -48,6 +48,18 @@ importers:
'@alicloud/tea-util':
specifier: ^1.4.7
version: 1.4.7
'@chakra-ui/next-js':
specifier: ^2.1.5
version: 2.1.5(@chakra-ui/react@2.8.1)(@emotion/react@11.11.1)(next@13.3.0)(react@18.2.0)
'@chakra-ui/react':
specifier: ^2.8.1
version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.0.37)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@emotion/react':
specifier: ^11.11.1
version: 11.11.1(@types/react@18.0.37)(react@18.2.0)
'@emotion/styled':
specifier: ^11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.0.37)(react@18.2.0)
'@kubernetes/client-node':
specifier: ^0.18.1
version: 0.18.1
@@ -75,6 +87,9 @@ importers:
eslint-config-next:
specifier: 13.3.0
version: 13.3.0(eslint@8.38.0)(typescript@5.2.2)
framer-motion:
specifier: ^10.16.4
version: 10.16.4(react-dom@18.2.0)(react@18.2.0)
i18next:
specifier: ^22.5.1
version: 22.5.1
@@ -2924,14 +2939,14 @@ packages:
resolution: {integrity: sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==}
dev: false
/@chakra-ui/focus-lock@2.1.0(react@18.2.0):
/@chakra-ui/focus-lock@2.1.0(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==}
peerDependencies:
react: '>=18'
dependencies:
'@chakra-ui/dom-utils': 2.1.0
react: 18.2.0
react-focus-lock: 2.9.5(react@18.2.0)
react-focus-lock: 2.9.5(@types/react@18.0.37)(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
dev: false
@@ -3080,7 +3095,7 @@ packages:
react: 18.2.0
dev: false
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0):
/@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.0.37)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==}
peerDependencies:
'@chakra-ui/system': '>=2.0.0'
@@ -3089,7 +3104,7 @@ packages:
react-dom: '>=18'
dependencies:
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/focus-lock': 2.1.0(react@18.2.0)
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.0.37)(react@18.2.0)
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/react-context': 2.1.0(react@18.2.0)
'@chakra-ui/react-types': 2.0.7(react@18.2.0)
@@ -3101,11 +3116,26 @@ packages:
framer-motion: 10.16.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.6(react@18.2.0)
react-remove-scroll: 2.5.6(@types/react@18.0.37)(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
dev: false
/@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1)(@emotion/react@11.11.1)(next@13.3.0)(react@18.2.0):
resolution: {integrity: sha512-bBd8zeXlAuVwDsjGN0/5ZZJknSbFeiZgJiGdbJo91v2lxKkOwo26b4ggc1ck1t+JR+daU6SrGBjlbQqSuWzyaA==}
peerDependencies:
'@chakra-ui/react': '>=2.4.0'
'@emotion/react': '>=11'
next: '>=13'
react: '>=18'
dependencies:
'@chakra-ui/react': 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.0.37)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@emotion/cache': 11.11.0
'@emotion/react': 11.11.1(@types/react@18.0.37)(react@18.2.0)
next: 13.3.0(@babel/core@7.23.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.68.0)
react: 18.2.0
dev: false
/@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1)(@emotion/react@11.11.1)(next@13.4.5)(react@18.2.0):
resolution: {integrity: sha512-bBd8zeXlAuVwDsjGN0/5ZZJknSbFeiZgJiGdbJo91v2lxKkOwo26b4ggc1ck1t+JR+daU6SrGBjlbQqSuWzyaA==}
peerDependencies:
@@ -3454,6 +3484,77 @@ packages:
react: 18.2.0
dev: false
/@chakra-ui/react@2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.0.37)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UL9Rtj4DovP3+oVbI06gsdfyJJb+wmS2RYnGNXjW9tsjCyXxjlBw9TAUj0jyOfWe0+zd/4juL8+J+QCwmdhptg==}
peerDependencies:
'@emotion/react': ^11.0.0
'@emotion/styled': ^11.0.0
framer-motion: '>=4.0.0'
react: '>=18'
react-dom: '>=18'
dependencies:
'@chakra-ui/accordion': 2.3.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0)
'@chakra-ui/alert': 2.2.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/avatar': 2.3.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/breadcrumb': 2.2.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/button': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/card': 2.2.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/checkbox': 2.3.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/counter': 2.1.0(react@18.2.0)
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.1)(react@18.2.0)
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.0.37)(react@18.2.0)
'@chakra-ui/form-control': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/hooks': 2.2.1(react@18.2.0)
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/image': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/input': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/live-region': 2.1.0(react@18.2.0)
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0)
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.0.37)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/number-input': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0)
'@chakra-ui/popper': 3.1.0(react@18.2.0)
'@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/provider': 2.4.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/radio': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/react-env': 3.1.0(react@18.2.0)
'@chakra-ui/select': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/skeleton': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/skip-nav': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/slider': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/stat': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/styled-system': 2.9.1
'@chakra-ui/switch': 2.1.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0)
'@chakra-ui/system': 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/textarea': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/theme': 3.3.0(@chakra-ui/styled-system@2.9.1)
'@chakra-ui/theme-utils': 2.0.20
'@chakra-ui/toast': 7.0.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/tooltip': 2.3.0(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/transition': 2.1.0(framer-motion@10.16.4)(react@18.2.0)
'@chakra-ui/utils': 2.0.15
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@emotion/react': 11.11.1(@types/react@18.0.37)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.0.37)(react@18.2.0)
framer-motion: 10.16.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
dev: false
/@chakra-ui/react@2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UL9Rtj4DovP3+oVbI06gsdfyJJb+wmS2RYnGNXjW9tsjCyXxjlBw9TAUj0jyOfWe0+zd/4juL8+J+QCwmdhptg==}
peerDependencies:
@@ -3475,7 +3576,7 @@ packages:
'@chakra-ui/counter': 2.1.0(react@18.2.0)
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.1)(react@18.2.0)
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/focus-lock': 2.1.0(react@18.2.0)
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.0.37)(react@18.2.0)
'@chakra-ui/form-control': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/hooks': 2.2.1(react@18.2.0)
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.1)(react@18.2.0)
@@ -3485,7 +3586,7 @@ packages:
'@chakra-ui/live-region': 2.1.0(react@18.2.0)
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0)
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.0.37)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/number-input': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0)
@@ -3956,6 +4057,7 @@ packages:
/@emotion/memoize@0.7.4:
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
requiresBuild: true
dev: false
optional: true
@@ -3984,6 +4086,27 @@ packages:
react: 18.2.0
dev: false
/@emotion/react@11.11.1(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==}
peerDependencies:
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.1
'@emotion/babel-plugin': 11.11.0
'@emotion/cache': 11.11.0
'@emotion/serialize': 1.1.2
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
'@emotion/utils': 1.2.1
'@emotion/weak-memoize': 0.3.1
'@types/react': 18.0.37
hoist-non-react-statics: 3.3.2
react: 18.2.0
dev: false
/@emotion/serialize@1.1.2:
resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==}
dependencies:
@@ -4019,6 +4142,27 @@ packages:
react: 18.2.0
dev: false
/@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
peerDependencies:
'@emotion/react': ^11.0.0-rc.0
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.1
'@emotion/babel-plugin': 11.11.0
'@emotion/is-prop-valid': 1.2.1
'@emotion/react': 11.11.1(@types/react@18.0.37)(react@18.2.0)
'@emotion/serialize': 1.1.2
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
'@emotion/utils': 1.2.1
'@types/react': 18.0.37
react: 18.2.0
dev: false
/@emotion/unitless@0.8.1:
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
dev: false
@@ -11172,6 +11316,7 @@ packages:
/jose@4.14.6:
resolution: {integrity: sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==}
requiresBuild: true
dev: false
optional: true
@@ -11781,6 +11926,7 @@ packages:
/memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
requiresBuild: true
dev: false
optional: true
@@ -12664,6 +12810,7 @@ packages:
/object-hash@2.2.0:
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
engines: {node: '>= 6'}
requiresBuild: true
dev: false
optional: true
@@ -12753,6 +12900,7 @@ packages:
/oidc-token-hash@5.0.3:
resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==}
engines: {node: ^10.13.0 || >=12.0.0}
requiresBuild: true
dev: false
optional: true
@@ -13255,7 +13403,7 @@ packages:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
dev: false
/react-focus-lock@2.9.5(react@18.2.0):
/react-focus-lock@2.9.5(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-h6vrdgUbsH2HeD5I7I3Cx1PPrmwGuKYICS+kB9m+32X/9xHRrAbxgvaBpG7BFBN9h3tO+C3qX1QAVESmi4CiIA==}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -13265,12 +13413,13 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.23.1
'@types/react': 18.0.37
focus-lock: 0.11.6
prop-types: 15.8.1
react: 18.2.0
react-clientside-effect: 1.2.6(react@18.2.0)
use-callback-ref: 1.3.0(react@18.2.0)
use-sidecar: 1.1.2(react@18.2.0)
use-callback-ref: 1.3.0(@types/react@18.0.37)(react@18.2.0)
use-sidecar: 1.1.2(@types/react@18.0.37)(react@18.2.0)
dev: false
/react-hook-form@7.46.2(react@18.2.0):
@@ -13349,7 +13498,7 @@ packages:
szfe-tools: 0.0.0-beta.7
dev: false
/react-remove-scroll-bar@2.3.4(react@18.2.0):
/react-remove-scroll-bar@2.3.4(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
engines: {node: '>=10'}
peerDependencies:
@@ -13359,12 +13508,13 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.0.37
react: 18.2.0
react-style-singleton: 2.2.1(react@18.2.0)
react-style-singleton: 2.2.1(@types/react@18.0.37)(react@18.2.0)
tslib: 2.6.2
dev: false
/react-remove-scroll@2.5.6(react@18.2.0):
/react-remove-scroll@2.5.6(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==}
engines: {node: '>=10'}
peerDependencies:
@@ -13374,15 +13524,16 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.0.37
react: 18.2.0
react-remove-scroll-bar: 2.3.4(react@18.2.0)
react-style-singleton: 2.2.1(react@18.2.0)
react-remove-scroll-bar: 2.3.4(@types/react@18.0.37)(react@18.2.0)
react-style-singleton: 2.2.1(@types/react@18.0.37)(react@18.2.0)
tslib: 2.6.2
use-callback-ref: 1.3.0(react@18.2.0)
use-sidecar: 1.1.2(react@18.2.0)
use-callback-ref: 1.3.0(@types/react@18.0.37)(react@18.2.0)
use-sidecar: 1.1.2(@types/react@18.0.37)(react@18.2.0)
dev: false
/react-style-singleton@2.2.1(react@18.2.0):
/react-style-singleton@2.2.1(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
peerDependencies:
@@ -13392,6 +13543,7 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.0.37
get-nonce: 1.0.1
invariant: 2.2.4
react: 18.2.0
@@ -14083,6 +14235,7 @@ packages:
/sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
requiresBuild: true
dependencies:
memory-pager: 1.5.0
dev: false
@@ -14864,7 +15017,7 @@ packages:
requires-port: 1.0.0
dev: true
/use-callback-ref@1.3.0(react@18.2.0):
/use-callback-ref@1.3.0(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
engines: {node: '>=10'}
peerDependencies:
@@ -14874,11 +15027,12 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.0.37
react: 18.2.0
tslib: 2.6.2
dev: false
/use-sidecar@1.1.2(react@18.2.0):
/use-sidecar@1.1.2(@types/react@18.0.37)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'}
peerDependencies:
@@ -14888,6 +15042,7 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.0.37
detect-node-es: 1.1.0
react: 18.2.0
tslib: 2.6.2
@@ -9,4 +9,4 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
}
}
};
@@ -9,4 +9,4 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
}
}
};
@@ -9,4 +9,4 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
}
}
};
@@ -9,4 +9,4 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
}
}
};
@@ -9,4 +9,4 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
}
}
};
@@ -9,4 +9,4 @@ module.exports = {
locales: ['en', 'zh'],
localeDetection: false
}
}
};
@@ -85,7 +85,8 @@ const Header = ({
h={'80px'}
borderRadius={'8px'}
backgroundColor={'#FBFBFC'}
border={' 1px solid rgba(255, 255, 255, 0.50)'}>
border={' 1px solid rgba(255, 255, 255, 0.50)'}
>
<Image src={templateDetail?.spec?.icon} alt="" width={'60px'} height={'60px'} />
</Flex>
<Flex ml={'24px'} w="520px" flexDirection={'column'}>
@@ -102,7 +103,8 @@ const Header = ({
_hover={{
background: '#F4F6F8'
}}
onClick={(e) => goGithub(e, templateDetail?.spec?.gitRepo)}>
onClick={(e) => goGithub(e, templateDetail?.spec?.gitRepo)}
>
<HomePageIcon />
<Text fontSize={'12px '} fontWeight={400} pl="6px">
{t('Home Page')}
@@ -119,7 +121,8 @@ const Header = ({
alignItems={'center'}
_hover={{
background: '#F4F6F8'
}}>
}}
>
<ShareIcon />
<Text fontSize={'12px '} fontWeight={400} color={'#485058'} pl="6px">
{t('Share')}
@@ -134,7 +137,8 @@ const Header = ({
w="60px"
flexDirection={'column'}
justifyContent={'center'}
alignItems={'center'}>
alignItems={'center'}
>
<Flex {...IconBox}>
<CopyLinkIcon />
</Flex>
@@ -150,7 +154,8 @@ const Header = ({
<Flex
flexDirection={'column'}
alignItems={'center'}
onClick={() => copyData(HtmlPart)}>
onClick={() => copyData(HtmlPart)}
>
<Flex {...IconBox}>
<HtmlIcon />
</Flex>
@@ -162,7 +167,8 @@ const Header = ({
flexDirection={'column'}
alignItems={'center'}
ml="auto"
onClick={() => copyData(MdPart)}>
onClick={() => copyData(MdPart)}
>
<Flex {...IconBox}>
<MdIcon />
</Flex>
@@ -188,7 +194,8 @@ const Header = ({
mt={'8px'}
fontSize={'12px'}
color={'5A646E'}
fontWeight={400}>
fontWeight={400}
>
{templateDetail?.spec?.description}
</Text>
</Tooltip>
@@ -203,7 +210,8 @@ const Header = ({
bg={'myWhite.600'}
borderColor={'myGray.200'}
variant={'base'}
onClick={handleExportYaml}>
onClick={handleExportYaml}
>
{t('Export')} Yaml
</Button>
<Button px={4} minW={'140px'} h={'40px'} variant={'primary'} onClick={applyCb}>
@@ -256,7 +256,8 @@ export default function EditApp({ appName }: { appName?: string }) {
overflow={'auto'}
position={'relative'}
borderRadius={'12px'}
background={'linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0.70) 100%)'}>
background={'linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0.70) 100%)'}
>
<Flex
zIndex={99}
position={'sticky'}
@@ -268,13 +269,15 @@ export default function EditApp({ appName }: { appName?: string }) {
justifyContent={'start'}
alignItems={'center'}
backgroundColor={'rgba(255, 255, 255)'}
backdropBlur={'100px'}>
backdropBlur={'100px'}
>
<Flex
alignItems={'center'}
fontWeight={500}
fontSize={16}
color={'#7B838B'}
cursor={'pointer'}>
cursor={'pointer'}
>
<Flex
alignItems={'center'}
css={{
@@ -285,14 +288,16 @@ export default function EditApp({ appName }: { appName?: string }) {
fill: '#219BF4'
}
}
}}>
}}
>
<Icon
ml={'19px'}
viewBox="0 0 15 15"
fill={'#24282C'}
w={'15px'}
h="15px"
onClick={() => router.push('/')}>
onClick={() => router.push('/')}
>
<path d="M9.1875 13.1875L3.92187 7.9375C3.85937 7.875 3.81521 7.80729 3.78937 7.73438C3.76312 7.66146 3.75 7.58333 3.75 7.5C3.75 7.41667 3.76312 7.33854 3.78937 7.26562C3.81521 7.19271 3.85937 7.125 3.92187 7.0625L9.1875 1.79687C9.33333 1.65104 9.51562 1.57812 9.73438 1.57812C9.95312 1.57812 10.1406 1.65625 10.2969 1.8125C10.4531 1.96875 10.5312 2.15104 10.5312 2.35938C10.5312 2.56771 10.4531 2.75 10.2969 2.90625L5.70312 7.5L10.2969 12.0938C10.4427 12.2396 10.5156 12.4192 10.5156 12.6325C10.5156 12.8463 10.4375 13.0312 10.2812 13.1875C10.125 13.3438 9.94271 13.4219 9.73438 13.4219C9.52604 13.4219 9.34375 13.3438 9.1875 13.1875Z" />
</Icon>
<Text ml="4px" onClick={() => router.push('/')}>
@@ -303,7 +308,8 @@ export default function EditApp({ appName }: { appName?: string }) {
<Text
onClick={copyTemplateLink}
_hover={{ fill: '#219BF4', color: '#219BF4' }}
color={router.pathname === '/deploy' ? '#262A32' : '#7B838B'}>
color={router.pathname === '/deploy' ? '#262A32' : '#7B838B'}
>
{data?.templateYaml?.metadata?.name}
</Text>
</Flex>
@@ -314,7 +320,8 @@ export default function EditApp({ appName }: { appName?: string }) {
flexDirection={'column'}
width={'100%'}
flexGrow={1}
backgroundColor={'rgba(255, 255, 255, 0.90)'}>
backgroundColor={'rgba(255, 255, 255, 0.90)'}
>
<Header
cloudDomain={platformEnvs?.SEALOS_CLOUD_DOMAIN || ''}
templateDetail={data?.templateYaml!}
@@ -190,7 +190,8 @@ export default function Develop() {
borderRadius={'8px'}
overflowY={'hidden'}
overflowX={'scroll'}
flex={1}>
flex={1}
>
{/* left */}
<Flex flexDirection={'column'} w="50%" borderRight={'1px solid #EFF0F1'}>
<Flex
@@ -200,7 +201,8 @@ export default function Develop() {
alignItems={'center'}
backgroundColor={'#F8FAFB'}
px="36px"
borderRadius={'8px 8px 0px 0px '}>
borderRadius={'8px 8px 0px 0px '}
>
<MyIcon name="dev" color={'#24282C'} w={'24px'} h={'24px'}></MyIcon>
<Text fontWeight={'500'} fontSize={'16px'} color={'#24282C'} ml="8px">
{t('develop.Development')}
@@ -228,7 +230,8 @@ export default function Develop() {
alignItems={'center'}
backgroundColor={'#F8FAFB'}
pl="42px"
borderRadius={'8px 8px 0px 0px '}>
borderRadius={'8px 8px 0px 0px '}
>
<MyIcon name="eyeShow" color={'#24282C'} w={'24px'} h={'24px'}></MyIcon>
<Text fontWeight={'500'} fontSize={'16px'} color={'#24282C'} ml="8px">
{t('develop.Preview')}
@@ -240,7 +243,8 @@ export default function Develop() {
pt="26px"
pr={{ sm: '20px', md: '60px' }}
borderBottom={'1px solid #EFF0F1'}
flexDirection={'column'}>
flexDirection={'column'}
>
<Text fontWeight={'500'} fontSize={'18px'} color={'#24282C'}>
{t('develop.Configure Form')}
</Text>
@@ -256,7 +260,8 @@ export default function Develop() {
minW={'100px'}
h={'34px'}
variant={'link'}
onClick={handleExportYaml}>
onClick={handleExportYaml}
>
{t('Export')} Yaml
</Button>
</Flex>
@@ -73,13 +73,15 @@ export default function AppList() {
borderRadius={'12px'}
background={'linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0.70) 100%)'}
py={'36px'}
px="42px">
px="42px"
>
<Grid
justifyContent={'center'}
w={'100%'}
gridTemplateColumns="repeat(auto-fill,minmax(300px,1fr))"
gridGap={'24px'}
minW={'765px'}>
minW={'765px'}
>
{filterData &&
filterData?.map((item: TemplateType) => {
return (
@@ -99,7 +101,8 @@ export default function AppList() {
borderRadius={'8px'}
backgroundColor={'#fff'}
boxShadow={'0px 2px 4px 0px rgba(187, 196, 206, 0.25)'}
border={'1px solid #EAEBF0'}>
border={'1px solid #EAEBF0'}
>
<Box
p={'6px'}
w={'48px'}
@@ -107,7 +110,8 @@ export default function AppList() {
boxShadow={'0px 1px 2px 0.5px rgba(84, 96, 107, 0.20)'}
borderRadius={'4px'}
backgroundColor={'#fff'}
border={' 1px solid rgba(255, 255, 255, 0.50)'}>
border={' 1px solid rgba(255, 255, 255, 0.50)'}
>
<Image src={item?.spec?.icon} alt="" width={'36px'} height={'36px'} />
</Box>
<Flex mt={'12px'} alignItems={'center'} justifyContent="space-between">
@@ -123,7 +127,8 @@ export default function AppList() {
h={'28px'}
borderRadius={'4px'}
border={'1px solid #DEE0E2'}
backgroundColor={'#F4F6F8'}>
backgroundColor={'#F4F6F8'}
>
Deploy
</Flex>
</Flex>
@@ -139,7 +144,8 @@ export default function AppList() {
mt={'8px'}
fontSize={'12px'}
color={'5A646E'}
fontWeight={400}>
fontWeight={400}
>
{item?.spec?.description}
</Text>
<Flex mt={'auto'} justifyContent={'space-between'} alignItems={'center'}>
@@ -155,7 +161,8 @@ export default function AppList() {
fill="#5A646E"
_hover={{
fill: '#0884DD'
}}>
}}
>
<path d="M13.6667 9.00004C13.4 9.00004 13.1667 9.23337 13.1667 9.50004V13.5C13.1667 13.6 13.1 13.6667 13 13.6667H3C2.9 13.6667 2.83333 13.6 2.83333 13.5V3.50004C2.83333 3.40004 2.9 3.33337 3 3.33337H7C7.26667 3.33337 7.5 3.10004 7.5 2.83337C7.5 2.56671 7.26667 2.33337 7 2.33337H3C2.36667 2.33337 1.83333 2.86671 1.83333 3.50004V13.5C1.83333 14.1334 2.36667 14.6667 3 14.6667H13C13.6333 14.6667 14.1667 14.1334 14.1667 13.5V9.50004C14.1667 9.23337 13.9333 9.00004 13.6667 9.00004Z" />
<path d="M13.6667 2.33337H10C9.73333 2.33337 9.5 2.56671 9.5 2.83337C9.5 3.10004 9.73333 3.33337 10 3.33337H12.4667L7.96667 7.80004C7.76667 8.00004 7.76667 8.30004 7.96667 8.50004C8.06667 8.60004 8.2 8.63337 8.33333 8.63337C8.46667 8.63337 8.6 8.60004 8.7 8.50004L13.1667 4.03337V6.50004C13.1667 6.76671 13.4 7.00004 13.6667 7.00004C13.9333 7.00004 14.1667 6.76671 14.1667 6.50004V2.83337C14.1667 2.56671 13.9333 2.33337 13.6667 2.33337Z" />
</Icon>
@@ -112,7 +112,8 @@ export default function AppList({ instanceName }: { instanceName: string }) {
variant={'base'}
leftIcon={<MyIcon name={'detail'} transform={'translateY(-1px)'} />}
px={3}
onClick={() => handleToDetailPage(item.name)}>
onClick={() => handleToDetailPage(item.name)}
>
{t('Details')}
</Button>
</Flex>
@@ -141,14 +142,16 @@ export default function AppList({ instanceName }: { instanceName: string }) {
justifyContent={'center'}
alignItems={'center'}
background={'white'}
p="32px">
p="32px"
>
<Flex
border={'1px dashed #9CA2A8'}
borderRadius="50%"
w={'48px'}
h={'48px'}
justifyContent="center"
alignItems={'center'}>
alignItems={'center'}
>
<MyIcon color={'#7B838B'} name="empty"></MyIcon>
</Flex>
<Text mt={'12px'} fontSize={14} color={'#5A646E'}>
@@ -100,7 +100,8 @@ export default function CronJobList({ instanceName }: { instanceName: string })
variant={'base'}
leftIcon={<MyIcon name={'detail'} transform={'translateY(-1px)'} />}
px={3}
onClick={() => handleToDetailPage(item.name)}>
onClick={() => handleToDetailPage(item.name)}
>
{t('Details')}
</Button>
</Flex>
@@ -129,14 +130,16 @@ export default function CronJobList({ instanceName }: { instanceName: string })
justifyContent={'center'}
alignItems={'center'}
background={'white'}
p="32px">
p="32px"
>
<Flex
border={'1px dashed #9CA2A8'}
borderRadius="50%"
w={'48px'}
h={'48px'}
justifyContent="center"
alignItems={'center'}>
alignItems={'center'}
>
<MyIcon color={'#7B838B'} name="empty"></MyIcon>
</Flex>
<Text mt={'12px'} fontSize={14} color={'#5A646E'}>
@@ -98,7 +98,8 @@ export default function AppList({ instanceName }: { instanceName: string }) {
variant={'base'}
leftIcon={<MyIcon name={'detail'} transform={'translateY(-1px)'} />}
px={3}
onClick={() => handleToDetailPage(item.name)}>
onClick={() => handleToDetailPage(item.name)}
>
{t('Details')}
</Button>
</Flex>
@@ -129,14 +130,16 @@ export default function AppList({ instanceName }: { instanceName: string }) {
justifyContent={'center'}
alignItems={'center'}
background={'white'}
p="32px">
p="32px"
>
<Flex
border={'1px dashed #9CA2A8'}
borderRadius="50%"
w={'48px'}
h={'48px'}
justifyContent="center"
alignItems={'center'}>
alignItems={'center'}
>
<MyIcon color={'#7B838B'} name="empty"></MyIcon>
</Flex>
<Text mt={'12px'} fontSize={14} color={'#5A646E'}>
@@ -86,7 +86,8 @@ const DelModal = ({
variant={'solid'}
isDisabled={inputValue !== name}
isLoading={loading}
onClick={handleDelApp}>
onClick={handleDelApp}
>
{t('Confirm deletion')}
</Button>
</ModalFooter>
@@ -81,14 +81,16 @@ export default function OtherList({ instanceName }: { instanceName: string }) {
justifyContent={'center'}
alignItems={'center'}
background={'white'}
p="32px">
p="32px"
>
<Flex
border={'1px dashed #9CA2A8'}
borderRadius="50%"
w={'48px'}
h={'48px'}
justifyContent="center"
alignItems={'center'}>
alignItems={'center'}
>
<MyIcon color={'#7B838B'} name="empty"></MyIcon>
</Flex>
<Text mt={'12px'} fontSize={14} color={'#5A646E'}>