Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:frontend license app #4203

Merged
merged 4 commits into from Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/providers/license/.env.template
@@ -1,5 +1,6 @@
NEXT_PUBLIC_MOCK_USER=
SEALOS_DOMAIN="cloud.sealos.io"
LICENSE_DOMAIN="dev.sealos.top"
MONGODB_URI=
# Same as desktop important
PASSWORD_SALT=
# PASSWORD_SALT=
7 changes: 7 additions & 0 deletions frontend/providers/license/deploy/manifests/deploy.yaml.tmpl
@@ -1,4 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
app: license-frontend
name: license-frontend
---
apiVersion: v1
kind: ConfigMap
metadata:
name: license-frontend-config
Expand Down
12 changes: 7 additions & 5 deletions frontend/providers/license/public/locales/en/common.json
@@ -1,12 +1,14 @@
{
"Upload Token File": "Upload Token File",
"Upload Token File": "Upload License File",
"Activate License": "Activate License",
"Activation Record": "Activation Record",
"Purchase License": "Purchase License",
"Please go to": "Please go to",
"Purchase": "purchase a license.",
"Purchase": "purchase",
"Activation Time": "Activation time: ",
"Go to the message center to see the results": "Go to the message center to see the results",
"token does not exist": "token does not exist",
"No Record": "No Record"
}
"token does not exist": "license does not exist",
"No Record": "No Record",
"Activation time": "Activation time",
"Activation Successful": "Activation Successful"
}
14 changes: 8 additions & 6 deletions frontend/providers/license/public/locales/zh/common.json
@@ -1,12 +1,14 @@
{
"Upload Token File": "上传 token 文件",
"Upload Token File": "上传 License 文件",
"Activate License": "激活 License",
"Activation Record": "激活记录",
"Purchase License": "购买 License",
"Please go to": "请到",
"Purchase": "购买 license",
"Please go to": "请前往",
"Purchase": "购买",
"Activation Time": "激活时间: ",
"Go to the message center to see the results": "前往消息中心查看结果",
"token does not exist": "token 不存在",
"No Record": "暂无记录"
}
"token does not exist": "license 不存在",
"No Record": "暂无记录",
"Activation time": "激活时间",
"Activation Successful": "激活成功"
}
4 changes: 2 additions & 2 deletions frontend/providers/license/src/api/license.ts
@@ -1,11 +1,11 @@
import { GET, POST } from '@/services/request';
import { License } from '@/types';
import { LicenseRecord } from '@/types';

export const applyLicense = (yamlList: string[], type: 'create' | 'replace' | 'update') =>
POST('/api/applyYamlList', { yamlList, type });

