Skip to content

Commit

Permalink
feat(frontend): static host (#4634)
Browse files Browse the repository at this point in the history
  • Loading branch information
xudaotutou committed Mar 28, 2024
1 parent af438cf commit 559154f
Show file tree
Hide file tree
Showing 16 changed files with 454 additions and 38 deletions.
27 changes: 27 additions & 0 deletions frontend/packages/ui/src/components/icons/WebHostIcon.tsx
@@ -0,0 +1,27 @@
import { createIcon } from '@chakra-ui/react';

const WebHostIcon = createIcon({
displayName: 'WebHostIcon',
viewBox: '0 0 24 25',
path: (
<g>
<path
d="M16.7888 8.95033L18.1049 9.03321C19.4093 9.11536 20.4433 10.2018 20.4433 11.5281C20.4433 12.3391 20.0571 13.0599 19.4586 13.5166C19.1336 13.7646 18.8718 14.118 18.8718 14.5268V14.8204C18.8718 15.4532 19.4609 15.9295 20.0253 15.6434C21.5259 14.8827 22.5543 13.3255 22.5543 11.5281C22.5543 9.08034 20.6471 7.07807 18.2376 6.92633L18.2298 6.9074C17.8901 6.08741 17.3923 5.34236 16.7647 4.71477C16.1371 4.08718 15.3921 3.58934 14.5721 3.24969C13.7521 2.91004 12.8732 2.73523 11.9857 2.73523C11.0981 2.73523 10.2193 2.91004 9.39931 3.24969C8.57932 3.58934 7.83426 4.08718 7.20667 4.71477C6.57908 5.34236 6.08125 6.08741 5.7416 6.9074L5.733 6.92827C3.33744 7.09439 1.44568 9.09031 1.44568 11.5281C1.44568 13.3217 2.46981 14.8762 3.96522 15.6386C4.52963 15.9263 5.12003 15.45 5.12003 14.8165V14.5209C5.12003 14.1134 4.85981 13.7609 4.53645 13.5128C3.94078 13.0559 3.55673 12.3368 3.55673 11.5281C3.55673 10.2073 4.58231 9.12419 5.87903 9.03427L7.18555 8.94368L7.69196 7.71527L7.69254 7.71387C7.92609 7.15056 8.26821 6.63871 8.69941 6.20751C9.13097 5.77595 9.64331 5.43361 10.2072 5.20006C10.771 4.9665 11.3754 4.84629 11.9857 4.84629C12.596 4.84629 13.2004 4.9665 13.7642 5.20006C14.3281 5.43361 14.8404 5.77595 15.272 6.20751C15.7035 6.63907 16.0459 7.15141 16.2794 7.71527L16.7888 8.95033Z"
fill="#7B838B"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.94662 10.9832C7.1317 10.9832 6.47107 11.6438 6.47107 12.4587V14.5482C6.47107 15.3631 7.13169 16.0237 7.94662 16.0237H16.0846C16.8995 16.0237 17.5602 15.3631 17.5602 14.5482V12.4587C17.5602 11.6438 16.8995 10.9832 16.0846 10.9832H7.94662ZM8.15741 12.6695V14.3374H15.8738V12.6695H8.15741Z"
fill="#7B838B"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.47107 18.3764C6.47107 17.5615 7.1317 16.9008 7.94662 16.9008H16.0846C16.8995 16.9008 17.5602 17.5615 17.5602 18.3764V20.4659C17.5602 21.2808 16.8995 21.9414 16.0846 21.9414H7.94662C7.13169 21.9414 6.47107 21.2808 6.47107 20.4659V18.3764ZM8.15741 20.2551V18.5872H15.8738V20.2551H8.15741Z"
fill="#7B838B"
/>
</g>
)
});
export default WebHostIcon;
4 changes: 3 additions & 1 deletion frontend/packages/ui/src/components/index.ts
@@ -1,4 +1,5 @@
import CopyIcon from './icons/CopyIcon';
import WebHostIcon from './icons/WebHostIcon';
import DownloadIcon from './icons/DownloadIcon';
import DeleteIcon from './icons/DeleteIcon';
import AddIcon from './icons/AddIcon';
Expand Down Expand Up @@ -94,5 +95,6 @@ export {
CloseIcon,
SortPolygonUpIcon,
SortPolygonDownIcon,
ProviderIcon
ProviderIcon,
WebHostIcon
};
1 change: 0 additions & 1 deletion frontend/providers/adminer/src/pages/api/apply.ts
Expand Up @@ -25,7 +25,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const kc = K8sApi(kubeconfig);

const kube_user = kc.getCurrentUser();

if (
!kube_user ||
!kube_user.name ||
Expand Down
Expand Up @@ -15,5 +15,11 @@
"s3ServiceParams": "Access Key",
"refresh": "Refresh",
"dailyEstimate": "Daily Estimate",
"deleteWarning": "Delete Warning"
"deleteWarning": "Delete Warning",
"Current Domain": "Current Domain",
"Enable Hosting": "Enable Hosting",
"Custom Domain": "Custom Domain",
"Disable Hosting": "Disable Hosting",
"Effective": "Effective",
"Unable to Access": "Unable to Access"
}
Expand Up @@ -9,7 +9,7 @@
"bucketList": "存储桶列表",
"noBucket": "您还没有创建存储桶",
"confirmDeleteBucket": "如果确认要删除这个存储桶吗?如果执行此操作,将删除该存储桶的所有数据。",
"enterBucketNameConfirmation": "请输入存储桶名字{{bucketName}}确认",
"enterBucketNameConfirmation": "请输入存储桶名字 {{bucketName}} 确认",
"enterValidBucketName": "请输入正确的存储桶名字",
"initializing": "正在初始化中"
}
Expand Up @@ -14,5 +14,11 @@
"refresh": "刷新",
"dailyEstimate": "每日估价",
"config": "基础配置",
"deleteWarning": "删除警告"
"deleteWarning": "删除警告",
"Current Domain": "当前域名",
"Custom Domain": "自定义域名",
"Disable Hosting": "关闭托管",
"Enable Hosting": "打开托管",
"Effective": "已生效",
"Unable to Access": "无法访问"
}
13 changes: 13 additions & 0 deletions frontend/providers/objectstorage/src/api/bucket.ts
@@ -1,6 +1,7 @@
import { Authority, QuotaData, TBucket, UserSecretData } from '@/consts';
import request from '@/services/request';
import { AxiosInstance } from 'axios';
import { ApiResp } from '@/services/backend/response';

