瀏覽代碼

perf: 数据看板支持选择时间粒度

CaIon 2 年之前
父節點
當前提交
00306aa142
共有 7 個文件被更改,包括 132 次插入65 次删除
  1. 2 1
      common/constants.go
  2. 21 20
      controller/misc.go
  3. 3 0
      model/option.go
  4. 1 0
      web/src/App.js
  5. 41 21
      web/src/components/OperationSetting.js
  6. 17 10
      web/src/helpers/utils.js
  7. 47 13
      web/src/pages/Detail/index.js

+ 2 - 1
common/constants.go

@@ -26,7 +26,8 @@ var DisplayInCurrencyEnabled = true
 var DisplayTokenStatEnabled = true
 var DisplayTokenStatEnabled = true
 var DrawingEnabled = true
 var DrawingEnabled = true
 var DataExportEnabled = true
 var DataExportEnabled = true
-var DataExportInterval = 5 // unit: minute
+var DataExportInterval = 5         // unit: minute
+var DataExportDefaultTime = "hour" // unit: minute
 
 
 // Any options with "Secret", "Token" in its key won't be return by GetOptions
 // Any options with "Secret", "Token" in its key won't be return by GetOptions
 
 

+ 21 - 20
controller/misc.go

@@ -16,26 +16,27 @@ func GetStatus(c *gin.Context) {
 		"success": true,
 		"success": true,
 		"message": "",
 		"message": "",
 		"data": gin.H{
 		"data": gin.H{
-			"start_time":          common.StartTime,
-			"email_verification":  common.EmailVerificationEnabled,
-			"github_oauth":        common.GitHubOAuthEnabled,
-			"github_client_id":    common.GitHubClientId,
-			"system_name":         common.SystemName,
-			"logo":                common.Logo,
-			"footer_html":         common.Footer,
-			"wechat_qrcode":       common.WeChatAccountQRCodeImageURL,
-			"wechat_login":        common.WeChatAuthEnabled,
-			"server_address":      common.ServerAddress,
-			"price":               common.Price,
-			"turnstile_check":     common.TurnstileCheckEnabled,
-			"turnstile_site_key":  common.TurnstileSiteKey,
-			"top_up_link":         common.TopUpLink,
-			"chat_link":           common.ChatLink,
-			"quota_per_unit":      common.QuotaPerUnit,
-			"display_in_currency": common.DisplayInCurrencyEnabled,
-			"enable_batch_update": common.BatchUpdateEnabled,
-			"enable_drawing":      common.DrawingEnabled,
-			"enable_data_export":  common.DataExportEnabled,
+			"start_time":               common.StartTime,
+			"email_verification":       common.EmailVerificationEnabled,
+			"github_oauth":             common.GitHubOAuthEnabled,
+			"github_client_id":         common.GitHubClientId,
+			"system_name":              common.SystemName,
+			"logo":                     common.Logo,
+			"footer_html":              common.Footer,
+			"wechat_qrcode":            common.WeChatAccountQRCodeImageURL,
+			"wechat_login":             common.WeChatAuthEnabled,
+			"server_address":           common.ServerAddress,
+			"price":                    common.Price,
+			"turnstile_check":          common.TurnstileCheckEnabled,
+			"turnstile_site_key":       common.TurnstileSiteKey,
+			"top_up_link":              common.TopUpLink,
+			"chat_link":                common.ChatLink,
+			"quota_per_unit":           common.QuotaPerUnit,
+			"display_in_currency":      common.DisplayInCurrencyEnabled,
+			"enable_batch_update":      common.BatchUpdateEnabled,
+			"enable_drawing":           common.DrawingEnabled,
+			"enable_data_export":       common.DataExportEnabled,
+			"data_export_default_time": common.DataExportDefaultTime,
 		},
 		},
 	})
 	})
 	return
 	return

+ 3 - 0
model/option.go

@@ -79,6 +79,7 @@ func InitOptionMap() {
 	common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
 	common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
 	common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
 	common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
 	common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval)
 	common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval)
+	common.OptionMap["DataExportDefaultTime"] = common.DataExportDefaultTime
 
 
 	common.OptionMapRWMutex.Unlock()
 	common.OptionMapRWMutex.Unlock()
 	loadOptionsFromDatabase()
 	loadOptionsFromDatabase()