export const getLicenseRecord = ({ page = 1, pageSize = 10 }: { page: number; pageSize: number }) =>
GET<{
totalCount: number;
items: License[];
items: LicenseRecord[];
}>('/api/license/getLicense', { page, pageSize });
Expand Up @@ -10,7 +10,7 @@ import { DragEvent, useCallback, useState } from 'react';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
const fileImgs = [
{
reg: /txt/gi,
reg: /yaml/gi,
src: (
<Icon viewBox="0 0 16 17" fill="#7B838B">
<path d="M9.33334 2.12411H4.00001C3.26667 2.12411 2.67334 2.72411 2.67334 3.45745L2.66667 14.1241C2.66667 14.8574 3.26 15.4574 3.99334 15.4574H12C12.7333 15.4574 13.3333 14.8574 13.3333 14.1241V6.12411L9.33334 2.12411ZM4.00001 14.1241V3.45745H8.66667V6.79078H12V14.1241H4.00001Z" />
Expand Down Expand Up @@ -49,15 +49,17 @@ const FileSelect = ({ fileExtension, setFiles, files, ...props }: Props) => {
// Parse file by file
let promise = Promise.resolve<FileItemType[]>([]);
files.forEach((file) => {
console.log(file, 'fiel');

promise = promise.then(async (result) => {
const extension = file?.name?.split('.')?.pop()?.toLowerCase();

/* text file */
const hardcodedRegex = /txt/gi;
const hardcodedRegex = /yaml/gi;
const icon = fileImgs.find((item) => hardcodedRegex.test(file.name))?.src;
let text = await (async () => {
switch (extension) {
case 'txt':
case 'yaml':
return readTxtContent(file);
}
return '';
Expand Down
12 changes: 4 additions & 8 deletions frontend/providers/license/src/pages/api/license/getLicense.tsx
Expand Up @@ -10,19 +10,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { kube_user } = await getK8s({
kubeconfig: await authSession(req)
});
const userId = kube_user.name;
console.log(userId, 'userId');
console.log(kube_user?.name, 'user get license record');

const skip = (parseInt(page) - 1) * parseInt(pageSize);

const license_collection = await connectToLicenseCollection();
const totalCountPipeline = [
{ $match: { uid: userId, 'meta.token': { $ne: '' } } },
{ $count: 'totalCount' }
];
const totalCountPipeline = [{ $match: { token: { $ne: '' } } }, { $count: 'totalCount' }];
const contentPipeline = [
{ $match: { uid: userId, 'meta.token': { $ne: '' } } },
{ $sort: { 'meta.createTime': -1 } },
{ $match: { token: { $ne: '' } } },
{ $sort: { activationTime: -1 } },
{ $skip: skip },
{ $limit: parseInt(pageSize) }
];
Expand Down
2 changes: 0 additions & 2 deletions frontend/providers/license/src/pages/api/platform/getEnv.ts
Expand Up @@ -5,15 +5,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';

export type Response = {
domain?: string;
hid: string; // PASSWORD_SALT
LICENSE_DOMAIN: string;
};

export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResp>) {
jsonRes<Response>(res, {
data: {
domain: process.env.SEALOS_DOMAIN || 'cloud.sealos.io',
hid: hashCrypto(process.env.PASSWORD_SALT || ''),
LICENSE_DOMAIN: process.env.LICENSE_DOMAIN || 'cloud.sealos.io'
}
});
Expand Down
92 changes: 44 additions & 48 deletions frontend/providers/license/src/pages/index.tsx
Expand Up @@ -8,9 +8,9 @@ import { useToast } from '@/hooks/useToast';
import download from '@/utils/downloadFIle';
import { serviceSideProps } from '@/utils/i18n';
import { json2License } from '@/utils/json2Yaml';
import { useCopyData } from '@/utils/tools';
import { Box, Flex, Icon, Image, Text } from '@chakra-ui/react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { addHoursToTime, formatTime, useCopyData } from '@/utils/tools';
import { Box, Flex, Icon, Image, Link, Text } from '@chakra-ui/react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { debounce } from 'lodash';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
Expand All @@ -23,32 +23,33 @@ export default function LicenseApp() {
const [pageSize, setPageSize] = useState(5);
const { copyData } = useCopyData();
const [purchaseLink, setPurchaseLink] = useState('');
const queryClient = useQueryClient();

const licenseMutation = useMutation({
mutationFn: (yamlList: string[]) => applyLicense(yamlList, 'create'),
onSuccess(data) {
console.log(data, 'data');
toast({
title: t('Go to the message center to see the results'),
title: t('Activation Successful'),
status: 'success'
});
queryClient.invalidateQueries(['getLicenseActive']);
},
onError(error) {
onError(error: { message?: string }) {
console.log(error);
if (error?.message && typeof error?.message === 'string') {
toast({
title: error.message,
status: 'error'
});
}
}
});

useQuery(['getPlatformEnv'], () => getPlatformEnv(), {
onSuccess(data) {
const hid = data.hid;
if (!hid) {
return toast({
title: 'env hid error',
status: 'error'
});
}
const encodedHid = encodeURIComponent(hid);
const main = data.LICENSE_DOMAIN;
const link = `https:/${main}/license?hid=${encodedHid}`;
const link = `https://${main}`;
setPurchaseLink(link);
}
});
Expand All @@ -68,16 +69,6 @@ export default function LicenseApp() {
licenseMutation.mutate(yamlList);
}, 500);

const copyLicenseLink = () => {
if (!purchaseLink) {
return toast({
title: 'env hid error',
status: 'error'
});
}
copyData(purchaseLink);
};

const downloadToken = (token: string) => {
const result = Buffer.from(token, 'binary').toString('base64');
download('token.txt', result);
Expand All @@ -102,23 +93,17 @@ export default function LicenseApp() {
src="/icons/license-bg.svg"
alt="license"
objectFit="cover"
borderRadius={'16px'}
/>
<Box position={'absolute'} color={'#FFF'} top={'180px'} left={'80px'}>
<Text fontSize={'32px'} fontWeight={600}>
{t('Purchase License')}
</Text>
<Flex mt="45px" cursor={'copy'} onClick={copyLicenseLink}>
<Flex mt="45px" cursor={'copy'}>
{t('Please go to')}
<Text
px="4px"
w="220px"
color={'#36ADEF'}
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
>
{purchaseLink}
</Text>
<Link color={'#36ADEF'} px={'4px'} href={purchaseLink} isExternal>
Sealos License
</Link>
{t('Purchase')}
</Flex>
</Box>
Expand All @@ -132,7 +117,7 @@ export default function LicenseApp() {
<Text color={'#262A32'} fontSize={'24px'} fontWeight={600}>
{t('Activate License')}
</Text>
<FileSelect fileExtension={'.txt'} files={files} setFiles={setFiles} />
<FileSelect fileExtension={'.yaml'} files={files} setFiles={setFiles} />
<Flex
userSelect={'none'}
ml={'auto'}
Expand Down Expand Up @@ -174,12 +159,21 @@ export default function LicenseApp() {
</Text>
<CurrencySymbol />
<Text ml="6px" color={'#5A646E'} fontSize={'14px'} fontWeight={500}>
{license.payload?.amt}
{license?.claims?.data?.amount}
</Text>
<Text color={'#5A646E'} fontSize={'12px'} fontWeight={500} ml="auto">
{t('Activation time')} {license.meta.createTime}
<Text
color={'#5A646E'}
fontSize={'12px'}
fontWeight={500}
ml="auto"
mr={{
sm: '20px',
md: '34px'
}}
>
{t('Activation time')} {addHoursToTime(license?.activationTime || '')}
</Text>
<Flex
{/* <Flex
alignItems={'center'}
mx={{
sm: '8px',
Expand All @@ -192,14 +186,14 @@ export default function LicenseApp() {
fontSize={'14px'}
fontWeight={600}
px="8px"
onClick={() => downloadToken(license?.meta?.token)}
onClick={() => downloadToken(license?.token)}
>
Token
License
</Text>
<Icon fill="#1D8CDC" viewBox="0 0 16 16">
<path d="M4.76693 14.0667C4.60026 13.9 4.51693 13.7027 4.51693 13.4747C4.51693 13.2471 4.60026 13.05 4.76693 12.8833L9.65026 8L4.75026 3.1C4.59471 2.94444 4.51693 2.75 4.51693 2.51666C4.51693 2.28333 4.60026 2.08333 4.76693 1.91666C4.93359 1.75 5.13093 1.66666 5.35893 1.66666C5.58648 1.66666 5.78359 1.75 5.95026 1.91666L11.5503 7.53333C11.6169 7.6 11.6643 7.67222 11.6923 7.75C11.7198 7.82778 11.7336 7.91111 11.7336 8C11.7336 8.08889 11.7198 8.17222 11.6923 8.25C11.6643 8.32778 11.6169 8.4 11.5503 8.46666L5.93359 14.0833C5.77804 14.2389 5.58648 14.3167 5.35893 14.3167C5.13093 14.3167 4.93359 14.2333 4.76693 14.0667Z" />
</Icon>
</Flex>
</Flex> */}
</Flex>
))}
</Box>
Expand All @@ -219,11 +213,13 @@ export default function LicenseApp() {
</Flex>
)}

<Pagination
totalItems={data?.totalCount || 0}
itemsPerPage={pageSize}
onPageChange={(page: number) => setPage(page)}
/>
{data?.totalCount !== 0 && (
<Pagination
totalItems={data?.totalCount || 0}
itemsPerPage={pageSize}
onPageChange={(page: number) => setPage(page)}
/>
)}
</Box>
</Flex>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/providers/license/src/services/request.ts
Expand Up @@ -94,7 +94,7 @@ request.interceptors.response.use(

const apiResp = data as ApiResp;
if (apiResp.code < 200 || apiResp.code >= 400) {
return Promise.reject(apiResp.code + ':' + apiResp.message);
return Promise.reject(apiResp);
}

response.data = apiResp.data;
Expand Down
50 changes: 36 additions & 14 deletions frontend/providers/license/src/types/license.d.ts
@@ -1,20 +1,42 @@
export interface License {
_id: string;
uid: string;
meta: {
token: string;
createTime: string; // 激活时间
export type LicenseRecord = {
_id?: string;
token: string;
activationTime: string;
claims: {
type: string;
data: {
amount: number;
};
registeredclaims: {
issuer: string;
subject: string;
audience: null | string[];
expiresat: {
time: Date;
};
notbefore: null | string;
issuedat: {
time: Date;
};
id: string;
};
};

payload: {
iss: string;
iat: Date; // 签发日期
exp: Date; // 有效期
amt: number; // 额度
};
}
};

export type LicenseCollection = {
uid: string;
license: License[];
};

export type LicenseYaml = {
apiVersion: string;
kind: string;
metadata: {
name: string;
namespace: string;
};
spec: {
type: string;
token: string;
};
};