export const _createBucket =
(request: AxiosInstance) => (data: { bucketName: string; bucketPolicy: Authority }) =>
Expand All @@ -26,3 +27,15 @@ export const _initUser = (request: AxiosInstance) => () =>
}
>('/api/user/init');
export const initUser = _initUser(request);

export const _getHostStatus = (request: AxiosInstance) => (data: { bucket: string }) =>
request.post<any, any[]>('/api/site/status', data);
export const getHostStatus = _getHostStatus(request);

export const _openHost = (request: AxiosInstance) => (data: { bucket: string }) =>
request.post<any>('/api/site/openHost', data);
export const openHost = _openHost(request);

export const _closeHost = (request: AxiosInstance) => (data: { bucket: string }) =>
request.post<any>('/api/site/closeHost', data);
export const closeHost = _closeHost(request);
@@ -0,0 +1,162 @@
import { Authority, QueryKey } from '@/consts';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import {
Box,
Circle,
HStack,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
Text
} from '@chakra-ui/react';
import MoreIcon from '@/components/Icons/MoreIcon';
import { WebHostIcon } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useOssStore } from '@/store/ossStore';
import { closeHost, getHostStatus, openHost } from '@/api/bucket';
import { useMemo } from 'react';
import { isArray } from 'lodash';
import { sealosApp } from 'sealos-desktop-sdk/app';

enum HostStatusType {
Running,
NotReady
}

export function HostStatus() {
const router = useRouter();
const { currentBucket } = useOssStore();
const { t } = useTranslation(['common', 'bucket']);
const queryClient = useQueryClient();
const { data, isSuccess } = useQuery(
[QueryKey.HostStatus, currentBucket?.name],
() =>
getHostStatus({
bucket: currentBucket!.name
}),
{
refetchInterval: 5 * 1000
}
);
const onHosting = isSuccess && !!data?.length;
const domain = useMemo(() => {
if (!isArray(data)) return '';
const ingress = data.find((x) => x.kind === 'Ingress');
if (!ingress) return '';
return ingress.spec.rules[0].host;
}, [data]);
const hostStatus =
currentBucket?.policy === Authority.private ? HostStatusType.NotReady : HostStatusType.Running;
const {
data: OpenData,
isSuccess: openIsSuccess,
mutate: openMutate,
isLoading: openLoading
} = useMutation(
[QueryKey.openHost, currentBucket?.name],
(bucket: string) => openHost({ bucket }),
{
onSuccess() {
queryClient.invalidateQueries([QueryKey.HostStatus]);
}
}
);
const {
data: closeData,
isSuccess: closeIsSuccess,
mutate: closeMutate,
isLoading: closeLoading
} = useMutation(
[QueryKey.closeHost, currentBucket?.name],
(bucket: string) => closeHost({ bucket }),
{
onSuccess() {
queryClient.invalidateQueries([QueryKey.HostStatus]);
}
}
);
return (
<Box
ml={'auto'}
my={'auto'}
pointerEvents={closeLoading || openLoading ? 'none' : 'initial'}
fontSize={'12px'}
>
{!onHosting ? (
<HStack
onClick={() => {
currentBucket?.name && openMutate(currentBucket?.name);
}}
cursor={'pointer'}
>
<WebHostIcon boxSize={'24px'} />
<Text fontSize={'12px'}>{t('Enable Hosting')}</Text>
</HStack>
) : (
<HStack>
<Text>
{t('Current Domain')}: {domain}
</Text>
<StatusTag hostStatus={hostStatus} />
<Menu>
<MenuButton
as={IconButton}
variant={'white-bg-icon'}
icon={<MoreIcon w="16px" h="16px" color="grayIron.600" />}
p="4px"
onClick={(e) => e.stopPropagation()}
>
{currentBucket?.policy === Authority.private ? '?' : 'ok'}
</MenuButton>
<MenuList p="6px" minW={'85px'} fontSize={'12px'} onClick={(e) => e.stopPropagation()}>
<MenuItem
px="4px"
py="6px"
minW={'100px'}
onClick={() => {
const name = `static-host-${currentBucket?.name}`;
sealosApp.runEvents('openDesktopApp', {
appKey: 'system-applaunchpad',
pathname: '/app/detail',
query: { name },
messageData: { type: 'InternalAppCall', name }
});
}}
>
{t('Custom Domain')}
</MenuItem>
<MenuItem
px="4px"
py="6px"
minW={'100px'}
onClick={() => {
currentBucket?.name && closeMutate(currentBucket?.name);
}}
>
{t('Disable Hosting')}
</MenuItem>
</MenuList>
</Menu>
</HStack>
)}
</Box>
);
}

