From 35fd44bb82e01792b4ae871b16d4aa731d0ab262 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Tue, 7 Jan 2025 15:09:18 +0800 Subject: [PATCH 01/48] feat: datePicker --- frontend/pnpm-lock.yaml | 6 + frontend/providers/applaunchpad/package.json | 2 + .../public/locales/en/common.json | 13 +- .../public/locales/zh/common.json | 13 +- .../src/components/DatePicker/index.tsx | 423 ++++++++++++++++++ .../src/components/Icon/icons/calendar.svg | 3 + .../src/components/Icon/icons/refresh.svg | 5 + .../src/components/Icon/icons/to.svg | 4 + .../src/components/Icon/index.tsx | 5 +- .../providers/applaunchpad/src/pages/_app.tsx | 2 + .../src/pages/app/detail/components/Logs.tsx | 57 +++ .../src/pages/app/detail/index.tsx | 14 +- .../providers/applaunchpad/src/store/date.ts | 28 ++ .../applaunchpad/src/styles/global.scss | 28 ++ 14 files changed, 590 insertions(+), 13 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/components/DatePicker/index.tsx create mode 100644 frontend/providers/applaunchpad/src/components/Icon/icons/calendar.svg create mode 100644 frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg create mode 100644 frontend/providers/applaunchpad/src/components/Icon/icons/to.svg create mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx create mode 100644 frontend/providers/applaunchpad/src/store/date.ts diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 826cb7e235c..4a9d4a716c5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -615,6 +615,9 @@ importers: base64-stream: specifier: ^1.0.0 version: 1.0.0 + date-fns: + specifier: ^2.30.0 + version: 2.30.0 dayjs: specifier: ^1.11.10 version: 1.11.10 @@ -672,6 +675,9 @@ importers: react: specifier: 18.2.0 version: 18.2.0 + react-day-picker: + specifier: ^8.8.2 + version: 8.9.1(date-fns@2.30.0)(react@18.2.0) react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) diff --git a/frontend/providers/applaunchpad/package.json b/frontend/providers/applaunchpad/package.json index 761f2dcabe9..eaab4efc708 100644 --- a/frontend/providers/applaunchpad/package.json +++ b/frontend/providers/applaunchpad/package.json @@ -26,6 +26,7 @@ "ansi_up": "^5.2.1", "axios": "^1.5.1", "base64-stream": "^1.0.0", + "date-fns": "^2.30.0", "dayjs": "^1.11.10", "decimal.js": "^10.4.3", "dns": "^0.2.2", @@ -45,6 +46,7 @@ "nprogress": "^0.2.0", "prettier": "^2.8.8", "react": "18.2.0", + "react-day-picker": "^8.8.2", "react-dom": "18.2.0", "react-hook-form": "^7.46.2", "react-i18next": "^14.1.2", diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 0062715f00c..6a8b2ddf1e5 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -257,7 +257,7 @@ "total_price_tip": "The estimated cost does not include port fees and traffic fees, and is subject to actual usage.", "nodeports": "NodePorts", "streaming_logs": "Streaming logs", - "within_5_minutes": "Within 5 minutes", + "within_5_minute": "Within 5 minute", "within_1_hour": "Within 1 hour", "within_1_day": "Within 1 day", "terminated_logs": "Terminated logs", @@ -277,5 +277,12 @@ "storage_path_placeholder": "For Example: /data" }, "guide_deploy_button": "Complete creation", - "shared": "Shared" -} + "filter": "Filter", + "start": "Start", + "end": "End", + "time_zone": "Time Zone", + "recently": "Recent", + "minute": "Minute", + "day": "Day", + "hour": "Hour" +} \ No newline at end of file diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 95df44ea1ac..2e92bb4b2c7 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -257,7 +257,7 @@ "total_price_tip": "预估费用不包括端口费用和流量费用,以实际使用为准", "nodeports": "外网端口", "streaming_logs": "实时日志", - "within_5_minutes": "五分钟内", + "within_5_minute": "五分钟内", "within_1_hour": "一小时内", "within_1_day": "一天内", "terminated_logs": "中断前", @@ -278,5 +278,12 @@ "storage_path_placeholder": "如:/data" }, "guide_deploy_button": "完成创建", - "shared": "共享" -} + "filter": "筛选", + "start": "开始", + "end": "结束", + "time_zone": "时区", + "recently": "最近", + "minute": "分钟", + "hour": "小时", + "day": "天" +} \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx new file mode 100644 index 00000000000..d18cc315a8b --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx @@ -0,0 +1,423 @@ +'use client'; + +import { + Button, + Flex, + Text, + FlexProps, + Input, + Popover, + PopoverContent, + PopoverTrigger, + Divider, + Grid, + GridItem, + ButtonGroup +} from '@chakra-ui/react'; +import { enUS, zhCN } from 'date-fns/locale'; +import { useTranslation } from 'next-i18next'; +import { useState } from 'react'; +import { DateRange, DayPicker, SelectRangeEventHandler } from 'react-day-picker'; +import { endOfDay, format, isAfter, isBefore, isValid, parse, startOfDay } from 'date-fns'; +import { useDisclosure } from '@chakra-ui/react'; + +import MyIcon from '../Icon'; +import useDateTimeStore from '@/store/date'; +import { MySelect } from '@sealos/ui'; + +interface DatePickerProps extends FlexProps { + isDisabled?: boolean; +} + +interface RecentDate { + label: string; + value: DateRange; + compareValue: string; +} + +const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { + const { t, i18n } = useTranslation(); + const currentLang = i18n.language; + const { isOpen, onClose, onOpen } = useDisclosure(); + + const { startDateTime, endDateTime, setStartDateTime, setEndDateTime, timeZone, setTimeZone } = + useDateTimeStore(); + + const initState = { + from: startDateTime, + to: endDateTime + }; + + const defaultRecentDate = { + label: `${t('recently')} 7 ${t('day')}`, + value: getDateRange('7d'), + compareValue: '7d' + }; + + const [inputState, setInputState] = useState<0 | 1>(0); + const [recentDate, setRecentDate] = useState(defaultRecentDate); + + const [fromDateTimeValue, setFromDateTimeValue] = useState(initState.from); + const [toDateTimeValue, setToDateTimeValue] = useState(initState.to); + + const [selectedRange, setSelectedRange] = useState(initState); + + const onSubmit = () => { + selectedRange?.from && setStartDateTime(selectedRange.from); + selectedRange?.to && setEndDateTime(selectedRange.to); + onClose(); + }; + + const handleFromChange = (value: string, type: 'date' | 'time') => { + const currentValue = format(fromDateTimeValue, 'y-MM-dd HH:mm:ss'); + let newDateString = currentValue; + + if (type === 'date') { + const newDate = value; + const oldTime = format(fromDateTimeValue, 'HH:mm:ss'); + newDateString = `${newDate} ${oldTime}`; + } else { + const newTime = value; + const oldDate = format(fromDateTimeValue, 'y-MM-dd'); + newDateString = `${oldDate} ${newTime}`; + } + + const date = parse(newDateString, 'y-MM-dd HH:mm:ss', new Date()); + + setFromDateTimeValue(date); + + if (!isValid(date)) { + return setSelectedRange({ from: undefined, to: selectedRange?.to }); + } + + if (selectedRange?.to) { + if (isAfter(date, selectedRange.to)) { + setSelectedRange({ from: selectedRange.to, to: date }); + } else { + setSelectedRange({ from: date, to: selectedRange?.to }); + } + } else { + setSelectedRange({ from: date, to: date }); + } + }; + + const handleToChange = (value: string, type: 'date' | 'time') => { + const currentValue = format(fromDateTimeValue, 'y-MM-dd HH:mm:ss'); + let newDateString = currentValue; + + if (type === 'date') { + const newDate = value; + const oldTime = format(fromDateTimeValue, 'HH:mm:ss'); + newDateString = `${newDate} ${oldTime}`; + } else { + const newTime = value; + const oldDate = format(fromDateTimeValue, 'y-MM-dd'); + newDateString = `${oldDate} ${newTime}`; + } + + const date = parse(newDateString, 'y-MM-dd HH:mm:ss', new Date()); + setToDateTimeValue(date); + + if (!isValid(date)) { + return setSelectedRange({ from: selectedRange?.from, to: undefined }); + } + if (selectedRange?.from) { + if (isBefore(date, selectedRange.from)) { + setSelectedRange({ from: date, to: selectedRange.from }); + } else { + setSelectedRange({ from: selectedRange?.from, to: date }); + } + } else { + setSelectedRange({ from: date, to: date }); + } + }; + + const handleRangeSelect: SelectRangeEventHandler = (range: DateRange | undefined) => { + if (range) { + let { from, to } = range; + if (inputState === 0) { + // from + if (from === selectedRange?.from) { + // when 'to' is changed + from = to; + } else { + to = from; + } + setInputState(1); + } else { + setInputState(0); + } + setSelectedRange({ + from, + to + }); + if (from) { + setFromDateTimeValue(startOfDay(from)); + } else { + setFromDateTimeValue(new Date()); + } + if (to) { + setToDateTimeValue(endOfDay(to)); + } else { + setToDateTimeValue(from ? from : new Date()); + } + } else { + // default is cancel + if (fromDateTimeValue && selectedRange?.from) { + setToDateTimeValue(fromDateTimeValue); + setSelectedRange({ + ...selectedRange, + to: selectedRange.from + }); + setInputState(1); + } + } + }; + + const handleRecentDateClick = (item: RecentDate) => { + setRecentDate(item); + setSelectedRange(item.value); + if (item.value.from) { + setFromDateTimeValue(item.value.from); + } + if (item.value.to) { + setToDateTimeValue(item.value.to); + } + }; + + const recentDateList = [ + { + label: `${t('recently')} 5 ${t('minute')}`, + value: getDateRange('5m'), + compareValue: '5m' + }, + { + label: `${t('recently')} 15 ${t('minute')}`, + value: getDateRange('15m'), + compareValue: '15m' + }, + { + label: `${t('recently')} 30 ${t('minute')}`, + value: getDateRange('30m'), + compareValue: '30m' + }, + { + label: `${t('recently')} 1 ${t('hour')}`, + value: getDateRange('1h'), + compareValue: '1h' + }, + { + label: `${t('recently')} 3 ${t('hour')}`, + value: getDateRange('3h'), + compareValue: '3h' + }, + { + label: `${t('recently')} 6 ${t('hour')}`, + value: getDateRange('6h'), + compareValue: '6h' + }, + { + label: `${t('recently')} 24 ${t('hour')}`, + value: getDateRange('24h'), + compareValue: '24h' + }, + { + label: `${t('recently')} 2 ${t('day')}`, + value: getDateRange('2d'), + compareValue: '2d' + }, + { + label: `${t('recently')} 3 ${t('day')}`, + value: getDateRange('3d'), + compareValue: '3d' + }, + { + label: `${t('recently')} 7 ${t('day')}`, + value: getDateRange('7d'), + compareValue: '7d' + } + ]; + + return ( + + {format(startDateTime, 'y-MM-dd HH:mm:ss')} + + {format(endDateTime, 'y-MM-dd HH:mm:ss')} + + + + + + + + + + + + {/* start date and time */} + + {t('start')} + + + handleFromChange(e.target.value, 'date')} + /> + handleFromChange(e.target.value, 'time')} + /> + + {/* end date and time */} + + {t('end')} + + + handleToChange(e.target.value, 'date')} + /> + handleToChange(e.target.value, 'time')} + /> + + + + + + + + + + {recentDateList.map((item) => ( + + ))} + + + + + + + setTimeZone(val)} + /> + + + + + + + + + + + + ); +}; + +const getDateRange = (value: string): DateRange => { + const now = new Date(); + const to = now; + const from = new Date(); + + const [amount, unit] = [parseInt(value), value.slice(-1)]; + + switch (unit) { + case 'm': + from.setMinutes(now.getMinutes() - amount); + break; + case 'h': + from.setHours(now.getHours() - amount); + break; + case 'd': + from.setDate(now.getDate() - amount); + break; + } + + return { from, to }; +}; + +export default DatePicker; diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/calendar.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/calendar.svg new file mode 100644 index 00000000000..f864a731f26 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg new file mode 100644 index 00000000000..6966ccd4fd1 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/to.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/to.svg new file mode 100644 index 00000000000..af8c21bdf83 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/to.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/providers/applaunchpad/src/components/Icon/index.tsx b/frontend/providers/applaunchpad/src/components/Icon/index.tsx index bf1f410e152..e023da501e9 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/index.tsx +++ b/frontend/providers/applaunchpad/src/components/Icon/index.tsx @@ -52,7 +52,10 @@ const map = { search: require('./icons/search.svg').default, pods: require('./icons/pods.svg').default, hardDrive: require('./icons/hardDrive.svg').default, - download: require('./icons/download.svg').default + download: require('./icons/download.svg').default, + calendar: require('./icons/calendar.svg').default, + to: require('./icons/to.svg').default, + refresh: require('./icons/refresh.svg').default }; export type IconType = keyof typeof map; diff --git a/frontend/providers/applaunchpad/src/pages/_app.tsx b/frontend/providers/applaunchpad/src/pages/_app.tsx index 3f4e5f25314..1ba7016a8b2 100644 --- a/frontend/providers/applaunchpad/src/pages/_app.tsx +++ b/frontend/providers/applaunchpad/src/pages/_app.tsx @@ -22,6 +22,8 @@ import '@sealos/driver/src/driver.css'; import { AppEditSyncedFields } from '@/types/app'; import Script from 'next/script'; +import 'react-day-picker/dist/style.css'; + //Binding events. Router.events.on('routeChangeStart', () => NProgress.start()); Router.events.on('routeChangeComplete', () => NProgress.done()); diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx new file mode 100644 index 00000000000..cd442b6f3d2 --- /dev/null +++ b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx @@ -0,0 +1,57 @@ +import { useTranslation } from 'next-i18next'; +import { Box, useTheme, Text, Flex } from '@chakra-ui/react'; + +import dynamic from 'next/dynamic'; + +const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); + +const Logs = () => { + const theme = useTheme(); + const { t } = useTranslation(); + + return ( + <> + + + + {t('filter')} + + + + + + 日志数量 + + + 日志数量 + + + ); +}; + +export default Logs; diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx index c9432e8e565..8aee1e237eb 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx @@ -12,6 +12,7 @@ import React, { useMemo, useState } from 'react'; import AppBaseInfo from './components/AppBaseInfo'; import Header from './components/Header'; import Pods from './components/Pods'; +import Logs from './components/Logs'; const AppMainInfo = dynamic(() => import('./components/AppMainInfo'), { ssr: false }); @@ -89,7 +90,7 @@ const AppDetail = ({ appName }: { appName: string }) => { /> - { })} > {appDetail ? : } - + */} - { minH={'300px'} > - + */} + {/* mask */} - {!isLargeScreen && showSlider && ( + {/* {!isLargeScreen && showSlider && ( { bottom={0} onClick={() => setShowSlider(false)} /> - )} + )} */} ); }; diff --git a/frontend/providers/applaunchpad/src/store/date.ts b/frontend/providers/applaunchpad/src/store/date.ts new file mode 100644 index 00000000000..9522fcce90b --- /dev/null +++ b/frontend/providers/applaunchpad/src/store/date.ts @@ -0,0 +1,28 @@ +import { subDays } from 'date-fns'; +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; + +type DateTimeState = { + startDateTime: Date; + endDateTime: Date; + timeZone: 'local' | 'utc'; + setStartDateTime: (time: Date) => void; + setEndDateTime: (time: Date) => void; + setTimeZone: (timeZone: 'local' | 'utc') => void; +}; + +const useDateTimeStore = create()( + devtools( + immer((set, get) => ({ + startDateTime: subDays(new Date(), 7), + endDateTime: new Date(), + timeZone: 'local', + setStartDateTime: (datetime) => set({ startDateTime: datetime }), + setEndDateTime: (datetime) => set({ endDateTime: datetime }), + setTimeZone: (timeZone) => set({ timeZone }) + })) + ) +); + +export default useDateTimeStore; diff --git a/frontend/providers/applaunchpad/src/styles/global.scss b/frontend/providers/applaunchpad/src/styles/global.scss index af9808bd3ec..6705bb8caee 100644 --- a/frontend/providers/applaunchpad/src/styles/global.scss +++ b/frontend/providers/applaunchpad/src/styles/global.scss @@ -33,3 +33,31 @@ max-width: 600px; border: 1px solid #94b5ff; } + +div.rdp { + --rdp-cell-size: 40px; + --rdp-accent-color: black; + --rdp-background-color: #f4f6f8; + --rdp-outline: 2px solid var(--rdp-accent-color); + --rdp-outline-selected: 2px solid rgba(0, 0, 0, 0.75); +} + +button.rdp-button_reset.rdp-button.rdp-day { + border-radius: 6px; +} + +button.rdp-day_today { + background-color: #dbf3ff; + color: #0884dd; +} + +button.rdp-button.rdp-day.rdp-day_range_middle { + border-radius: 0px; + background-color: #f4f4f7; + color: #111824; +} + +button.rdp-button.rdp-day.rdp-day_range_end { + background-color: #111824; + color: white; +} From 220498cab2f2ecd261596cf57c976d4c67492cd7 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Tue, 7 Jan 2025 16:27:58 +0800 Subject: [PATCH 02/48] feat: header ui --- .../public/locales/en/common.json | 5 +- .../public/locales/zh/common.json | 5 +- .../src/components/Icon/icons/container.svg | 5 + .../src/components/Icon/icons/refresh.svg | 2 +- .../src/components/Icon/index.tsx | 3 +- .../src/components/MySelect/index.tsx | 159 ++++++++++++++++ .../src/pages/app/detail/components/Logs.tsx | 14 +- .../app/detail/components/logs/Header.tsx | 174 ++++++++++++++++++ 8 files changed, 352 insertions(+), 15 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/components/Icon/icons/container.svg create mode 100644 frontend/providers/applaunchpad/src/components/MySelect/index.tsx create mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/components/logs/Header.tsx diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 6a8b2ddf1e5..19d28319f43 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -284,5 +284,8 @@ "recently": "Recent", "minute": "Minute", "day": "Day", - "hour": "Hour" + "hour": "Hour", + "time": "Time", + "log_number": "Log Number", + "close": "Close" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 2e92bb4b2c7..5019c2fb486 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -285,5 +285,8 @@ "recently": "最近", "minute": "分钟", "hour": "小时", - "day": "天" + "day": "天", + "time": "时间", + "log_number": "日志数", + "close": "关闭" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/container.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/container.svg new file mode 100644 index 00000000000..c15441ffd1c --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/container.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg index 6966ccd4fd1..302e566a1f5 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/refresh.svg @@ -1,5 +1,5 @@ - + diff --git a/frontend/providers/applaunchpad/src/components/Icon/index.tsx b/frontend/providers/applaunchpad/src/components/Icon/index.tsx index e023da501e9..ee6c8ac1ab1 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/index.tsx +++ b/frontend/providers/applaunchpad/src/components/Icon/index.tsx @@ -55,7 +55,8 @@ const map = { download: require('./icons/download.svg').default, calendar: require('./icons/calendar.svg').default, to: require('./icons/to.svg').default, - refresh: require('./icons/refresh.svg').default + refresh: require('./icons/refresh.svg').default, + container: require('./icons/container.svg').default }; export type IconType = keyof typeof map; diff --git a/frontend/providers/applaunchpad/src/components/MySelect/index.tsx b/frontend/providers/applaunchpad/src/components/MySelect/index.tsx new file mode 100644 index 00000000000..3e3562a8bd6 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/MySelect/index.tsx @@ -0,0 +1,159 @@ +'use client'; + +import React, { useRef, forwardRef, useMemo } from 'react'; +import { + Menu, + Box, + MenuList, + MenuItem, + Button, + useDisclosure, + useOutsideClick, + MenuButton, + Flex +} from '@chakra-ui/react'; +import type { BoxProps, ButtonProps } from '@chakra-ui/react'; +import { ChevronDownIcon } from '@chakra-ui/icons'; + +interface Props extends ButtonProps { + width?: string; + height?: string; + value?: string; + placeholder?: string; + list: { + label: string | React.ReactNode; + value: string; + }[]; + onchange?: (val: string) => void; + isInvalid?: boolean; + boxStyle?: BoxProps; +} + +const MySelect = ( + { + placeholder, + leftIcon, + value, + width = 'auto', + height = '30px', + list, + onchange, + isInvalid, + boxStyle, + ...props + }: Props, + selectRef: any +) => { + const ref = useRef(null); + const SelectRef = useRef(null); + const { isOpen, onOpen, onClose } = useDisclosure(); + + useOutsideClick({ + ref: SelectRef, + handler: () => { + onClose(); + } + }); + + const activeMenu = useMemo(() => list.find((item) => item.value === value), [list, value]); + + return ( + + { + isOpen ? onClose() : onOpen(); + }} + {...boxStyle} + > + } + width={width} + height={height} + ref={ref} + display={'flex'} + alignItems={'center'} + justifyContent={'center'} + border={'1px solid #E8EBF0'} + borderRadius={'md'} + fontSize={'12px'} + fontWeight={'400'} + color={'grayModern.900'} + variant={'outline'} + _hover={{ + borderColor: 'brightBlue.300', + bg: 'grayModern.50' + }} + _active={{ + transform: '' + }} + {...(isOpen + ? { + boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)', + borderColor: 'brightBlue.500', + bg: '#FFF' + } + : { + bg: '#F7F8FA', + borderColor: isInvalid ? 'red' : '' + })} + {...props} + > + + {activeMenu ? activeMenu.label : placeholder} + + + + { + const w = ref.current?.clientWidth; + if (w) { + return `${w}px !important`; + } + return Array.isArray(width) + ? width.map((item) => `${item} !important`) + : `${width} !important`; + })()} + p={'6px'} + borderRadius={'base'} + border={'1px solid #E8EBF0'} + boxShadow={ + '0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)' + } + zIndex={99} + overflow={'overlay'} + maxH={'300px'} + > + {list.map((item) => ( + { + if (onchange && value !== item.value) { + onchange(item.value); + } + }} + > + {item.label} + + ))} + + + + ); +}; + +export default React.memo(forwardRef(MySelect)); diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx index cd442b6f3d2..8eb4d22a9eb 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx @@ -1,13 +1,10 @@ import { useTranslation } from 'next-i18next'; -import { Box, useTheme, Text, Flex } from '@chakra-ui/react'; +import { Box, useTheme, Flex } from '@chakra-ui/react'; -import dynamic from 'next/dynamic'; - -const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); +import { Header } from './logs/Header'; const Logs = () => { const theme = useTheme(); - const { t } = useTranslation(); return ( <> @@ -21,12 +18,7 @@ const Logs = () => { minH={'50px'} alignItems={'center'} > - - - {t('filter')} - - - +
import('@/components/DatePicker'), { ssr: false }); + +export const Header = () => { + const { t } = useTranslation(); + + const [refreshInterval, setRefreshInterval] = useState(0); + + const refreshIntervalList = [ + { value: 0, label: t('close') }, + { value: 1000, label: '1s' }, + { value: 2000, label: '2s' }, + { value: 5000, label: '5s' }, + { value: 10000, label: '10s' } + ]; + + return ( + + {/* time */} + + + {t('time')} + + + + {/* pod */} + + + Pod + + } + width={'fit-content'} + value={'hello-sql-postgresql-0'} + list={[ + { value: 'hello-sql-postgresql-0', label: 'hello-sql-postgresql-0' }, + { value: 'hello-sql-postgresql-1', label: 'hello-sql-postgresql-1' } + ]} + /> + + {/* container */} + + + Container + + } + width={'fit-content'} + value={'hello-sql-postgresql-0'} + list={[ + { value: 'hello-sql-postgresql-0', label: 'hello-sql-postgresql-0' }, + { value: 'hello-sql-postgresql-1', label: 'hello-sql-postgresql-1' } + ]} + /> + + {/* log number */} + + + {t('log_number')} + + { + console.log(e.target.value); + }} + /> + + + + + + {refreshInterval === 0 ? null : ( + {`${refreshInterval / 1000}s`} + )} + + + + + {refreshIntervalList.map((item) => ( + { + setRefreshInterval(item.value); + }} + {...(refreshInterval === item.value + ? { + color: 'brightBlue.600' + } + : {})} + borderRadius={'4px'} + _hover={{ + bg: 'rgba(17, 24, 36, 0.05)', + color: 'brightBlue.600' + }} + p={'6px'} + > + {item.label} + + ))} + + + + + + ); +}; From abdc473b0b4671a9cd9cdf5a66a78e6d75585695 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Wed, 8 Jan 2025 11:29:51 +0800 Subject: [PATCH 03/48] fix: DatePicker input status bug --- .../src/components/DatePicker/index.tsx | 187 +++++++++++++----- 1 file changed, 137 insertions(+), 50 deletions(-) diff --git a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx index d18cc315a8b..cc853bfe0f4 100644 --- a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx +++ b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx @@ -16,9 +16,9 @@ import { } from '@chakra-ui/react'; import { enUS, zhCN } from 'date-fns/locale'; import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; +import { ChangeEvent, ChangeEventHandler, useState } from 'react'; import { DateRange, DayPicker, SelectRangeEventHandler } from 'react-day-picker'; -import { endOfDay, format, isAfter, isBefore, isValid, parse, startOfDay } from 'date-fns'; +import { endOfDay, format, isAfter, isBefore, isMatch, isValid, parse, startOfDay } from 'date-fns'; import { useDisclosure } from '@chakra-ui/react'; import MyIcon from '../Icon'; @@ -57,34 +57,66 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { const [inputState, setInputState] = useState<0 | 1>(0); const [recentDate, setRecentDate] = useState(defaultRecentDate); - const [fromDateTimeValue, setFromDateTimeValue] = useState(initState.from); - const [toDateTimeValue, setToDateTimeValue] = useState(initState.to); + const [fromDateString, setFromDateString] = useState(format(initState.from, 'y-MM-dd')); + const [toDateString, setToDateString] = useState(format(initState.to, 'y-MM-dd')); + const [fromTimeString, setFromTimeString] = useState(format(initState.from, 'HH:mm:ss')); + const [toTimeString, setToTimeString] = useState(format(initState.to, 'HH:mm:ss')); + + const [fromDateError, setFromDateError] = useState(null); + const [toDateError, setToDateError] = useState(null); + const [fromTimeError, setFromTimeError] = useState(null); + const [toTimeError, setToTimeError] = useState(null); + const [fromDateShake, setFromDateShake] = useState(false); + const [toDateShake, setToDateShake] = useState(false); + const [fromTimeShake, setFromTimeShake] = useState(false); + const [toTimeShake, setToTimeShake] = useState(false); const [selectedRange, setSelectedRange] = useState(initState); const onSubmit = () => { + if (fromDateError || fromTimeError || toDateError || toTimeError) { + if (fromDateError) setFromDateShake(true); + if (toDateError) setToDateShake(true); + if (fromTimeError) setFromTimeShake(true); + if (toTimeError) setToTimeShake(true); + setTimeout(() => { + setFromDateShake(false); + setToDateShake(false); + setFromTimeShake(false); + setToTimeShake(false); + }, 300); + + return; + } selectedRange?.from && setStartDateTime(selectedRange.from); selectedRange?.to && setEndDateTime(selectedRange.to); onClose(); }; const handleFromChange = (value: string, type: 'date' | 'time') => { - const currentValue = format(fromDateTimeValue, 'y-MM-dd HH:mm:ss'); - let newDateString = currentValue; + let newDateTimeString; if (type === 'date') { - const newDate = value; - const oldTime = format(fromDateTimeValue, 'HH:mm:ss'); - newDateString = `${newDate} ${oldTime}`; + setFromDateString(value); + if (!isMatch(value, 'y-MM-dd')) { + setFromDateError('Invalid date format'); + return; + } + setFromDateError(null); + newDateTimeString = `${value} ${fromTimeString}`; } else { - const newTime = value; - const oldDate = format(fromDateTimeValue, 'y-MM-dd'); - newDateString = `${oldDate} ${newTime}`; + setFromTimeString(value); + if (!isMatch(value, 'HH:mm:ss')) { + setFromTimeError('Invalid time format'); + return; + } + setFromTimeError(null); + newDateTimeString = `${fromDateString} ${value}`; } - const date = parse(newDateString, 'y-MM-dd HH:mm:ss', new Date()); + console.log(newDateTimeString); - setFromDateTimeValue(date); + const date = parse(newDateTimeString, 'y-MM-dd HH:mm:ss', new Date()); if (!isValid(date)) { return setSelectedRange({ from: undefined, to: selectedRange?.to }); @@ -102,21 +134,27 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { }; const handleToChange = (value: string, type: 'date' | 'time') => { - const currentValue = format(fromDateTimeValue, 'y-MM-dd HH:mm:ss'); - let newDateString = currentValue; + let newDateTimeString; if (type === 'date') { - const newDate = value; - const oldTime = format(fromDateTimeValue, 'HH:mm:ss'); - newDateString = `${newDate} ${oldTime}`; + setToDateString(value); + if (!isMatch(value, 'y-MM-dd')) { + setToDateError('Invalid date format'); + return; + } + setToDateError(null); + newDateTimeString = `${value} ${toTimeString}`; } else { - const newTime = value; - const oldDate = format(fromDateTimeValue, 'y-MM-dd'); - newDateString = `${oldDate} ${newTime}`; + setToTimeString(value); + if (!isMatch(value, 'HH:mm:ss')) { + setToTimeError('Invalid time format'); + return; + } + setToTimeError(null); + newDateTimeString = `${toDateString} ${value}`; } - const date = parse(newDateString, 'y-MM-dd HH:mm:ss', new Date()); - setToDateTimeValue(date); + const date = parse(newDateTimeString, 'y-MM-dd HH:mm:ss', new Date()); if (!isValid(date)) { return setSelectedRange({ from: selectedRange?.from, to: undefined }); @@ -152,19 +190,24 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { to }); if (from) { - setFromDateTimeValue(startOfDay(from)); + setFromDateString(format(startOfDay(from), 'y-MM-dd')); + setFromTimeString(format(startOfDay(from), 'HH:mm:ss')); } else { - setFromDateTimeValue(new Date()); + setFromDateString(format(new Date(), 'y-MM-dd')); + setFromTimeString(format(new Date(), 'HH:mm:ss')); } if (to) { - setToDateTimeValue(endOfDay(to)); + setToDateString(format(endOfDay(to), 'y-MM-dd')); + setToTimeString(format(endOfDay(to), 'HH:mm:ss')); } else { - setToDateTimeValue(from ? from : new Date()); + setToDateString(format(from ? from : new Date(), 'y-MM-dd')); + setToTimeString(format(from ? from : new Date(), 'HH:mm:ss')); } } else { // default is cancel - if (fromDateTimeValue && selectedRange?.from) { - setToDateTimeValue(fromDateTimeValue); + if (fromDateString && fromTimeString && selectedRange?.from) { + setToDateString(fromDateString); + setToTimeString(fromTimeString); setSelectedRange({ ...selectedRange, to: selectedRange.from @@ -175,13 +218,20 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { }; const handleRecentDateClick = (item: RecentDate) => { + setFromDateError(null); + setFromTimeError(null); + setToDateError(null); + setToTimeError(null); + setRecentDate(item); setSelectedRange(item.value); if (item.value.from) { - setFromDateTimeValue(item.value.from); + setFromDateString(format(item.value.from, 'y-MM-dd')); + setFromTimeString(format(item.value.from, 'HH:mm:ss')); } if (item.value.to) { - setToDateTimeValue(item.value.to); + setToDateString(format(item.value.to, 'y-MM-dd')); + setToTimeString(format(item.value.to, 'HH:mm:ss')); } }; @@ -283,41 +333,44 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { weekStartsOn={0} /> + {/* start date and time */} - {/* start date and time */} {t('start')} - handleFromChange(e.target.value, 'date')} + error={!!fromDateError} + showError={fromDateShake} /> - handleFromChange(e.target.value, 'time')} + error={!!fromTimeError} + showError={fromTimeShake} /> - {/* end date and time */} + + + {/* end date and time */} + {t('end')} - handleToChange(e.target.value, 'date')} + error={!!toDateError} + showError={toDateShake} /> - handleToChange(e.target.value, 'time')} + error={!!toTimeError} + showError={toTimeShake} /> @@ -398,6 +451,40 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { ); }; +interface DatePickerInputProps { + value: string; + onChange: ChangeEventHandler | undefined; + error: boolean; + showError: boolean; +} + +const DatePickerInput = ({ value, onChange, error, showError }: DatePickerInputProps) => { + return ( + + ); +}; + const getDateRange = (value: string): DateRange => { const now = new Date(); const to = now; From 577f5020071e5863ac2cd062dfd199371be498fb Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Wed, 8 Jan 2025 11:40:48 +0800 Subject: [PATCH 04/48] feat: header ui --- .../public/locales/en/common.json | 13 +- .../public/locales/zh/common.json | 13 +- .../src/components/LangSelect/index.tsx | 2 +- .../src/components/MySelect/index.tsx | 7 +- .../applaunchpad/src/hooks/useRequest.tsx | 2 +- .../src/pages/app/detail/components/Logs.tsx | 11 +- .../app/detail/components/logs/Filter.tsx | 196 ++++++++++++++++++ .../app/detail/components/logs/Header.tsx | 8 +- .../app/edit/components/CustomAccessModal.tsx | 2 +- 9 files changed, 240 insertions(+), 14 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/components/logs/Filter.tsx diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 19d28319f43..b80f601ae09 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -287,5 +287,16 @@ "hour": "Hour", "time": "Time", "log_number": "Log Number", - "close": "Close" + "close": "Close", + "normal_filter": "Normal", + "advanced_filter": "Advance", + "json_mode": "JSON Mode", + "only_stderr": "Only Stderr", + "keyword": "Keyword", + "search": "Search", + "field_name": "Field Name", + "equal": "Equal", + "greater_than": "Greater than", + "less_than": "Less than", + "value": "Value" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 5019c2fb486..30d53a1686a 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -288,5 +288,16 @@ "day": "天", "time": "时间", "log_number": "日志数", - "close": "关闭" + "close": "关闭", + "normal_filter": "普通筛选", + "advanced_filter": "高级筛选", + "json_mode": "JSON模式", + "only_stderr": "只看 Stderr", + "keyword": "关键词", + "search": "查询", + "field_name": "字段名", + "equal": "等于", + "greater_than": "大于", + "less_than": "小于", + "value": "值" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/LangSelect/index.tsx b/frontend/providers/applaunchpad/src/components/LangSelect/index.tsx index d848a87309f..3e1401a1d1a 100644 --- a/frontend/providers/applaunchpad/src/components/LangSelect/index.tsx +++ b/frontend/providers/applaunchpad/src/components/LangSelect/index.tsx @@ -1,6 +1,6 @@ import { setLangStore } from '@/utils/cookieUtils'; import { Menu, MenuButton, MenuButtonProps, MenuItem, MenuList, Text } from '@chakra-ui/react'; -import { useTranslation } from 'react-i18next'; +import { useTranslation } from 'next-i18next'; const langIcon = ( - + {activeMenu ? activeMenu.label : placeholder} diff --git a/frontend/providers/applaunchpad/src/hooks/useRequest.tsx b/frontend/providers/applaunchpad/src/hooks/useRequest.tsx index b491a1abf38..ca2a33d354e 100644 --- a/frontend/providers/applaunchpad/src/hooks/useRequest.tsx +++ b/frontend/providers/applaunchpad/src/hooks/useRequest.tsx @@ -2,7 +2,7 @@ import { useToast } from '@/hooks/useToast'; import { useMutation } from '@tanstack/react-query'; import type { UseMutationOptions } from '@tanstack/react-query'; import { getErrText } from '@/utils/tools'; -import { useTranslation } from 'react-i18next'; +import { useTranslation } from 'next-i18next'; interface Props extends UseMutationOptions { successToast?: string | null; diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx index 8eb4d22a9eb..b6948cde60b 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx @@ -1,7 +1,8 @@ import { useTranslation } from 'next-i18next'; -import { Box, useTheme, Flex } from '@chakra-ui/react'; +import { Box, useTheme, Flex, Divider } from '@chakra-ui/react'; import { Header } from './logs/Header'; +import { Filter } from './logs/Filter'; const Logs = () => { const theme = useTheme(); @@ -11,14 +12,14 @@ const Logs = () => {
+ +
' | '<'; +} + +export const Filter = () => { + const { t } = useTranslation(); + + const [activeId, setActiveId] = useState('normal_filter'); + const [openJsonMode, setOpenJsonMode] = useState(false); + const [openOnlyStderr, setOpenOnlyStderr] = useState(false); + const [jsonFormList, setJsonFormList] = useState([]); + + return ( + + {/* tab */} + + + + {/* operator button */} + + + + {t('json_mode')} + + setOpenJsonMode(!openJsonMode)} /> + + + + {t('only_stderr')} + + setOpenOnlyStderr(!openOnlyStderr)} /> + + + + } + > + {t('search')} + + + + {/* json mode */} + {openJsonMode && ( + + {jsonFormList.length === 0 && ( + + setJsonFormList([ + ...jsonFormList, + { + jsonKey: '', + jsonValue: '', + jsonOperator: '=' + } + ]) + } + /> + )} + {jsonFormList.map((item, index) => ( + + setJsonFormList(val)} + /> + ', label: t('greater_than') }, + { value: '<', label: t('less_than') } + ]} + onchange={(val: any) => setJsonFormList(val)} + /> + + + {index === jsonFormList.length - 1 && ( + + setJsonFormList([ + ...jsonFormList, + { + jsonKey: '', + jsonValue: '', + jsonOperator: '=' + } + ]) + } + /> + )} + + ))} + + )} + + ); +}; + +const AppendJSONFormItemButton = (props: ButtonProps) => { + const { t } = useTranslation(); + return ( + + ); +}; diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/Header.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/Header.tsx index 44462d1a205..15501937eea 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/Header.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/Header.tsx @@ -11,10 +11,11 @@ import { MenuList, Text } from '@chakra-ui/react'; -import MySelect from '@/components/MySelect'; -import MyIcon from '@/components/Icon'; +import { useState } from 'react'; import { ChevronDownIcon } from '@chakra-ui/icons'; -import { useRef, useState } from 'react'; + +import MyIcon from '@/components/Icon'; +import MySelect from '@/components/MySelect'; const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); @@ -33,6 +34,7 @@ export const Header = () => { return ( Date: Wed, 8 Jan 2025 11:44:54 +0800 Subject: [PATCH 05/48] feat: log number ui --- .../applaunchpad/src/pages/app/detail/components/Logs.tsx | 3 ++- .../src/pages/app/detail/components/logs/LogNumber.tsx | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/components/logs/LogNumber.tsx diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx index b6948cde60b..42f1b3c666e 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx @@ -3,6 +3,7 @@ import { Box, useTheme, Flex, Divider } from '@chakra-ui/react'; import { Header } from './logs/Header'; import { Filter } from './logs/Filter'; +import { LogNumber } from './logs/LogNumber'; const Logs = () => { const theme = useTheme(); @@ -30,7 +31,7 @@ const Logs = () => { flexShrink={0} minH={'257px'} > - 日志数量 + { + return
日志数量
; +}; From 38caebd9c2a30d87e06d320ced9bdf40699e25da Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Wed, 8 Jan 2025 14:51:41 +0800 Subject: [PATCH 06/48] feat: LogCount ui --- .../public/locales/en/common.json | 3 +- .../public/locales/zh/common.json | 3 +- .../src/components/Icon/icons/arrowRight.svg | 3 + .../src/components/Icon/index.tsx | 3 +- .../src/components/LogBarChart/index.tsx | 226 ++++++++++++++++++ .../src/pages/app/detail/components/Logs.tsx | 4 +- .../app/detail/components/logs/Filter.tsx | 2 +- .../app/detail/components/logs/LogCounts.tsx | 57 +++++ .../app/detail/components/logs/LogNumber.tsx | 3 - 9 files changed, 295 insertions(+), 9 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/components/Icon/icons/arrowRight.svg create mode 100644 frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx create mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/components/logs/LogCounts.tsx delete mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/components/logs/LogNumber.tsx diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index b80f601ae09..641ced08f09 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -298,5 +298,6 @@ "equal": "Equal", "greater_than": "Greater than", "less_than": "Less than", - "value": "Value" + "value": "Value", + "logNumber": "Log Counts" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 30d53a1686a..d26f97e79b8 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -299,5 +299,6 @@ "equal": "等于", "greater_than": "大于", "less_than": "小于", - "value": "值" + "value": "值", + "logNumber": "日志数量" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/arrowRight.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/arrowRight.svg new file mode 100644 index 00000000000..80e943436d2 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/arrowRight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/Icon/index.tsx b/frontend/providers/applaunchpad/src/components/Icon/index.tsx index ee6c8ac1ab1..3a1df8c8787 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/index.tsx +++ b/frontend/providers/applaunchpad/src/components/Icon/index.tsx @@ -56,7 +56,8 @@ const map = { calendar: require('./icons/calendar.svg').default, to: require('./icons/to.svg').default, refresh: require('./icons/refresh.svg').default, - container: require('./icons/container.svg').default + container: require('./icons/container.svg').default, + arrowRight: require('./icons/arrowRight.svg').default }; export type IconType = keyof typeof map; diff --git a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx new file mode 100644 index 00000000000..7753e24f22c --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx @@ -0,0 +1,226 @@ +import dayjs from 'dayjs'; +import * as echarts from 'echarts'; +import React, { useEffect, useMemo, useRef } from 'react'; + +import { useGlobalStore } from '@/store/global'; +import { MonitorDataResult } from '@/types/monitor'; + +const map = { + blue: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(3, 190, 232, 0.42)' // 0% 处的颜色 + }, + { + offset: 1, + color: 'rgba(0, 182, 240, 0)' + } + ], + global: false // 缺省为 false + }, + lineColor: '#36ADEF' + }, + deepBlue: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(47, 112, 237, 0.42)' // 0% 处的颜色 + }, + { + offset: 1, + color: 'rgba(94, 159, 235, 0)' + } + ], + global: false + }, + lineColor: '#3293EC' + }, + purple: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(211, 190, 255, 0.42)' // 0% 处的颜色 + }, + { + offset: 1, + color: 'rgba(52, 60, 255, 0)' + } + ], + global: false // 缺省为 false + }, + lineColor: '#8172D8' + }, + green: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(4, 209, 148, 0.42)' // 0% 处的颜色 + }, + { + offset: 1, + color: 'rgba(19, 217, 181, 0)' + } + ], + global: false // 缺省为 false + }, + lineColor: '#00A9A6', + max: 100 + } +}; + +const LogBarChart = ({ + type, + data, + isShowLabel = false +}: { + type: 'blue' | 'deepBlue' | 'green' | 'purple'; + data?: MonitorDataResult; + isShowLabel?: boolean; +}) => { + const { screenWidth } = useGlobalStore(); + const xData = + data?.xData?.map((time) => (time ? dayjs(time * 1000).format('HH:mm') : '')) || + new Array(30).fill(0); + const yData = data?.yData || new Array(30).fill(''); + + const Dom = useRef(null); + const myChart = useRef(); + + const optionStyle = useMemo( + () => ({ + areaStyle: { + color: map[type].backgroundColor + }, + lineStyle: { + width: '1', + color: map[type].lineColor + }, + itemStyle: { + width: 1.5, + color: map[type].lineColor + } + }), + [type] + ); + + const option = useRef({ + xAxis: { + type: 'category', + show: isShowLabel, + boundaryGap: false, + data: xData, + axisLabel: { + show: isShowLabel + }, + axisTick: { + show: false + }, + axisLine: { + show: false + } + }, + yAxis: { + type: 'value', + boundaryGap: false, + splitNumber: 3, + max: 150, + min: 0, + axisLabel: { + show: isShowLabel + } + }, + grid: { + containLabel: isShowLabel, + show: false, + left: 0, + right: isShowLabel ? 14 : 0, + top: 10, + bottom: 2 + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'line' + }, + formatter: (params: any[]) => { + const axisValue = params[0]?.axisValue; + return `${axisValue} ${params[0]?.value || 0}%`; + } + }, + series: [ + { + data: yData, + type: 'bar', + showSymbol: false, + smooth: true, + animationDuration: 300, + barCategoryGap: '5%', + animationEasingUpdate: 'linear', + ...optionStyle, + emphasis: { + disabled: true + } + } + ] + }); + + // init chart + useEffect(() => { + if (!Dom.current || myChart?.current?.getOption()) return; + myChart.current = echarts.init(Dom.current); + myChart.current && myChart.current.setOption(option.current); + }, [Dom]); + + // data changed, update + useEffect(() => { + if (!myChart.current || !myChart?.current?.getOption()) return; + option.current.xAxis.data = xData; + option.current.series[0].data = yData; + myChart.current.setOption(option.current); + }, [xData, yData]); + + // type changed, update + useEffect(() => { + if (!myChart.current || !myChart?.current?.getOption()) return; + option.current.series[0] = { + ...option.current.series[0], + ...optionStyle + }; + myChart.current.setOption(option.current); + }, [optionStyle]); + + // resize chart + useEffect(() => { + if (!myChart.current || !myChart.current.getOption()) return; + myChart.current.resize(); + }, [screenWidth]); + + return
; +}; + +export default LogBarChart; diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx index 42f1b3c666e..d6ec5c9ad45 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/components/Logs.tsx @@ -3,7 +3,7 @@ import { Box, useTheme, Flex, Divider } from '@chakra-ui/react'; import { Header } from './logs/Header'; import { Filter } from './logs/Filter'; -import { LogNumber } from './logs/LogNumber'; +import { LogCounts } from './logs/LogCounts'; const Logs = () => { const theme = useTheme(); @@ -31,7 +31,7 @@ const Logs = () => { flexShrink={0} minH={'257px'} > - + { /> )} {jsonFormList.map((item, index) => ( - + { + const { t } = useTranslation(); + + const [onOpenChart, setOnOpenChart] = useState(true); + + return ( + + + + + {/* charts */} + + + + + + + ); +}; + +const mockData: MonitorDataResult = { + name: 'log', + xData: [1, 2, 3, 4, 5, 6, 7], + yData: ['50', '80', '70', '60', '50', '60', '70'] +}; diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/LogNumber.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/LogNumber.tsx deleted file mode 100644 index 487f24a2be3..00000000000 --- a/frontend/providers/applaunchpad/src/pages/app/detail/components/logs/LogNumber.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const LogNumber = () => { - return
日志数量
; -}; From 2f5f372b9c95414b68fffe53522b8841b6ae052e Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 8 Jan 2025 10:04:19 +0800 Subject: [PATCH 07/48] add layout --- .../public/locales/en/common.json | 5 +- .../public/locales/zh/common.json | 5 +- .../src/components/Icon/icons/monitor.svg | 4 + .../src/components/Icon/index.tsx | 1 + .../src/components/Sidebar/index.tsx | 91 ++++++++++++++++++ .../src/components/Sidebar/layout.tsx | 73 +++++++++++++++ .../src/pages/app/detail/index.tsx | 93 ++++--------------- .../src/pages/app/detail/monitor/index.tsx | 46 +++++++++ 8 files changed, 243 insertions(+), 75 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/components/Icon/icons/monitor.svg create mode 100644 frontend/providers/applaunchpad/src/components/Sidebar/index.tsx create mode 100644 frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx create mode 100644 frontend/providers/applaunchpad/src/pages/app/detail/monitor/index.tsx diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 641ced08f09..86dcc79c489 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -299,5 +299,8 @@ "greater_than": "Greater than", "less_than": "Less than", "value": "Value", - "logNumber": "Log Counts" + "logNumber": "Log Counts", + "overview": "Overview", + "monitor": "Monitor", + "logs": "Logs" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index d26f97e79b8..cc606720edf 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -300,5 +300,8 @@ "greater_than": "大于", "less_than": "小于", "value": "值", - "logNumber": "日志数量" + "logNumber": "日志数量", + "overview": "概览", + "monitor": "监控", + "logs": "日志" } \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/monitor.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/monitor.svg new file mode 100644 index 00000000000..0e6543a7277 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/monitor.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/Icon/index.tsx b/frontend/providers/applaunchpad/src/components/Icon/index.tsx index 3a1df8c8787..43ae4766093 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/index.tsx +++ b/frontend/providers/applaunchpad/src/components/Icon/index.tsx @@ -51,6 +51,7 @@ const map = { upload: require('./icons/upload.svg').default, search: require('./icons/search.svg').default, pods: require('./icons/pods.svg').default, + monitor: require('./icons/monitor.svg').default, hardDrive: require('./icons/hardDrive.svg').default, download: require('./icons/download.svg').default, calendar: require('./icons/calendar.svg').default, diff --git a/frontend/providers/applaunchpad/src/components/Sidebar/index.tsx b/frontend/providers/applaunchpad/src/components/Sidebar/index.tsx new file mode 100644 index 00000000000..6fddaf5ba62 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Sidebar/index.tsx @@ -0,0 +1,91 @@ +import { Center, Text, Stack } from '@chakra-ui/react'; +import MyIcon from '../Icon'; +import { useTranslation } from 'next-i18next'; +import { useRouter } from 'next/router'; + +const ROUTES = { + OVERVIEW: '/app/detail', + MONITOR: '/app/detail/monitor', + LOGS: '/app/detail/logs' +} as const; + +export default function Sidebar() { + const { t } = useTranslation(); + const router = useRouter(); + + const siderbarMap = [ + { + label: t('overview'), + icon: ( + + ), + path: ROUTES.OVERVIEW + }, + { + label: t('monitor'), + icon: ( + + ), + path: ROUTES.MONITOR + }, + { + label: t('logs'), + icon: ( + + ), + path: ROUTES.LOGS + } + ]; + + return ( + + {siderbarMap.map((item) => ( +
{ + console.log(router.query); + router.push({ + pathname: item.path, + query: { ...router.query } + }); + }} + > + {item.icon} + + {item.label} + +
+ ))} +
+ ); +} diff --git a/frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx b/frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx new file mode 100644 index 00000000000..a0704dc93bc --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx @@ -0,0 +1,73 @@ +import Sidebar from '@/components/Sidebar'; +import { useToast } from '@/hooks/useToast'; +import { MOCK_APP_DETAIL } from '@/mock/apps'; +import Header from '@/pages/app/detail/components/Header'; +import { useAppStore } from '@/store/app'; +import { useGlobalStore } from '@/store/global'; +import { Box, Flex } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import React, { useMemo, useState } from 'react'; + +interface DetailLayoutProps { + children: React.ReactNode; + appName: string; +} + +export default function DetailLayout({ children, appName }: DetailLayoutProps) { + const { toast } = useToast(); + const { screenWidth } = useGlobalStore(); + const isLargeScreen = useMemo(() => screenWidth > 1280, [screenWidth]); + const { + appDetail = MOCK_APP_DETAIL, + setAppDetail, + intervalLoadPods, + loadDetailMonitorData + } = useAppStore(); + + const [showSlider, setShowSlider] = useState(false); + + const { refetch } = useQuery(['setAppDetail'], () => setAppDetail(appName), { + onError(err) { + toast({ + title: String(err), + status: 'error' + }); + } + }); + + useQuery( + ['loadDetailMonitorData', appName, appDetail?.isPause], + () => { + if (appDetail?.isPause) return null; + return loadDetailMonitorData(appName); + }, + { + refetchOnMount: true, + refetchInterval: 2 * 60 * 1000 + } + ); + + return ( + +
+ + + {children} + + + ); +} diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx index 8aee1e237eb..492256cb85e 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx @@ -12,7 +12,7 @@ import React, { useMemo, useState } from 'react'; import AppBaseInfo from './components/AppBaseInfo'; import Header from './components/Header'; import Pods from './components/Pods'; -import Logs from './components/Logs'; +import DetailLayout from '@/components/Sidebar/layout'; const AppMainInfo = dynamic(() => import('./components/AppMainInfo'), { ssr: false }); @@ -71,83 +71,30 @@ const AppDetail = ({ appName }: { appName: string }) => { ); return ( - - -
- - - {/* + + - {appDetail ? : } - */} - - {/* - {appDetail ? : } - - - - */} - - - - {/* mask */} - {/* {!isLargeScreen && showSlider && ( + {appDetail ? : } + setShowSlider(false)} - /> - )} */} - + bg={'white'} + border={theme.borders.base} + borderRadius={'lg'} + h={0} + flex={1} + minH={'300px'} + > + + + + ); }; diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/monitor/index.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/monitor/index.tsx new file mode 100644 index 00000000000..14cb98bc179 --- /dev/null +++ b/frontend/providers/applaunchpad/src/pages/app/detail/monitor/index.tsx @@ -0,0 +1,46 @@ +// pages/app/detail/monitor.tsx +import { Box } from '@chakra-ui/react'; +import DetailLayout from '@/components/Sidebar/layout'; +import { serviceSideProps } from '@/utils/i18n'; +import { useAppStore } from '@/store/app'; +import { useQuery } from '@tanstack/react-query'; +import { useToast } from '@/hooks/useToast'; + +export default function MonitorPage({ appName }: { appName: string }) { + const { toast } = useToast(); + const { appDetail } = useAppStore(); + + const { data: monitorData } = useQuery( + ['monitor-data', appName], + async () => { + return []; + }, + { + onError(err) { + toast({ + title: String(err), + status: 'error' + }); + } + } + ); + + return ( + + + Monitor Page Content + + + ); +} + +export async function getServerSideProps(content: any) { + const appName = content?.query?.name || ''; + + return { + props: { + appName, + ...(await serviceSideProps(content)) + } + }; +} From b4b8549de8226f1fcf7d4fec3492d2758c4aae05 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 8 Jan 2025 10:56:59 +0800 Subject: [PATCH 08/48] update layouts --- .../src/components/Icon/index.tsx | 17 ++++-- .../layout.tsx => layouts/DetailLayout.tsx} | 4 +- .../index.tsx => layouts/Sidebar.tsx} | 0 .../pages/app/detail/components/Header.tsx | 56 ++++++++++--------- .../src/pages/app/detail/components/Pods.tsx | 22 +++++--- .../src/pages/app/detail/index.tsx | 39 +++++++------ .../src/pages/app/detail/monitor/index.tsx | 2 +- .../applaunchpad/src/pages/icons/index.tsx | 51 +++++++++++++++++ 8 files changed, 130 insertions(+), 61 deletions(-) rename frontend/providers/applaunchpad/src/components/{Sidebar/layout.tsx => layouts/DetailLayout.tsx} (96%) rename frontend/providers/applaunchpad/src/components/{Sidebar/index.tsx => layouts/Sidebar.tsx} (100%) create mode 100644 frontend/providers/applaunchpad/src/pages/icons/index.tsx diff --git a/frontend/providers/applaunchpad/src/components/Icon/index.tsx b/frontend/providers/applaunchpad/src/components/Icon/index.tsx index 43ae4766093..305a656389c 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/index.tsx +++ b/frontend/providers/applaunchpad/src/components/Icon/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { IconProps } from '@chakra-ui/react'; import { Icon } from '@chakra-ui/react'; -const map = { +export const IconMap = { more: require('./icons/more.svg').default, store: require('./icons/store.svg').default, configMap: require('./icons/configMap.svg').default, @@ -61,16 +61,23 @@ const map = { arrowRight: require('./icons/arrowRight.svg').default }; -export type IconType = keyof typeof map; +export type IconType = keyof typeof IconMap; const MyIcon = ({ name, w = 'auto', h = 'auto', ...props -}: { name: keyof typeof map } & IconProps) => { - return map[name] ? ( - +}: { name: keyof typeof IconMap } & IconProps) => { + return IconMap[name] ? ( + ) : null; }; diff --git a/frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx b/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx similarity index 96% rename from frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx rename to frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx index a0704dc93bc..0085124ecc7 100644 --- a/frontend/providers/applaunchpad/src/components/Sidebar/layout.tsx +++ b/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx @@ -1,4 +1,4 @@ -import Sidebar from '@/components/Sidebar'; +import Sidebar from '@/components/layouts/Sidebar'; import { useToast } from '@/hooks/useToast'; import { MOCK_APP_DETAIL } from '@/mock/apps'; import Header from '@/pages/app/detail/components/Header'; @@ -52,7 +52,7 @@ export default function DetailLayout({ children, appName }: DetailLayoutProps) { flexDirection={'column'} height={'100vh'} backgroundColor={'grayModern.100'} - px={'32px'} + px={'16px'} pb={4} >
+
router.replace('/apps')}>
- + {appName} - {!isLargeScreen && ( + {/* {!isLargeScreen && ( - )} + )} */} {/* btns */} {isPause ? ( ) : ( )} - {index === jsonFormList.length - 1 && ( + {index === jsonFilters.length - 1 && ( - setJsonFormList([ - ...jsonFormList, + formHook.setValue('jsonFilters', [ + ...jsonFilters, { jsonKey: '', jsonValue: '', diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx index c0cd5798fd8..e46ed58c8bc 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx @@ -19,22 +19,12 @@ import { useAppStore } from '@/store/app'; import AdvancedSelect, { ListItem } from '@/components/AdvancedSelect'; import useDateTimeStore from '@/store/date'; import { REFRESH_INTERVAL_OPTIONS } from '@/constants/monitor'; +import { UseFormReturn } from 'react-hook-form'; +import { LogsFormData } from '@/pages/app/detail/logs'; const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); -export const Header = ({ - podList, - setPodList, - refetchData, - containerList, - setContainerList -}: { - podList: ListItem[]; - setPodList: (podList: ListItem[]) => void; - refetchData: () => void; - containerList: ListItem[]; - setContainerList: (containerList: ListItem[]) => void; -}) => { +export const Header = ({ formHook }: { formHook: UseFormReturn }) => { const { t } = useTranslation(); const { refreshInterval, setRefreshInterval } = useDateTimeStore(); @@ -69,9 +59,9 @@ export const Header = ({ width={'fit-content'} value={'hello-sql-postgresql-0'} onCheckboxChange={(val) => { - setPodList(val); + formHook.setValue('pods', val); }} - list={podList} + list={formHook.watch('pods')} />
{/* container */} @@ -87,9 +77,9 @@ export const Header = ({ leftIcon={} width={'fit-content'} value={'hello-sql-postgresql-0'} - list={containerList} + list={formHook.watch('containers')} onCheckboxChange={(val) => { - setContainerList(val); + formHook.setValue('containers', val); }} /> @@ -101,9 +91,18 @@ export const Header = ({ { - console.log(e.target.value); + const val = Number(e.target.value); + if (isNaN(val)) { + formHook.setValue('limit', 1); + } else if (val > 500) { + formHook.setValue('limit', 500); + } else if (val < 1) { + formHook.setValue('limit', 1); + } else { + formHook.setValue('limit', val); + } }} /> diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx index 7fb363fc438..495463a240d 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx @@ -15,6 +15,7 @@ export const LogCounts = () => { + + {currentPage} + / + {totalPage} + + + + + {pageSize} + + + /{t('Page')} + + + ); +} diff --git a/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx b/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx new file mode 100644 index 00000000000..149a4ae402f --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx @@ -0,0 +1,107 @@ +import { + HTMLChakraProps, + Spinner, + Table, + TableContainer, + TableContainerProps, + Tbody, + Td, + Th, + Thead, + Tr +} from '@chakra-ui/react'; +import { Column, Table as ReactTable, flexRender } from '@tanstack/react-table'; +import { CSSProperties } from 'react'; + +const getCommonPinningStyles = (column: Column): CSSProperties => { + const isPinned = column.getIsPinned(); + + return { + position: isPinned ? 'sticky' : 'relative', + left: isPinned === 'left' ? 0 : undefined, + right: isPinned === 'right' ? 0 : undefined, + zIndex: isPinned ? 10 : 0 + }; +}; + +export function BaseTable({ + table, + isLoading, + tdStyle, + ...props +}: { + table: ReactTable; + isLoading: boolean; + tdStyle?: HTMLChakraProps<'td'>; +} & TableContainerProps) { + return ( + + + + {table.getHeaderGroups().map((headers) => { + return ( + + {headers.headers.map((header, i) => { + return ( + + ); + })} + + ); + })} + + + {isLoading ? ( + + + + ) : ( + table.getRowModel().rows.map((item, index) => { + return ( + + {item.getAllCells().map((cell, i) => { + const isPinned = cell.column.getIsPinned(); + return ( + + ); + })} + + ); + }) + )} + +
)} + > + {flexRender(header.column.columnDef.header, header.getContext())} +
+ +
)} + {...tdStyle} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +} diff --git a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx index 659371c9637..beb4432f48d 100644 --- a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx @@ -25,7 +25,7 @@ const map = { ], global: false // 缺省为 false }, - lineColor: '#36ADEF' + lineColor: '#85CCFF' }, deepBlue: { backgroundColor: { @@ -96,20 +96,25 @@ const map = { const LogBarChart = ({ type, data, - isShowLabel = false + isShowLabel = false, + visible = true }: { type: 'blue' | 'deepBlue' | 'green' | 'purple'; data?: MonitorDataResult; isShowLabel?: boolean; + visible?: boolean; }) => { const { screenWidth } = useGlobalStore(); - const xData = - data?.xData?.map((time) => (time ? dayjs(time * 1000).format('HH:mm') : '')) || - new Array(30).fill(0); + const xData = useMemo( + () => + data?.xData?.map((time) => dayjs(time * 1000).format('MM-DD HH:mm')) || new Array(30).fill(0), + [data?.xData] + ); const yData = data?.yData || new Array(30).fill(''); const Dom = useRef(null); const myChart = useRef(); + const resizeObserver = useRef(); const optionStyle = useMemo( () => ({ @@ -132,7 +137,15 @@ const LogBarChart = ({ xAxis: { type: 'category', data: xData, - boundaryGap: true + boundaryGap: true, + axisLine: { + lineStyle: { + color: '#E8EBF0' + } + }, + axisLabel: { + color: '#667085' + } }, yAxis: { type: 'value', @@ -148,6 +161,7 @@ const LogBarChart = ({ data: yData, type: 'bar', animationDuration: 300, + barWidth: '90%', ...optionStyle } ], @@ -165,25 +179,25 @@ const LogBarChart = ({ }, formatter: (params: any[]) => { const axisValue = params[0]?.axisValue; - return `${axisValue} ${params[0]?.value || 0}%`; + return `${axisValue} ${params[0]?.value || 0}`; } } }); // init chart useEffect(() => { - if (!Dom.current || myChart?.current?.getOption()) return; + if (!Dom.current || myChart?.current?.getOption() || !visible) return; myChart.current = echarts.init(Dom.current); myChart.current && myChart.current.setOption(option.current); - }, [Dom]); + }, [Dom, visible]); // data changed, update useEffect(() => { - if (!myChart.current || !myChart?.current?.getOption()) return; + if (!myChart.current || !myChart?.current?.getOption() || !visible) return; option.current.xAxis.data = xData; option.current.series[0].data = yData; myChart.current.setOption(option.current); - }, [xData, yData]); + }, [xData, yData, visible]); // type changed, update useEffect(() => { @@ -201,6 +215,36 @@ const LogBarChart = ({ myChart.current.resize(); }, [screenWidth]); + useEffect(() => { + if (!Dom.current || !visible) return; + + resizeObserver.current = new ResizeObserver((entries) => { + console.log(entries, 'entries'); + + const entry = entries[0]; + if (entry?.contentRect && myChart.current) { + if (entry.contentRect.width > 0 && entry.contentRect.height > 0) { + myChart.current.resize(); + } + } + }); + + resizeObserver.current.observe(Dom.current); + + return () => { + resizeObserver.current?.disconnect(); + }; + }, [visible]); + + useEffect(() => { + return () => { + if (myChart.current) { + myChart.current.dispose(); + } + resizeObserver.current?.disconnect(); + }; + }, []); + return
; }; diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx index 435712ebcd1..fcf128e4d04 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx @@ -4,16 +4,26 @@ import { useTranslation } from 'next-i18next'; import { useState } from 'react'; import MyIcon from '@/components/Icon'; import { MySelect } from '@sealos/ui'; -import { UseFormReturn } from 'react-hook-form'; +import { UseFormReturn, useFieldArray } from 'react-hook-form'; import { LogsFormData, JsonFilterItem } from '@/pages/app/detail/logs'; -export const Filter = ({ formHook }: { formHook: UseFormReturn }) => { +export const Filter = ({ + formHook, + refetchData +}: { + formHook: UseFormReturn; + refetchData: () => void; +}) => { const { t } = useTranslation(); const [activeId, setActiveId] = useState('normal_filter'); + const [inputKeyword, setInputKeyword] = useState(formHook.watch('keyword')); const isJsonMode = formHook.watch('isJsonMode'); const isOnlyStderr = formHook.watch('isOnlyStderr'); - const jsonFilters = formHook.watch('jsonFilters'); + const { fields, append, remove } = useFieldArray({ + control: formHook.control, + name: 'jsonFilters' + }); return ( @@ -56,11 +66,22 @@ export const Filter = ({ formHook }: { formHook: UseFormReturn }) /> - + setInputKeyword(e.target.value)} + /> @@ -77,52 +98,49 @@ export const Filter = ({ formHook }: { formHook: UseFormReturn }) flexWrap={'wrap'} borderRadius={'0px 8px 8px 8px'} > - {jsonFilters.length === 0 && ( + {fields.length === 0 && ( - formHook.setValue('jsonFilters', [ - ...jsonFilters, - { - jsonKey: '', - jsonValue: '', - jsonOperator: '=' - } - ]) + append({ + key: '', + value: '', + mode: '=' + }) } /> )} - {jsonFilters.map((item, index) => ( - + {fields.map((field, index) => ( + formHook.setValue('jsonFilters', val)} + value={formHook.watch(`jsonFilters.${index}.key`)} + list={formHook.watch('filterKeys')} + onchange={(val: string) => formHook.setValue(`jsonFilters.${index}.key`, val)} /> formHook.setValue('jsonFilters', val)} + onchange={(val: string) => + formHook.setValue(`jsonFilters.${index}.mode`, val as JsonFilterItem['mode']) + } /> formHook.setValue(`jsonFilters.${index}.value`, e.target.value)} border={'1px solid #E8EBF0'} boxShadow={ '0px 1px 2px 0px rgba(19, 51, 107, 0.05),0px 0px 1px 0px rgba(19, 51, 107, 0.08)' @@ -135,11 +153,7 @@ export const Filter = ({ formHook }: { formHook: UseFormReturn }) _hover={{ bg: 'grayModern.50' }} - onClick={() => { - const newList = [...jsonFilters]; - newList.splice(index, 1); - formHook.setValue('jsonFilters', newList); - }} + onClick={() => remove(index)} > }) }} /> - {index === jsonFilters.length - 1 && ( + {index === fields.length - 1 && ( - formHook.setValue('jsonFilters', [ - ...jsonFilters, - { - jsonKey: '', - jsonValue: '', - jsonOperator: '=' - } - ]) + append({ + key: '', + value: '', + mode: '=' + }) } /> )} diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx index e46ed58c8bc..b8fdd42a1da 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx @@ -24,7 +24,13 @@ import { LogsFormData } from '@/pages/app/detail/logs'; const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); -export const Header = ({ formHook }: { formHook: UseFormReturn }) => { +export const Header = ({ + formHook, + refetchData +}: { + formHook: UseFormReturn; + refetchData: () => void; +}) => { const { t } = useTranslation(); const { refreshInterval, setRefreshInterval } = useDateTimeStore(); @@ -112,6 +118,9 @@ export const Header = ({ formHook }: { formHook: UseFormReturn }) _hover={{ bg: 'grayModern.50' }} + onClick={() => { + refetchData(); + }} > { +export const LogCounts = ({ + logCountsData, + isLogCountsLoading +}: { + logCountsData: { logs_total: string; _time: string }[]; + isLogCountsLoading: boolean; +}) => { const { t } = useTranslation(); - const [onOpenChart, setOnOpenChart] = useState(true); + const processChartData = (rawData: Array<{ _time: string; logs_total: string }>) => { + const sortedData = rawData.sort( + (a, b) => new Date(a._time).getTime() - new Date(b._time).getTime() + ); + const xData = sortedData.map((item) => Math.floor(new Date(item._time).getTime() / 1000)); + const yData = sortedData.map((item) => item.logs_total); + + return { + xData, + yData + }; + }; + return ( @@ -47,15 +63,15 @@ export const LogCounts = () => { {/* charts */} - + {isLogCountsLoading ? ( +
+ +
+ ) : ( + + )}
); }; - -const mockData: MonitorDataResult = { - name: 'log', - xData: [1, 2, 3, 4, 5, 6, 7, 8], - yData: ['50', '800', '70', '60', '50', '60', '70', '80'] -}; diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index bedd3369681..e2f35b1a1aa 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -1,117 +1,148 @@ -import { useMemo, useState } from 'react'; -import { useTranslation } from 'next-i18next'; +import { BaseTable } from '@/components/BaseTable/index'; import { Box, Button, + Checkbox, + CheckboxGroup, Collapse, Divider, Flex, - Text, - Checkbox, - CheckboxGroup + Text } from '@chakra-ui/react'; -import AdvancedTable from '@/components/AdvancedTable'; +import { + ColumnDef, + getCoreRowModel, + getFilteredRowModel, + useReactTable +} from '@tanstack/react-table'; +import { get } from 'lodash'; +import { useTranslation } from 'next-i18next'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import MyIcon from '@/components/Icon'; +import { formatTime } from '@/utils/tools'; +import { LogsFormData } from '@/pages/app/detail/logs'; +import { UseFormReturn } from 'react-hook-form'; interface FieldItem { value: string; label: string; checked: boolean; + accessorKey: string; } -export const LogTable = () => { +export const LogTable = ({ + data, + isLoading, + formHook +}: { + data: any[]; + isLoading: boolean; + formHook: UseFormReturn; +}) => { const { t, i18n } = useTranslation(); const lang = i18n.language; const [onOpenField, setOnOpenField] = useState(false); const [hiddenFieldCount, setHiddenFieldCount] = useState(0); const [visibleFieldCount, setVisibleFieldCount] = useState(0); + const isJsonMode = formHook.watch('isJsonMode'); + const globalFilter = formHook.watch('keyword'); - const [fieldList, setFieldList] = useState([ - { - value: 'test', - label: 'test', - checked: true - } - ]); + const generateFieldList = useCallback( + (data: any[]) => { + if (!data.length) return []; - const columns = useMemo< - { - title: string; - dataIndex?: string; - key: string; - render?: (item: any) => JSX.Element; - }[] - >( - () => [ - { - title: 'time', - dataIndex: 'time', - key: 'time', - render: (item: any) => ( - - {item.time} - - ) - }, - { - title: 'log_tag', - dataIndex: 'log_tag', - key: 'log_tag', - render: (item: any) => ( - - {item.log_tag} - - ) - }, - { - title: 'message', - dataIndex: 'message', - key: 'message', - render: (item: any) => ( - - {item.message} - - ) - }, - { - title: 'authority', - dataIndex: 'authority', - key: 'authority', - render: (item: any) => ( - - {item.authority} - - ) - }, - { - title: 'bytes_received', - dataIndex: 'source', - key: 'source', - render: (item: any) => ( - - {item.source} - - ) - }, - { - title: 'bytes_sent', - dataIndex: 'bytes_sent', - key: 'bytes_sent', - render: (item: any) => ( - - {item.bytes_sent} - - ) + if (!isJsonMode) { + return [ + { + value: 'time', + label: 'Time', + checked: true, + accessorKey: '_time' + }, + { + value: 'message', + label: 'Message', + checked: true, + accessorKey: '_msg' + } + ]; } - ], - [] + + const firstItem = data[0]; + if (Array.isArray(firstItem)) { + return firstItem.map((_, index) => ({ + value: `column${index}`, + label: `Column ${index + 1}`, + checked: index === 0, + accessorKey: index.toString() + })); + } + + return Object.keys(firstItem) + .filter((key) => key !== '_msg') + .map((key) => ({ + value: key, + label: key, + checked: ['_time', 'stream', 'id', 'ctx'].includes(key), + accessorKey: key + })); + }, + [isJsonMode] ); + const [fieldList, setFieldList] = useState(() => generateFieldList(data)); + + useEffect(() => { + setFieldList(generateFieldList(data)); + formHook.setValue( + 'filterKeys', + generateFieldList(data).map((field) => ({ value: field.value, label: field.label })) + ); + }, [data, generateFieldList, isJsonMode, formHook]); + + useEffect(() => { + const visibleCount = fieldList.filter((field) => field.checked).length; + setVisibleFieldCount(visibleCount); + setHiddenFieldCount(fieldList.length - visibleCount); + }, [fieldList]); + + const columns = useMemo>>(() => { + return fieldList + .filter((field) => field.checked) + .map((field) => ({ + accessorKey: field.accessorKey, + header: () => field.label, + cell: ({ row }) => { + let value = get(row.original, field.accessorKey, ''); + + if (field.accessorKey === '_time') { + value = formatTime(value, 'YYYY-MM-DD HH:mm:ss'); + } + + return ( + + {value?.toString() || ''} + + ); + } + })); + }, [fieldList]); + + const table = useReactTable({ + data: data, + columns, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + state: { + globalFilter: globalFilter + } + }); + return ( - + { > {t('Log')} - - - - - {t('visible')}: - - - {visibleFieldCount} {lang === 'zh' ? t('piece') : ''} - - - - - - - - {t('hidden')}: - - - {hiddenFieldCount} {lang === 'zh' ? t('piece') : ''} - + + + + {t('visible')}: + + + {visibleFieldCount} {lang === 'zh' ? t('piece') : ''} + + + + + + + + {t('hidden')}: + + + {hiddenFieldCount} {lang === 'zh' ? t('piece') : ''} + + - + )} - {/* fields */} - - - - {fieldList.map((item) => ( - - setFieldList( - fieldList.map((field) => - field.value === item.value ? { ...field, checked: !field.checked } : field + + {isJsonMode && ( + + + + {fieldList.map((item) => ( + + setFieldList( + fieldList.map((field) => + field.value === item.value ? { ...field, checked: !field.checked } : field + ) ) - ) - } - sx={{ - 'span.chakra-checkbox__control[data-checked]': { - background: '#f0f4ff ', - border: '1px solid #219bf4 ', - boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)', - color: '#219bf4', - borderRadius: '4px' } - }} - > - {item.label} - - ))} - - - - - {/* table */} - + sx={{ + 'span.chakra-checkbox__control[data-checked]': { + background: '#f0f4ff ', + border: '1px solid #219bf4 ', + boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)', + color: '#219bf4', + borderRadius: '4px' + } + }} + > + {item.label} + + ))} + + + + )} + + ); }; - -const mockData = [ - { - time: '2024-01-01 10:00:00', - log_tag: 'test', - message: 'test', - authority: 'test', - source: 'test', - bytes_sent: 'test' - }, - { - time: '2024-01-01 10:00:00', - log_tag: 'test', - message: 'test', - authority: 'test', - source: 'test', - bytes_sent: 'test' - }, - { - time: '2024-01-01 10:00:00', - log_tag: 'test', - message: 'test', - authority: 'test', - source: 'test', - bytes_sent: 'test' - }, - { - time: '2024-01-01 10:00:00', - log_tag: 'test', - message: 'test', - authority: 'test', - source: 'test', - bytes_sent: 'test' - }, - { - time: '2024-01-01 10:00:00', - log_tag: 'test', - message: 'test', - authority: 'test', - source: 'test', - bytes_sent: 'test' - } -]; diff --git a/frontend/providers/applaunchpad/src/pages/api/log/get.ts b/frontend/providers/applaunchpad/src/pages/api/log/get.ts index 95697ff31f4..75d6ad9219e 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/get.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/get.ts @@ -1,3 +1,4 @@ +import { JsonFilterItem } from '@/pages/app/detail/logs'; import { authSession } from '@/services/backend/auth'; import { getK8s } from '@/services/backend/kubernetes'; import { jsonRes } from '@/services/backend/response'; @@ -17,7 +18,7 @@ export interface LogQueryPayload { pod?: string[]; // 可选,默认 [] container?: string[]; // 可选,默认 [] keyword?: string; // 可选,默认 "" - jsonQuery?: any[]; // 可选,默认 [] + jsonQuery?: JsonFilterItem[]; // 可选,默认 [] } export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -45,7 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { time = '30d', app = '', - limit = '1', + limit = '10', jsonMode = 'true', stderrMode = 'false', numberMode = 'false', @@ -77,7 +78,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< console.log(params, 'params'); - const result = await fetch('http://192.168.10.63:8428/queryLogsByParams', { + const result = await fetch('http://tipmjzasbtqv.sealoshzh.site/queryLogsByParams', { method: 'POST', body: JSON.stringify(params), headers: { @@ -90,22 +91,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const data = await result.text(); // 按行分割并过滤空行 const logLines = data.split('\n').filter((line) => line.trim()); + console.log(logLines, 'logLines'); // 解析每行日志 const parsedLogs = logLines.map((line) => { try { const logEntry = JSON.parse(line); // 如果_msg是JSON字符串,也将其解析 - if (logEntry._msg) { - try { - logEntry._msg = JSON.parse(logEntry._msg); - } catch (e) { - // 如果_msg不是JSON格式,保持原样 - } - } + // if (logEntry._msg) { + // try { + // logEntry._msg = JSON.parse(logEntry._msg); + // } catch (e) { + // // 如果_msg不是JSON格式,保持原样 + // } + // } return logEntry; } catch (e) { - return line; // 如果解析失败,返回原始行 + throw new Error('parse log error'); } }); diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 8e21d28b24f..ca23cc45c3e 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -16,11 +16,12 @@ import { ListItem } from '@/components/AdvancedSelect'; import useDateTimeStore from '@/store/date'; import { getAppLogs } from '@/api/app'; import { useForm } from 'react-hook-form'; +import { formatTimeRange } from '@/utils/timeRange'; export interface JsonFilterItem { - jsonKey: string; - jsonValue: string; - jsonOperator: '=' | '>' | '<' | 'contains' | 'not_contains'; + key: string; + value: string; + mode: '=' | '!=' | '~'; } export interface LogsFormData { @@ -32,6 +33,10 @@ export interface LogsFormData { isOnlyStderr: boolean; jsonFilters: JsonFilterItem[]; refreshInterval: number; + filterKeys: { + value: string; + label: string; + }[]; } export default function LogsPage({ appName }: { appName: string }) { @@ -46,7 +51,7 @@ export default function LogsPage({ appName }: { appName: string }) { defaultValues: { pods: [], containers: [], - limit: 100, + limit: 10, keyword: '', isJsonMode: false, isOnlyStderr: false, @@ -55,6 +60,7 @@ export default function LogsPage({ appName }: { appName: string }) { } }); + // init pods and containers useEffect(() => { if (!isInitialized && appDetailPods?.length > 0) { const pods = appDetailPods.map((pod) => ({ @@ -80,21 +86,61 @@ export default function LogsPage({ appName }: { appName: string }) { const selectedPods = formHook.watch('pods').filter((pod) => pod.checked); const selectedContainers = formHook.watch('containers').filter((container) => container.checked); - - const { data: logsData } = useQuery( - ['logs-data', appName], + const timeRange = formatTimeRange(startDateTime, endDateTime); + + const { + data: logsData, + isLoading, + refetch: refetchLogsData + } = useQuery( + [ + 'logs-data', + appName, + timeRange, + formHook.watch('isOnlyStderr'), + formHook.watch('limit'), + formHook.watch('isJsonMode'), + formHook.watch('jsonFilters') + ], () => getAppLogs({ - app: appName + time: timeRange, + app: appName, + limit: formHook.watch('limit').toString(), + jsonMode: formHook.watch('isJsonMode').toString(), + stderrMode: formHook.watch('isOnlyStderr').toString(), + jsonQuery: formHook.watch('jsonFilters') }), { - refetchInterval: refreshInterval + refetchInterval: refreshInterval, + onError: (error: any) => { + console.log(error, 'error'); + setRefreshInterval(0); + }, + retry: 1 } ); - console.log(logsData, 'logsData'); + const { + data: logCountsData, + refetch: refetchLogCountsData, + isLoading: isLogCountsLoading + } = useQuery(['log-counts-data', appName, timeRange, formHook.watch('isOnlyStderr')], () => + getAppLogs({ + app: appName, + numberMode: 'true', + numberLevel: timeRange.slice(-1), + time: timeRange, + stderrMode: formHook.watch('isOnlyStderr').toString() + }) + ); + + console.log(formHook.getValues(), logsData, logCountsData, 'logsData'); - const refetchData = () => {}; + const refetchData = () => { + refetchLogsData(); + refetchLogCountsData(); + }; return ( @@ -103,14 +149,13 @@ export default function LogsPage({ appName }: { appName: string }) { -
+
- + - + - + diff --git a/frontend/providers/applaunchpad/src/store/date.ts b/frontend/providers/applaunchpad/src/store/date.ts index 3bde1e3a9f7..3839d5cbef4 100644 --- a/frontend/providers/applaunchpad/src/store/date.ts +++ b/frontend/providers/applaunchpad/src/store/date.ts @@ -17,7 +17,7 @@ type DateTimeState = { const useDateTimeStore = create()( devtools( immer((set, get) => ({ - startDateTime: subDays(new Date(), 7), + startDateTime: subDays(new Date(), 3), endDateTime: new Date(), timeZone: 'local', refreshInterval: 0, diff --git a/frontend/providers/applaunchpad/src/types/monitor.d.ts b/frontend/providers/applaunchpad/src/types/monitor.d.ts index 658b991ccf5..2ea2ac1eae2 100644 --- a/frontend/providers/applaunchpad/src/types/monitor.d.ts +++ b/frontend/providers/applaunchpad/src/types/monitor.d.ts @@ -42,7 +42,7 @@ export type MonitorQueryKey = { }; export type MonitorDataResult = { - name: string; + name?: string; xData: number[]; yData: string[]; }; diff --git a/frontend/providers/applaunchpad/src/utils/timeRange.ts b/frontend/providers/applaunchpad/src/utils/timeRange.ts new file mode 100644 index 00000000000..c9562070eff --- /dev/null +++ b/frontend/providers/applaunchpad/src/utils/timeRange.ts @@ -0,0 +1,71 @@ +import { subHours, subDays, subMinutes, subMonths } from 'date-fns'; + +type TimeUnit = 'h' | 'm' | 'd' | 'M'; + +interface TimeRange { + startTime: Date; + endTime: Date; +} + +/** + * Parse time range string + * @param range Time range string, e.g. "1h", "7d", "30m", "1M" + * @param endTime End time, defaults to current time + * @returns Object containing start and end time + */ +export function parseTimeRange(range: string, endTime: Date = new Date()): TimeRange { + const match = range.match(/^(\d+)([hmdM])$/i); + if (!match) { + throw new Error('Invalid time range format. Supported formats: 1h, 7d, 30m, 1M'); + } + + const value = parseInt(match[1], 10); + const unit = match[2].toLowerCase() as TimeUnit; + + let startTime: Date; + switch (unit) { + case 'h': + startTime = subHours(endTime, value); + break; + case 'm': + startTime = subMinutes(endTime, value); + break; + case 'd': + startTime = subDays(endTime, value); + break; + case 'M': + startTime = subMonths(endTime, value); + break; + default: + throw new Error('Unsupported time unit'); + } + + return { + startTime, + endTime + }; +} + +/** + * Convert time range to string format + * @param startTime Start time + * @param endTime End time + * @returns Time range string, e.g. "1h", "7d" + */ +export function formatTimeRange(startTime: Date, endTime: Date): string { + const diffMs = endTime.getTime() - startTime.getTime(); + const diffMinutes = Math.round(diffMs / (1000 * 60)); + const diffHours = Math.round(diffMs / (1000 * 60 * 60)); + const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24)); + const diffMonths = Math.round(diffMs / (1000 * 60 * 60 * 24 * 30)); + + if (diffMinutes < 60) { + return `${diffMinutes}m`; + } else if (diffHours < 24) { + return `${diffHours}h`; + } else if (diffDays < 30) { + return `${diffDays}d`; + } else { + return `${diffMonths}M`; + } +} From 27f4d5b70e96d1a3bdf91ea2777aeab5f3c738d0 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Tue, 21 Jan 2025 19:53:38 +0800 Subject: [PATCH 26/48] update logs --- frontend/pnpm-lock.yaml | 3 + .../providers/applaunchpad/src/api/app.ts | 11 +-- .../src/components/BaseTable/index.tsx | 9 ++- .../src/components/DatePicker/index.tsx | 11 +-- .../src/components/app/detail/logs/Filter.tsx | 7 +- .../components/app/detail/logs/LogCounts.tsx | 2 +- .../components/app/detail/logs/LogTable.tsx | 33 ++++++--- .../pages/api/log/{get.ts => queryLogs.ts} | 33 ++------- .../src/pages/app/detail/logs.tsx | 67 +++++++++++------- .../providers/applaunchpad/src/store/date.ts | 23 +++--- .../applaunchpad/src/store/logStore.ts | 70 +++++++++++++++++++ 11 files changed, 176 insertions(+), 93 deletions(-) rename frontend/providers/applaunchpad/src/pages/api/log/{get.ts => queryLogs.ts} (77%) create mode 100644 frontend/providers/applaunchpad/src/store/logStore.ts diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 605d741f0fc..8105e8d1e89 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -606,6 +606,9 @@ importers: '@tanstack/react-query': specifier: ^4.35.3 version: 4.36.1(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-table': + specifier: ^8.10.7 + version: 8.10.7(react-dom@18.2.0)(react@18.2.0) ansi_up: specifier: ^5.2.1 version: 5.2.1 diff --git a/frontend/providers/applaunchpad/src/api/app.ts b/frontend/providers/applaunchpad/src/api/app.ts index e2bfc188e40..d8c69f418ba 100644 --- a/frontend/providers/applaunchpad/src/api/app.ts +++ b/frontend/providers/applaunchpad/src/api/app.ts @@ -9,7 +9,7 @@ import { } from '@/utils/adapt'; import type { AppPatchPropsType, PodDetailType } from '@/types/app'; import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor'; -import { LogQueryPayload } from '@/pages/api/log/get'; +import { LogQueryPayload } from '@/pages/api/log/queryLogs'; export const postDeployApp = (yamlList: string[]) => POST('/api/applyApp', { yamlList }); @@ -63,11 +63,4 @@ export const getAppMonitorData = (payload: { end?: number; }) => GET(`/api/monitor/getMonitorData`, payload); -export const getAppLogs = (payload: LogQueryPayload) => - POST< - { - logs_total: string; - _msg: string; - _time: string; - }[] - >('/api/log/get', payload); +export const getAppLogs = (payload: LogQueryPayload) => POST('/api/log/queryLogs', payload); diff --git a/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx b/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx index 149a4ae402f..017019ac33e 100644 --- a/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx +++ b/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx @@ -84,7 +84,14 @@ export function BaseTable({ { - + - + { - + {recentDateList.map((item) => ( @@ -277,8 +293,7 @@ export const LogTable = ({ maxH={'500px'} tdStyle={{ p: '10px 24px', - borderBottom: '1px solid', - borderBottomColor: '#F0F1F6' + borderBottom: 'none' }} /> diff --git a/frontend/providers/applaunchpad/src/pages/api/log/get.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts similarity index 77% rename from frontend/providers/applaunchpad/src/pages/api/log/get.ts rename to frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts index 75d6ad9219e..34510b015a2 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/get.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts @@ -3,7 +3,7 @@ import { authSession } from '@/services/backend/auth'; import { getK8s } from '@/services/backend/kubernetes'; import { jsonRes } from '@/services/backend/response'; import { ApiResp } from '@/services/kubernet'; -import { monitorFetch } from '@/services/monitorFetch'; + import type { NextApiRequest, NextApiResponse } from 'next'; export interface LogQueryPayload { @@ -19,6 +19,7 @@ export interface LogQueryPayload { container?: string[]; // 可选,默认 [] keyword?: string; // 可选,默认 "" jsonQuery?: JsonFilterItem[]; // 可选,默认 [] + exportMode?: boolean; // 新增:是否导出文件模式 } export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -54,7 +55,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< pod = [], container = [], keyword = '', - jsonQuery = [] + jsonQuery = [], + exportMode = false } = req.body as LogQueryPayload; const params: LogQueryPayload = { @@ -87,37 +89,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } }); const contentType = result.headers.get('content-type'); - console.log(contentType, 'contentType'); - const data = await result.text(); - // 按行分割并过滤空行 - const logLines = data.split('\n').filter((line) => line.trim()); - console.log(logLines, 'logLines'); - // 解析每行日志 - const parsedLogs = logLines.map((line) => { - try { - const logEntry = JSON.parse(line); - // 如果_msg是JSON字符串,也将其解析 - // if (logEntry._msg) { - // try { - // logEntry._msg = JSON.parse(logEntry._msg); - // } catch (e) { - // // 如果_msg不是JSON格式,保持原样 - // } - // } - return logEntry; - } catch (e) { - throw new Error('parse log error'); - } - }); + console.log(result.status, contentType); + const data = await result.text(); jsonRes(res, { code: 200, - data: parsedLogs + data: data }); } catch (error) { console.log(error, 'error'); - jsonRes(res, { code: 500, error: error diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index ca23cc45c3e..03523494043 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -17,6 +17,8 @@ import useDateTimeStore from '@/store/date'; import { getAppLogs } from '@/api/app'; import { useForm } from 'react-hook-form'; import { formatTimeRange } from '@/utils/timeRange'; +import { downLoadBold } from '@/utils/tools'; +import { useLogStore } from '@/store/logStore'; export interface JsonFilterItem { key: string; @@ -46,6 +48,7 @@ export default function LogsPage({ appName }: { appName: string }) { const { appDetail, appDetailPods } = useAppStore(); const [isInitialized, setIsInitialized] = useState(false); const { refreshInterval, setRefreshInterval, startDateTime, endDateTime } = useDateTimeStore(); + const { setLogs, exportLogs, parsedLogs, logCounts, setLogCounts } = useLogStore(); const formHook = useForm({ defaultValues: { @@ -88,11 +91,7 @@ export default function LogsPage({ appName }: { appName: string }) { const selectedContainers = formHook.watch('containers').filter((container) => container.checked); const timeRange = formatTimeRange(startDateTime, endDateTime); - const { - data: logsData, - isLoading, - refetch: refetchLogsData - } = useQuery( + const { isLoading, refetch: refetchLogsData } = useQuery( [ 'logs-data', appName, @@ -100,7 +99,7 @@ export default function LogsPage({ appName }: { appName: string }) { formHook.watch('isOnlyStderr'), formHook.watch('limit'), formHook.watch('isJsonMode'), - formHook.watch('jsonFilters') + formHook.watch('keyword') ], () => getAppLogs({ @@ -109,7 +108,10 @@ export default function LogsPage({ appName }: { appName: string }) { limit: formHook.watch('limit').toString(), jsonMode: formHook.watch('isJsonMode').toString(), stderrMode: formHook.watch('isOnlyStderr').toString(), - jsonQuery: formHook.watch('jsonFilters') + jsonQuery: formHook + .watch('jsonFilters') + .filter((item) => item.key && item.key.trim() !== ''), + keyword: formHook.watch('keyword') }), { refetchInterval: refreshInterval, @@ -117,27 +119,45 @@ export default function LogsPage({ appName }: { appName: string }) { console.log(error, 'error'); setRefreshInterval(0); }, + onSuccess: (data) => { + setLogs(data); + }, retry: 1 } ); - const { - data: logCountsData, - refetch: refetchLogCountsData, - isLoading: isLogCountsLoading - } = useQuery(['log-counts-data', appName, timeRange, formHook.watch('isOnlyStderr')], () => - getAppLogs({ - app: appName, - numberMode: 'true', - numberLevel: timeRange.slice(-1), - time: timeRange, - stderrMode: formHook.watch('isOnlyStderr').toString() - }) + const { refetch: refetchLogCountsData, isLoading: isLogCountsLoading } = useQuery( + [ + 'log-counts-data', + appName, + timeRange, + formHook.watch('isOnlyStderr'), + formHook.watch('jsonFilters'), + formHook.watch('keyword') + ], + () => + getAppLogs({ + app: appName, + numberMode: 'true', + numberLevel: timeRange.slice(-1), + time: timeRange, + stderrMode: formHook.watch('isOnlyStderr').toString() + // jsonQuery: formHook + // .watch('jsonFilters') + // .filter((item) => item.key && item.key.trim() !== ''), + // keyword: formHook.watch('keyword') + }), + { + onSuccess: (data) => { + setLogCounts(data); + } + } ); - console.log(formHook.getValues(), logsData, logCountsData, 'logsData'); + console.log(formHook.getValues(), logCounts, parsedLogs, 'logsData'); const refetchData = () => { + console.log('refetchData'); refetchLogsData(); refetchLogCountsData(); }; @@ -165,10 +185,7 @@ export default function LogsPage({ appName }: { appName: string }) { borderRadius={'lg'} flexShrink={0} > - + - + diff --git a/frontend/providers/applaunchpad/src/store/date.ts b/frontend/providers/applaunchpad/src/store/date.ts index 3839d5cbef4..7123c3eb15f 100644 --- a/frontend/providers/applaunchpad/src/store/date.ts +++ b/frontend/providers/applaunchpad/src/store/date.ts @@ -1,6 +1,5 @@ import { subDays } from 'date-fns'; import { create } from 'zustand'; -import { devtools } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; type DateTimeState = { @@ -15,18 +14,16 @@ type DateTimeState = { }; const useDateTimeStore = create()( - devtools( - immer((set, get) => ({ - startDateTime: subDays(new Date(), 3), - endDateTime: new Date(), - timeZone: 'local', - refreshInterval: 0, - setStartDateTime: (datetime) => set({ startDateTime: datetime }), - setEndDateTime: (datetime) => set({ endDateTime: datetime }), - setTimeZone: (timeZone) => set({ timeZone }), - setRefreshInterval: (val) => set({ refreshInterval: val }) - })) - ) + immer((set, get) => ({ + startDateTime: subDays(new Date(), 7), + endDateTime: new Date(), + timeZone: 'local', + refreshInterval: 0, + setStartDateTime: (datetime) => set({ startDateTime: datetime }), + setEndDateTime: (datetime) => set({ endDateTime: datetime }), + setTimeZone: (timeZone) => set({ timeZone }), + setRefreshInterval: (val) => set({ refreshInterval: val }) + })) ); export default useDateTimeStore; diff --git a/frontend/providers/applaunchpad/src/store/logStore.ts b/frontend/providers/applaunchpad/src/store/logStore.ts new file mode 100644 index 00000000000..01a10c08bde --- /dev/null +++ b/frontend/providers/applaunchpad/src/store/logStore.ts @@ -0,0 +1,70 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import { devtools } from 'zustand/middleware'; + +interface LogState { + rawLogs: string; + parsedLogs: { + logs_total: string; + _msg: string; + _time: string; + stream: 'stderr' | 'stdout'; + }[]; + logCounts: { logs_total: string; _time: string }[]; + setLogs: (data: string) => void; + exportLogs: () => void; + setLogCounts: (data: string) => void; +} + +export const useLogStore = create()( + devtools( + immer((set, get) => ({ + rawLogs: '', + parsedLogs: [], + logCounts: [], + setLogs: (data: string) => + set((state) => { + state.rawLogs = data; + const logLines = data.split('\n').filter((line) => line.trim()); + state.parsedLogs = logLines.map((line) => { + try { + return JSON.parse(line); + } catch (e) { + return { raw: line, parseError: true }; + } + }); + }), + setLogCounts: (data: string) => + set((state) => { + const logLines = data.split('\n').filter((line) => line.trim()); + console.log( + logLines.map((line) => { + try { + return JSON.parse(line); + } catch (e) { + return { raw: line, parseError: true }; + } + }), + 'data' + ); + state.logCounts = logLines.map((line) => { + try { + return JSON.parse(line); + } catch (e) { + return { raw: line, parseError: true }; + } + }); + }), + exportLogs: () => { + const { rawLogs } = get(); + const blob = new Blob([rawLogs], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `logs-${new Date().toISOString()}.txt`; + a.click(); + URL.revokeObjectURL(url); + } + })) + ) +); From e9611628d2b6e8ba3de064861b119bb9155e465d Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 22 Jan 2025 16:04:24 +0800 Subject: [PATCH 27/48] update --- .../providers/applaunchpad/data/config.yaml | 2 + .../src/components/DatePicker/index.tsx | 161 ++++++++---------- .../src/components/LogBarChart/index.tsx | 2 - .../src/components/MonitorChart/index.tsx | 72 ++++++-- .../src/components/app/detail/logs/Filter.tsx | 9 +- .../src/components/app/detail/logs/Header.tsx | 36 ++-- .../components/app/detail/logs/LogCounts.tsx | 14 +- .../components/app/detail/logs/LogTable.tsx | 37 ++-- .../src/components/layouts/DetailLayout.tsx | 7 +- .../src/components/layouts/Sidebar.tsx | 2 +- .../src/pages/api/log/queryLogs.ts | 26 +-- .../src/pages/api/platform/getInitData.ts | 3 + .../src/pages/app/detail/index.tsx | 15 -- .../src/pages/app/detail/logs.tsx | 43 +++-- .../src/pages/app/detail/monitor.tsx | 4 +- .../applaunchpad/src/store/logStore.ts | 9 + .../applaunchpad/src/types/index.d.ts | 3 + 17 files changed, 245 insertions(+), 200 deletions(-) diff --git a/frontend/providers/applaunchpad/data/config.yaml b/frontend/providers/applaunchpad/data/config.yaml index 998a3f0d05e..0fadb71bbd5 100644 --- a/frontend/providers/applaunchpad/data/config.yaml +++ b/frontend/providers/applaunchpad/data/config.yaml @@ -19,6 +19,8 @@ launchpad: url: http://launchpad-monitor.sealos.svc.cluster.local:8428 billing: url: "http://account-service.account-system.svc:2333" + log: + url: "http://localhost:8080" appResourceFormSliderConfig: default: cpu: [100, 200, 500, 1000, 2000, 3000, 4000, 8000] diff --git a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx index 82c193f907d..5f41b9e203a 100644 --- a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx +++ b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx @@ -2,28 +2,28 @@ import { Button, + ButtonGroup, + Divider, Flex, - Text, FlexProps, + Grid, + GridItem, Input, Popover, PopoverContent, PopoverTrigger, - Divider, - Grid, - GridItem, - ButtonGroup + Text, + useDisclosure } from '@chakra-ui/react'; +import { endOfDay, format, isAfter, isBefore, isMatch, isValid, parse, startOfDay } from 'date-fns'; import { enUS, zhCN } from 'date-fns/locale'; import { useTranslation } from 'next-i18next'; -import { ChangeEvent, ChangeEventHandler, useState } from 'react'; +import { ChangeEventHandler, useMemo, useState } from 'react'; import { DateRange, DayPicker, SelectRangeEventHandler } from 'react-day-picker'; -import { endOfDay, format, isAfter, isBefore, isMatch, isValid, parse, startOfDay } from 'date-fns'; -import { useDisclosure } from '@chakra-ui/react'; - -import MyIcon from '../Icon'; import useDateTimeStore from '@/store/date'; +import { formatTimeRange, parseTimeRange } from '@/utils/timeRange'; import { MySelect } from '@sealos/ui'; +import MyIcon from '../Icon'; interface DatePickerProps extends FlexProps { isDisabled?: boolean; @@ -48,11 +48,70 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { to: endDateTime }; - const defaultRecentDate = { - label: `${t('recently')} 7 ${t('day')}`, - value: getDateRange('7d'), - compareValue: '7d' - }; + const recentDateList = useMemo( + () => [ + { + label: `${t('recently')} 5 ${t('minute')}`, + value: getDateRange('5m'), + compareValue: '5m' + }, + { + label: `${t('recently')} 15 ${t('minute')}`, + value: getDateRange('15m'), + compareValue: '15m' + }, + { + label: `${t('recently')} 30 ${t('minute')}`, + value: getDateRange('30m'), + compareValue: '30m' + }, + { + label: `${t('recently')} 1 ${t('hour-singular')}`, + value: getDateRange('1h'), + compareValue: '1h' + }, + { + label: `${t('recently')} 3 ${t('hour')}`, + value: getDateRange('3h'), + compareValue: '3h' + }, + { + label: `${t('recently')} 6 ${t('hour')}`, + value: getDateRange('6h'), + compareValue: '6h' + }, + { + label: `${t('recently')} 24 ${t('hour')}`, + value: getDateRange('24h'), + compareValue: '24h' + }, + { + label: `${t('recently')} 2 ${t('day')}`, + value: getDateRange('2d'), + compareValue: '2d' + }, + { + label: `${t('recently')} 3 ${t('day')}`, + value: getDateRange('3d'), + compareValue: '3d' + }, + { + label: `${t('recently')} 7 ${t('day')}`, + value: getDateRange('7d'), + compareValue: '7d' + } + ], + [t] + ); + + const defaultRecentDate = useMemo(() => { + const currentTimeRange = formatTimeRange(startDateTime, endDateTime); + return ( + recentDateList.find((item) => item.compareValue === currentTimeRange) || + recentDateList.find((item) => item.compareValue === '7d') || + recentDateList[0] + ); + }, [startDateTime, endDateTime, recentDateList]); const [inputState, setInputState] = useState<0 | 1>(0); const [recentDate, setRecentDate] = useState(defaultRecentDate); @@ -235,59 +294,6 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { } }; - const recentDateList = [ - { - label: `${t('recently')} 5 ${t('minute')}`, - value: getDateRange('5m'), - compareValue: '5m' - }, - { - label: `${t('recently')} 15 ${t('minute')}`, - value: getDateRange('15m'), - compareValue: '15m' - }, - { - label: `${t('recently')} 30 ${t('minute')}`, - value: getDateRange('30m'), - compareValue: '30m' - }, - { - label: `${t('recently')} 1 ${t('hour-singular')}`, - value: getDateRange('1h'), - compareValue: '1h' - }, - { - label: `${t('recently')} 3 ${t('hour')}`, - value: getDateRange('3h'), - compareValue: '3h' - }, - { - label: `${t('recently')} 6 ${t('hour')}`, - value: getDateRange('6h'), - compareValue: '6h' - }, - { - label: `${t('recently')} 24 ${t('hour')}`, - value: getDateRange('24h'), - compareValue: '24h' - }, - { - label: `${t('recently')} 2 ${t('day')}`, - value: getDateRange('2d'), - compareValue: '2d' - }, - { - label: `${t('recently')} 3 ${t('day')}`, - value: getDateRange('3d'), - compareValue: '3d' - }, - { - label: `${t('recently')} 7 ${t('day')}`, - value: getDateRange('7d'), - compareValue: '7d' - } - ]; - return ( { - const now = new Date(); - const to = now; - const from = new Date(); - - const [amount, unit] = [parseInt(value), value.slice(-1)]; - - switch (unit) { - case 'm': - from.setMinutes(now.getMinutes() - amount); - break; - case 'h': - from.setHours(now.getHours() - amount); - break; - case 'd': - from.setDate(now.getDate() - amount); - break; - } - + const { startTime: from, endTime: to } = parseTimeRange(value); return { from, to }; }; diff --git a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx index beb4432f48d..84a7a3adf1b 100644 --- a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx @@ -219,8 +219,6 @@ const LogBarChart = ({ if (!Dom.current || !visible) return; resizeObserver.current = new ResizeObserver((entries) => { - console.log(entries, 'entries'); - const entry = entries[0]; if (entry?.contentRect && myChart.current) { if (entry.contentRect.width > 0 && entry.contentRect.height > 0) { diff --git a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx index dc342dff8c2..bb3ae8fe805 100644 --- a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx @@ -5,6 +5,7 @@ import dayjs from 'dayjs'; import { LineStyleMap } from '@/constants/monitor'; import { Flex, FlexProps, Text } from '@chakra-ui/react'; import MyIcon from '../Icon'; +import { useTranslation } from 'next-i18next'; type MonitorChart = FlexProps & { data: { @@ -37,23 +38,74 @@ const MonitorChart = ({ const { screenWidth } = useGlobalStore(); const chartDom = useRef(null); const myChart = useRef(); + const { t } = useTranslation(); const option = useMemo( () => ({ tooltip: { trigger: 'axis', + enterable: true, formatter: (params: any) => { let axisValue = params[0]?.axisValue; - const content = params - .map( - (item: any) => - `${item?.marker} ${item?.seriesName}   ${ - item?.value - }${unit ? unit : ''}
` - ) - .join(''); - const str = axisValue + '
' + content; - return str; + return ` +
+
${axisValue}
+ ${params + .map( + (item: any) => ` +
+ + ${item.seriesName} + ${item.value}${unit || ''} + +
+ ` + ) + .join('')} +
+ `; }, // @ts-ignore position: (point, params, dom, rect, size) => { diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx index 3e13efba98b..a918b8cd6e2 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx @@ -1,11 +1,10 @@ -import { Tabs } from '@sealos/ui'; -import { Box, Button, ButtonProps, Flex, Input, Switch, Text } from '@chakra-ui/react'; -import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; import MyIcon from '@/components/Icon'; +import { JsonFilterItem, LogsFormData } from '@/pages/app/detail/logs'; +import { Button, ButtonProps, Flex, Input, Switch, Text } from '@chakra-ui/react'; import { MySelect } from '@sealos/ui'; +import { useTranslation } from 'next-i18next'; +import { useState } from 'react'; import { UseFormReturn, useFieldArray } from 'react-hook-form'; -import { LogsFormData, JsonFilterItem } from '@/pages/app/detail/logs'; export const Filter = ({ formHook, diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx index b8fdd42a1da..f1a44bfe133 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Header.tsx @@ -1,5 +1,4 @@ -import dynamic from 'next/dynamic'; -import { useTranslation } from 'next-i18next'; +import { ChevronDownIcon } from '@chakra-ui/icons'; import { Button, ButtonGroup, @@ -11,16 +10,15 @@ import { MenuList, Text } from '@chakra-ui/react'; -import { useState } from 'react'; -import { ChevronDownIcon } from '@chakra-ui/icons'; +import { useTranslation } from 'next-i18next'; +import dynamic from 'next/dynamic'; +import AdvancedSelect from '@/components/AdvancedSelect'; import MyIcon from '@/components/Icon'; -import { useAppStore } from '@/store/app'; -import AdvancedSelect, { ListItem } from '@/components/AdvancedSelect'; -import useDateTimeStore from '@/store/date'; import { REFRESH_INTERVAL_OPTIONS } from '@/constants/monitor'; -import { UseFormReturn } from 'react-hook-form'; import { LogsFormData } from '@/pages/app/detail/logs'; +import useDateTimeStore from '@/store/date'; +import { UseFormReturn } from 'react-hook-form'; const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); @@ -35,23 +33,14 @@ export const Header = ({ const { refreshInterval, setRefreshInterval } = useDateTimeStore(); return ( - - {/* time */} + {t('time')} - {/* pod */} + Pods @@ -70,18 +59,17 @@ export const Header = ({ list={formHook.watch('pods')} /> - {/* container */} - + + Containers } - width={'fit-content'} value={'hello-sql-postgresql-0'} list={formHook.watch('containers')} onCheckboxChange={(val) => { @@ -89,7 +77,7 @@ export const Header = ({ }} /> - {/* log number */} + {t('log_number')} diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx index 4c8361a18c6..5a963906da8 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogCounts.tsx @@ -1,15 +1,16 @@ import MyIcon from '@/components/Icon'; import LogBarChart from '@/components/LogBarChart'; -import { Box, Button, Center, Collapse, Flex, Spinner } from '@chakra-ui/react'; +import { Box, Button, Center, Collapse, Flex, Spinner, Text } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useState } from 'react'; +import EmptyChart from '@/components/Icon/icons/emptyChart.svg'; export const LogCounts = ({ logCountsData, isLogCountsLoading }: { logCountsData: { logs_total: string; _time: string }[]; - isLogCountsLoading: boolean; + isLogCountsLoading?: boolean; }) => { const { t } = useTranslation(); const [onOpenChart, setOnOpenChart] = useState(true); @@ -67,8 +68,15 @@ export const LogCounts = ({
- ) : ( + ) : logCountsData.length > 0 ? ( + ) : ( +
+ + + {t('no_data_available')} + +
)} diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index df3ed44071a..43ab0b10014 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -37,10 +37,6 @@ interface LogData { [key: string]: any; } -interface LogColumnMeta { - isError?: (row: LogData) => boolean; -} - export const LogTable = ({ data, isLoading, @@ -80,29 +76,28 @@ export const LogTable = ({ ]; } - const firstItem = data[0]; - if (Array.isArray(firstItem)) { - return firstItem.map((_, index) => ({ - value: `column${index}`, - label: `Column ${index + 1}`, - checked: index === 0, - accessorKey: index.toString() - })); - } + const uniqueKeys = new Set(); - return Object.keys(firstItem) - .filter((key) => key !== '_msg') - .map((key) => ({ - value: key, - label: key, - checked: true, - accessorKey: key - })); + data.forEach((item) => { + Object.keys(item).forEach((key) => { + if (key !== '_msg') { + uniqueKeys.add(key); + } + }); + }); + + return Array.from(uniqueKeys).map((key) => ({ + value: key, + label: key, + checked: true, + accessorKey: key + })); }, [isJsonMode] ); const [fieldList, setFieldList] = useState(() => generateFieldList(data)); + console.log(fieldList, 'fieldList'); useEffect(() => { setFieldList(generateFieldList(data)); diff --git a/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx b/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx index d862fb15f6b..030e1daed99 100644 --- a/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx +++ b/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx @@ -1,4 +1,4 @@ -import Sidebar from '@/components/layouts/Sidebar'; +import Sidebar, { ROUTES } from '@/components/layouts/Sidebar'; import { useToast } from '@/hooks/useToast'; import { MOCK_APP_DETAIL } from '@/mock/apps'; import Header from '@/components/app/detail/index/Header'; @@ -7,6 +7,7 @@ import { useGlobalStore } from '@/store/global'; import { Flex } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import React, { useMemo, useState } from 'react'; +import { useRouter } from 'next/router'; interface DetailLayoutProps { children: React.ReactNode; @@ -15,6 +16,7 @@ interface DetailLayoutProps { export default function DetailLayout({ children, appName }: DetailLayoutProps) { const { toast } = useToast(); + const router = useRouter(); const { screenWidth } = useGlobalStore(); const isLargeScreen = useMemo(() => screenWidth > 1280, [screenWidth]); @@ -44,7 +46,8 @@ export default function DetailLayout({ children, appName }: DetailLayoutProps) { }, { refetchOnMount: true, - refetchInterval: 3000 + refetchInterval: router.pathname === ROUTES.OVERVIEW ? 3000 : 10000, + staleTime: router.pathname === ROUTES.OVERVIEW ? 3000 : 10000 } ); diff --git a/frontend/providers/applaunchpad/src/components/layouts/Sidebar.tsx b/frontend/providers/applaunchpad/src/components/layouts/Sidebar.tsx index 829dd766dee..e0e85565827 100644 --- a/frontend/providers/applaunchpad/src/components/layouts/Sidebar.tsx +++ b/frontend/providers/applaunchpad/src/components/layouts/Sidebar.tsx @@ -3,7 +3,7 @@ import MyIcon from '../Icon'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; -const ROUTES = { +export const ROUTES = { OVERVIEW: '/app/detail', MONITOR: '/app/detail/monitor', LOGS: '/app/detail/logs' diff --git a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts index 34510b015a2..22c43185d79 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts @@ -23,6 +23,15 @@ export interface LogQueryPayload { } export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const logUrl = global.AppConfig.launchpad.components.log.url; + + if (!logUrl) { + return jsonRes(res, { + code: 400, + error: 'logUrl is not set' + }); + } + if (req.method !== 'POST') { return jsonRes(res, { code: 405, @@ -61,10 +70,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const params: LogQueryPayload = { time: time, - // dev + // // dev namespace: 'sealos', - app: '', - // === // namespace: namespace, // app: app, limit: limit, @@ -72,15 +79,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< stderrMode: stderrMode, numberMode: numberMode, ...(numberLevel && { numberLevel: numberLevel }), - pod: Array.isArray(pod) ? pod : [], - container: Array.isArray(container) ? container : [], + // pod: Array.isArray(pod) ? pod : [], + // container: Array.isArray(container) ? container : [], keyword: keyword, jsonQuery: Array.isArray(jsonQuery) ? jsonQuery : [] }; - console.log(params, 'params'); - - const result = await fetch('http://tipmjzasbtqv.sealoshzh.site/queryLogsByParams', { + console.log('numberMode:', numberMode, 'params', params); + const result = await fetch(logUrl + '/queryLogsByParams', { method: 'POST', body: JSON.stringify(params), headers: { @@ -88,9 +94,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< // Authorization: encodeURIComponent(kubeconfig) } }); - const contentType = result.headers.get('content-type'); - - console.log(result.status, contentType); + console.log('fetch log result: ', result.status); const data = await result.text(); jsonRes(res, { diff --git a/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts b/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts index 0162c5488ff..e2a052b8192 100644 --- a/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts +++ b/frontend/providers/applaunchpad/src/pages/api/platform/getInitData.ts @@ -51,6 +51,9 @@ export const defaultAppConfig: AppConfigType = { }, billing: { url: 'http://account-service.account-system.svc:2333' + }, + log: { + url: 'http://localhost:8080' } }, appResourceFormSliderConfig: { diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx index 42b2ef83335..d1f212a43d2 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx @@ -42,21 +42,6 @@ const AppDetail = ({ appName }: { appName: string }) => { } }); - // useQuery( - // ['app-detail-pod'], - // () => { - // if (appDetail?.isPause) return null; - // return intervalLoadPods(appName, true); - // }, - // { - // refetchOnMount: true, - // refetchInterval: 3000, - // onSettled() { - // setPodsLoaded(true); - // } - // } - // ); - useQuery( ['loadDetailMonitorData', appName, appDetail?.isPause], () => { diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 03523494043..0903a57c7e7 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -11,7 +11,7 @@ import { Header } from '@/components/app/detail/logs/Header'; import { Filter } from '@/components/app/detail/logs/Filter'; import { LogTable } from '@/components/app/detail/logs/LogTable'; import { LogCounts } from '@/components/app/detail/logs/LogCounts'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { ListItem } from '@/components/AdvancedSelect'; import useDateTimeStore from '@/store/date'; import { getAppLogs } from '@/api/app'; @@ -89,6 +89,9 @@ export default function LogsPage({ appName }: { appName: string }) { const selectedPods = formHook.watch('pods').filter((pod) => pod.checked); const selectedContainers = formHook.watch('containers').filter((container) => container.checked); + const jsonFilters = formHook + .watch('jsonFilters') + .filter((item) => item.key && item.key.trim() !== ''); const timeRange = formatTimeRange(startDateTime, endDateTime); const { isLoading, refetch: refetchLogsData } = useQuery( @@ -99,21 +102,26 @@ export default function LogsPage({ appName }: { appName: string }) { formHook.watch('isOnlyStderr'), formHook.watch('limit'), formHook.watch('isJsonMode'), - formHook.watch('keyword') + formHook.watch('keyword'), + selectedPods, + selectedContainers ], () => getAppLogs({ time: timeRange, app: appName, + stderrMode: formHook.watch('isOnlyStderr').toString(), limit: formHook.watch('limit').toString(), jsonMode: formHook.watch('isJsonMode').toString(), - stderrMode: formHook.watch('isOnlyStderr').toString(), - jsonQuery: formHook - .watch('jsonFilters') - .filter((item) => item.key && item.key.trim() !== ''), - keyword: formHook.watch('keyword') + keyword: formHook.watch('keyword'), + pod: selectedPods.map((pod) => pod.value), + container: selectedContainers.map((container) => container.value), + jsonQuery: jsonFilters }), { + retry: 1, + staleTime: 3000, + cacheTime: 3000, refetchInterval: refreshInterval, onError: (error: any) => { console.log(error, 'error'); @@ -121,19 +129,19 @@ export default function LogsPage({ appName }: { appName: string }) { }, onSuccess: (data) => { setLogs(data); - }, - retry: 1 + } } ); + // log counts const { refetch: refetchLogCountsData, isLoading: isLogCountsLoading } = useQuery( [ 'log-counts-data', appName, timeRange, formHook.watch('isOnlyStderr'), - formHook.watch('jsonFilters'), - formHook.watch('keyword') + selectedPods, + selectedContainers ], () => getAppLogs({ @@ -141,21 +149,20 @@ export default function LogsPage({ appName }: { appName: string }) { numberMode: 'true', numberLevel: timeRange.slice(-1), time: timeRange, - stderrMode: formHook.watch('isOnlyStderr').toString() - // jsonQuery: formHook - // .watch('jsonFilters') - // .filter((item) => item.key && item.key.trim() !== ''), - // keyword: formHook.watch('keyword') + stderrMode: formHook.watch('isOnlyStderr').toString(), + pod: selectedPods.map((pod) => pod.value), + container: selectedContainers.map((container) => container.value) }), { + refetchInterval: refreshInterval, + staleTime: 3000, + cacheTime: 3000, onSuccess: (data) => { setLogCounts(data); } } ); - console.log(formHook.getValues(), logCounts, parsedLogs, 'logsData'); - const refetchData = () => { console.log('refetchData'); refetchLogsData(); diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx index 1691e307795..abad8b546a4 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx @@ -106,7 +106,7 @@ export default function MonitorPage({ appName }: { appName: string }) { const xData = filteredData?.[0]?.xData.map(String) || []; const yData = filteredData?.map((item) => ({ - name: item.name, + name: item.name || 'unknown', type: 'line', data: item.yData.map(Number) })) || []; @@ -133,7 +133,7 @@ export default function MonitorPage({ appName }: { appName: string }) { const xData = filteredData?.[0]?.xData.map(String) || []; const yData = filteredData?.map((item) => ({ - name: item.name, + name: item.name || 'unknown', type: 'line', data: item.yData.map(Number) })) || []; diff --git a/frontend/providers/applaunchpad/src/store/logStore.ts b/frontend/providers/applaunchpad/src/store/logStore.ts index 01a10c08bde..3d695691154 100644 --- a/frontend/providers/applaunchpad/src/store/logStore.ts +++ b/frontend/providers/applaunchpad/src/store/logStore.ts @@ -24,6 +24,11 @@ export const useLogStore = create()( logCounts: [], setLogs: (data: string) => set((state) => { + if (!data) { + state.rawLogs = ''; + state.parsedLogs = []; + return; + } state.rawLogs = data; const logLines = data.split('\n').filter((line) => line.trim()); state.parsedLogs = logLines.map((line) => { @@ -36,6 +41,10 @@ export const useLogStore = create()( }), setLogCounts: (data: string) => set((state) => { + if (!data) { + state.logCounts = []; + return; + } const logLines = data.split('\n').filter((line) => line.trim()); console.log( logLines.map((line) => { diff --git a/frontend/providers/applaunchpad/src/types/index.d.ts b/frontend/providers/applaunchpad/src/types/index.d.ts index 51cbc29e43c..2e4b4a74578 100644 --- a/frontend/providers/applaunchpad/src/types/index.d.ts +++ b/frontend/providers/applaunchpad/src/types/index.d.ts @@ -53,6 +53,9 @@ export type AppConfigType = { billing: { url: string; }; + log: { + url: string; + }; }; appResourceFormSliderConfig: FormSliderListType; fileManger: FileMangerType; From 7ca41a4860fa21c824a20cdb5933349868df2243 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 22 Jan 2025 16:38:02 +0800 Subject: [PATCH 28/48] update MonitorChart --- .../applaunchpad/src/components/MonitorChart/index.tsx | 10 +++++++++- .../applaunchpad/src/pages/app/detail/logs.tsx | 8 ++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx index bb3ae8fe805..711f00638d9 100644 --- a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx @@ -96,7 +96,15 @@ const MonitorChart = ({ border-radius: 4px; cursor: pointer; font-size: 12px; - ">${t('logs')} + " onclick="(() => { + const currentUrl = window.location.href; + const urlParams = currentUrl.split('?')[1] || ''; + const baseUrl = window.location.pathname.replace('/monitor', '/logs'); + const separator = urlParams ? '?' : ''; + window.location.href = baseUrl + separator + urlParams + (urlParams ? '&' : '?') + 'pod=${ + item.seriesName + }'; + })()">${t('logs')} diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 0903a57c7e7..f6f994d9581 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -19,6 +19,7 @@ import { useForm } from 'react-hook-form'; import { formatTimeRange } from '@/utils/timeRange'; import { downLoadBold } from '@/utils/tools'; import { useLogStore } from '@/store/logStore'; +import { useRouter } from 'next/router'; export interface JsonFilterItem { key: string; @@ -43,6 +44,7 @@ export interface LogsFormData { export default function LogsPage({ appName }: { appName: string }) { const theme = useTheme(); + const router = useRouter(); const { toast } = useToast(); const { t } = useTranslation(); const { appDetail, appDetailPods } = useAppStore(); @@ -66,11 +68,13 @@ export default function LogsPage({ appName }: { appName: string }) { // init pods and containers useEffect(() => { if (!isInitialized && appDetailPods?.length > 0) { + const urlPodName = router.query.pod as string; const pods = appDetailPods.map((pod) => ({ value: pod.podName, label: pod.podName, - checked: true + checked: urlPodName ? pod.podName === urlPodName : true })); + const containers = appDetailPods .flatMap((pod) => pod.spec?.containers || []) .map((container) => ({ @@ -85,7 +89,7 @@ export default function LogsPage({ appName }: { appName: string }) { setIsInitialized(true); } - }, [appDetailPods, isInitialized, formHook]); + }, [appDetailPods, isInitialized, formHook, router.query.pod]); const selectedPods = formHook.watch('pods').filter((pod) => pod.checked); const selectedContainers = formHook.watch('containers').filter((container) => container.checked); From d091443ca1963662130c7152d1d57f69edd37d2e Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 22 Jan 2025 17:03:06 +0800 Subject: [PATCH 29/48] add prevFieldList --- .../src/components/app/detail/logs/Filter.tsx | 5 ++++- .../src/components/app/detail/logs/LogTable.tsx | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx index a918b8cd6e2..499f78e1af3 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx @@ -52,7 +52,10 @@ export const Filter = ({
formHook.setValue('isJsonMode', !isJsonMode)} + onChange={() => { + formHook.setValue('isJsonMode', !isJsonMode); + formHook.setValue('jsonFilters', []); + }} />
diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index 43ab0b10014..d9211506511 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -56,7 +56,7 @@ export const LogTable = ({ const { exportLogs } = useLogStore(); const generateFieldList = useCallback( - (data: any[]) => { + (data: any[], prevFieldList: FieldItem[] = []) => { if (!data.length) return []; if (!isJsonMode) { @@ -77,7 +77,6 @@ export const LogTable = ({ } const uniqueKeys = new Set(); - data.forEach((item) => { Object.keys(item).forEach((key) => { if (key !== '_msg') { @@ -86,21 +85,25 @@ export const LogTable = ({ }); }); + const prevFieldStates = prevFieldList.reduce((acc, field) => { + acc[field.value] = field.checked; + return acc; + }, {} as Record); + return Array.from(uniqueKeys).map((key) => ({ value: key, label: key, - checked: true, + checked: key in prevFieldStates ? prevFieldStates[key] : true, accessorKey: key })); }, [isJsonMode] ); - const [fieldList, setFieldList] = useState(() => generateFieldList(data)); - console.log(fieldList, 'fieldList'); + const [fieldList, setFieldList] = useState(() => generateFieldList(data, [])); useEffect(() => { - setFieldList(generateFieldList(data)); + setFieldList((prevFieldList) => generateFieldList(data, prevFieldList)); formHook.setValue( 'filterKeys', generateFieldList(data).map((field) => ({ value: field.value, label: field.label })) From 04d3026592476d6feb66bb1e2b33f871c78c7bd5 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 22 Jan 2025 17:08:45 +0800 Subject: [PATCH 30/48] update api --- .../src/pages/api/log/queryLogs.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts index 22c43185d79..9fd72464e22 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts @@ -7,19 +7,19 @@ import { ApiResp } from '@/services/kubernet'; import type { NextApiRequest, NextApiResponse } from 'next'; export interface LogQueryPayload { - app?: string; // 必填 - time?: string; // 可选,默认 "1h" - namespace?: string; // 必填 - limit?: string; // 可选,默认 10 - jsonMode?: string; // 可选,默认 "false" - stderrMode?: string; // 可选,默认 "false" - numberMode?: string; // 可选,默认 "false" - numberLevel?: string; // 可选 - pod?: string[]; // 可选,默认 [] - container?: string[]; // 可选,默认 [] - keyword?: string; // 可选,默认 "" - jsonQuery?: JsonFilterItem[]; // 可选,默认 [] - exportMode?: boolean; // 新增:是否导出文件模式 + app?: string; + time?: string; + namespace?: string; + limit?: string; + jsonMode?: string; + stderrMode?: string; + numberMode?: string; + numberLevel?: string; + pod?: string[]; + container?: string[]; + keyword?: string; + jsonQuery?: JsonFilterItem[]; + exportMode?: boolean; } export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -45,7 +45,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< kubeconfig: kubeconfig }); - // 检查必填参数 if (!req.body.app) { return jsonRes(res, { code: 400, @@ -70,28 +69,29 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const params: LogQueryPayload = { time: time, - // // dev - namespace: 'sealos', - // namespace: namespace, - // app: app, + // dev + // namespace: 'sealos', + namespace: namespace, + app: app, limit: limit, jsonMode: jsonMode, stderrMode: stderrMode, numberMode: numberMode, ...(numberLevel && { numberLevel: numberLevel }), - // pod: Array.isArray(pod) ? pod : [], - // container: Array.isArray(container) ? container : [], + pod: Array.isArray(pod) ? pod : [], + container: Array.isArray(container) ? container : [], keyword: keyword, jsonQuery: Array.isArray(jsonQuery) ? jsonQuery : [] }; console.log('numberMode:', numberMode, 'params', params); + const result = await fetch(logUrl + '/queryLogsByParams', { method: 'POST', body: JSON.stringify(params), headers: { - 'Content-Type': 'application/json' - // Authorization: encodeURIComponent(kubeconfig) + 'Content-Type': 'application/json', + Authorization: encodeURIComponent(kubeconfig) } }); console.log('fetch log result: ', result.status); From cda4a4ffb995caa22cd9ecb7e1614dbd90e9d97d Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Wed, 22 Jan 2025 17:40:44 +0800 Subject: [PATCH 31/48] delete table --- .../src/components/AdvancedTable/index.tsx | 77 ------------------- .../components/app/detail/logs/LogTable.tsx | 36 ++++++--- 2 files changed, 25 insertions(+), 88 deletions(-) delete mode 100644 frontend/providers/applaunchpad/src/components/AdvancedTable/index.tsx diff --git a/frontend/providers/applaunchpad/src/components/AdvancedTable/index.tsx b/frontend/providers/applaunchpad/src/components/AdvancedTable/index.tsx deleted file mode 100644 index 5590e84dca8..00000000000 --- a/frontend/providers/applaunchpad/src/components/AdvancedTable/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import { Box, BoxProps, Grid, Flex } from '@chakra-ui/react'; - -interface Props extends BoxProps { - columns: { - title: string; - dataIndex?: string; - key: string; - render?: (item: any) => JSX.Element; - }[]; - data: any[]; - itemClass?: string; -} - -const AdvancedTable = ({ columns, data, itemClass = '' }: Props) => { - return ( - <> - - {columns.map((item, i) => ( - - {item.title} - - ))} - - {data.map((item: any, index1) => ( - - {columns.map((col, index2) => ( - - {col.render ? col.render(item) : col.dataIndex ? `${item[col.dataIndex]}` : ''} - - ))} - - ))} - - ); -}; - -export default AdvancedTable; diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index d9211506511..585f65aa671 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -283,17 +283,31 @@ export const LogTable = ({ )} - + {data.length > 0 ? ( + + ) : ( + + + + {t('no_data_available')} + + + )} ); }; From ab4da35a64d739a252311204eb4400cd6dda3c51 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Thu, 23 Jan 2025 10:52:59 +0800 Subject: [PATCH 32/48] update log api --- .../components/MonitorChart/index.module.css | 58 +++++++++++++++ .../src/components/MonitorChart/index.tsx | 71 ++++++------------- .../src/pages/api/log/queryLogs.ts | 2 - 3 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/components/MonitorChart/index.module.css diff --git a/frontend/providers/applaunchpad/src/components/MonitorChart/index.module.css b/frontend/providers/applaunchpad/src/components/MonitorChart/index.module.css new file mode 100644 index 00000000000..24a191906a6 --- /dev/null +++ b/frontend/providers/applaunchpad/src/components/MonitorChart/index.module.css @@ -0,0 +1,58 @@ +.tooltip { + background: white; + border-radius: 8px; + padding: 16px; + border: 1px solid #e8ebf0; + box-shadow: 0px 24px 48px -12px rgba(19, 51, 107, 0.2), 0px 0px 1px 0px rgba(19, 51, 107, 0.2); +} + +.tooltipHeader { + font-size: 12px; + font-weight: 500; + color: #111824; + margin-bottom: 12px; + border-bottom: 1px solid #eee; + padding-bottom: 12px; +} + +.tooltipItem { + display: flex; + align-items: center; +} + +.tooltipItem:not(:last-child) { + margin-bottom: 12px; +} + +.tooltipDot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 8px; +} + +.tooltipName { + color: #333; + margin-right: 12px; +} + +.tooltipValue { + font-weight: 500; + margin-right: 12px; +} + +.tooltipButton { + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; + background: #f4f4f7; + color: #485264; + border: none; + padding: 6px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; +} diff --git a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx index 711f00638d9..a9ca3d31187 100644 --- a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx @@ -6,6 +6,7 @@ import { LineStyleMap } from '@/constants/monitor'; import { Flex, FlexProps, Text } from '@chakra-ui/react'; import MyIcon from '../Icon'; import { useTranslation } from 'next-i18next'; +import styles from './index.module.css'; type MonitorChart = FlexProps & { data: { @@ -45,58 +46,26 @@ const MonitorChart = ({ tooltip: { trigger: 'axis', enterable: true, + triggerOn: 'click', + extraCssText: ` + box-shadow: none; + padding: 0; + background-color: transparent; + border: none; + `, formatter: (params: any) => { let axisValue = params[0]?.axisValue; return ` -
-
${axisValue}
+
+
${axisValue}
${params .map( (item: any) => ` -
- - ${item.seriesName} - ${item.value}${unit || ''} - + })()">${t('logs')} + + + +
` ) @@ -115,6 +85,7 @@ const MonitorChart = ({
`; }, + // @ts-ignore position: (point, params, dom, rect, size) => { let xPos = point[0]; diff --git a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts index 9fd72464e22..6f4f6e5b8ba 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts @@ -84,8 +84,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< jsonQuery: Array.isArray(jsonQuery) ? jsonQuery : [] }; - console.log('numberMode:', numberMode, 'params', params); - const result = await fetch(logUrl + '/queryLogsByParams', { method: 'POST', body: JSON.stringify(params), From f3f453af912ac0f1dfdaef9ee749a3b8f43cb186 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Thu, 23 Jan 2025 11:52:08 +0800 Subject: [PATCH 33/48] update scroll --- frontend/providers/applaunchpad/data/config.yaml | 2 +- .../applaunchpad/src/components/MonitorChart/index.tsx | 1 - frontend/providers/applaunchpad/src/constants/theme.ts | 2 +- frontend/providers/applaunchpad/src/pages/_app.tsx | 4 ++-- .../applaunchpad/src/pages/app/detail/index.tsx | 2 +- .../applaunchpad/src/pages/app/detail/monitor.tsx | 10 +++++++++- .../src/pages/app/edit/components/Form.tsx | 2 +- .../applaunchpad/src/pages/app/edit/index.tsx | 1 + 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/providers/applaunchpad/data/config.yaml b/frontend/providers/applaunchpad/data/config.yaml index 0fadb71bbd5..c9174559f93 100644 --- a/frontend/providers/applaunchpad/data/config.yaml +++ b/frontend/providers/applaunchpad/data/config.yaml @@ -20,7 +20,7 @@ launchpad: billing: url: "http://account-service.account-system.svc:2333" log: - url: "http://localhost:8080" + url: "http://service-vlogs.sealos.svc.cluster.local:8428" appResourceFormSliderConfig: default: cpu: [100, 200, 500, 1000, 2000, 3000, 4000, 8000] diff --git a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx index a9ca3d31187..da851edfba4 100644 --- a/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/MonitorChart/index.tsx @@ -46,7 +46,6 @@ const MonitorChart = ({ tooltip: { trigger: 'axis', enterable: true, - triggerOn: 'click', extraCssText: ` box-shadow: none; padding: 0; diff --git a/frontend/providers/applaunchpad/src/constants/theme.ts b/frontend/providers/applaunchpad/src/constants/theme.ts index 482ab43103e..8abdefb552b 100644 --- a/frontend/providers/applaunchpad/src/constants/theme.ts +++ b/frontend/providers/applaunchpad/src/constants/theme.ts @@ -23,7 +23,7 @@ export const theme = extendTheme(sealosTheme, { 'html, body': { fontSize: 'md', height: '100%', - overflow: 'overlay', + // overflow: 'overlay', fontWeight: 400, minWidth: '1024px' } diff --git a/frontend/providers/applaunchpad/src/pages/_app.tsx b/frontend/providers/applaunchpad/src/pages/_app.tsx index 1ba7016a8b2..67053b7ae0c 100644 --- a/frontend/providers/applaunchpad/src/pages/_app.tsx +++ b/frontend/providers/applaunchpad/src/pages/_app.tsx @@ -186,7 +186,7 @@ const App = ({ Component, pageProps }: AppProps) => { - {/* */} + diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx index d1f212a43d2..5842914996b 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/index.tsx @@ -75,7 +75,7 @@ const AppDetail = ({ appName }: { appName: string }) => { - + diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx index abad8b546a4..0d624ec4f79 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx @@ -151,7 +151,15 @@ export default function MonitorPage({ appName }: { appName: string }) { return ( - +
{!isLoading ? ( <> diff --git a/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx b/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx index 8aec2ab98d2..734a089e905 100644 --- a/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx @@ -435,7 +435,7 @@ const Form = ({ pr={`${pxVal}px`} height={'100%'} position={'relative'} - overflowY={'scroll'} + // overflowY={'scroll'} > {/* base info */} diff --git a/frontend/providers/applaunchpad/src/pages/app/edit/index.tsx b/frontend/providers/applaunchpad/src/pages/app/edit/index.tsx index e252323346c..21aba2919a7 100644 --- a/frontend/providers/applaunchpad/src/pages/app/edit/index.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/edit/index.tsx @@ -328,6 +328,7 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) => h={'100%'} minWidth={'1024px'} backgroundColor={'grayModern.100'} + overflowY={'auto'} >
Date: Thu, 23 Jan 2025 12:15:07 +0800 Subject: [PATCH 34/48] update common.json --- .../public/locales/en/common.json | 12 +- .../public/locales/zh/common.json | 4 +- .../src/components/Monitor/Header.tsx | 3 +- .../app/detail/index/AdvancedInfo.tsx | 4 +- .../app/detail/index/AppBaseInfo.tsx | 249 ------------------ .../providers/applaunchpad/src/pages/_app.tsx | 4 +- 6 files changed, 14 insertions(+), 262 deletions(-) diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 4fe6a4779f2..1a3e45efd24 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -51,8 +51,8 @@ "ConfigMap Path Conflict": "ConfigMap Path Conflict", "ConfigMap Tip": "ConfigMap", "Configurable number of instances or automatic horizontal scaling": "Configurable replica count and auto horizontal scaling", - "Configuration File": "Configmap", - "Confirm": "Yes", + "Configuration File": "Configmaps", + "Confirm": "Confirm", "Confirm deletion": "Yes", "Confirm Deploy Application?": "Are you sure you want to deploy the application?", "Confirm to restart this application?": "Are you sure you want to update the application?", @@ -86,7 +86,7 @@ "Edit Env Variable": "Edit Environment Variables", "Edit Environment Variables": "Edit Environment Variables", "Env Placeholder": "one per line, key and value separated by colon or equals sign, e.g.:\nmongoUrl=127.0.0.1:8000\nredisUrl:127.0.0.0:8001\n-env1 =test", - "Environment Variables": "Environment", + "Environment Variables": "Environment Variables", "Export": "Export", "Export Domain": "Assigned Domain", "file": "File", @@ -197,7 +197,7 @@ "Stateless": "Stateless", "Status": "Status", "storage": "Storage", - "Storage": "Storage", + "Storage": "Mounted Volumes", "Storage path can not empty": "Storage mount path is required", "Storage Range": "Storage Range", "Storage Value can not empty": "Storage size is required", @@ -301,7 +301,7 @@ "value": "Enter Value", "logNumber": "Log Counts", "overview": "Overview", - "monitor": "Monitor", + "monitor": "Monitors", "logs": "Logs", "application_source": "Source", "contains": "contains", @@ -316,4 +316,4 @@ "field_settings": "Field Setting", "selected": "Selected", "please_select": "Please Select" -} \ No newline at end of file +} diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 2d6e5cc3279..bf6fa7af107 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -51,7 +51,7 @@ "ConfigMap Path Conflict": "配置文件路径冲突", "ConfigMap Tip": "配置文件", "Configurable number of instances or automatic horizontal scaling": "可配置实例数或自动横向伸缩", - "Configuration File": "配置文件", + "Configuration File": "Configmap 配置文件", "Confirm": "确认", "Confirm deletion": "确认删除", "Confirm Deploy Application?": "确认部署应用?", @@ -316,4 +316,4 @@ "field_settings": "字段设置", "selected": "已选中", "please_select": "请选择" -} \ No newline at end of file +} diff --git a/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx b/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx index 820c2e37e16..e65df3c502b 100644 --- a/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx +++ b/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx @@ -46,7 +46,8 @@ export default function Header({ fontWeight={'400'} alignItems={'center'} > - ({t('Update Time')} ) + ({t('Update Time')}   + ) diff --git a/frontend/providers/applaunchpad/src/components/app/detail/index/AdvancedInfo.tsx b/frontend/providers/applaunchpad/src/components/app/detail/index/AdvancedInfo.tsx index 1f0d1dab949..253795956cf 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/index/AdvancedInfo.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/index/AdvancedInfo.tsx @@ -74,7 +74,7 @@ const AdvancedInfo = ({ app = MOCK_APP_DETAIL }: { app: AppDetailType }) => { mx={'16px'} borderColor={'grayModern.300'} /> - ConfigMap: {app.configMapList?.length} + ConfigMaps: {app.configMapList?.length} { - ConfigMap {t('Configuration File')} + {t('Configuration File')} { return ( - {/* <> - - - {t('Application Type')} - - - {appTags.map((tag) => ( - - {t(tag)} - - ))} - - */} - {appInfoTable.map((info, index) => ( { {index !== appInfoTable.length - 1 && } ))} - {/* - - - {t('Advanced Configuration')} - - - {[ - { label: 'Command', value: app.runCMD || 'Not Configured' }, - { label: 'Parameters', value: app.cmdParam || 'Not Configured' } - ].map((item) => ( - - - {t(item.label)} - - - ))} - - - - - {t('Environment Variables')} - - - - {app.envs?.length > 0 && ( - - {app.envs.map((env, index) => { - const valText = env.value - ? env.value - : env.valueFrom - ? 'value from | ***' - : ''; - return ( - - - {env.key} - - - copyData(valText)} - > - {valText} - - - - ); - })} - - )} - - - - - - - - {t('Configuration File')} - - - - 0 - ? { - mb: 3, - border: theme.borders.base - } - : {})} - > - {app.configMapList.map((item) => ( - setDetailConfigMap(item)} - _notLast={{ - borderBottom: theme.borders.base - }} - > - - - {item.mountPath} - - {item.value} - - - - ))} - - - - - - - - - {t('Storage')} - - - - 0 || persistentVolumes.length > 0 - ? { - mb: 4, - border: theme.borders.base - } - : {})} - > - {app.storeList.map((item) => ( - - - - - {item.path} - - - {item.value} Gi - - - - ))} - {persistentVolumes.map((item) => ( - - - - - {item.path} - - - - {t('shared')} - - - ))} - - - - - - */} {detailConfigMap && ( setDetailConfigMap(undefined)} /> diff --git a/frontend/providers/applaunchpad/src/pages/_app.tsx b/frontend/providers/applaunchpad/src/pages/_app.tsx index 67053b7ae0c..1ba7016a8b2 100644 --- a/frontend/providers/applaunchpad/src/pages/_app.tsx +++ b/frontend/providers/applaunchpad/src/pages/_app.tsx @@ -186,7 +186,7 @@ const App = ({ Component, pageProps }: AppProps) => { - + */} From c1a75371ed24fcd536feda9de71ac318efdf5763 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Thu, 23 Jan 2025 13:12:37 +0800 Subject: [PATCH 35/48] update defaultRecentDate --- .../applaunchpad/src/components/DatePicker/index.tsx | 3 ++- .../providers/applaunchpad/src/pages/api/log/queryLogs.ts | 2 -- frontend/providers/applaunchpad/src/store/date.ts | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx index 5f41b9e203a..2798f68a30c 100644 --- a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx +++ b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx @@ -108,10 +108,11 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { const currentTimeRange = formatTimeRange(startDateTime, endDateTime); return ( recentDateList.find((item) => item.compareValue === currentTimeRange) || - recentDateList.find((item) => item.compareValue === '7d') || + recentDateList.find((item) => item.compareValue === '30m') || recentDateList[0] ); }, [startDateTime, endDateTime, recentDateList]); + console.log(defaultRecentDate, 'defaultRecentDate'); const [inputState, setInputState] = useState<0 | 1>(0); const [recentDate, setRecentDate] = useState(defaultRecentDate); diff --git a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts index 6f4f6e5b8ba..c50555af00c 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts @@ -69,8 +69,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const params: LogQueryPayload = { time: time, - // dev - // namespace: 'sealos', namespace: namespace, app: app, limit: limit, diff --git a/frontend/providers/applaunchpad/src/store/date.ts b/frontend/providers/applaunchpad/src/store/date.ts index 7123c3eb15f..36681c2e59e 100644 --- a/frontend/providers/applaunchpad/src/store/date.ts +++ b/frontend/providers/applaunchpad/src/store/date.ts @@ -1,4 +1,4 @@ -import { subDays } from 'date-fns'; +import { subDays, subMinutes } from 'date-fns'; import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; @@ -15,7 +15,7 @@ type DateTimeState = { const useDateTimeStore = create()( immer((set, get) => ({ - startDateTime: subDays(new Date(), 7), + startDateTime: subMinutes(new Date(), 30), endDateTime: new Date(), timeZone: 'local', refreshInterval: 0, From 390b6ab121feec6b278cd58d69466f66fad0844e Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Thu, 23 Jan 2025 17:10:31 +0800 Subject: [PATCH 36/48] fix --- .../providers/applaunchpad/public/locales/zh/common.json | 2 +- .../applaunchpad/src/components/DatePicker/index.tsx | 1 - .../applaunchpad/src/pages/app/edit/components/Form.tsx | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index bf6fa7af107..6e88e201f51 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -51,7 +51,7 @@ "ConfigMap Path Conflict": "配置文件路径冲突", "ConfigMap Tip": "配置文件", "Configurable number of instances or automatic horizontal scaling": "可配置实例数或自动横向伸缩", - "Configuration File": "Configmap 配置文件", + "Configuration File": "配置文件", "Confirm": "确认", "Confirm deletion": "确认删除", "Confirm Deploy Application?": "确认部署应用?", diff --git a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx index 2798f68a30c..415279c8675 100644 --- a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx +++ b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx @@ -112,7 +112,6 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { recentDateList[0] ); }, [startDateTime, endDateTime, recentDateList]); - console.log(defaultRecentDate, 'defaultRecentDate'); const [inputState, setInputState] = useState<0 | 1>(0); const [recentDate, setRecentDate] = useState(defaultRecentDate); diff --git a/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx b/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx index 734a089e905..261efd3aa99 100644 --- a/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/edit/components/Form.tsx @@ -1108,7 +1108,7 @@ const Form = ({ onClick={() => setConfigEdit(item)} bg={'grayModern.25'} > - + {item.mountPath} @@ -1180,7 +1180,7 @@ const Form = ({ bg={'grayModern.25'} onClick={() => setStoreEdit(item)} > - + {item.path} @@ -1232,7 +1232,7 @@ const Form = ({ cursor={'not-allowed'} bg={'grayModern.25'} > - + {item.path} From 1669a211fc574531f61951b60ec455a264e371b1 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Fri, 24 Jan 2025 14:17:26 +0800 Subject: [PATCH 37/48] update keys --- .../src/components/BaseTable/index.tsx | 10 ++- .../components/app/detail/logs/LogTable.tsx | 79 +++++++++---------- .../src/components/layouts/DetailLayout.tsx | 4 +- .../src/pages/app/detail/logs.tsx | 22 ++++-- 4 files changed, 66 insertions(+), 49 deletions(-) diff --git a/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx b/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx index 017019ac33e..fc867a16879 100644 --- a/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx +++ b/frontend/providers/applaunchpad/src/components/BaseTable/index.tsx @@ -28,16 +28,22 @@ export function BaseTable({ table, isLoading, tdStyle, + isHeaderFixed = false, ...props }: { table: ReactTable; isLoading: boolean; tdStyle?: HTMLChakraProps<'td'>; + isHeaderFixed?: boolean; } & TableContainerProps) { return ( - + - + {table.getHeaderGroups().map((headers) => { return ( diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index 585f65aa671..5236219ebd3 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -55,58 +55,56 @@ export const LogTable = ({ const isJsonMode = formHook.watch('isJsonMode'); const { exportLogs } = useLogStore(); - const generateFieldList = useCallback( - (data: any[], prevFieldList: FieldItem[] = []) => { - if (!data.length) return []; + const generateFieldList = useCallback((data: any[], prevFieldList: FieldItem[] = []) => { + if (!data.length) return []; - if (!isJsonMode) { - return [ - { - value: 'time', - label: 'Time', - checked: true, - accessorKey: '_time' - }, - { - value: 'message', - label: 'Message', - checked: true, - accessorKey: '_msg' - } - ]; - } + // if (!isJsonMode) { + // return [ + // { + // value: 'time', + // label: 'Time', + // checked: true, + // accessorKey: '_time' + // }, + // { + // value: 'message', + // label: 'Message', + // checked: true, + // accessorKey: '_msg' + // } + // ]; + // } - const uniqueKeys = new Set(); - data.forEach((item) => { - Object.keys(item).forEach((key) => { - if (key !== '_msg') { - uniqueKeys.add(key); - } - }); + const uniqueKeys = new Set(); + data.forEach((item) => { + Object.keys(item).forEach((key) => { + uniqueKeys.add(key); }); + }); - const prevFieldStates = prevFieldList.reduce((acc, field) => { - acc[field.value] = field.checked; - return acc; - }, {} as Record); + const prevFieldStates = prevFieldList.reduce((acc, field) => { + acc[field.value] = field.checked; + return acc; + }, {} as Record); - return Array.from(uniqueKeys).map((key) => ({ - value: key, - label: key, - checked: key in prevFieldStates ? prevFieldStates[key] : true, - accessorKey: key - })); - }, - [isJsonMode] - ); + return Array.from(uniqueKeys).map((key) => ({ + value: key, + label: key, + checked: key in prevFieldStates ? prevFieldStates[key] : true, + accessorKey: key + })); + }, []); const [fieldList, setFieldList] = useState(() => generateFieldList(data, [])); useEffect(() => { setFieldList((prevFieldList) => generateFieldList(data, prevFieldList)); + const excludeFields = ['_time', '_msg', 'container', 'pod', 'stream']; formHook.setValue( 'filterKeys', - generateFieldList(data).map((field) => ({ value: field.value, label: field.label })) + generateFieldList(data) + .filter((field) => !excludeFields.includes(field.value)) + .map((field) => ({ value: field.value, label: field.label })) ); }, [data, generateFieldList, isJsonMode, formHook]); @@ -290,6 +288,7 @@ export const LogTable = ({ isLoading={isLoading} overflowY={'auto'} maxH={'500px'} + isHeaderFixed={true} tdStyle={{ p: '10px 24px', borderBottom: 'none' diff --git a/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx b/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx index 030e1daed99..5bf271955fd 100644 --- a/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx +++ b/frontend/providers/applaunchpad/src/components/layouts/DetailLayout.tsx @@ -46,8 +46,8 @@ export default function DetailLayout({ children, appName }: DetailLayoutProps) { }, { refetchOnMount: true, - refetchInterval: router.pathname === ROUTES.OVERVIEW ? 3000 : 10000, - staleTime: router.pathname === ROUTES.OVERVIEW ? 3000 : 10000 + refetchInterval: router.pathname === ROUTES.OVERVIEW ? 3000 : 5000, + staleTime: router.pathname === ROUTES.OVERVIEW ? 3000 : 5000 } ); diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index f6f994d9581..1661026921f 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -56,7 +56,7 @@ export default function LogsPage({ appName }: { appName: string }) { defaultValues: { pods: [], containers: [], - limit: 10, + limit: 100, keyword: '', isJsonMode: false, isOnlyStderr: false, @@ -118,8 +118,14 @@ export default function LogsPage({ appName }: { appName: string }) { limit: formHook.watch('limit').toString(), jsonMode: formHook.watch('isJsonMode').toString(), keyword: formHook.watch('keyword'), - pod: selectedPods.map((pod) => pod.value), - container: selectedContainers.map((container) => container.value), + pod: + selectedPods.length === formHook.watch('pods').length + ? [] + : selectedPods.map((pod) => pod.value), + container: + selectedContainers.length === formHook.watch('containers').length + ? [] + : selectedContainers.map((container) => container.value), jsonQuery: jsonFilters }), { @@ -154,8 +160,14 @@ export default function LogsPage({ appName }: { appName: string }) { numberLevel: timeRange.slice(-1), time: timeRange, stderrMode: formHook.watch('isOnlyStderr').toString(), - pod: selectedPods.map((pod) => pod.value), - container: selectedContainers.map((container) => container.value) + pod: + selectedPods.length === formHook.watch('pods').length + ? [] + : selectedPods.map((pod) => pod.value), + container: + selectedContainers.length === formHook.watch('containers').length + ? [] + : selectedContainers.map((container) => container.value) }), { refetchInterval: refreshInterval, From f366c1e2ea67fc2a05598135c69a311ba1c77669 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Fri, 24 Jan 2025 16:27:00 +0800 Subject: [PATCH 38/48] update timerange --- .../public/locales/en/common.json | 7 ++-- .../public/locales/zh/common.json | 3 +- .../src/components/Icon/icons/arrowLeft.svg | 11 ++----- .../src/components/app/detail/logs/Filter.tsx | 32 ++++++++++++++----- .../components/app/detail/logs/LogTable.tsx | 26 ++++----------- .../src/pages/app/detail/logs.tsx | 12 +++---- .../applaunchpad/src/store/logStore.ts | 2 ++ .../applaunchpad/src/utils/timeRange.ts | 2 ++ 8 files changed, 49 insertions(+), 46 deletions(-) diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 1a3e45efd24..677b3e6e22e 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -294,7 +294,7 @@ "only_stderr": "Stderr Only", "keyword": "Keywords", "search": "Search", - "equal": "Equal", + "equal": "equal", "greater_than": "Greater than", "less_than": "Less than", "field_name": "Select Field", @@ -312,8 +312,9 @@ "no_data_available": "No data available", "all": "All", "hour-singular": "hour", - "not_equal": "is not", + "not_equal": "not", "field_settings": "Field Setting", "selected": "Selected", - "please_select": "Please Select" + "please_select": "Please Select", + "refetching_success": "Refresh Successful" } diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 6e88e201f51..3c30e7100ec 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -315,5 +315,6 @@ "not_equal": "不等于", "field_settings": "字段设置", "selected": "已选中", - "please_select": "请选择" + "please_select": "请选择", + "refetching_success": "刷新成功" } diff --git a/frontend/providers/applaunchpad/src/components/Icon/icons/arrowLeft.svg b/frontend/providers/applaunchpad/src/components/Icon/icons/arrowLeft.svg index 9e246b28759..045c63cbce5 100644 --- a/frontend/providers/applaunchpad/src/components/Icon/icons/arrowLeft.svg +++ b/frontend/providers/applaunchpad/src/components/Icon/icons/arrowLeft.svg @@ -1,10 +1,3 @@ - - - - - - - - - + + \ No newline at end of file diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx index 499f78e1af3..28b30c85c68 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx @@ -1,6 +1,6 @@ import MyIcon from '@/components/Icon'; import { JsonFilterItem, LogsFormData } from '@/pages/app/detail/logs'; -import { Button, ButtonProps, Flex, Input, Switch, Text } from '@chakra-ui/react'; +import { Button, ButtonProps, Center, Flex, Input, Switch, Text } from '@chakra-ui/react'; import { MySelect } from '@sealos/ui'; import { useTranslation } from 'next-i18next'; import { useState } from 'react'; @@ -75,7 +75,6 @@ export const Filter = ({ /> } onClick={() => { formHook.setValue('keyword', inputKeyword); @@ -86,6 +85,7 @@ export const Filter = ({ + {/* json mode */} {isJsonMode && ( - {fields.length === 0 && ( + {fields.length > 0 ? ( append({ @@ -107,7 +107,20 @@ export const Filter = ({ }) } /> + ) : ( +
+ + {t('no_data_available')} + +
)} + {fields.map((field, index) => ( formHook.setValue(`jsonFilters.${index}.mode`, val as JsonFilterItem['mode']) } diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index 5236219ebd3..81c09c046f5 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -58,23 +58,6 @@ export const LogTable = ({ const generateFieldList = useCallback((data: any[], prevFieldList: FieldItem[] = []) => { if (!data.length) return []; - // if (!isJsonMode) { - // return [ - // { - // value: 'time', - // label: 'Time', - // checked: true, - // accessorKey: '_time' - // }, - // { - // value: 'message', - // label: 'Message', - // checked: true, - // accessorKey: '_msg' - // } - // ]; - // } - const uniqueKeys = new Set(); data.forEach((item) => { Object.keys(item).forEach((key) => { @@ -95,7 +78,7 @@ export const LogTable = ({ })); }, []); - const [fieldList, setFieldList] = useState(() => generateFieldList(data, [])); + const [fieldList, setFieldList] = useState([]); useEffect(() => { setFieldList((prevFieldList) => generateFieldList(data, prevFieldList)); @@ -106,7 +89,7 @@ export const LogTable = ({ .filter((field) => !excludeFields.includes(field.value)) .map((field) => ({ value: field.value, label: field.label })) ); - }, [data, generateFieldList, isJsonMode, formHook]); + }, [data, generateFieldList, formHook]); useEffect(() => { const visibleCount = fieldList.filter((field) => field.checked).length; @@ -133,6 +116,11 @@ export const LogTable = ({ fontSize={'12px'} fontWeight={400} lineHeight={'16px'} + {...(field.accessorKey === '_msg' && { + maxW: '600px', + whiteSpace: 'pre-wrap', + wordBreak: 'break-word' + })} > {value?.toString() || ''} diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 1661026921f..3a690f3b5dc 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -1,12 +1,9 @@ import { useTranslation } from 'next-i18next'; import { useQuery } from '@tanstack/react-query'; import { Box, useTheme, Flex, Divider } from '@chakra-ui/react'; - import { useAppStore } from '@/store/app'; -import { useToast } from '@/hooks/useToast'; import { serviceSideProps } from '@/utils/i18n'; import DetailLayout from '@/components/layouts/DetailLayout'; - import { Header } from '@/components/app/detail/logs/Header'; import { Filter } from '@/components/app/detail/logs/Filter'; import { LogTable } from '@/components/app/detail/logs/LogTable'; @@ -20,11 +17,12 @@ import { formatTimeRange } from '@/utils/timeRange'; import { downLoadBold } from '@/utils/tools'; import { useLogStore } from '@/store/logStore'; import { useRouter } from 'next/router'; +import { useMessage } from '@sealos/ui'; export interface JsonFilterItem { key: string; value: string; - mode: '=' | '!=' | '~'; + mode: '=' | '!=' | '~' | '!~'; } export interface LogsFormData { @@ -45,7 +43,7 @@ export interface LogsFormData { export default function LogsPage({ appName }: { appName: string }) { const theme = useTheme(); const router = useRouter(); - const { toast } = useToast(); + const { message } = useMessage(); const { t } = useTranslation(); const { appDetail, appDetailPods } = useAppStore(); const [isInitialized, setIsInitialized] = useState(false); @@ -180,7 +178,9 @@ export default function LogsPage({ appName }: { appName: string }) { ); const refetchData = () => { - console.log('refetchData'); + message({ + title: t('refetching_success') + }); refetchLogsData(); refetchLogCountsData(); }; diff --git a/frontend/providers/applaunchpad/src/store/logStore.ts b/frontend/providers/applaunchpad/src/store/logStore.ts index 3d695691154..d7ad63cb8dd 100644 --- a/frontend/providers/applaunchpad/src/store/logStore.ts +++ b/frontend/providers/applaunchpad/src/store/logStore.ts @@ -5,6 +5,8 @@ import { devtools } from 'zustand/middleware'; interface LogState { rawLogs: string; parsedLogs: { + container: string; + pod: string; logs_total: string; _msg: string; _time: string; diff --git a/frontend/providers/applaunchpad/src/utils/timeRange.ts b/frontend/providers/applaunchpad/src/utils/timeRange.ts index c9562070eff..c94a4d7dd39 100644 --- a/frontend/providers/applaunchpad/src/utils/timeRange.ts +++ b/frontend/providers/applaunchpad/src/utils/timeRange.ts @@ -63,6 +63,8 @@ export function formatTimeRange(startTime: Date, endTime: Date): string { return `${diffMinutes}m`; } else if (diffHours < 24) { return `${diffHours}h`; + } else if (diffDays === 1) { + return '24h'; } else if (diffDays < 30) { return `${diffDays}d`; } else { From 4cb4c7c764b5a59cda6a693f01634d6d3b7594f4 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Sat, 25 Jan 2025 10:15:45 +0800 Subject: [PATCH 39/48] done --- .../ui/src/components/Select/index.tsx | 11 ++- .../src/components/app/detail/logs/Filter.tsx | 4 +- .../components/app/detail/logs/LogTable.tsx | 4 +- .../applaunchpad/src/constants/theme.ts | 3 +- .../src/pages/api/log/queryLogs.ts | 1 + .../src/pages/app/detail/logs.tsx | 77 ++++++++++--------- .../src/pages/app/detail/monitor.tsx | 6 +- 7 files changed, 62 insertions(+), 44 deletions(-) diff --git a/frontend/packages/ui/src/components/Select/index.tsx b/frontend/packages/ui/src/components/Select/index.tsx index f75268a28e9..8958a5ab098 100644 --- a/frontend/packages/ui/src/components/Select/index.tsx +++ b/frontend/packages/ui/src/components/Select/index.tsx @@ -54,7 +54,16 @@ const MySelect = ( } }); - const activeMenu = useMemo(() => list.find((item) => item.value === value), [list, value]); + const activeMenu = useMemo(() => { + const foundItem = list.find((item) => item.value === value); + if (!foundItem && value) { + return { + label: value, + value: value + }; + } + return foundItem; + }, [list, value]); return ( diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx index 28b30c85c68..bc534fb2115 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/Filter.tsx @@ -19,6 +19,8 @@ export const Filter = ({ const isJsonMode = formHook.watch('isJsonMode'); const isOnlyStderr = formHook.watch('isOnlyStderr'); + const filterKeys = formHook.watch('filterKeys'); + const { fields, append, remove } = useFieldArray({ control: formHook.control, name: 'jsonFilters' @@ -97,7 +99,7 @@ export const Filter = ({ flexWrap={'wrap'} borderRadius={'0px 8px 8px 8px'} > - {fields.length > 0 ? ( + {filterKeys.length > 0 || fields.length > 0 ? ( append({ diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index 81c09c046f5..a2611e6ed36 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -140,7 +140,7 @@ export const LogTable = ({ }); return ( - + 0 ? ( getAppLogs({ app: appName, numberMode: 'true', numberLevel: timeRange.slice(-1), + jsonMode: formHook.watch('isJsonMode').toString(), time: timeRange, stderrMode: formHook.watch('isOnlyStderr').toString(), pod: @@ -165,7 +168,9 @@ export default function LogsPage({ appName }: { appName: string }) { container: selectedContainers.length === formHook.watch('containers').length ? [] - : selectedContainers.map((container) => container.value) + : selectedContainers.map((container) => container.value), + jsonQuery: jsonFilters, + keyword: formHook.watch('keyword') }), { refetchInterval: refreshInterval, @@ -187,41 +192,39 @@ export default function LogsPage({ appName }: { appName: string }) { return ( - - <> - -
- - - - - - - - - - - + + +
+ + + + + + + + + + ); } diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx index 0d624ec4f79..b58847850ed 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/monitor.tsx @@ -48,7 +48,8 @@ export default function MonitorPage({ appName }: { appName: string }) { end: endDateTime.getTime() }), { - refetchInterval: refreshInterval + refetchInterval: refreshInterval, + enabled: !!appDetailPods?.[0]?.podName } ); @@ -74,7 +75,8 @@ export default function MonitorPage({ appName }: { appName: string }) { end: endDateTime.getTime() }), { - refetchInterval: refreshInterval + refetchInterval: refreshInterval, + enabled: !!appDetailPods?.[0]?.podName } ); From e9f1562fa5eb37035faa238391a485cc64a5b92f Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Fri, 7 Feb 2025 15:49:43 +0800 Subject: [PATCH 40/48] update datepicker --- .../public/locales/en/common.json | 3 +- .../public/locales/zh/common.json | 3 +- .../src/components/AdvancedSelect/index.tsx | 3 +- .../src/components/DatePicker/index.tsx | 253 +++++++-------- .../src/components/LogBarChart/index.tsx | 1 + .../src/components/Monitor/Header.tsx | 24 +- .../src/components/app/detail/logs/Filter.tsx | 7 +- .../src/components/app/detail/logs/Header.tsx | 294 ++++++++++-------- .../components/app/detail/logs/LogTable.tsx | 7 +- .../providers/applaunchpad/src/pages/_app.tsx | 7 +- .../src/pages/app/detail/logs.tsx | 5 +- .../applaunchpad/src/styles/global.scss | 4 +- 12 files changed, 314 insertions(+), 297 deletions(-) diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index 677b3e6e22e..bb85315a9f7 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -316,5 +316,6 @@ "field_settings": "Field Setting", "selected": "Selected", "please_select": "Please Select", - "refetching_success": "Refresh Successful" + "refetching_success": "Refresh Successful", + "refresh": "refresh" } diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 3c30e7100ec..17da556fe06 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -316,5 +316,6 @@ "field_settings": "字段设置", "selected": "已选中", "please_select": "请选择", - "refetching_success": "刷新成功" + "refetching_success": "刷新成功", + "refresh": "刷新" } diff --git a/frontend/providers/applaunchpad/src/components/AdvancedSelect/index.tsx b/frontend/providers/applaunchpad/src/components/AdvancedSelect/index.tsx index 59d165942b3..e90f931ad73 100644 --- a/frontend/providers/applaunchpad/src/components/AdvancedSelect/index.tsx +++ b/frontend/providers/applaunchpad/src/components/AdvancedSelect/index.tsx @@ -114,9 +114,10 @@ const AdvancedSelect = ( _active={{ transform: '' }} + boxShadow={'none'} {...(isOpen ? { - boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)', + // boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)', borderColor: 'brightBlue.500', bg: '#FFF' } diff --git a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx index 415279c8675..746c60dcc6c 100644 --- a/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx +++ b/frontend/providers/applaunchpad/src/components/DatePicker/index.tsx @@ -6,8 +6,6 @@ import { Divider, Flex, FlexProps, - Grid, - GridItem, Input, Popover, PopoverContent, @@ -306,154 +304,139 @@ const DatePicker = ({ isDisabled = false, ...props }: DatePickerProps) => { borderColor={'grayModern.200'} borderRadius="6px" color={'grayModern.900'} + fontSize={'12px'} {...props} > - {format(startDateTime, 'y-MM-dd HH:mm:ss')} - - {format(endDateTime, 'y-MM-dd HH:mm:ss')} - + + {format(startDateTime, 'y-MM-dd HH:mm:ss')} + + {format(endDateTime, 'y-MM-dd HH:mm:ss')} + + - - - - - - {/* start date and time */} - - - {t('start')} - - - handleFromChange(e.target.value, 'date')} - error={!!fromDateError} - showError={fromDateShake} - /> - handleFromChange(e.target.value, 'time')} - error={!!fromTimeError} - showError={fromTimeShake} - /> - - - - {/* end date and time */} - - - {t('end')} - - - handleToChange(e.target.value, 'date')} - error={!!toDateError} - showError={toDateShake} - /> - handleToChange(e.target.value, 'time')} - error={!!toTimeError} - showError={toTimeShake} - /> - + + + + + + + {t('start')} + + + handleFromChange(e.target.value, 'date')} + error={!!fromDateError} + showError={fromDateShake} + /> + handleFromChange(e.target.value, 'time')} + error={!!fromTimeError} + showError={fromTimeShake} + /> - - - - - - - {recentDateList.map((item) => ( - - ))} + + + + {t('end')} + + + handleToChange(e.target.value, 'date')} + error={!!toDateError} + showError={toDateShake} + /> + handleToChange(e.target.value, 'time')} + error={!!toTimeError} + showError={toTimeShake} + /> - - - - - setTimeZone(val)} - /> - + + + + + {recentDateList.map((item) => ( - - - + ))} - - + + + + + setTimeZone(val)} + /> + + + + + + diff --git a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx index 84a7a3adf1b..a2b7c079532 100644 --- a/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx +++ b/frontend/providers/applaunchpad/src/components/LogBarChart/index.tsx @@ -149,6 +149,7 @@ const LogBarChart = ({ }, yAxis: { type: 'value', + splitNumber: 3, splitLine: { lineStyle: { type: 'dashed', diff --git a/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx b/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx index e65df3c502b..97ae5ea38cf 100644 --- a/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx +++ b/frontend/providers/applaunchpad/src/components/Monitor/Header.tsx @@ -1,5 +1,6 @@ import MyIcon from '@/components/Icon'; import { + Box, Button, ButtonGroup, Flex, @@ -17,6 +18,7 @@ import { REFRESH_INTERVAL_OPTIONS } from '@/constants/monitor'; import useDateTimeStore from '@/store/date'; import { ChevronDownIcon } from '@chakra-ui/icons'; import dynamic from 'next/dynamic'; +import { MyTooltip } from '@sealos/ui'; const DatePicker = dynamic(() => import('@/components/DatePicker'), { ssr: false }); export default function Header({ @@ -79,15 +81,19 @@ export default function Header({ refetchData(); }} > - + + + + + + {/* tab */} {/* ( - + import('@/components/DatePicker'), { ssr: false }); @@ -31,153 +35,169 @@ export const Header = ({ }) => { const { t } = useTranslation(); const { refreshInterval, setRefreshInterval } = useDateTimeStore(); + const [isLargerThan1440] = useMediaQuery('(min-width: 1440px)'); return ( - - - - {t('time')} - - - - - - - Pods - - } - width={'fit-content'} - value={'hello-sql-postgresql-0'} - onCheckboxChange={(val) => { - formHook.setValue('pods', val); - }} - list={formHook.watch('pods')} - /> - - - - - Containers - - } - value={'hello-sql-postgresql-0'} - list={formHook.watch('containers')} - onCheckboxChange={(val) => { - formHook.setValue('containers', val); - }} - /> + + + + + {t('time')} + + + + + + Pods + + } + width={'fit-content'} + value={'hello-sql-postgresql-0'} + onCheckboxChange={(val) => { + formHook.setValue('pods', val); + }} + list={formHook.watch('pods')} + /> + - - - - {t('log_number')} - - { - const val = Number(e.target.value); - if (isNaN(val)) { - formHook.setValue('limit', 1); - } else if (val > 500) { - formHook.setValue('limit', 500); - } else if (val < 1) { - formHook.setValue('limit', 1); - } else { - formHook.setValue('limit', val); - } - }} - /> - - - - + + - + + + + + + + + + + + {refreshInterval === 0 ? null : ( + {`${refreshInterval / 1000}s`} + )} + + + + + {REFRESH_INTERVAL_OPTIONS.map((item) => ( + { + setRefreshInterval(item.value); + }} + {...(refreshInterval === item.value + ? { + color: 'brightBlue.600' + } + : {})} + borderRadius={'4px'} + _hover={{ + bg: 'rgba(17, 24, 36, 0.05)', + color: 'brightBlue.600' + }} + p={'6px'} + > + {item.label} + + ))} + + + + ); diff --git a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx index a2611e6ed36..f8669f70b15 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/logs/LogTable.tsx @@ -102,7 +102,12 @@ export const LogTable = ({ .filter((field) => field.checked) .map((field) => ({ accessorKey: field.accessorKey, - header: () => field.label, + header: () => { + if (field.label === '_time' || field.label === '_msg') { + return field.label.substring(1); + } + return field.label; + }, cell: ({ row }) => { let value = get(row.original, field.accessorKey, ''); diff --git a/frontend/providers/applaunchpad/src/pages/_app.tsx b/frontend/providers/applaunchpad/src/pages/_app.tsx index 1ba7016a8b2..61db547f2d4 100644 --- a/frontend/providers/applaunchpad/src/pages/_app.tsx +++ b/frontend/providers/applaunchpad/src/pages/_app.tsx @@ -12,17 +12,14 @@ import { appWithTranslation, useTranslation } from 'next-i18next'; import type { AppProps } from 'next/app'; import Head from 'next/head'; import Router, { useRouter } from 'next/router'; -import NProgress from 'nprogress'; //nprogress module +import NProgress from 'nprogress'; import { useEffect, useState } from 'react'; import { EVENT_NAME } from 'sealos-desktop-sdk'; import { createSealosApp, sealosApp } from 'sealos-desktop-sdk/app'; +import 'react-day-picker/dist/style.css'; import '@/styles/reset.scss'; import 'nprogress/nprogress.css'; import '@sealos/driver/src/driver.css'; -import { AppEditSyncedFields } from '@/types/app'; -import Script from 'next/script'; - -import 'react-day-picker/dist/style.css'; //Binding events. Router.events.on('routeChangeStart', () => NProgress.start()); diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 3795af8ca06..21c4ca110f0 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -206,7 +206,7 @@ export default function LogsPage({ appName }: { appName: string }) { diff --git a/frontend/providers/applaunchpad/src/styles/global.scss b/frontend/providers/applaunchpad/src/styles/global.scss index 6e238e5eb1a..bd7270d51c7 100644 --- a/frontend/providers/applaunchpad/src/styles/global.scss +++ b/frontend/providers/applaunchpad/src/styles/global.scss @@ -49,13 +49,13 @@ } // date picker - div.rdp { - --rdp-cell-size: 40px; + --rdp-cell-size: 30px; --rdp-accent-color: black; --rdp-background-color: #f4f6f8; --rdp-outline: 2px solid var(--rdp-accent-color); --rdp-outline-selected: 2px solid rgba(0, 0, 0, 0.75); + margin: 12px 16px 8px 16px; } button.rdp-button_reset.rdp-button.rdp-day { From 88cd09d8a7fe5c7769b2abc8ef61e86d46ce273a Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Sat, 8 Feb 2025 10:32:03 +0800 Subject: [PATCH 41/48] add queryPodList api --- .../providers/applaunchpad/src/api/app.ts | 4 + .../src/pages/api/log/queryLogs.ts | 2 +- .../src/pages/api/log/queryPodList.ts | 84 +++++++++++++++++++ .../src/pages/app/detail/logs.tsx | 49 +++++++++-- .../applaunchpad/src/store/logStore.ts | 11 +-- 5 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 frontend/providers/applaunchpad/src/pages/api/log/queryPodList.ts diff --git a/frontend/providers/applaunchpad/src/api/app.ts b/frontend/providers/applaunchpad/src/api/app.ts index d8c69f418ba..10246498165 100644 --- a/frontend/providers/applaunchpad/src/api/app.ts +++ b/frontend/providers/applaunchpad/src/api/app.ts @@ -10,6 +10,7 @@ import { import type { AppPatchPropsType, PodDetailType } from '@/types/app'; import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor'; import { LogQueryPayload } from '@/pages/api/log/queryLogs'; +import { PodListQueryPayload } from '@/pages/api/log/queryPodList'; export const postDeployApp = (yamlList: string[]) => POST('/api/applyApp', { yamlList }); @@ -64,3 +65,6 @@ export const getAppMonitorData = (payload: { }) => GET(`/api/monitor/getMonitorData`, payload); export const getAppLogs = (payload: LogQueryPayload) => POST('/api/log/queryLogs', payload); + +export const getLogPodList = (payload: PodListQueryPayload) => + POST('/api/log/queryPodList', payload); diff --git a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts index 466173f0c7a..2f96f174c98 100644 --- a/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryLogs.ts @@ -91,7 +91,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< Authorization: encodeURIComponent(kubeconfig) } }); - console.log('fetch log result: ', result.status); + console.log('fetch /queryLogsByParams: ', result.status); const data = await result.text(); jsonRes(res, { diff --git a/frontend/providers/applaunchpad/src/pages/api/log/queryPodList.ts b/frontend/providers/applaunchpad/src/pages/api/log/queryPodList.ts new file mode 100644 index 00000000000..554c9308233 --- /dev/null +++ b/frontend/providers/applaunchpad/src/pages/api/log/queryPodList.ts @@ -0,0 +1,84 @@ +import { JsonFilterItem } from '@/pages/app/detail/logs'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; +import { ApiResp } from '@/services/kubernet'; + +import type { NextApiRequest, NextApiResponse } from 'next'; + +export interface PodListQueryPayload { + app?: string; + time?: string; + namespace?: string; + podQuery?: string; +} + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const logUrl = global.AppConfig.launchpad.components.log.url; + + if (!logUrl) { + return jsonRes(res, { + code: 400, + error: 'logUrl is not set' + }); + } + + if (req.method !== 'POST') { + return jsonRes(res, { + code: 405, + error: 'Method not allowed' + }); + } + + try { + const kubeconfig = await authSession(req.headers); + const { namespace } = await getK8s({ + kubeconfig: kubeconfig + }); + + if (!req.body.app) { + return jsonRes(res, { + code: 400, + error: 'app is required' + }); + } + + const { time = '30d', app = '', podQuery = 'true' } = req.body as PodListQueryPayload; + + const params: PodListQueryPayload = { + time: time, + namespace: namespace, + app: app, + podQuery: podQuery + }; + + console.log(params, 'params'); + const result = await fetch(logUrl + '/queryPodList', { + method: 'POST', + body: JSON.stringify(params), + headers: { + 'Content-Type': 'application/json', + Authorization: encodeURIComponent(kubeconfig) + } + }); + console.log('fetch /queryPodList: ', result.status); + if (result.status !== 200) { + return jsonRes(res, { + data: [] + }); + } + + const data = await result.json(); + + jsonRes(res, { + code: 200, + data: data + }); + } catch (error) { + console.log(error, 'error'); + jsonRes(res, { + code: 500, + error: error + }); + } +} diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 21c4ca110f0..633736eedbe 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -11,7 +11,7 @@ import { LogCounts } from '@/components/app/detail/logs/LogCounts'; import { useEffect, useMemo, useState } from 'react'; import { ListItem } from '@/components/AdvancedSelect'; import useDateTimeStore from '@/store/date'; -import { getAppLogs } from '@/api/app'; +import { getAppLogs, getLogPodList } from '@/api/app'; import { useForm } from 'react-hook-form'; import { formatTimeRange } from '@/utils/timeRange'; import { downLoadBold } from '@/utils/tools'; @@ -67,11 +67,11 @@ export default function LogsPage({ appName }: { appName: string }) { useEffect(() => { if (!isInitialized && appDetailPods?.length > 0) { const urlPodName = router.query.pod as string; - const pods = appDetailPods.map((pod) => ({ - value: pod.podName, - label: pod.podName, - checked: urlPodName ? pod.podName === urlPodName : true - })); + // const pods = appDetailPods.map((pod) => ({ + // value: pod.podName, + // label: pod.podName, + // checked: urlPodName ? pod.podName === urlPodName : true + // })); const containers = appDetailPods .flatMap((pod) => pod.spec?.containers || []) @@ -82,7 +82,8 @@ export default function LogsPage({ appName }: { appName: string }) { })) .filter((item, index, self) => index === self.findIndex((t) => t.value === item.value)); - formHook.setValue('pods', pods); + // console.log(pods, containers); + // formHook.setValue('pods', pods); formHook.setValue('containers', containers); setIsInitialized(true); @@ -182,12 +183,46 @@ export default function LogsPage({ appName }: { appName: string }) { } ); + const { refetch: refetchPodListData, isLoading: isPodListLoading } = useQuery( + ['log-pod-list-data', appName, timeRange], + () => + getLogPodList({ + app: appName, + time: timeRange + }), + { + staleTime: 3000, + cacheTime: 3000, + onSuccess: (data) => { + const podList = Array.isArray(data) ? data : []; + const formattedPods = podList.map((podName: string) => ({ + value: podName, + label: podName, + checked: true + })); + const urlPodName = router.query.pod as string; + const detailPods = appDetailPods.map((pod) => ({ + value: pod.podName, + label: pod.podName, + checked: urlPodName ? pod.podName === urlPodName : true + })); + + const mergedPods = [...formattedPods, ...detailPods]; + const uniquePods = Array.from(new Map(mergedPods.map((pod) => [pod.value, pod])).values()); + + console.log(uniquePods, 'uniquePods'); + formHook.setValue('pods', uniquePods); + } + } + ); + const refetchData = () => { message({ title: t('refetching_success') }); refetchLogsData(); refetchLogCountsData(); + refetchPodListData(); }; return ( diff --git a/frontend/providers/applaunchpad/src/store/logStore.ts b/frontend/providers/applaunchpad/src/store/logStore.ts index d7ad63cb8dd..7327b4fcd96 100644 --- a/frontend/providers/applaunchpad/src/store/logStore.ts +++ b/frontend/providers/applaunchpad/src/store/logStore.ts @@ -48,16 +48,7 @@ export const useLogStore = create()( return; } const logLines = data.split('\n').filter((line) => line.trim()); - console.log( - logLines.map((line) => { - try { - return JSON.parse(line); - } catch (e) { - return { raw: line, parseError: true }; - } - }), - 'data' - ); + state.logCounts = logLines.map((line) => { try { return JSON.parse(line); From 12d5c1484a5858a62db16642964bff1000839d18 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Sat, 8 Feb 2025 13:58:09 +0800 Subject: [PATCH 42/48] update podname --- .../public/locales/en/common.json | 2 +- .../public/locales/zh/common.json | 4 +- .../src/components/app/detail/index/Pods.tsx | 14 +++---- .../src/pages/app/detail/logs.tsx | 38 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/frontend/providers/applaunchpad/public/locales/en/common.json b/frontend/providers/applaunchpad/public/locales/en/common.json index bb85315a9f7..dc5a9fdd511 100644 --- a/frontend/providers/applaunchpad/public/locales/en/common.json +++ b/frontend/providers/applaunchpad/public/locales/en/common.json @@ -163,7 +163,7 @@ "please enter app name": "please enter: {{appName}}", "Pod": "Pod", "Pod Name": "Pod Name", - "Pods List": "Pods List", + "Pods List": "Pod List", "Port": "Port", "private": "private", "Private": "Private", diff --git a/frontend/providers/applaunchpad/public/locales/zh/common.json b/frontend/providers/applaunchpad/public/locales/zh/common.json index 17da556fe06..107bf9a5b46 100644 --- a/frontend/providers/applaunchpad/public/locales/zh/common.json +++ b/frontend/providers/applaunchpad/public/locales/zh/common.json @@ -162,8 +162,8 @@ "Please enter": "请输入", "please enter app name": "请输入:{{appName}}", "Pod": "实例", - "Pod Name": "实例名", - "Pods List": "实例列表", + "Pod Name": "Pod 名称", + "Pods List": "Pod 列表", "Port": "端口", "private": "私有", "Private": "私有", diff --git a/frontend/providers/applaunchpad/src/components/app/detail/index/Pods.tsx b/frontend/providers/applaunchpad/src/components/app/detail/index/Pods.tsx index 0b96a856318..a60406916cb 100644 --- a/frontend/providers/applaunchpad/src/components/app/detail/index/Pods.tsx +++ b/frontend/providers/applaunchpad/src/components/app/detail/index/Pods.tsx @@ -80,7 +80,7 @@ const Pods = ({ pods = [], appName }: { pods: PodDetailType[]; appName: string } key: 'podName', render: (_: PodDetailType, i: number) => ( - {appName}-{i + 1} + {_?.podName} ) }, @@ -298,10 +298,10 @@ const Pods = ({ pods = [], appName }: { pods: PodDetailType[]; appName: string } pods={pods .filter((pod) => pod.status.value === PodStatusEnum.running) .map((item, i) => ({ - alias: `${appName}-${i + 1}`, + alias: item.podName, podName: item.podName }))} - podAlias={`${appName}-${logsPodIndex + 1}`} + podAlias={pods[logsPodIndex]?.podName || ''} setLogsPodName={(name: string) => setLogsPodIndex(pods.findIndex((item) => item.podName === name)) } @@ -311,9 +311,9 @@ const Pods = ({ pods = [], appName }: { pods: PodDetailType[]; appName: string } {detailPodIndex !== undefined && ( ({ - alias: `${appName}-${i + 1}`, + alias: item.podName, podName: item.podName }))} setPodDetail={(e: string) => @@ -328,9 +328,9 @@ const Pods = ({ pods = [], appName }: { pods: PodDetailType[]; appName: string } isOpen={isOpenPodFile} onClose={onClosePodFile} pod={pods[detailFilePodIndex]} - podAlias={`${appName}-${detailFilePodIndex + 1}`} + podAlias={pods[detailFilePodIndex]?.podName || ''} pods={pods.map((item, i) => ({ - alias: `${appName}-${i + 1}`, + alias: item.podName, podName: item.podName }))} setPodDetail={(e: string) => diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 633736eedbe..50568058b07 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -67,11 +67,11 @@ export default function LogsPage({ appName }: { appName: string }) { useEffect(() => { if (!isInitialized && appDetailPods?.length > 0) { const urlPodName = router.query.pod as string; - // const pods = appDetailPods.map((pod) => ({ - // value: pod.podName, - // label: pod.podName, - // checked: urlPodName ? pod.podName === urlPodName : true - // })); + const initialPods = appDetailPods.map((pod) => ({ + value: pod.podName, + label: pod.podName, + checked: urlPodName ? pod.podName === urlPodName : true + })); const containers = appDetailPods .flatMap((pod) => pod.spec?.containers || []) @@ -82,10 +82,8 @@ export default function LogsPage({ appName }: { appName: string }) { })) .filter((item, index, self) => index === self.findIndex((t) => t.value === item.value)); - // console.log(pods, containers); - // formHook.setValue('pods', pods); + formHook.setValue('pods', initialPods); formHook.setValue('containers', containers); - setIsInitialized(true); } }, [appDetailPods, isInitialized, formHook, router.query.pod]); @@ -195,22 +193,24 @@ export default function LogsPage({ appName }: { appName: string }) { cacheTime: 3000, onSuccess: (data) => { const podList = Array.isArray(data) ? data : []; - const formattedPods = podList.map((podName: string) => ({ + const urlPodName = router.query.pod as string; + + const currentPodsState = new Map( + formHook.watch('pods').map((pod) => [pod.value, pod.checked]) + ); + + const allPods = podList.map((podName: string) => ({ value: podName, label: podName, - checked: true - })); - const urlPodName = router.query.pod as string; - const detailPods = appDetailPods.map((pod) => ({ - value: pod.podName, - label: pod.podName, - checked: urlPodName ? pod.podName === urlPodName : true + checked: currentPodsState.has(podName) + ? !!currentPodsState.get(podName) + : urlPodName + ? podName === urlPodName + : true })); - const mergedPods = [...formattedPods, ...detailPods]; - const uniquePods = Array.from(new Map(mergedPods.map((pod) => [pod.value, pod])).values()); + const uniquePods = Array.from(new Map(allPods.map((pod) => [pod.value, pod])).values()); - console.log(uniquePods, 'uniquePods'); formHook.setValue('pods', uniquePods); } } From e4f50cb76c9cd3c247cb669ae11483dfdc7510f2 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Mon, 10 Feb 2025 13:30:41 +0800 Subject: [PATCH 43/48] fix init pods --- .../src/pages/app/detail/logs.tsx | 72 ++++++++----------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx index 50568058b07..44e29beebe1 100644 --- a/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/detail/logs.tsx @@ -46,7 +46,7 @@ export default function LogsPage({ appName }: { appName: string }) { const { message } = useMessage(); const { t } = useTranslation(); const { appDetail, appDetailPods } = useAppStore(); - const [isInitialized, setIsInitialized] = useState(false); + const { refreshInterval, setRefreshInterval, startDateTime, endDateTime } = useDateTimeStore(); const { setLogs, exportLogs, parsedLogs, logCounts, setLogCounts } = useLogStore(); @@ -63,31 +63,6 @@ export default function LogsPage({ appName }: { appName: string }) { } }); - // init pods and containers - useEffect(() => { - if (!isInitialized && appDetailPods?.length > 0) { - const urlPodName = router.query.pod as string; - const initialPods = appDetailPods.map((pod) => ({ - value: pod.podName, - label: pod.podName, - checked: urlPodName ? pod.podName === urlPodName : true - })); - - const containers = appDetailPods - .flatMap((pod) => pod.spec?.containers || []) - .map((container) => ({ - value: container.name, - label: container.name, - checked: true - })) - .filter((item, index, self) => index === self.findIndex((t) => t.value === item.value)); - - formHook.setValue('pods', initialPods); - formHook.setValue('containers', containers); - setIsInitialized(true); - } - }, [appDetailPods, isInitialized, formHook, router.query.pod]); - const selectedPods = formHook.watch('pods').filter((pod) => pod.checked); const selectedContainers = formHook.watch('containers').filter((container) => container.checked); const jsonFilters = formHook @@ -182,7 +157,7 @@ export default function LogsPage({ appName }: { appName: string }) { ); const { refetch: refetchPodListData, isLoading: isPodListLoading } = useQuery( - ['log-pod-list-data', appName, timeRange], + ['log-pod-list-data', appName, timeRange, appDetailPods?.length], () => getLogPodList({ app: appName, @@ -192,26 +167,37 @@ export default function LogsPage({ appName }: { appName: string }) { staleTime: 3000, cacheTime: 3000, onSuccess: (data) => { - const podList = Array.isArray(data) ? data : []; - const urlPodName = router.query.pod as string; + console.log('isInitialized', appDetailPods); + + if (appDetailPods?.length > 0) { + const podList = Array.isArray(data) ? data : []; + const urlPodName = router.query.pod as string; - const currentPodsState = new Map( - formHook.watch('pods').map((pod) => [pod.value, pod.checked]) - ); + const podNamesSet = new Set([ + ...podList, + ...appDetailPods + .map((pod) => pod.metadata?.name) + .filter((name): name is string => !!name) + ]); - const allPods = podList.map((podName: string) => ({ - value: podName, - label: podName, - checked: currentPodsState.has(podName) - ? !!currentPodsState.get(podName) - : urlPodName - ? podName === urlPodName - : true - })); + const allPods: ListItem[] = Array.from(podNamesSet).map((podName) => ({ + value: podName, + label: podName, + checked: urlPodName ? podName === urlPodName : true + })); - const uniquePods = Array.from(new Map(allPods.map((pod) => [pod.value, pod])).values()); + formHook.setValue('pods', allPods); - formHook.setValue('pods', uniquePods); + const containers = appDetailPods + .flatMap((pod) => pod.spec?.containers || []) + .map((container) => ({ + value: container.name, + label: container.name, + checked: true + })) + .filter((item, index, self) => index === self.findIndex((t) => t.value === item.value)); + formHook.setValue('containers', containers); + } } } ); From e92b47266ba77fe2247cb69fb4c224488e9a7d24 Mon Sep 17 00:00:00 2001 From: zjy <3161362058@qq.com> Date: Mon, 10 Feb 2025 14:50:10 +0800 Subject: [PATCH 44/48] fix ReleaseModal fix ReleaseModal fix ReleaseModal --- .../components/modals/release-modal.tsx | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 frontend/providers/devbox/components/modals/release-modal.tsx diff --git a/frontend/providers/devbox/components/modals/release-modal.tsx b/frontend/providers/devbox/components/modals/release-modal.tsx new file mode 100644 index 00000000000..dd64221aa50 --- /dev/null +++ b/frontend/providers/devbox/components/modals/release-modal.tsx @@ -0,0 +1,171 @@ +import { + Box, + Button, + Flex, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Textarea +} from '@chakra-ui/react'; +import { useMessage } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import { useCallback, useState } from 'react'; + +import { pauseDevbox, releaseDevbox, startDevbox } from '@/api/devbox'; +import { useConfirm } from '@/hooks/useConfirm'; +import { useEnvStore } from '@/stores/env'; +import { DevboxListItemTypeV2 } from '@/types/devbox'; +import { versionSchema } from '@/utils/vaildate'; + +const ReleaseModal = ({ + onClose, + onSuccess, + devbox +}: { + devbox: Omit; + onClose: () => void; + onSuccess: () => void; +}) => { + const t = useTranslations(); + const { message: toast } = useMessage(); + + const { env } = useEnvStore(); + + const [tag, setTag] = useState(''); + const [loading, setLoading] = useState(false); + const [tagError, setTagError] = useState(false); + const [releaseDes, setReleaseDes] = useState(''); + + const { openConfirm, ConfirmChild } = useConfirm({ + content: 'release_confirm_info', + showCheckbox: true, + checkboxLabel: 'pause_devbox_info' + }); + + const handleSubmit = () => { + const tagResult = versionSchema.safeParse(tag); + if (!tag) { + setTagError(true); + } else if (versionSchema.safeParse(tag).success === false) { + toast({ + title: t('tag_format_error'), + status: 'error' + }); + } else { + setTagError(false); + openConfirm((enableRestartMachine: boolean) => handleReleaseDevbox(enableRestartMachine))(); + } + }; + + const handleReleaseDevbox = useCallback( + async (enableRestartMachine: boolean) => { + try { + setLoading(true); + // 1.pause devbox + if (devbox.status.value === 'Running') { + await pauseDevbox({ devboxName: devbox.name }); + // wait 3s + await new Promise((resolve) => setTimeout(resolve, 3000)); + } + // 2.release devbox + await releaseDevbox({ + devboxName: devbox.name, + tag, + releaseDes, + devboxUid: devbox.id + }); + // 3.start devbox + if (enableRestartMachine) { + await startDevbox({ devboxName: devbox.name }); + } + toast({ + title: t('submit_release_successful'), + status: 'success' + }); + onSuccess(); + onClose(); + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('submit_release_failed'), + status: 'error' + }); + console.error(error); + } + setLoading(false); + }, + [devbox.status.value, devbox.name, devbox.id, tag, releaseDes, toast, t, onSuccess, onClose] + ); + + return ( + + + + + + + {t('release_version')} + + + + + + + {t('image_name')} + + + + + + {t('version_config')} + + + {t('version_number')} + setTag(e.target.value)} + mb={'8px'} + borderColor={tagError ? 'red.500' : undefined} + /> + {tagError && ( + + {t('tag_required')} + + )} + {t('version_description')} +