| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import React from 'react'; |
| import { Progress, Divider, Empty } from '@douyinfe/semi-ui'; |
| import { |
| IllustrationConstruction, |
| IllustrationConstructionDark, |
| } from '@douyinfe/semi-illustrations'; |
| import { |
| timestamp2string, |
| timestamp2string1, |
| copy, |
| showSuccess, |
| } from './utils'; |
| import { |
| STORAGE_KEYS, |
| DEFAULT_TIME_INTERVALS, |
| DEFAULTS, |
| ILLUSTRATION_SIZE, |
| } from '../constants/dashboard.constants'; |
|
|
| |
| export const getDefaultTime = () => { |
| return localStorage.getItem(STORAGE_KEYS.DATA_EXPORT_DEFAULT_TIME) || 'hour'; |
| }; |
|
|
| export const getTimeInterval = (timeType, isSeconds = false) => { |
| const intervals = |
| DEFAULT_TIME_INTERVALS[timeType] || DEFAULT_TIME_INTERVALS.hour; |
| return isSeconds ? intervals.seconds : intervals.minutes; |
| }; |
|
|
| export const getInitialTimestamp = () => { |
| const defaultTime = getDefaultTime(); |
| const now = new Date().getTime() / 1000; |
|
|
| switch (defaultTime) { |
| case 'hour': |
| return timestamp2string(now - 86400); |
| case 'week': |
| return timestamp2string(now - 86400 * 30); |
| default: |
| return timestamp2string(now - 86400 * 7); |
| } |
| }; |
|
|
| |
| export const updateMapValue = (map, key, value) => { |
| if (!map.has(key)) { |
| map.set(key, 0); |
| } |
| map.set(key, map.get(key) + value); |
| }; |
|
|
| export const initializeMaps = (key, ...maps) => { |
| maps.forEach((map) => { |
| if (!map.has(key)) { |
| map.set(key, 0); |
| } |
| }); |
| }; |
|
|
| |
| export const updateChartSpec = ( |
| setterFunc, |
| newData, |
| subtitle, |
| newColors, |
| dataId, |
| ) => { |
| setterFunc((prev) => ({ |
| ...prev, |
| data: [{ id: dataId, values: newData }], |
| title: { |
| ...prev.title, |
| subtext: subtitle, |
| }, |
| color: { |
| specified: newColors, |
| }, |
| })); |
| }; |
|
|
| export const getTrendSpec = (data, color) => ({ |
| type: 'line', |
| data: [{ id: 'trend', values: data.map((val, idx) => ({ x: idx, y: val })) }], |
| xField: 'x', |
| yField: 'y', |
| height: 40, |
| width: 100, |
| axes: [ |
| { |
| orient: 'bottom', |
| visible: false, |
| }, |
| { |
| orient: 'left', |
| visible: false, |
| }, |
| ], |
| padding: 0, |
| autoFit: false, |
| legends: { visible: false }, |
| tooltip: { visible: false }, |
| crosshair: { visible: false }, |
| line: { |
| style: { |
| stroke: color, |
| lineWidth: 2, |
| }, |
| }, |
| point: { |
| visible: false, |
| }, |
| background: { |
| fill: 'transparent', |
| }, |
| }); |
|
|
| |
| export const createSectionTitle = (Icon, text) => ( |
| <div className='flex items-center gap-2'> |
| <Icon size={16} /> |
| {text} |
| </div> |
| ); |
|
|
| export const createFormField = (Component, props, FORM_FIELD_PROPS) => ( |
| <Component {...FORM_FIELD_PROPS} {...props} /> |
| ); |
|
|
| |
| export const handleCopyUrl = async (url, t) => { |
| if (await copy(url)) { |
| showSuccess(t('复制成功')); |
| } |
| }; |
|
|
| export const handleSpeedTest = (apiUrl) => { |
| const encodedUrl = encodeURIComponent(apiUrl); |
| const speedTestUrl = `https://www.tcptest.cn/http/${encodedUrl}`; |
| window.open(speedTestUrl, '_blank', 'noopener,noreferrer'); |
| }; |
|
|
| |
| export const getUptimeStatusColor = (status, uptimeStatusMap) => |
| uptimeStatusMap[status]?.color || '#8b9aa7'; |
|
|
| export const getUptimeStatusText = (status, uptimeStatusMap, t) => |
| uptimeStatusMap[status]?.text || t('未知'); |
|
|
| |
| export const renderMonitorList = ( |
| monitors, |
| getUptimeStatusColor, |
| getUptimeStatusText, |
| t, |
| ) => { |
| if (!monitors || monitors.length === 0) { |
| return ( |
| <div className='flex justify-center items-center py-4'> |
| <Empty |
| image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />} |
| darkModeImage={ |
| <IllustrationConstructionDark style={ILLUSTRATION_SIZE} /> |
| } |
| title={t('暂无监控数据')} |
| /> |
| </div> |
| ); |
| } |
|
|
| const grouped = {}; |
| monitors.forEach((m) => { |
| const g = m.group || ''; |
| if (!grouped[g]) grouped[g] = []; |
| grouped[g].push(m); |
| }); |
|
|
| const renderItem = (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> |
| ); |
|
|
| return Object.entries(grouped).map(([gname, list]) => ( |
| <div key={gname || 'default'} className='mb-2'> |
| {gname && ( |
| <> |
| <div className='text-md font-semibold text-gray-500 px-2 py-1'> |
| {gname} |
| </div> |
| <Divider /> |
| </> |
| )} |
| {list.map(renderItem)} |
| </div> |
| )); |
| }; |
|
|
| |
| export const processRawData = ( |
| data, |
| dataExportDefaultTime, |
| initializeMaps, |
| updateMapValue, |
| ) => { |
| const result = { |
| totalQuota: 0, |
| totalTimes: 0, |
| totalTokens: 0, |
| uniqueModels: new Set(), |
| timePoints: [], |
| timeQuotaMap: new Map(), |
| timeTokensMap: new Map(), |
| timeCountMap: new Map(), |
| }; |
|
|
| data.forEach((item) => { |
| result.uniqueModels.add(item.model_name); |
| result.totalTokens += item.token_used; |
| result.totalQuota += item.quota; |
| result.totalTimes += item.count; |
|
|
| const timeKey = timestamp2string1(item.created_at, dataExportDefaultTime); |
| if (!result.timePoints.includes(timeKey)) { |
| result.timePoints.push(timeKey); |
| } |
|
|
| initializeMaps( |
| timeKey, |
| result.timeQuotaMap, |
| result.timeTokensMap, |
| result.timeCountMap, |
| ); |
| updateMapValue(result.timeQuotaMap, timeKey, item.quota); |
| updateMapValue(result.timeTokensMap, timeKey, item.token_used); |
| updateMapValue(result.timeCountMap, timeKey, item.count); |
| }); |
|
|
| result.timePoints.sort(); |
| return result; |
| }; |
|
|
| export const calculateTrendData = ( |
| timePoints, |
| timeQuotaMap, |
| timeTokensMap, |
| timeCountMap, |
| dataExportDefaultTime, |
| ) => { |
| const quotaTrend = timePoints.map((time) => timeQuotaMap.get(time) || 0); |
| const tokensTrend = timePoints.map((time) => timeTokensMap.get(time) || 0); |
| const countTrend = timePoints.map((time) => timeCountMap.get(time) || 0); |
|
|
| const rpmTrend = []; |
| const tpmTrend = []; |
|
|
| if (timePoints.length >= 2) { |
| const interval = getTimeInterval(dataExportDefaultTime); |
|
|
| for (let i = 0; i < timePoints.length; i++) { |
| rpmTrend.push(timeCountMap.get(timePoints[i]) / interval); |
| tpmTrend.push(timeTokensMap.get(timePoints[i]) / interval); |
| } |
| } |
|
|
| return { |
| balance: [], |
| usedQuota: [], |
| requestCount: [], |
| times: countTrend, |
| consumeQuota: quotaTrend, |
| tokens: tokensTrend, |
| rpm: rpmTrend, |
| tpm: tpmTrend, |
| }; |
| }; |
|
|
| export const aggregateDataByTimeAndModel = (data, dataExportDefaultTime) => { |
| const aggregatedData = new Map(); |
|
|
| data.forEach((item) => { |
| const timeKey = timestamp2string1(item.created_at, dataExportDefaultTime); |
| const modelKey = item.model_name; |
| const key = `${timeKey}-${modelKey}`; |
|
|
| if (!aggregatedData.has(key)) { |
| aggregatedData.set(key, { |
| time: timeKey, |
| model: modelKey, |
| quota: 0, |
| count: 0, |
| }); |
| } |
|
|
| const existing = aggregatedData.get(key); |
| existing.quota += item.quota; |
| existing.count += item.count; |
| }); |
|
|
| return aggregatedData; |
| }; |
|
|
| export const generateChartTimePoints = ( |
| aggregatedData, |
| data, |
| dataExportDefaultTime, |
| ) => { |
| let chartTimePoints = Array.from( |
| new Set([...aggregatedData.values()].map((d) => d.time)), |
| ); |
|
|
| if (chartTimePoints.length < DEFAULTS.MAX_TREND_POINTS) { |
| const lastTime = Math.max(...data.map((item) => item.created_at)); |
| const interval = getTimeInterval(dataExportDefaultTime, true); |
|
|
| chartTimePoints = Array.from( |
| { length: DEFAULTS.MAX_TREND_POINTS }, |
| (_, i) => |
| timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime), |
| ); |
| } |
|
|
| return chartTimePoints; |
| }; |
|
|