Commit 4c673d26 by Apple\Apple

feat: major refactor and enhancement of Detail dashboard component & add api url display

- **Code Organization & Architecture:**
  - Restructured component with clear sections (Hooks, Constants, Helper Functions, etc.)
  - Added comprehensive code organization comments for better maintainability
  - Extracted reusable helper functions and constants for better separation of concerns

- **Performance Optimizations:**
  - Implemented extensive use of useCallback and useMemo hooks for expensive operations
  - Optimized data processing pipeline with dedicated processing functions
  - Memoized chart configurations, performance metrics, and grouped stats data
  - Cached helper functions like getTrendSpec, handleCopyUrl, and modal handlers

- **UI/UX Enhancements:**
  - Added Empty state component with construction illustrations for better UX
  - Implemented responsive grid layout with conditional API info section visibility
  - Enhanced button styling with consistent rounded design and hover effects
  - Added mini trend charts to statistics cards for visual data representation
  - Improved form field consistency with reusable createFormField helper

- **Feature Improvements:**
  - Added self-use mode detection to conditionally hide/show API information section
  - Enhanced chart configurations with centralized CHART_CONFIG constant
  - Improved time handling with dedicated helper functions (getTimeInterval, getInitialTimestamp)
  - Added comprehensive performance metrics calculation (RPM/TPM trends)
  - Implemented advanced data aggregation and processing workflows

- **Code Quality & Maintainability:**
  - Extracted complex data processing logic into dedicated functions
  - Added proper prop destructuring and state organization
  - Implemented consistent naming conventions and helper utilities
  - Enhanced error handling and loading states management
  - Added comprehensive JSDoc-style comments for better code documentation

- **Technical Debt Reduction:**
  - Replaced repetitive form field definitions with reusable components
  - Consolidated chart update logic into centralized updateChartSpec function
  - Improved data flow with better state management patterns
  - Reduced code duplication through strategic use of helper functions

