index.jsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useContext, useEffect } from 'react';
  16. import { getRelativeTime } from '../../helpers';
  17. import { UserContext } from '../../context/User';
  18. import { StatusContext } from '../../context/Status';
  19. import DashboardHeader from './DashboardHeader';
  20. import StatsCards from './StatsCards';
  21. import ChartsPanel from './ChartsPanel';
  22. import ApiInfoPanel from './ApiInfoPanel';
  23. import AnnouncementsPanel from './AnnouncementsPanel';
  24. import FaqPanel from './FaqPanel';
  25. import UptimePanel from './UptimePanel';
  26. import SearchModal from './modals/SearchModal';
  27. import { useDashboardData } from '../../hooks/dashboard/useDashboardData';
  28. import { useDashboardStats } from '../../hooks/dashboard/useDashboardStats';
  29. import { useDashboardCharts } from '../../hooks/dashboard/useDashboardCharts';
  30. import {
  31. CHART_CONFIG,
  32. CARD_PROPS,
  33. FLEX_CENTER_GAP2,
  34. ILLUSTRATION_SIZE,
  35. ANNOUNCEMENT_LEGEND_DATA,
  36. UPTIME_STATUS_MAP,
  37. } from '../../constants/dashboard.constants';
  38. import {
  39. getTrendSpec,
  40. handleCopyUrl,
  41. handleSpeedTest,
  42. getUptimeStatusColor,
  43. getUptimeStatusText,
  44. renderMonitorList,
  45. } from '../../helpers/dashboard';
  46. const Dashboard = () => {
  47. // ========== Context ==========
  48. const [userState, userDispatch] = useContext(UserContext);
  49. const [statusState, statusDispatch] = useContext(StatusContext);
  50. // ========== 主要数据管理 ==========
  51. const dashboardData = useDashboardData(userState, userDispatch, statusState);
  52. // ========== 图表管理 ==========
  53. const dashboardCharts = useDashboardCharts(
  54. dashboardData.dataExportDefaultTime,
  55. dashboardData.setTrendData,
  56. dashboardData.setConsumeQuota,
  57. dashboardData.setTimes,
  58. dashboardData.setConsumeTokens,
  59. dashboardData.setPieData,
  60. dashboardData.setLineData,
  61. dashboardData.setModelColors,
  62. dashboardData.t,
  63. );
  64. // ========== 统计数据 ==========
  65. const { groupedStatsData } = useDashboardStats(
  66. userState,
  67. dashboardData.consumeQuota,
  68. dashboardData.consumeTokens,
  69. dashboardData.times,
  70. dashboardData.trendData,
  71. dashboardData.performanceMetrics,
  72. dashboardData.navigate,
  73. dashboardData.t,
  74. );
  75. // ========== 数据处理 ==========
  76. const loadUserData = async () => {
  77. if (dashboardData.isAdminUser) {
  78. const userData = await dashboardData.loadUserQuotaData();
  79. if (userData && userData.length > 0) {
  80. dashboardCharts.updateUserChartData(userData);
  81. }
  82. }
  83. };
  84. const initChart = async () => {
  85. await dashboardData.loadQuotaData().then((data) => {
  86. if (data && data.length > 0) {
  87. dashboardCharts.updateChartData(data);
  88. }
  89. });
  90. await loadUserData();
  91. await dashboardData.loadUptimeData();
  92. };
  93. const handleRefresh = async () => {
  94. const data = await dashboardData.refresh();
  95. if (data && data.length > 0) {
  96. dashboardCharts.updateChartData(data);
  97. }
  98. await loadUserData();
  99. };
  100. const handleSearchConfirm = async () => {
  101. await dashboardData.handleSearchConfirm(dashboardCharts.updateChartData);
  102. await loadUserData();
  103. };
  104. // ========== 数据准备 ==========
  105. const apiInfoData = statusState?.status?.api_info || [];
  106. const announcementData = (statusState?.status?.announcements || []).map(
  107. (item) => {
  108. const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
  109. const absoluteTime =
  110. pubDate && !isNaN(pubDate.getTime())
  111. ? `${pubDate.getFullYear()}-${String(pubDate.getMonth() + 1).padStart(2, '0')}-${String(pubDate.getDate()).padStart(2, '0')} ${String(pubDate.getHours()).padStart(2, '0')}:${String(pubDate.getMinutes()).padStart(2, '0')}`
  112. : item?.publishDate || '';
  113. const relativeTime = getRelativeTime(item.publishDate);
  114. return {
  115. ...item,
  116. time: absoluteTime,
  117. relative: relativeTime,
  118. };
  119. },
  120. );
  121. const faqData = statusState?.status?.faq || [];
  122. const uptimeLegendData = Object.entries(UPTIME_STATUS_MAP).map(
  123. ([status, info]) => ({
  124. status: Number(status),
  125. color: info.color,
  126. label: dashboardData.t(info.label),
  127. }),
  128. );
  129. // ========== Effects ==========
  130. useEffect(() => {
  131. initChart();
  132. }, []);
  133. return (
  134. <div className='h-full'>
  135. <DashboardHeader
  136. getGreeting={dashboardData.getGreeting}
  137. greetingVisible={dashboardData.greetingVisible}
  138. showSearchModal={dashboardData.showSearchModal}
  139. refresh={handleRefresh}
  140. loading={dashboardData.loading}
  141. t={dashboardData.t}
  142. />
  143. <SearchModal
  144. searchModalVisible={dashboardData.searchModalVisible}
  145. handleSearchConfirm={handleSearchConfirm}
  146. handleCloseModal={dashboardData.handleCloseModal}
  147. isMobile={dashboardData.isMobile}
  148. isAdminUser={dashboardData.isAdminUser}
  149. inputs={dashboardData.inputs}
  150. dataExportDefaultTime={dashboardData.dataExportDefaultTime}
  151. timeOptions={dashboardData.timeOptions}
  152. handleInputChange={dashboardData.handleInputChange}
  153. t={dashboardData.t}
  154. />
  155. <StatsCards
  156. groupedStatsData={groupedStatsData}
  157. loading={dashboardData.loading}
  158. getTrendSpec={getTrendSpec}
  159. CARD_PROPS={CARD_PROPS}
  160. CHART_CONFIG={CHART_CONFIG}
  161. />
  162. {/* API信息和图表面板 */}
  163. <div className='mb-4'>
  164. <div
  165. className={`grid grid-cols-1 gap-4 ${dashboardData.hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}
  166. >
  167. <ChartsPanel
  168. activeChartTab={dashboardData.activeChartTab}
  169. setActiveChartTab={dashboardData.setActiveChartTab}
  170. spec_line={dashboardCharts.spec_line}
  171. spec_model_line={dashboardCharts.spec_model_line}
  172. spec_pie={dashboardCharts.spec_pie}
  173. spec_rank_bar={dashboardCharts.spec_rank_bar}
  174. spec_user_rank={dashboardCharts.spec_user_rank}
  175. spec_user_trend={dashboardCharts.spec_user_trend}
  176. isAdminUser={dashboardData.isAdminUser}
  177. CARD_PROPS={CARD_PROPS}
  178. CHART_CONFIG={CHART_CONFIG}
  179. FLEX_CENTER_GAP2={FLEX_CENTER_GAP2}
  180. hasApiInfoPanel={dashboardData.hasApiInfoPanel}
  181. t={dashboardData.t}
  182. />
  183. {dashboardData.hasApiInfoPanel && (
  184. <ApiInfoPanel
  185. apiInfoData={apiInfoData}
  186. handleCopyUrl={(url) => handleCopyUrl(url, dashboardData.t)}
  187. handleSpeedTest={handleSpeedTest}
  188. CARD_PROPS={CARD_PROPS}
  189. FLEX_CENTER_GAP2={FLEX_CENTER_GAP2}
  190. ILLUSTRATION_SIZE={ILLUSTRATION_SIZE}
  191. t={dashboardData.t}
  192. />
  193. )}
  194. </div>
  195. </div>
  196. {/* 系统公告和常见问答卡片 */}
  197. {dashboardData.hasInfoPanels && (
  198. <div className='mb-4'>
  199. <div className='grid grid-cols-1 lg:grid-cols-4 gap-4'>
  200. {/* 公告卡片 */}
  201. {dashboardData.announcementsEnabled && (
  202. <AnnouncementsPanel
  203. announcementData={announcementData}
  204. announcementLegendData={ANNOUNCEMENT_LEGEND_DATA.map(
  205. (item) => ({
  206. ...item,
  207. label: dashboardData.t(item.label),
  208. }),
  209. )}
  210. CARD_PROPS={CARD_PROPS}
  211. ILLUSTRATION_SIZE={ILLUSTRATION_SIZE}
  212. t={dashboardData.t}
  213. />
  214. )}
  215. {/* 常见问答卡片 */}
  216. {dashboardData.faqEnabled && (
  217. <FaqPanel
  218. faqData={faqData}
  219. CARD_PROPS={CARD_PROPS}
  220. FLEX_CENTER_GAP2={FLEX_CENTER_GAP2}
  221. ILLUSTRATION_SIZE={ILLUSTRATION_SIZE}
  222. t={dashboardData.t}
  223. />
  224. )}
  225. {/* 服务可用性卡片 */}
  226. {dashboardData.uptimeEnabled && (
  227. <UptimePanel
  228. uptimeData={dashboardData.uptimeData}
  229. uptimeLoading={dashboardData.uptimeLoading}
  230. activeUptimeTab={dashboardData.activeUptimeTab}
  231. setActiveUptimeTab={dashboardData.setActiveUptimeTab}
  232. loadUptimeData={dashboardData.loadUptimeData}
  233. uptimeLegendData={uptimeLegendData}
  234. renderMonitorList={(monitors) =>
  235. renderMonitorList(
  236. monitors,
  237. (status) => getUptimeStatusColor(status, UPTIME_STATUS_MAP),
  238. (status) =>
  239. getUptimeStatusText(
  240. status,
  241. UPTIME_STATUS_MAP,
  242. dashboardData.t,
  243. ),
  244. dashboardData.t,
  245. )
  246. }
  247. CARD_PROPS={CARD_PROPS}
  248. ILLUSTRATION_SIZE={ILLUSTRATION_SIZE}
  249. t={dashboardData.t}
  250. />
  251. )}
  252. </div>
  253. </div>
  254. )}
  255. </div>
  256. );
  257. };
  258. export default Dashboard;