diff --git a/frontend/providers/dbprovider/public/locales/en/common.json b/frontend/providers/dbprovider/public/locales/en/common.json index 43bd60002..a0c4dc387 100644 --- a/frontend/providers/dbprovider/public/locales/en/common.json +++ b/frontend/providers/dbprovider/public/locales/en/common.json @@ -103,6 +103,7 @@ "cancel": "Cancel", "cannot_change_name": "Database name cannot be changed", "change_log": "History", + "click_for_details": "Click for details", "clipboard_read_failed": "Reading clipboard failed!", "clipboard_unsupported": "Browser does not support Clipper API", "collection": "Collection", diff --git a/frontend/providers/dbprovider/public/locales/zh/common.json b/frontend/providers/dbprovider/public/locales/zh/common.json index c14d95970..1a8044396 100644 --- a/frontend/providers/dbprovider/public/locales/zh/common.json +++ b/frontend/providers/dbprovider/public/locales/zh/common.json @@ -102,6 +102,7 @@ "cancel": "取消", "cannot_change_name": "不允许修改数据库名称", "change_log": "变更历史", + "click_for_details": "点击查看详情", "clipboard_read_failed": "读取剪切板失败!", "clipboard_unsupported": "浏览器不支持 Clipboard API", "collection": "集合", diff --git a/frontend/providers/dbprovider/src/api/db.ts b/frontend/providers/dbprovider/src/api/db.ts index 3de8bd70b..16d89fe2a 100644 --- a/frontend/providers/dbprovider/src/api/db.ts +++ b/frontend/providers/dbprovider/src/api/db.ts @@ -24,6 +24,19 @@ import { RequiredByKeys } from '@/utils/tools'; export const getMyDBList = () => GET('/api/getDBList'); +// Database Alerts +export type DatabaseAlertItem = { + name: string; + status: string; + reason: string; + details: string; +}; + +export const getDatabaseAlerts = (namespace: string) => + GET(`/api/proxy/get_db_alerts`, { + namespace + }); + export const getDBByName = ({ name, mock = false, diff --git a/frontend/providers/dbprovider/src/components/DBStatusTag/index.tsx b/frontend/providers/dbprovider/src/components/DBStatusTag/index.tsx index f14da62a7..23297871b 100644 --- a/frontend/providers/dbprovider/src/components/DBStatusTag/index.tsx +++ b/frontend/providers/dbprovider/src/components/DBStatusTag/index.tsx @@ -8,90 +8,147 @@ import { ModalHeader, ModalBody, useDisclosure, - ModalCloseButton + ModalCloseButton, + IconButton, + Tooltip, + Text } from '@chakra-ui/react'; import type { DBConditionItemType, DBStatusMapType } from '@/types/db'; import MyIcon from '../Icon'; import { useTranslation } from 'next-i18next'; import { formatPodTime } from '@/utils/tools'; import { I18nCommonKey } from '@/types/i18next'; +import { DBStatusEnum } from '@/constants/db'; const DBStatusTag = ({ conditions = [], status, - showBorder = false + showBorder = false, + alertReason, + alertDetails }: { conditions: DBConditionItemType[]; status: DBStatusMapType; showBorder?: boolean; + alertReason?: string; + alertDetails?: string; }) => { const { t } = useTranslation(); const { isOpen, onOpen, onClose } = useDisclosure(); + const { + isOpen: isQuestionOpen, + onOpen: onQuestionOpen, + onClose: onQuestionClose + } = useDisclosure(); const label = t(status.label as I18nCommonKey); + // Check if status is not Running or Stopped + const shouldShowQuestionIcon = + status.value !== DBStatusEnum.Running && status.value !== DBStatusEnum.Stopped && alertReason; + return ( <> - - - - {label} - - - - - - - - {label} - - - - {conditions.map((item, i) => ( - - - - {item.reason}, Last Occur:{' '} - {formatPodTime(item.lastTransitionTime as unknown as Date)} - - - {item.message} + + + + + {label} + + + + {shouldShowQuestionIcon && ( + + } + onClick={onQuestionOpen} + color="#F04438" + backgroundColor="#FEF3F2" + w="14px" + h="14px" + minW="14px" + minH="14px" + /> + + )} + + {/* Status details modal */} + + + + + {label} + + + + {conditions.map((item, i) => ( + + + + {item.reason}, Last Occur:{' '} + {formatPodTime(item.lastTransitionTime as unknown as Date)} + + + {item.message} + + ))} + + + + + {/* Question mark modal */} + + + + + {alertReason || 'Status Details'} + + + + + {alertDetails || ''} - ))} - - - + + + + ); }; diff --git a/frontend/providers/dbprovider/src/pages/api/proxy/get_db_alerts.ts b/frontend/providers/dbprovider/src/pages/api/proxy/get_db_alerts.ts new file mode 100644 index 000000000..09e9f053d --- /dev/null +++ b/frontend/providers/dbprovider/src/pages/api/proxy/get_db_alerts.ts @@ -0,0 +1,65 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@/services/backend/response'; +import { authSession } from '@/services/backend/auth'; +import { ResponseCode, ResponseMessages } from '@/types/response'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'GET') { + res.status(405).end(); + return; + } + + try { + const kubeconfig = await authSession(req); + if (!kubeconfig) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + } catch (error) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + + const { namespace } = req.query; + const apiUrl = `${ + process.env.DATABASE_ALERT_URL || 'http://database-alert.sealos.svc:8000' + }/v1/databases?namespace=${namespace}`; + + try { + const response = await fetch(apiUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + ...(req.headers.authorization ? { Authorization: req.headers.authorization } : {}), + ...(req.headers['auth_user'] + ? { + auth_user: Array.isArray(req.headers['auth_user']) + ? req.headers['auth_user'][0] + : req.headers['auth_user'] + } + : {}), + 'Time-Zone': Array.isArray(req.headers['time-zone']) + ? req.headers['time-zone'][0] + : req.headers['time-zone'] || 'Asia/Shanghai' + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + jsonRes(res, data); + } catch (error) { + console.error('Database alerts proxy error:', error); + jsonRes(res, { + code: 500, + error: 'Failed to fetch database alerts', + message: error instanceof Error ? error.message : 'Unknown error' + }); + } +} diff --git a/frontend/providers/dbprovider/src/pages/db/detail/components/Header.tsx b/frontend/providers/dbprovider/src/pages/db/detail/components/Header.tsx index 20e2bdd6e..33eb3ca26 100644 --- a/frontend/providers/dbprovider/src/pages/db/detail/components/Header.tsx +++ b/frontend/providers/dbprovider/src/pages/db/detail/components/Header.tsx @@ -1,4 +1,10 @@ -import { delDBServiceByName, pauseDBByName, restartDB, startDBByName } from '@/api/db'; +import { + delDBServiceByName, + pauseDBByName, + restartDB, + startDBByName, + type DatabaseAlertItem +} from '@/api/db'; import DBStatusTag from '@/components/DBStatusTag'; import MyIcon from '@/components/Icon'; import { defaultDBDetail } from '@/constants/db'; @@ -35,12 +41,14 @@ const Header = ({ db = defaultDBDetail, conn, isLargeScreen = true, - setShowSlider + setShowSlider, + alerts = {} }: { db: DBDetailType; conn: ConnectionInfo | null; isLargeScreen: boolean; setShowSlider: Dispatch; + alerts: Record; }) => { const { t } = useTranslation(); const router = useRouter(); @@ -285,7 +293,12 @@ const Header = ({ {router.query.name || db.dbName} - + {/* {!isLargeScreen && (