Skip to content

Commit

Permalink
feat:template support parsing option lists (#4533)
Browse files Browse the repository at this point in the history
* feat:template support parsing option lists

Signed-off-by: jingyang <3161362058@qq.com>

* fix

---------

Signed-off-by: jingyang <3161362058@qq.com>
  • Loading branch information
zjy365 committed Feb 26, 2024
1 parent 649c535 commit ff26b69
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 70 deletions.
3 changes: 3 additions & 0 deletions frontend/desktop/deploy/manifests/rbac.yaml
Expand Up @@ -86,6 +86,9 @@ rules:
- apiGroups: ['account.sealos.io']
resources: ['accounts']
verbs: ['list', 'get', 'create', 'update', 'patch', 'watch']
- apiGroups: ['notification.sealos.io']
resources: ['notifications']
verbs: ['list', 'get', 'create', 'update', 'patch', 'watch']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down
6 changes: 5 additions & 1 deletion frontend/desktop/src/api/platform.ts
@@ -1,5 +1,5 @@
import request from '@/services/request';
import { ApiResp, Session, SystemConfigType, SystemEnv } from '@/types';
import { ApiResp, NotificationItem, Session, SystemConfigType, SystemEnv } from '@/types';
import { AccountCRD } from '@/types/user';

// handle baidu
Expand Down Expand Up @@ -53,3 +53,7 @@ export const getWechatResult = (payload: { code: string }) =>
request.get<any, ApiResp<Session>>('/api/auth/publicWechat/getWechatResult', {
params: payload
});

export const getGlobalNotification = () => {
return request.get<any, ApiResp<NotificationItem>>('/api/notification/global');
};
20 changes: 20 additions & 0 deletions frontend/desktop/src/components/desktop_content/index.tsx
Expand Up @@ -12,6 +12,9 @@ import { MouseEvent, useCallback, useEffect, useState } from 'react';
import { createMasterAPP, masterApp } from 'sealos-desktop-sdk/master';
import IframeWindow from './iframe_window';
import styles from './index.module.scss';
import { useQuery } from '@tanstack/react-query';
import { getGlobalNotification } from '@/api/platform';
import { useMessage } from '@sealos/ui';

const TimeComponent = dynamic(() => import('./time'), {
ssr: false
Expand All @@ -22,6 +25,7 @@ export default function DesktopContent(props: any) {
const { installedApps: apps, runningInfo, openApp, setToHighestLayerById } = useAppStore();
const renderApps = apps.filter((item: TApp) => item?.displayType === 'normal');
const [maxItems, setMaxItems] = useState(10);
const { message } = useMessage();

const handleDoubleClick = (e: MouseEvent<HTMLDivElement>, item: TApp) => {
e.preventDefault();
Expand Down Expand Up @@ -83,6 +87,22 @@ export default function DesktopContent(props: any) {

const { UserGuide, showGuide } = useDriver({ openDesktopApp });

useQuery(['getGlobalNotification'], getGlobalNotification, {
onSuccess(data) {
const newID = data.data?.metadata?.uid;
if (!newID || newID === localStorage.getItem('GlobalNotification')) return;
localStorage.setItem('GlobalNotification', newID);
const title =
i18n.language === 'zh' && data.data?.spec?.i18ns?.zh?.message
? data.data?.spec?.i18ns?.zh?.message
: data.data?.spec?.message;
message({
title: title,
status: 'info'
});
}
});

return (
<Box
id="desktop"
Expand Down
27 changes: 1 addition & 26 deletions frontend/desktop/src/components/notification/index.tsx
Expand Up @@ -10,32 +10,7 @@ import { produce } from 'immer';
import { useTranslation } from 'next-i18next';
import { useEffect, useState } from 'react';
import styles from './index.module.scss';

type NotificationItem = {
metadata: {
creationTimestamp: string;
labels: {
isRead: string;
};
name: string;
namespace: string;
uid: string;
};
spec: {
from: string;
message: string;
timestamp: number;
title: string;
desktopPopup?: boolean;
i18ns?: {
zh?: {
from: string;
message: string;
title: string;
};
};
};
};
import { NotificationItem } from '@/types';

type TNotification = {
disclosure: UseDisclosureReturn;
Expand Down
41 changes: 41 additions & 0 deletions frontend/desktop/src/pages/api/notification/global.ts
@@ -0,0 +1,41 @@
import { authSession } from '@/services/backend/auth';
import { K8sApiDefault } from '@/services/backend/kubernetes/admin';
import { CRDMeta, ListCRD } from '@/services/backend/kubernetes/user';
import { jsonRes } from '@/services/backend/response';
import { NotificationItem } from '@/types';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const payload = await authSession(req.headers);
if (!payload) return jsonRes(res, { code: 401, message: 'failed to get info' });
const defaultKc = K8sApiDefault();

const notification_meta: CRDMeta = {
group: 'notification.sealos.io',
version: 'v1',
namespace: 'sealos',
plural: 'notifications'
};

const listCrd = (await ListCRD(defaultKc, notification_meta)) as {
body: {
items: NotificationItem[];
};
};

const compareByTimestamp = (a: NotificationItem, b: NotificationItem) =>
b?.spec?.timestamp - a?.spec?.timestamp;

if (listCrd.body?.items) {
listCrd.body.items.sort(compareByTimestamp);
if (listCrd.body.items[0]) {
return jsonRes(res, { data: listCrd.body.items[0] });
}
}

jsonRes(res, { data: listCrd.body });
} catch (err) {
jsonRes(res, { code: 500, data: err });
}
}
26 changes: 26 additions & 0 deletions frontend/desktop/src/types/crd.ts
Expand Up @@ -96,3 +96,29 @@ export type TAppCRList = {
kind: 'AppList';
metadata: { continue: string; resourceVersion: string };
};

export type NotificationItem = {
metadata: {
creationTimestamp: string;
labels: {
isRead: string;
};
name: string;
namespace: string;
uid: string;
};
spec: {
from: string;
message: string;
timestamp: number;
title: string;
desktopPopup?: boolean;
i18ns?: {
zh?: {
from: string;
message: string;
title: string;
};
};
};
};
1 change: 1 addition & 0 deletions frontend/providers/template/src/components/Icon/index.tsx
Expand Up @@ -50,5 +50,6 @@ const MyIcon = ({
<Icon as={map[name]} verticalAlign={'text-top'} fill={'currentColor'} w={w} h={h} {...props} />
) : null;
};
export type IconType = keyof typeof map;

export default MyIcon;
134 changes: 95 additions & 39 deletions frontend/providers/template/src/components/Select/index.tsx
@@ -1,21 +1,34 @@
import React from 'react';
import { Menu, MenuButton, MenuList, MenuItem, Button, useDisclosure } from '@chakra-ui/react';
import React, { useRef, forwardRef, useMemo } from 'react';
import {
Menu,
Box,
MenuList,
MenuItem,
Button,
useDisclosure,
useOutsideClick
} from '@chakra-ui/react';
import type { ButtonProps } from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { useTranslation } from 'next-i18next';
import MyIcon, { type IconType } from '../Icon';

interface Props extends ButtonProps {
value?: string;
placeholder?: string;
list: {
label: string;
id: string;
icon?: string;
label: string | React.ReactNode;
value: string;
}[];
width?: number | string;
onchange?: (val: string) => void;
}

const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props }: Props) => {
const { t } = useTranslation();
const MySelect = (
{ placeholder, value, width = 'auto', list, onchange, ...props }: Props,
selectRef: any
) => {
const ref = useRef<HTMLButtonElement>(null);
const SelectRef = useRef<HTMLDivElement>(null);
const menuItemStyles = {
borderRadius: 'sm',
py: 2,
Expand All @@ -27,56 +40,99 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props
};
const { isOpen, onOpen, onClose } = useDisclosure();

useOutsideClick({
ref: SelectRef,
handler: () => {
onClose();
}
});

const activeMenu = useMemo(() => list.find((item) => item.value === value), [list, value]);

return (
<Menu autoSelect={false} onOpen={onOpen} onClose={onClose}>
<MenuButton as={'span'}>
<Menu autoSelect={false} isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
<Box
ref={SelectRef}
position={'relative'}
onClick={() => {
isOpen ? onClose() : onOpen();
}}
>
<Button
ref={ref}
width={width}
px={3}
variant={'base'}
display={'flex'}
alignItems={'center'}
justifyContent={'space-between'}
_active={{
transform: ''
}}
{...(isOpen
? {
boxShadow: '0px 0px 4px #A8DBFF',
borderColor: 'myBlue.600'
}
: {})}
: { borderColor: '#DEE0E2' })}
{...props}
>
{list.find((item) => item.id === value)?.label || placeholder}
{activeMenu ? (
<>
{!!activeMenu.icon && <MyIcon mr={2} name={activeMenu.icon as IconType} w={'18px'} />}
<Box>{activeMenu.label}</Box>
</>
) : (
<>
<Box>{placeholder}</Box>
</>
)}

<Box flex={1} />
<ChevronDownIcon />
</Button>
</MenuButton>
<MenuList
minW={`${width} !important`}
p={'6px'}
border={'1px solid #fff'}
boxShadow={'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'}
zIndex={99}
>
{list.map((item) => (
<MenuItem
key={item.id}
{...menuItemStyles}
{...(value === item.id
? {
color: 'myBlue.600'

<MenuList
minW={(() => {
const w = ref.current?.clientWidth;
if (w) {
return `${w}px !important`;
}
return Array.isArray(width)
? width.map((item) => `${item} !important`)
: `${width} !important`;
})()}
p={'6px'}
border={'1px solid #fff'}
boxShadow={
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
}
zIndex={99}
transform={'translateY(35px) !important'}
>
{list.map((item) => (
<MenuItem
key={item.value}
{...menuItemStyles}
{...(value === item.value
? {
color: 'myBlue.600'
}
: {})}
onClick={() => {
if (onchange && value !== item.value) {
onchange(item.value);
}
: {})}
onClick={() => {
if (onchange && value !== item.id) {
onchange(item.id);
}
}}
>
{t(item.label)}
</MenuItem>
))}
</MenuList>
}}
>
{!!item.icon && <MyIcon mr={2} name={item.icon as IconType} w={'18px'} />}
<Box>{item.label}</Box>
</MenuItem>
))}
</MenuList>
</Box>
</Menu>
);
};

export default MySelect;
export default React.memo(forwardRef(MySelect));

0 comments on commit ff26b69

Please sign in to comment.