This refactor significantly improves component performance, maintainability, and user experience while maintaining backward compatibility and existing functionality.
parent 03645264
......@@ -74,11 +74,33 @@ func GetStatus(c *gin.Context) {
"oidc_client_id": system_setting.GetOIDCSettings().ClientId,
"oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint,
"setup": constant.Setup,
"api_info": getApiInfo(),
},
})
return
}
func getApiInfo() []map[string]interface{} {
// 从OptionMap中获取API信息,如果不存在则返回空数组
common.OptionMapRWMutex.RLock()
apiInfoStr, exists := common.OptionMap["ApiInfo"]
common.OptionMapRWMutex.RUnlock()
if !exists || apiInfoStr == "" {
// 如果没有配置,返回空数组
return []map[string]interface{}{}
}
// 解析存储的API信息
var apiInfo []map[string]interface{}
if err := json.Unmarshal([]byte(apiInfoStr), &apiInfo); err != nil {
// 如果解析失败,返回空数组
return []map[string]interface{}{}
}
return apiInfo
}
func GetNotice(c *gin.Context) {
common.OptionMapRWMutex.RLock()
defer common.OptionMapRWMutex.RUnlock()
......
......@@ -2,16 +2,110 @@ package controller
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"one-api/common"
"one-api/model"
"one-api/setting"
"one-api/setting/system_setting"
"regexp"
"strings"
"github.com/gin-gonic/gin"
)
func validateApiInfo(apiInfoStr string) error {
if apiInfoStr == "" {
return nil // 空字符串是合法的
}
var apiInfoList []map[string]interface{}
if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil {
return fmt.Errorf("API信息格式错误:%s", err.Error())
}
// 验证数组长度
if len(apiInfoList) > 50 {
return fmt.Errorf("API信息数量不能超过50个")
}
// 允许的颜色值
validColors := map[string]bool{
"blue": true, "green": true, "cyan": true, "purple": true, "pink": true,
"red": true, "orange": true, "amber": true, "yellow": true, "lime": true,
"light-green": true, "teal": true, "light-blue": true, "indigo": true,
"violet": true, "grey": true,
}
// URL正则表达式
urlRegex := regexp.MustCompile(`^https?://[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(/.*)?$`)
for i, apiInfo := range apiInfoList {
// 检查必填字段
urlStr, ok := apiInfo["url"].(string)
if !ok || urlStr == "" {
return fmt.Errorf("第%d个API信息缺少URL字段", i+1)
}
route, ok := apiInfo["route"].(string)
if !ok || route == "" {
return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1)
}
description, ok := apiInfo["description"].(string)
if !ok || description == "" {
return fmt.Errorf("第%d个API信息缺少说明字段", i+1)
}
color, ok := apiInfo["color"].(string)
if !ok || color == "" {
return fmt.Errorf("第%d个API信息缺少颜色字段", i+1)
}
// 验证URL格式
if !urlRegex.MatchString(urlStr) {
return fmt.Errorf("第%d个API信息的URL格式不正确", i+1)
}
// 验证URL可解析性
if _, err := url.Parse(urlStr); err != nil {
return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error())
}
// 验证字段长度
if len(urlStr) > 500 {
return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1)
}
if len(route) > 100 {
return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1)
}
if len(description) > 200 {
return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1)
}
// 验证颜色值
if !validColors[color] {
return fmt.Errorf("第%d个API信息的颜色值不合法", i+1)
}
// 检查并过滤危险字符(防止XSS)
dangerousChars := []string{"<script", "<iframe", "javascript:", "onload=", "onerror=", "onclick="}
for _, dangerous := range dangerousChars {
if strings.Contains(strings.ToLower(description), dangerous) {
return fmt.Errorf("第%d个API信息的说明包含不允许的内容", i+1)
}
if strings.Contains(strings.ToLower(route), dangerous) {
return fmt.Errorf("第%d个API信息的线路描述包含不允许的内容", i+1)
}
}
}
return nil
}
func GetOptions(c *gin.Context) {
var options []*model.Option
common.OptionMapRWMutex.Lock()
......@@ -119,7 +213,15 @@ func UpdateOption(c *gin.Context) {
})
return
}
case "ApiInfo":
err = validateApiInfo(option.Value)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
}
err = model.UpdateOption(option.Key, option.Value)
if err != nil {
......
......@@ -122,6 +122,7 @@ func InitOptionMap() {
common.OptionMap["SensitiveWords"] = setting.SensitiveWordsToString()
common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(setting.StreamCacheQueueLength)
common.OptionMap["AutomaticDisableKeywords"] = operation_setting.AutomaticDisableKeywordsToString()
common.OptionMap["ApiInfo"] = ""
// 自动添加所有注册的模型配置
modelConfigs := config.GlobalConfig.ExportAllConfigs()
......
import React, { useEffect, useState } from 'react';
import { Card, Spin } from '@douyinfe/semi-ui';
import { API, showError } from '../../helpers';
import SettingsAPIInfo from '../../pages/Setting/Dashboard/SettingsAPIInfo.js';
const DashboardSetting = () => {
let [inputs, setInputs] = useState({
ApiInfo: '',
});
let [loading, setLoading] = useState(false);
const getOptions = async () => {
const res = await API.get('/api/option/');
const { success, message, data } = res.data;
if (success) {
let newInputs = {};
data.forEach((item) => {
if (item.key in inputs) {
newInputs[item.key] = item.value;
}
});
setInputs(newInputs);
} else {
showError(message);
}
};
async function onRefresh() {
try {
setLoading(true);
await getOptions();
} catch (error) {
showError('刷新失败');
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
onRefresh();
}, []);
return (
<>
<Spin spinning={loading} size='large'>
{/* API信息管理 */}
<Card style={{ marginTop: '10px' }}>
<SettingsAPIInfo options={inputs} refresh={onRefresh} />
</Card>
</Spin>
</>
);
};
export default DashboardSetting;
\ No newline at end of file
......@@ -1568,5 +1568,22 @@
"资源消耗": "Resource Consumption",
"性能指标": "Performance Indicators",
"模型数据分析": "Model Data Analysis",
"搜索无结果": "No results found"
"搜索无结果": "No results found",
"仪表盘配置": "Dashboard Configuration",
"API信息管理,可以配置多个API地址用于状态展示和负载均衡": "API information management, you can configure multiple API addresses for status display and load balancing",
"线路描述": "Route description",
"颜色": "Color",
"标识颜色": "Identifier color",
"添加API": "Add API",
"保存配置": "Save Configuration",
"API信息": "API Information",
"暂无API信息配置": "No API information configured",
"暂无API信息": "No API information",
"请输入API地址": "Please enter the API address",
"请输入线路描述": "Please enter the route description",
"如:大带宽批量分析图片推荐": "e.g. Large bandwidth batch analysis of image recommendations",
"请输入说明": "Please enter the description",
"如:香港线路": "e.g. Hong Kong line",
"请联系管理员在系统设置中配置API信息": "Please contact the administrator to configure API information in the system settings.",
"确定要删除此API信息吗?": "Are you sure you want to delete this API information?"
}
\ No newline at end of file
......@@ -74,6 +74,9 @@ code {
.semi-navigation-item,
.semi-tag-closable,
.semi-input-wrapper,
.semi-tabs-tab-button,
.semi-select,
.semi-button,
.semi-datepicker-range-input {
border-radius: 9999px !important;
}
......@@ -323,6 +326,24 @@ code {
font-size: 1.1em;
}
/* API信息卡片样式 */
.api-info-container {
position: relative;
}
.api-info-fade-indicator {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30px;
background: linear-gradient(transparent, var(--semi-color-bg-1));
pointer-events: none;
z-index: 1;
opacity: 0;
transition: opacity 0.3s ease;
}
/* ==================== 调试面板特定样式 ==================== */
.debug-panel .semi-tabs {
height: 100% !important;
......@@ -379,6 +400,7 @@ code {
}
/* 隐藏模型设置区域的滚动条 */
.api-info-scroll::-webkit-scrollbar,
.model-settings-scroll::-webkit-scrollbar,
.thinking-content-scroll::-webkit-scrollbar,
.custom-request-textarea .semi-input::-webkit-scrollbar,
......@@ -386,6 +408,7 @@ code {
display: none;
}
.api-info-scroll,
.model-settings-scroll,
.thinking-content-scroll,
.custom-request-textarea .semi-input,
......
......@@ -6,10 +6,10 @@ import { useTranslation } from 'react-i18next';
import SystemSetting from '../../components/settings/SystemSetting.js';
import { isRoot } from '../../helpers';
import OtherSetting from '../../components/settings/OtherSetting';
import PersonalSetting from '../../components/settings/PersonalSetting.js';
import OperationSetting from '../../components/settings/OperationSetting.js';
import RateLimitSetting from '../../components/settings/RateLimitSetting.js';
import ModelSetting from '../../components/settings/ModelSetting.js';
import DashboardSetting from '../../components/settings/DashboardSetting.js';
const Setting = () => {
const { t } = useTranslation();
......@@ -44,6 +44,11 @@ const Setting = () => {
content: <OtherSetting />,
itemKey: 'other',
});
panes.push({
tab: t('仪表盘配置'),
content: <DashboardSetting />,
itemKey: 'dashboard',
});
}
const onChangeTab = (key) => {
setTabActiveKey(key);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment