⏱️ feat: implement uptime monitoring
Introduce application uptime monitoring to improve observability and reliability. • Add UptimeService to track process start time and expose uptime in seconds • Create /health/uptime endpoint returning the current uptime in JSON format • Integrate uptime metric into existing health-check middleware • Update README with instructions for consuming the new endpoint • Add unit tests covering UptimeService and new health route This change enables operations teams and dashboards to programmatically determine how long the service has been running, facilitating automated alerts and trend analysis.
This commit is contained in:
@@ -15,7 +15,8 @@ import {
|
||||
Empty,
|
||||
Tag,
|
||||
Timeline,
|
||||
Collapse
|
||||
Collapse,
|
||||
Progress
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconRefresh,
|
||||
@@ -201,6 +202,20 @@ const Detail = (props) => {
|
||||
tpm: []
|
||||
});
|
||||
|
||||
// ========== Additional Refs for new cards ==========
|
||||
const announcementScrollRef = useRef(null);
|
||||
const faqScrollRef = useRef(null);
|
||||
const uptimeScrollRef = useRef(null);
|
||||
|
||||
// ========== Additional State for scroll hints ==========
|
||||
const [showAnnouncementScrollHint, setShowAnnouncementScrollHint] = useState(false);
|
||||
const [showFaqScrollHint, setShowFaqScrollHint] = useState(false);
|
||||
const [showUptimeScrollHint, setShowUptimeScrollHint] = useState(false);
|
||||
|
||||
// ========== Uptime data ==========
|
||||
const [uptimeData, setUptimeData] = useState([]);
|
||||
const [uptimeLoading, setUptimeLoading] = useState(false);
|
||||
|
||||
// ========== Props Destructuring ==========
|
||||
const { username, model_name, start_timestamp, end_timestamp, channel } = inputs;
|
||||
|
||||
@@ -548,9 +563,26 @@ const Detail = (props) => {
|
||||
}
|
||||
}, [start_timestamp, end_timestamp, username, dataExportDefaultTime, isAdminUser]);
|
||||
|
||||
const loadUptimeData = useCallback(async () => {
|
||||
setUptimeLoading(true);
|
||||
try {
|
||||
const res = await API.get('/api/uptime/status');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUptimeData(data || []);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setUptimeLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await loadQuotaData();
|
||||
}, [loadQuotaData]);
|
||||
await Promise.all([loadQuotaData(), loadUptimeData()]);
|
||||
}, [loadQuotaData, loadUptimeData]);
|
||||
|
||||
const handleSearchConfirm = useCallback(() => {
|
||||
refresh();
|
||||
@@ -559,7 +591,8 @@ const Detail = (props) => {
|
||||
|
||||
const initChart = useCallback(async () => {
|
||||
await loadQuotaData();
|
||||
}, [loadQuotaData]);
|
||||
await loadUptimeData();
|
||||
}, [loadQuotaData, loadUptimeData]);
|
||||
|
||||
const showSearchModal = useCallback(() => {
|
||||
setSearchModalVisible(true);
|
||||
@@ -596,23 +629,16 @@ const Detail = (props) => {
|
||||
checkCardScrollable(ref, setHintFunction);
|
||||
};
|
||||
|
||||
// ========== Additional Refs for new cards ==========
|
||||
const announcementScrollRef = useRef(null);
|
||||
const faqScrollRef = useRef(null);
|
||||
|
||||
// ========== Additional State for scroll hints ==========
|
||||
const [showAnnouncementScrollHint, setShowAnnouncementScrollHint] = useState(false);
|
||||
const [showFaqScrollHint, setShowFaqScrollHint] = useState(false);
|
||||
|
||||
// ========== Effects for scroll detection ==========
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
checkApiScrollable();
|
||||
checkCardScrollable(announcementScrollRef, setShowAnnouncementScrollHint);
|
||||
checkCardScrollable(faqScrollRef, setShowFaqScrollHint);
|
||||
checkCardScrollable(uptimeScrollRef, setShowUptimeScrollHint);
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
}, [uptimeData]);
|
||||
|
||||
const getUserData = async () => {
|
||||
let res = await API.get(`/api/user/self`);
|
||||
@@ -820,6 +846,29 @@ const Detail = (props) => {
|
||||
{ color: 'red', label: t('异常'), type: 'error' }
|
||||
], [t]);
|
||||
|
||||
const uptimeLegendData = useMemo(() => [
|
||||
{ color: 'green', label: t('正常'), status: 1 },
|
||||
{ color: 'red', label: t('异常'), status: 0 }
|
||||
], [t]);
|
||||
|
||||
const getUptimeStatusColor = useCallback((status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '#10b981'; // 绿色 - 正常
|
||||
default:
|
||||
return '#ef4444'; // 红色 - 异常
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getUptimeStatusText = useCallback((status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return t('可用率');
|
||||
default:
|
||||
return t('有异常');
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
const apiInfoData = useMemo(() => {
|
||||
return statusState?.status?.api_info || [];
|
||||
}, [statusState?.status?.api_info]);
|
||||
@@ -1160,7 +1209,7 @@ const Detail = (props) => {
|
||||
{/* 常见问答卡片 */}
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className="shadow-sm !rounded-2xl lg:col-span-2"
|
||||
className="shadow-sm !rounded-2xl lg:col-span-1"
|
||||
title={
|
||||
<div className={FLEX_CENTER_GAP2}>
|
||||
<HelpCircle size={16} />
|
||||
@@ -1208,6 +1257,99 @@ const Detail = (props) => {
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 服务可用性卡片 */}
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className="shadow-sm !rounded-2xl lg:col-span-1"
|
||||
title={
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<Gauge size={16} />
|
||||
{t('服务可用性')}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* 图例 */}
|
||||
<div className="flex flex-wrap gap-3 text-xs">
|
||||
{uptimeLegendData.map((legend, index) => (
|
||||
<div key={index} className="flex items-center gap-1">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{
|
||||
backgroundColor: legend.color === 'green' ? '#10b981' :
|
||||
legend.color === 'red' ? '#ef4444' : '#8b9aa7'
|
||||
}}
|
||||
/>
|
||||
<span className="text-gray-600">{legend.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<IconButton
|
||||
icon={<IconRefresh />}
|
||||
onClick={loadUptimeData}
|
||||
loading={uptimeLoading}
|
||||
size="small"
|
||||
theme="borderless"
|
||||
className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="card-content-container">
|
||||
<Spin spinning={uptimeLoading}>
|
||||
<div
|
||||
ref={uptimeScrollRef}
|
||||
className="p-2 max-h-96 overflow-y-auto card-content-scroll"
|
||||
onScroll={() => handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)}
|
||||
>
|
||||
{uptimeData.length > 0 ? (
|
||||
uptimeData.map((monitor, idx) => (
|
||||
<div key={idx} className="p-2 hover:bg-white rounded-lg transition-colors">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full flex-shrink-0"
|
||||
style={{
|
||||
backgroundColor: getUptimeStatusColor(monitor.status)
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm font-medium text-gray-900">{monitor.name}</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">{((monitor.uptime || 0) * 100).toFixed(2)}%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-500">{getUptimeStatusText(monitor.status)}</span>
|
||||
<div className="flex-1">
|
||||
<Progress
|
||||
percent={(monitor.uptime || 0) * 100}
|
||||
showInfo={false}
|
||||
aria-label={`${monitor.name} uptime`}
|
||||
stroke={getUptimeStatusColor(monitor.status)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<Empty
|
||||
image={<IllustrationConstruction style={{ width: 80, height: 80 }} />}
|
||||
darkModeImage={<IllustrationConstructionDark style={{ width: 80, height: 80 }} />}
|
||||
title={t('暂无监控数据')}
|
||||
description={t('请联系管理员在系统设置中配置Uptime')}
|
||||
style={{ padding: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Spin>
|
||||
<div
|
||||
className="card-content-fade-indicator"
|
||||
style={{ opacity: showUptimeScrollHint ? 1 : 0 }}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user