@@ -228,6 +229,8 @@ func updateOptionMap(key string, value string) (err error) {
 		common.RetryTimes, _ = strconv.Atoi(value)
 		common.RetryTimes, _ = strconv.Atoi(value)
 	case "DataExportInterval":
 	case "DataExportInterval":
 		common.DataExportInterval, _ = strconv.Atoi(value)
 		common.DataExportInterval, _ = strconv.Atoi(value)
+	case "DataExportDefaultTime":
+		common.DataExportDefaultTime = value
 	case "ModelRatio":
 	case "ModelRatio":
 		err = common.UpdateModelRatioByJSONString(value)
 		err = common.UpdateModelRatioByJSONString(value)
 	case "GroupRatio":
 	case "GroupRatio":

+ 1 - 0
web/src/App.js

@@ -51,6 +51,7 @@ function App() {
       localStorage.setItem('display_in_currency', data.display_in_currency);
       localStorage.setItem('display_in_currency', data.display_in_currency);
       localStorage.setItem('enable_drawing', data.enable_drawing);
       localStorage.setItem('enable_drawing', data.enable_drawing);
       localStorage.setItem('enable_data_export', data.enable_data_export);
       localStorage.setItem('enable_data_export', data.enable_data_export);
+      localStorage.setItem('data_export_default_time', data.data_export_default_time);
       if (data.chat_link) {
       if (data.chat_link) {
         localStorage.setItem('chat_link', data.chat_link);
         localStorage.setItem('chat_link', data.chat_link);
       } else {
       } else {

+ 41 - 21
web/src/components/OperationSetting.js

@@ -23,13 +23,19 @@ const OperationSetting = () => {
         DisplayTokenStatEnabled: '',
         DisplayTokenStatEnabled: '',
         DrawingEnabled: '',
         DrawingEnabled: '',
         DataExportEnabled: '',
         DataExportEnabled: '',
+        DataExportDefaultTime: 'hour',
         DataExportInterval: 5,
         DataExportInterval: 5,
         RetryTimes: 0
         RetryTimes: 0
     });
     });
     const [originInputs, setOriginInputs] = useState({});
     const [originInputs, setOriginInputs] = useState({});
     let [loading, setLoading] = useState(false);
     let [loading, setLoading] = useState(false);
     let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago
     let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago
-
+    // 精确时间选项(小时,天,周)
+    const timeOptions = [
+        {key: 'hour', text: '小时', value: 'hour'},
+        {key: 'day', text: '天', value: 'day'},
+        {key: 'week', text: '周', value: 'week'}
+    ];
     const getOptions = async () => {
     const getOptions = async () => {
         const res = await API.get('/api/option/');
         const res = await API.get('/api/option/');
         const {success, message, data} = res.data;
         const {success, message, data} = res.data;
@@ -71,7 +77,10 @@ const OperationSetting = () => {
     };
     };
 
 
     const handleInputChange = async (e, {name, value}) => {
     const handleInputChange = async (e, {name, value}) => {
-        if (name.endsWith('Enabled') || name === 'DataExportInterval') {
+        if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime') {
+            if (name === 'DataExportDefaultTime') {
+                localStorage.setItem('data_export_default_time', value);
+            }
             await updateOption(name, value);
             await updateOption(name, value);
         } else {
         } else {
             setInputs((inputs) => ({...inputs, [name]: value}));
             setInputs((inputs) => ({...inputs, [name]: value}));
@@ -234,15 +243,28 @@ const OperationSetting = () => {
                             name='LogConsumeEnabled'
                             name='LogConsumeEnabled'
                             onChange={handleInputChange}
                             onChange={handleInputChange}
                         />
                         />
-
                     </Form.Group>
                     </Form.Group>
-                    <Form.Group inline>
-                        <Form.Checkbox
-                            checked={inputs.DataExportEnabled === 'true'}
-                            label='启用数据看板(实验性)'
-                            name='DataExportEnabled'
-                            onChange={handleInputChange}
-                        />
+                    <Form.Group widths={4}>
+                        <Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
+                                    name='history_timestamp'
+                                    onChange={(e, {name, value}) => {
+                                        setHistoryTimestamp(value);
+                                    }}/>
+                    </Form.Group>
+                    <Form.Button onClick={() => {
+                        deleteHistoryLogs().then();
+                    }}>清理历史日志</Form.Button>
+                    <Divider/>
+                    <Header as='h3'>
+                        数据看板
+                    </Header>
+                    <Form.Checkbox
+                        checked={inputs.DataExportEnabled === 'true'}
+                        label='启用数据看板(实验性)'
+                        name='DataExportEnabled'
+                        onChange={handleInputChange}
+                    />
+                    <Form.Group>
                         <Form.Input
                         <Form.Input
                             label='数据看板更新间隔(分钟,设置过短会影响数据库性能)'
                             label='数据看板更新间隔(分钟,设置过短会影响数据库性能)'
                             name='DataExportInterval'
                             name='DataExportInterval'
@@ -254,19 +276,17 @@ const OperationSetting = () => {
                             value={inputs.DataExportInterval}
                             value={inputs.DataExportInterval}
                             placeholder='数据看板更新间隔(分钟,设置过短会影响数据库性能)'
                             placeholder='数据看板更新间隔(分钟,设置过短会影响数据库性能)'
                         />
                         />
+                        <Form.Select
+                            label='数据看板默认时间粒度(仅修改展示粒度,统计精确到小时)'
+                            options={timeOptions}
+                            name='DataExportDefaultTime'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.DataExportDefaultTime}
+                            placeholder='数据看板默认时间粒度'
+                        />
                     </Form.Group>
                     </Form.Group>
                     <Divider/>
                     <Divider/>
-                    <Form.Group widths={4}>
-                        <Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
-                                    name='history_timestamp'
-                                    onChange={(e, {name, value}) => {
-                                        setHistoryTimestamp(value);
-                                    }}/>
-                    </Form.Group>
-                    <Form.Button onClick={() => {
-                        deleteHistoryLogs().then();
-                    }}>清理历史日志</Form.Button>
-                    <Divider/>
                     <Header as='h3'>
                     <Header as='h3'>
                         监控设置
                         监控设置
                     </Header>
                     </Header>

+ 17 - 10
web/src/helpers/utils.js

@@ -171,7 +171,7 @@ export function timestamp2string(timestamp) {
   );
   );
 }
 }
 
 
-export function timestamp2string1(timestamp) {
+export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
   let date = new Date(timestamp * 1000);
   let date = new Date(timestamp * 1000);
   // let year = date.getFullYear().toString();
   // let year = date.getFullYear().toString();
   let month = (date.getMonth() + 1).toString();
   let month = (date.getMonth() + 1).toString();
@@ -186,15 +186,22 @@ export function timestamp2string1(timestamp) {
   if (hour.length === 1) {
   if (hour.length === 1) {
     hour = '0' + hour;
     hour = '0' + hour;
   }
   }
-  return (
-      // year +
-      // '-' +
-      month +
-      '-' +
-      day +
-      ' ' +
-      hour + ":00"
-  );
+  let str = month + '-' + day
+  if (dataExportDefaultTime === 'hour') {
+    str += ' ' + hour + ":00"
+  } else if (dataExportDefaultTime === 'week') {
+    let nextWeek = new Date(timestamp * 1000 + 6 * 24 * 60 * 60 * 1000);
+    let nextMonth = (nextWeek.getMonth() + 1).toString();
+    let nextDay = nextWeek.getDate().toString();
+    if (nextMonth.length === 1) {
+        nextMonth = '0' + nextMonth;
+    }
+    if (nextDay.length === 1) {
+        nextDay = '0' + nextDay;
+    }
+    str += ' - ' + nextMonth + '-' + nextDay
+  }
+  return str;
 }
 }
 
 
 export function downloadTextAsFile(text, filename) {
 export function downloadTextAsFile(text, filename) {

+ 47 - 13
web/src/pages/Detail/index.js

@@ -12,17 +12,18 @@ import {
 } from "../../helpers/render";
 } from "../../helpers/render";
 
 
 const Detail = (props) => {
 const Detail = (props) => {
-
+    const formRef = useRef();
     let now = new Date();
     let now = new Date();
     const [inputs, setInputs] = useState({
     const [inputs, setInputs] = useState({
         username: '',
         username: '',
         token_name: '',
         token_name: '',
         model_name: '',
         model_name: '',
-        start_timestamp: timestamp2string(now.getTime() / 1000 - 86400),
+        start_timestamp: localStorage.getItem('data_export_default_time') === 'hour' ? timestamp2string(now.getTime() / 1000 - 86400) : (localStorage.getItem('data_export_default_time') === 'week' ? timestamp2string(now.getTime() / 1000 - 86400 * 30) : timestamp2string(now.getTime() / 1000 - 86400 * 7)),
         end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
         end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
-        channel: ''
+        channel: '',
+        data_export_default_time: ''
     });
     });
-    const {username, token_name, model_name, start_timestamp, end_timestamp, channel} = inputs;
+    const {username, model_name, start_timestamp, end_timestamp, channel} = inputs;
     const isAdminUser = isAdmin();
     const isAdminUser = isAdmin();
     const initialized = useRef(false)
     const initialized = useRef(false)
     const [modelDataChart, setModelDataChart] = useState(null);
     const [modelDataChart, setModelDataChart] = useState(null);
@@ -31,8 +32,13 @@ const Detail = (props) => {
     const [quotaData, setQuotaData] = useState([]);
     const [quotaData, setQuotaData] = useState([]);
     const [consumeQuota, setConsumeQuota] = useState(0);
     const [consumeQuota, setConsumeQuota] = useState(0);
     const [times, setTimes] = useState(0);
     const [times, setTimes] = useState(0);
+    const [dataExportDefaultTime, setDataExportDefaultTime] = useState(localStorage.getItem('data_export_default_time') || 'hour');
 
 
     const handleInputChange = (value, name) => {
     const handleInputChange = (value, name) => {
+        if (name === 'data_export_default_time') {
+            setDataExportDefaultTime(value);
+            return
+        }
         setInputs((inputs) => ({...inputs, [name]: value}));
         setInputs((inputs) => ({...inputs, [name]: value}));
     };
     };
 
 
@@ -41,8 +47,7 @@ const Detail = (props) => {
         data: [
         data: [
             {
             {
                 id: 'barData',
                 id: 'barData',
-                values: [
-                ]
+                values: []
             }
             }
         ],
         ],
         xField: 'Time',
         xField: 'Time',
@@ -54,7 +59,7 @@ const Detail = (props) => {
         },
         },
         title: {
         title: {
             visible: true,
             visible: true,
-            text: '模型消耗分布(小时)',
+            text: '模型消耗分布',
             subtext: '0'
             subtext: '0'
         },
         },
         bar: {
         bar: {
@@ -104,7 +109,7 @@ const Detail = (props) => {
             {
             {
                 id: 'id0',
                 id: 'id0',
                 values: [
                 values: [
-                    { type: 'null', value: '0' },
+                    {type: 'null', value: '0'},
                 ]
                 ]
             }
             }
         ],
         ],
@@ -163,9 +168,9 @@ const Detail = (props) => {
         let localStartTimestamp = Date.parse(start_timestamp) / 1000;
         let localStartTimestamp = Date.parse(start_timestamp) / 1000;
         let localEndTimestamp = Date.parse(end_timestamp) / 1000;
         let localEndTimestamp = Date.parse(end_timestamp) / 1000;
         if (isAdminUser) {
         if (isAdminUser) {
-            url = `/api/data/?username=${username}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
+            url = `/api/data/?username=${username}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&default_time=${dataExportDefaultTime}`;
         } else {
         } else {
-            url = `/api/data/self/?start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
+            url = `/api/data/self/?start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&default_time=${dataExportDefaultTime}`;
         }
         }
         const res = await API.get(url);
         const res = await API.get(url);
         const {success, message, data} = res.data;
         const {success, message, data} = res.data;
@@ -179,6 +184,16 @@ const Detail = (props) => {
                     'created_at': now.getTime() / 1000
                     'created_at': now.getTime() / 1000
                 })
                 })
             }
             }
+            // 根据dataExportDefaultTime重制时间粒度
+            let timeGranularity = 3600;
+            if (dataExportDefaultTime === 'day') {
+                timeGranularity = 86400;
+            } else if (dataExportDefaultTime === 'week') {
+                timeGranularity = 604800;
+            }
+            data.forEach(item => {
+                item['created_at'] = Math.floor(item['created_at'] / timeGranularity) * timeGranularity;
+            });
             updateChart(lineChart, pieChart, data);
             updateChart(lineChart, pieChart, data);
         } else {
         } else {
             showError(message);
             showError(message);
@@ -190,7 +205,7 @@ const Detail = (props) => {
         await loadQuotaData(modelDataChart, modelDataPieChart);
         await loadQuotaData(modelDataChart, modelDataPieChart);
     };
     };
 
 
-    const initChart  = async () => {
+    const initChart = async () => {
         let lineChart = modelDataChart
         let lineChart = modelDataChart
         if (!modelDataChart) {
         if (!modelDataChart) {
             lineChart = new VChart(spec_line, {dom: 'model_data'});
             lineChart = new VChart(spec_line, {dom: 'model_data'});
@@ -231,7 +246,7 @@ const Detail = (props) => {
             }
             }
             // 合并created_at和model_name 为 lineData, created_at 数据类型是小时的时间戳
             // 合并created_at和model_name 为 lineData, created_at 数据类型是小时的时间戳
             // 转换日期格式
             // 转换日期格式
-            let createTime = timestamp2string1(item.created_at);
+            let createTime = timestamp2string1(item.created_at, dataExportDefaultTime);
             let lineItem = lineData.find(it => it.Time === createTime && it.Model === item.model_name);
             let lineItem = lineData.find(it => it.Time === createTime && it.Model === item.model_name);
             if (lineItem) {
             if (lineItem) {
                 lineItem.Usage += parseFloat(getQuotaWithUnit(item.quota));
                 lineItem.Usage += parseFloat(getQuotaWithUnit(item.quota));
@@ -263,6 +278,13 @@ const Detail = (props) => {
     }
     }
 
 
     useEffect(() => {
     useEffect(() => {
+        // setDataExportDefaultTime(localStorage.getItem('data_export_default_time'));
+        // if (dataExportDefaultTime === 'day') {
+        //     // 设置开始时间为7天前
+        //     let st = timestamp2string(now.getTime() / 1000 - 86400 * 7)
+        //     inputs.start_timestamp = st;
+        //     formRef.current.formApi.setValue('start_timestamp', st);
+        // }
         if (!initialized.current) {
         if (!initialized.current) {
             initialized.current = true;
             initialized.current = true;
             initChart();
             initChart();
@@ -276,7 +298,7 @@ const Detail = (props) => {
                     <h3>数据看板</h3>
                     <h3>数据看板</h3>
                 </Layout.Header>
                 </Layout.Header>
                 <Layout.Content>
                 <Layout.Content>
-                    <Form layout='horizontal' style={{marginTop: 10}}>
+                    <Form ref={formRef} layout='horizontal' style={{marginTop: 10}}>
                         <>
                         <>
                             <Form.DatePicker field="start_timestamp" label='起始时间' style={{width: 272}}
                             <Form.DatePicker field="start_timestamp" label='起始时间' style={{width: 272}}
                                              initValue={start_timestamp}
                                              initValue={start_timestamp}
@@ -288,6 +310,18 @@ const Detail = (props) => {
                                              value={end_timestamp} type='dateTime'
                                              value={end_timestamp} type='dateTime'
                                              name='end_timestamp'
                                              name='end_timestamp'
                                              onChange={value => handleInputChange(value, 'end_timestamp')}/>
                                              onChange={value => handleInputChange(value, 'end_timestamp')}/>
+                            <Form.Select field="data_export_default_time" label='时间粒度' style={{width: 176}}
+                                         initValue={dataExportDefaultTime}
+                                         placeholder={'时间粒度'} name='data_export_default_time'
+                                         optionList={
+                                             [
+                                                 {label: '小时', value: 'hour'},
+                                                 {label: '天', value: 'day'},
+                                                 {label: '周', value: 'week'}
+                                             ]
+                                         }
+                                         onChange={value => handleInputChange(value, 'data_export_default_time')}>
+                            </Form.Select>
                             {
                             {
                                 isAdminUser && <>
                                 isAdminUser && <>
                                     <Form.Input field="username" label='用户名称' style={{width: 176}} value={username}
                                     <Form.Input field="username" label='用户名称' style={{width: 176}} value={username}