function StatusTag({ hostStatus }: { hostStatus: HostStatusType }) {
const { t } = useTranslation('common');
return hostStatus === HostStatusType.Running ? (
<HStack gap={'4px'}>
<Circle size={'6px'} bg={hostStatus === HostStatusType.Running ? '#00A9A6' : 'warn.600'} />
<Text>{t('Effective')}</Text>
</HStack>
) : (
<HStack gap={'4px'}>
<Circle size={'6px'} bg={'warn.600'} />
<Text color={'warn.600'}>{t('Unable to Access')}</Text>
</HStack>
);
}
Expand Up @@ -17,6 +17,8 @@ import BucketHeader from './BucketHeader';
import FileManager from './FileManager';
import DataMonitor from './Monitor';
import { useTranslation } from 'next-i18next';
import { Authority } from '@/consts';
import { HostStatus } from '@/components/BucketContainer/HostStatus';

export default function BucketContainer(props: StackProps) {
const bucket = useOssStore((s) => s.currentBucket);
Expand Down Expand Up @@ -60,6 +62,7 @@ export default function BucketContainer(props: StackProps) {
<Text fontSize={'14px'}>{item.title}</Text>
</Tab>
))}
{bucket.policy !== Authority.private && <HostStatus />}
</TabList>
<TabPanels h="0" flex="auto" overflow={'auto'}>
<TabPanel h="full" p="0">
Expand Down
Expand Up @@ -15,6 +15,7 @@ import {
Link
} from '@chakra-ui/react';
import UploadIcon from '@/components/Icons/UploadIcon';

type TFileItem = {
file: File;
path: string;
Expand All @@ -27,6 +28,7 @@ import { useDropzone } from 'react-dropzone';
import { FolderPlaceholder, QueryKey } from '@/consts';
import { useTranslation } from 'next-i18next';
import { useToast } from '@/hooks/useToast';

export default function UploadModal({ ...styles }: Omit<IconButtonProps, 'aria-label'> & {}) {
const { onOpen, onClose, isOpen } = useDisclosure();
const { t, i18n } = useTranslation('file');
Expand Down Expand Up @@ -76,41 +78,57 @@ export default function UploadModal({ ...styles }: Omit<IconButtonProps, 'aria-l
onDropAccepted(files) {
if (!bucket) return;
setIsLoading(true);
const values = files.map((file) => {
// clear '/' and merege path
const Key = [...prefix, ...(Reflect.get(file, 'path') as string).split('/')]
.filter((v) => v !== '')
.join('/');
const reqV = {
Bucket: bucket.name,
Key,
ContentType: file.type,
Body: file
};
return reqV;
});
const values2: typeof values = [];
Promise.allSettled(
files.map((file) => {
// clear '/' and merege path
const Key = [...prefix, ...(Reflect.get(file, 'path') as string).split('/')]
.filter((v) => v !== '')
.join('/');
return mutation.mutateAsync({
Bucket: bucket.name,
Key,
ContentType: file.type,
Body: file
});
values.map((v) => {
return mutation.mutateAsync(v);
})
).then((results) => {
if (
results.some((res) => {
return res.status === 'rejected';
})
) {
toast({
status: 'error',
title: 'upload error'
});
} else {
toast({
status: 'success',
title: 'upload success'
)
.then((results) => {
// retry
results.forEach((res, i) => {
if (res.status === 'rejected') {
values2.push(values[i]);
}
});
}
// @ts-ignore
inputRef.current && (inputRef.current.value = null);
setIsLoading(false);
queryClient.invalidateQueries([QueryKey.minioFileList]);
onClose();
});
values.length = 0;
return Promise.allSettled(values2.map((v) => mutation.mutateAsync(v)));
})
.then((results) => {
if (
results.some((res) => {
return res.status === 'rejected';
})
) {
toast({
status: 'error',
title: 'upload error'
});
} else {
toast({
status: 'success',
title: 'upload success'
});
}
// @ts-ignore
inputRef.current && (inputRef.current.value = null);
setIsLoading(false);
queryClient.invalidateQueries([QueryKey.minioFileList]);
onClose();
});
},
async getFilesFromEvent(event) {
const files: File[] = [];
Expand Down

0 comments on commit 559154f

Please sign in to comment.