✨ feat: support paging / sorting (#64)
* ✨ feat: support paging / sorting * 🔥 del: delete unused files
This commit is contained in:
parent
3789c30154
commit
57cd68e7e3
@ -67,3 +67,10 @@ func AbortWithMessage(c *gin.Context, statusCode int, message string) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
LogError(c.Request.Context(), message)
|
LogError(c.Request.Context(), message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func APIRespondWithError(c *gin.Context, status int, err error) {
|
||||||
|
c.JSON(status, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -104,7 +104,7 @@ func UpdateChannelBalance(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateAllChannelsBalance() error {
|
func updateAllChannelsBalance() error {
|
||||||
channels, err := model.GetAllChannels(0, 0, true)
|
channels, err := model.GetAllChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,7 @@ func testAllChannels(notify bool) error {
|
|||||||
}
|
}
|
||||||
testAllChannelsRunning = true
|
testAllChannelsRunning = true
|
||||||
testAllChannelsLock.Unlock()
|
testAllChannelsLock.Unlock()
|
||||||
channels, err := model.GetAllChannels(0, 0, true)
|
channels, err := model.GetAllChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -10,34 +10,16 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllChannels(c *gin.Context) {
|
func GetChannelsList(c *gin.Context) {
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
var params model.GenericParams
|
||||||
if p < 0 {
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
p = 0
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
}
|
|
||||||
channels, err := model.GetAllChannels(p*common.ItemsPerPage, common.ItemsPerPage, false)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
"message": "",
|
|
||||||
"data": channels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func SearchChannels(c *gin.Context) {
|
channels, err := model.GetChannelsList(¶ms)
|
||||||
keyword := c.Query("keyword")
|
|
||||||
channels, err := model.SearchChannels(keyword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
@ -9,24 +9,16 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllLogs(c *gin.Context) {
|
func GetLogsList(c *gin.Context) {
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
var params model.LogsListParams
|
||||||
if p < 0 {
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
p = 0
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
logType, _ := strconv.Atoi(c.Query("type"))
|
|
||||||
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
logs, err := model.GetLogsList(¶ms)
|
||||||
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
|
|
||||||
username := c.Query("username")
|
|
||||||
tokenName := c.Query("token_name")
|
|
||||||
modelName := c.Query("model_name")
|
|
||||||
channel, _ := strconv.Atoi(c.Query("channel"))
|
|
||||||
logs, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, p*common.ItemsPerPage, common.ItemsPerPage, channel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@ -36,23 +28,18 @@ func GetAllLogs(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserLogs(c *gin.Context) {
|
func GetUserLogsList(c *gin.Context) {
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
|
||||||
if p < 0 {
|
|
||||||
p = 0
|
|
||||||
}
|
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
logType, _ := strconv.Atoi(c.Query("type"))
|
|
||||||
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
var params model.LogsListParams
|
||||||
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
tokenName := c.Query("token_name")
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
modelName := c.Query("model_name")
|
return
|
||||||
logs, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, p*common.ItemsPerPage, common.ItemsPerPage)
|
}
|
||||||
|
|
||||||
|
logs, err := model.GetUserLogsList(userId, ¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
@ -1,42 +1,24 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
"one-api/model"
|
"one-api/model"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllRedemptions(c *gin.Context) {
|
func GetRedemptionsList(c *gin.Context) {
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
var params model.GenericParams
|
||||||
if p < 0 {
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
p = 0
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
}
|
|
||||||
redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
"message": "",
|
|
||||||
"data": redemptions,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchRedemptions(c *gin.Context) {
|
redemptions, err := model.GetRedemptionsList(¶ms)
|
||||||
keyword := c.Query("keyword")
|
|
||||||
redemptions, err := model.SearchRedemptions(keyword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@ -44,7 +26,6 @@ func SearchRedemptions(c *gin.Context) {
|
|||||||
"message": "",
|
"message": "",
|
||||||
"data": redemptions,
|
"data": redemptions,
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRedemption(c *gin.Context) {
|
func GetRedemption(c *gin.Context) {
|
||||||
@ -69,7 +50,6 @@ func GetRedemption(c *gin.Context) {
|
|||||||
"message": "",
|
"message": "",
|
||||||
"data": redemption,
|
"data": redemption,
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddRedemption(c *gin.Context) {
|
func AddRedemption(c *gin.Context) {
|
||||||
@ -129,7 +109,6 @@ func AddRedemption(c *gin.Context) {
|
|||||||
"message": "",
|
"message": "",
|
||||||
"data": keys,
|
"data": keys,
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteRedemption(c *gin.Context) {
|
func DeleteRedemption(c *gin.Context) {
|
||||||
@ -146,7 +125,6 @@ func DeleteRedemption(c *gin.Context) {
|
|||||||
"success": true,
|
"success": true,
|
||||||
"message": "",
|
"message": "",
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateRedemption(c *gin.Context) {
|
func UpdateRedemption(c *gin.Context) {
|
||||||
@ -188,5 +166,4 @@ func UpdateRedemption(c *gin.Context) {
|
|||||||
"message": "",
|
"message": "",
|
||||||
"data": cleanRedemption,
|
"data": cleanRedemption,
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -9,36 +9,17 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllTokens(c *gin.Context) {
|
func GetUserTokensList(c *gin.Context) {
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
var params model.GenericParams
|
||||||
if p < 0 {
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
p = 0
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
}
|
|
||||||
tokens, err := model.GetAllUserTokens(userId, p*common.ItemsPerPage, common.ItemsPerPage)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
"message": "",
|
|
||||||
"data": tokens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func SearchTokens(c *gin.Context) {
|
tokens, err := model.GetUserTokensList(userId, ¶ms)
|
||||||
userId := c.GetInt("id")
|
|
||||||
keyword := c.Query("keyword")
|
|
||||||
tokens, err := model.SearchUserTokens(userId, keyword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
@ -176,34 +176,16 @@ func Register(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUsers(c *gin.Context) {
|
func GetUsersList(c *gin.Context) {
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
var params model.GenericParams
|
||||||
if p < 0 {
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
p = 0
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
}
|
|
||||||
users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
"message": "",
|
|
||||||
"data": users,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func SearchUsers(c *gin.Context) {
|
users, err := model.GetUsersList(¶ms)
|
||||||
keyword := c.Query("keyword")
|
|
||||||
users, err := model.SearchUsers(keyword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.APIRespondWithError(c, http.StatusOK, err)
|
||||||
"success": false,
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
@ -29,23 +29,35 @@ type Channel struct {
|
|||||||
TestModel string `json:"test_model" gorm:"type:varchar(50);default:''"`
|
TestModel string `json:"test_model" gorm:"type:varchar(50);default:''"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
var allowedChannelOrderFields = map[string]bool{
|
||||||
var channels []*Channel
|
"id": true,
|
||||||
var err error
|
"name": true,
|
||||||
if selectAll {
|
"group": true,
|
||||||
err = DB.Order("id desc").Find(&channels).Error
|
"type": true,
|
||||||
} else {
|
"status": true,
|
||||||
err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error
|
"response_time": true,
|
||||||
}
|
"balance": true,
|
||||||
return channels, err
|
"priority": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchChannels(keyword string) (channels []*Channel, err error) {
|
func GetChannelsList(params *GenericParams) (*DataResult, error) {
|
||||||
|
var channels []*Channel
|
||||||
|
|
||||||
|
db := DB.Omit("key")
|
||||||
|
if params.Keyword != "" {
|
||||||
keyCol := "`key`"
|
keyCol := "`key`"
|
||||||
if common.UsingPostgreSQL {
|
if common.UsingPostgreSQL {
|
||||||
keyCol = `"key"`
|
keyCol = `"key"`
|
||||||
}
|
}
|
||||||
err = DB.Omit("key").Where("id = ? or name LIKE ? or "+keyCol+" = ?", common.String2Int(keyword), keyword+"%", keyword).Find(&channels).Error
|
db = db.Where("id = ? or name LIKE ? or "+keyCol+" = ?", common.String2Int(params.Keyword), params.Keyword+"%", params.Keyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginateAndOrder(db, ¶ms.PaginationParams, &channels, allowedChannelOrderFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllChannels() ([]*Channel, error) {
|
||||||
|
var channels []*Channel
|
||||||
|
err := DB.Order("id desc").Find(&channels).Error
|
||||||
return channels, err
|
return channels, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,91 @@ package model
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GenericParams struct {
|
||||||
|
PaginationParams
|
||||||
|
Keyword string `form:"keyword"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginationParams struct {
|
||||||
|
Page int `form:"page"`
|
||||||
|
Size int `form:"size"`
|
||||||
|
Order string `form:"order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataResult struct {
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PaginateAndOrder(db *gorm.DB, params *PaginationParams, result interface{}, allowedOrderFields map[string]bool) (*DataResult, error) {
|
||||||
|
// 获取总数
|
||||||
|
var totalCount int64
|
||||||
|
err := db.Model(result).Count(&totalCount).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("totalCount", totalCount)
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
if params.Page < 1 {
|
||||||
|
params.Page = 1
|
||||||
|
}
|
||||||
|
if params.Size < 1 {
|
||||||
|
params.Size = common.ItemsPerPage
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Size > common.MaxRecentItems {
|
||||||
|
return nil, fmt.Errorf("size 参数不能超过 %d", common.MaxRecentItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (params.Page - 1) * params.Size
|
||||||
|
db = db.Offset(offset).Limit(params.Size)
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
if params.Order != "" {
|
||||||
|
orderFields := strings.Split(params.Order, ",")
|
||||||
|
for _, field := range orderFields {
|
||||||
|
field = strings.TrimSpace(field)
|
||||||
|
desc := strings.HasPrefix(field, "-")
|
||||||
|
if desc {
|
||||||
|
field = field[1:]
|
||||||
|
}
|
||||||
|
if !allowedOrderFields[field] {
|
||||||
|
return nil, fmt.Errorf("不允许对字段 '%s' 进行排序", field)
|
||||||
|
}
|
||||||
|
if desc {
|
||||||
|
field = field + " DESC"
|
||||||
|
}
|
||||||
|
db = db.Order(field)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 默认排序
|
||||||
|
db = db.Order("id DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
err = db.Find(result).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
return &DataResult{
|
||||||
|
Data: result,
|
||||||
|
Page: params.Page,
|
||||||
|
Size: params.Size,
|
||||||
|
TotalCount: totalCount,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getDateFormat(groupType string) string {
|
func getDateFormat(groupType string) string {
|
||||||
var dateFormat string
|
var dateFormat string
|
||||||
if groupType == "day" {
|
if groupType == "day" {
|
||||||
|
105
model/log.go
105
model/log.go
@ -74,56 +74,79 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int) (logs []*Log, err error) {
|
type LogsListParams struct {
|
||||||
var tx *gorm.DB
|
PaginationParams
|
||||||
if logType == LogTypeUnknown {
|
LogType int `form:"log_type"`
|
||||||
tx = DB
|
StartTimestamp int64 `form:"start_timestamp"`
|
||||||
} else {
|
EndTimestamp int64 `form:"end_timestamp"`
|
||||||
tx = DB.Where("type = ?", logType)
|
ModelName string `form:"model_name"`
|
||||||
}
|
Username string `form:"username"`
|
||||||
if modelName != "" {
|
TokenName string `form:"token_name"`
|
||||||
tx = tx.Where("model_name = ?", modelName)
|
Channel int `form:"channel"`
|
||||||
}
|
|
||||||
if username != "" {
|
|
||||||
tx = tx.Where("username = ?", username)
|
|
||||||
}
|
|
||||||
if tokenName != "" {
|
|
||||||
tx = tx.Where("token_name = ?", tokenName)
|
|
||||||
}
|
|
||||||
if startTimestamp != 0 {
|
|
||||||
tx = tx.Where("created_at >= ?", startTimestamp)
|
|
||||||
}
|
|
||||||
if endTimestamp != 0 {
|
|
||||||
tx = tx.Where("created_at <= ?", endTimestamp)
|
|
||||||
}
|
|
||||||
if channel != 0 {
|
|
||||||
tx = tx.Where("channel_id = ?", channel)
|
|
||||||
}
|
|
||||||
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error
|
|
||||||
return logs, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int) (logs []*Log, err error) {
|
var allowedLogsOrderFields = map[string]bool{
|
||||||
|
"created_at": true,
|
||||||
|
"channel_id": true,
|
||||||
|
"user_id": true,
|
||||||
|
"token_name": true,
|
||||||
|
"model_name": true,
|
||||||
|
"type": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogsList(params *LogsListParams) (*DataResult, error) {
|
||||||
var tx *gorm.DB
|
var tx *gorm.DB
|
||||||
if logType == LogTypeUnknown {
|
var logs []*Log
|
||||||
tx = DB.Where("user_id = ?", userId)
|
|
||||||
|
if params.LogType == LogTypeUnknown {
|
||||||
|
tx = DB
|
||||||
} else {
|
} else {
|
||||||
tx = DB.Where("user_id = ? and type = ?", userId, logType)
|
tx = DB.Where("type = ?", params.LogType)
|
||||||
}
|
}
|
||||||
if modelName != "" {
|
if params.ModelName != "" {
|
||||||
tx = tx.Where("model_name = ?", modelName)
|
tx = tx.Where("model_name = ?", params.ModelName)
|
||||||
}
|
}
|
||||||
if tokenName != "" {
|
if params.Username != "" {
|
||||||
tx = tx.Where("token_name = ?", tokenName)
|
tx = tx.Where("username = ?", params.Username)
|
||||||
}
|
}
|
||||||
if startTimestamp != 0 {
|
if params.TokenName != "" {
|
||||||
tx = tx.Where("created_at >= ?", startTimestamp)
|
tx = tx.Where("token_name = ?", params.TokenName)
|
||||||
}
|
}
|
||||||
if endTimestamp != 0 {
|
if params.StartTimestamp != 0 {
|
||||||
tx = tx.Where("created_at <= ?", endTimestamp)
|
tx = tx.Where("created_at >= ?", params.StartTimestamp)
|
||||||
}
|
}
|
||||||
err = tx.Order("id desc").Limit(num).Offset(startIdx).Omit("id").Find(&logs).Error
|
if params.EndTimestamp != 0 {
|
||||||
return logs, err
|
tx = tx.Where("created_at <= ?", params.EndTimestamp)
|
||||||
|
}
|
||||||
|
if params.Channel != 0 {
|
||||||
|
tx = tx.Where("channel_id = ?", params.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginateAndOrder(tx, ¶ms.PaginationParams, &logs, allowedLogsOrderFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserLogsList(userId int, params *LogsListParams) (*DataResult, error) {
|
||||||
|
var logs []*Log
|
||||||
|
|
||||||
|
tx := DB.Where("user_id = ?", userId).Omit("id")
|
||||||
|
|
||||||
|
if params.LogType != LogTypeUnknown {
|
||||||
|
tx = DB.Where("type = ?", params.LogType)
|
||||||
|
}
|
||||||
|
if params.ModelName != "" {
|
||||||
|
tx = tx.Where("model_name = ?", params.ModelName)
|
||||||
|
}
|
||||||
|
if params.TokenName != "" {
|
||||||
|
tx = tx.Where("token_name = ?", params.TokenName)
|
||||||
|
}
|
||||||
|
if params.StartTimestamp != 0 {
|
||||||
|
tx = tx.Where("created_at >= ?", params.StartTimestamp)
|
||||||
|
}
|
||||||
|
if params.EndTimestamp != 0 {
|
||||||
|
tx = tx.Where("created_at <= ?", params.EndTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginateAndOrder(tx, ¶ms.PaginationParams, &logs, allowedLogsOrderFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchAllLogs(keyword string) (logs []*Log, err error) {
|
func SearchAllLogs(keyword string) (logs []*Log, err error) {
|
||||||
|
@ -20,16 +20,23 @@ type Redemption struct {
|
|||||||
Count int `json:"count" gorm:"-:all"` // only for api request
|
Count int `json:"count" gorm:"-:all"` // only for api request
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) {
|
var allowedRedemptionslOrderFields = map[string]bool{
|
||||||
var redemptions []*Redemption
|
"id": true,
|
||||||
var err error
|
"name": true,
|
||||||
err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
|
"status": true,
|
||||||
return redemptions, err
|
"quota": true,
|
||||||
|
"created_time": true,
|
||||||
|
"redeemed_time": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) {
|
func GetRedemptionsList(params *GenericParams) (*DataResult, error) {
|
||||||
err = DB.Where("id = ? or name LIKE ?", common.String2Int(keyword), keyword+"%").Find(&redemptions).Error
|
var redemptions []*Redemption
|
||||||
return redemptions, err
|
db := DB
|
||||||
|
if params.Keyword != "" {
|
||||||
|
db = db.Where("id = ? or name LIKE ?", common.String2Int(params.Keyword), params.Keyword+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginateAndOrder(db, ¶ms.PaginationParams, &redemptions, allowedRedemptionslOrderFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRedemptionById(id int) (*Redemption, error) {
|
func GetRedemptionById(id int) (*Redemption, error) {
|
||||||
|
@ -22,16 +22,25 @@ type Token struct {
|
|||||||
UsedQuota int `json:"used_quota" gorm:"default:0"` // used quota
|
UsedQuota int `json:"used_quota" gorm:"default:0"` // used quota
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
|
var allowedTokenOrderFields = map[string]bool{
|
||||||
var tokens []*Token
|
"id": true,
|
||||||
var err error
|
"name": true,
|
||||||
err = DB.Where("user_id = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Find(&tokens).Error
|
"status": true,
|
||||||
return tokens, err
|
"expired_time": true,
|
||||||
|
"created_time": true,
|
||||||
|
"remain_quota": true,
|
||||||
|
"used_quota": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) {
|
func GetUserTokensList(userId int, params *GenericParams) (*DataResult, error) {
|
||||||
err = DB.Where("user_id = ?", userId).Where("name LIKE ?", keyword+"%").Find(&tokens).Error
|
var tokens []*Token
|
||||||
return tokens, err
|
db := DB.Where("user_id = ?", userId)
|
||||||
|
|
||||||
|
if params.Keyword != "" {
|
||||||
|
db = db.Where("name LIKE ?", params.Keyword+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginateAndOrder(db, ¶ms.PaginationParams, &tokens, allowedTokenOrderFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateUserToken(key string) (token *Token, err error) {
|
func ValidateUserToken(key string) (token *Token, err error) {
|
||||||
|
@ -38,15 +38,22 @@ func GetMaxUserId() int {
|
|||||||
return user.Id
|
return user.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUsers(startIdx int, num int) (users []*User, err error) {
|
var allowedUserOrderFields = map[string]bool{
|
||||||
err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
|
"id": true,
|
||||||
return users, err
|
"username": true,
|
||||||
|
"role": true,
|
||||||
|
"status": true,
|
||||||
|
"created_time": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchUsers(keyword string) (users []*User, err error) {
|
func GetUsersList(params *GenericParams) (*DataResult, error) {
|
||||||
err = DB.Omit("password").Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", common.String2Int(keyword), keyword+"%", keyword+"%", keyword+"%").Find(&users).Error
|
var users []*User
|
||||||
|
db := DB.Omit("password")
|
||||||
|
if params.Keyword != "" {
|
||||||
|
db = db.Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", common.String2Int(params.Keyword), params.Keyword+"%", params.Keyword+"%", params.Keyword+"%")
|
||||||
|
}
|
||||||
|
|
||||||
return users, err
|
return PaginateAndOrder(db, ¶ms.PaginationParams, &users, allowedUserOrderFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserById(id int, selectAll bool) (*User, error) {
|
func GetUserById(id int, selectAll bool) (*User, error) {
|
||||||
|
@ -48,8 +48,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
adminRoute := userRoute.Group("/")
|
adminRoute := userRoute.Group("/")
|
||||||
adminRoute.Use(middleware.AdminAuth())
|
adminRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
adminRoute.GET("/", controller.GetAllUsers)
|
adminRoute.GET("/", controller.GetUsersList)
|
||||||
adminRoute.GET("/search", controller.SearchUsers)
|
|
||||||
adminRoute.GET("/:id", controller.GetUser)
|
adminRoute.GET("/:id", controller.GetUser)
|
||||||
adminRoute.POST("/", controller.CreateUser)
|
adminRoute.POST("/", controller.CreateUser)
|
||||||
adminRoute.POST("/manage", controller.ManageUser)
|
adminRoute.POST("/manage", controller.ManageUser)
|
||||||
@ -66,8 +65,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
channelRoute := apiRouter.Group("/channel")
|
channelRoute := apiRouter.Group("/channel")
|
||||||
channelRoute.Use(middleware.AdminAuth())
|
channelRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
channelRoute.GET("/", controller.GetAllChannels)
|
channelRoute.GET("/", controller.GetChannelsList)
|
||||||
channelRoute.GET("/search", controller.SearchChannels)
|
|
||||||
channelRoute.GET("/models", controller.ListModelsForAdmin)
|
channelRoute.GET("/models", controller.ListModelsForAdmin)
|
||||||
channelRoute.GET("/:id", controller.GetChannel)
|
channelRoute.GET("/:id", controller.GetChannel)
|
||||||
channelRoute.GET("/test", controller.TestAllChannels)
|
channelRoute.GET("/test", controller.TestAllChannels)
|
||||||
@ -82,8 +80,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
tokenRoute := apiRouter.Group("/token")
|
tokenRoute := apiRouter.Group("/token")
|
||||||
tokenRoute.Use(middleware.UserAuth())
|
tokenRoute.Use(middleware.UserAuth())
|
||||||
{
|
{
|
||||||
tokenRoute.GET("/", controller.GetAllTokens)
|
tokenRoute.GET("/", controller.GetUserTokensList)
|
||||||
tokenRoute.GET("/search", controller.SearchTokens)
|
|
||||||
tokenRoute.GET("/:id", controller.GetToken)
|
tokenRoute.GET("/:id", controller.GetToken)
|
||||||
tokenRoute.POST("/", controller.AddToken)
|
tokenRoute.POST("/", controller.AddToken)
|
||||||
tokenRoute.PUT("/", controller.UpdateToken)
|
tokenRoute.PUT("/", controller.UpdateToken)
|
||||||
@ -92,21 +89,20 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
redemptionRoute := apiRouter.Group("/redemption")
|
redemptionRoute := apiRouter.Group("/redemption")
|
||||||
redemptionRoute.Use(middleware.AdminAuth())
|
redemptionRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
redemptionRoute.GET("/", controller.GetAllRedemptions)
|
redemptionRoute.GET("/", controller.GetRedemptionsList)
|
||||||
redemptionRoute.GET("/search", controller.SearchRedemptions)
|
|
||||||
redemptionRoute.GET("/:id", controller.GetRedemption)
|
redemptionRoute.GET("/:id", controller.GetRedemption)
|
||||||
redemptionRoute.POST("/", controller.AddRedemption)
|
redemptionRoute.POST("/", controller.AddRedemption)
|
||||||
redemptionRoute.PUT("/", controller.UpdateRedemption)
|
redemptionRoute.PUT("/", controller.UpdateRedemption)
|
||||||
redemptionRoute.DELETE("/:id", controller.DeleteRedemption)
|
redemptionRoute.DELETE("/:id", controller.DeleteRedemption)
|
||||||
}
|
}
|
||||||
logRoute := apiRouter.Group("/log")
|
logRoute := apiRouter.Group("/log")
|
||||||
logRoute.GET("/", middleware.AdminAuth(), controller.GetAllLogs)
|
logRoute.GET("/", middleware.AdminAuth(), controller.GetLogsList)
|
||||||
logRoute.DELETE("/", middleware.AdminAuth(), controller.DeleteHistoryLogs)
|
logRoute.DELETE("/", middleware.AdminAuth(), controller.DeleteHistoryLogs)
|
||||||
logRoute.GET("/stat", middleware.AdminAuth(), controller.GetLogsStat)
|
logRoute.GET("/stat", middleware.AdminAuth(), controller.GetLogsStat)
|
||||||
logRoute.GET("/self/stat", middleware.UserAuth(), controller.GetLogsSelfStat)
|
logRoute.GET("/self/stat", middleware.UserAuth(), controller.GetLogsSelfStat)
|
||||||
logRoute.GET("/search", middleware.AdminAuth(), controller.SearchAllLogs)
|
// logRoute.GET("/search", middleware.AdminAuth(), controller.SearchAllLogs)
|
||||||
logRoute.GET("/self", middleware.UserAuth(), controller.GetUserLogs)
|
logRoute.GET("/self", middleware.UserAuth(), controller.GetUserLogsList)
|
||||||
logRoute.GET("/self/search", middleware.UserAuth(), controller.SearchUserLogs)
|
// logRoute.GET("/self/search", middleware.UserAuth(), controller.SearchUserLogs)
|
||||||
groupRoute := apiRouter.Group("/group")
|
groupRoute := apiRouter.Group("/group")
|
||||||
groupRoute.Use(middleware.AdminAuth())
|
groupRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
|
47
web/src/ui-component/TableHead.js
Normal file
47
web/src/ui-component/TableHead.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { TableCell, TableHead, TableRow, TableSortLabel } from '@mui/material';
|
||||||
|
|
||||||
|
const KeywordTableHead = ({ order, orderBy, headLabel, onRequestSort }) => {
|
||||||
|
const onSort = (property) => (event) => {
|
||||||
|
onRequestSort(event, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{headLabel.map((headCell) =>
|
||||||
|
headCell.hide && headCell.hide === true ? null : (
|
||||||
|
<TableCell
|
||||||
|
key={headCell.id}
|
||||||
|
align={headCell.align || 'left'}
|
||||||
|
// sortDirection={orderBy === headCell.id ? order : false}
|
||||||
|
sx={{ width: headCell.width, minWidth: headCell.minWidth }}
|
||||||
|
>
|
||||||
|
{headCell.disableSort ? (
|
||||||
|
headCell.label
|
||||||
|
) : (
|
||||||
|
<TableSortLabel
|
||||||
|
hideSortIcon
|
||||||
|
active={orderBy === headCell.id}
|
||||||
|
direction={orderBy === headCell.id ? order : 'asc'}
|
||||||
|
onClick={onSort(headCell.id)}
|
||||||
|
>
|
||||||
|
{headCell.label}
|
||||||
|
</TableSortLabel>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KeywordTableHead;
|
||||||
|
|
||||||
|
KeywordTableHead.propTypes = {
|
||||||
|
order: PropTypes.oneOf(['asc', 'desc']),
|
||||||
|
orderBy: PropTypes.string,
|
||||||
|
onRequestSort: PropTypes.func,
|
||||||
|
headLabel: PropTypes.array
|
||||||
|
};
|
@ -9,7 +9,7 @@ import { IconSearch } from '@tabler/icons-react';
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
export default function TableToolBar({ filterName, handleFilterName, placeholder }) {
|
export default function TableToolBar({ placeholder }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const grey500 = theme.palette.grey[500];
|
const grey500 = theme.palette.grey[500];
|
||||||
|
|
||||||
@ -24,11 +24,10 @@ export default function TableToolBar({ filterName, handleFilterName, placeholder
|
|||||||
>
|
>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
id="keyword"
|
id="keyword"
|
||||||
|
name="keyword"
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: '100%'
|
minWidth: '100%'
|
||||||
}}
|
}}
|
||||||
value={filterName}
|
|
||||||
onChange={handleFilterName}
|
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
startAdornment={
|
startAdornment={
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
|
||||||
|
|
||||||
const ChannelTableHead = () => {
|
|
||||||
return (
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>ID</TableCell>
|
|
||||||
<TableCell>名称</TableCell>
|
|
||||||
<TableCell>分组</TableCell>
|
|
||||||
<TableCell>类型</TableCell>
|
|
||||||
<TableCell>状态</TableCell>
|
|
||||||
<TableCell>响应时间</TableCell>
|
|
||||||
<TableCell>余额</TableCell>
|
|
||||||
<TableCell>优先级</TableCell>
|
|
||||||
<TableCell>操作</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChannelTableHead;
|
|
@ -15,83 +15,53 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
|||||||
|
|
||||||
import { Button, IconButton, Card, Box, Stack, Container, Typography, Divider } from '@mui/material';
|
import { Button, IconButton, Card, Box, Stack, Container, Typography, Divider } from '@mui/material';
|
||||||
import ChannelTableRow from './component/TableRow';
|
import ChannelTableRow from './component/TableRow';
|
||||||
import ChannelTableHead from './component/TableHead';
|
import KeywordTableHead from 'ui-component/TableHead';
|
||||||
import TableToolBar from 'ui-component/TableToolBar';
|
import TableToolBar from 'ui-component/TableToolBar';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { ITEMS_PER_PAGE } from 'constants';
|
|
||||||
import { IconRefresh, IconHttpDelete, IconPlus, IconBrandSpeedtest, IconCoinYuan } from '@tabler/icons-react';
|
import { IconRefresh, IconHttpDelete, IconPlus, IconBrandSpeedtest, IconCoinYuan } from '@tabler/icons-react';
|
||||||
import EditeModal from './component/EditModal';
|
import EditeModal from './component/EditModal';
|
||||||
|
import { ITEMS_PER_PAGE } from 'constants';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// CHANNEL_OPTIONS,
|
// CHANNEL_OPTIONS,
|
||||||
export default function ChannelPage() {
|
export default function ChannelPage() {
|
||||||
const [channels, setChannels] = useState([]);
|
const [page, setPage] = useState(0);
|
||||||
const [activePage, setActivePage] = useState(0);
|
const [order, setOrder] = useState('desc');
|
||||||
const [searching, setSearching] = useState(false);
|
const [orderBy, setOrderBy] = useState('id');
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||||
|
const [listCount, setListCount] = useState(0);
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
const [channels, setChannels] = useState([]);
|
||||||
|
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matchUpMd = useMediaQuery(theme.breakpoints.up('sm'));
|
const matchUpMd = useMediaQuery(theme.breakpoints.up('sm'));
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [editChannelId, setEditChannelId] = useState(0);
|
const [editChannelId, setEditChannelId] = useState(0);
|
||||||
|
|
||||||
const loadChannels = async (startIdx) => {
|
const handleSort = (event, id) => {
|
||||||
setSearching(true);
|
const isAsc = orderBy === id && order === 'asc';
|
||||||
try {
|
if (id !== '') {
|
||||||
const res = await API.get(`/api/channel/?p=${startIdx}`);
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
const { success, message, data } = res.data;
|
setOrderBy(id);
|
||||||
if (success) {
|
|
||||||
if (startIdx === 0) {
|
|
||||||
setChannels(data);
|
|
||||||
} else {
|
|
||||||
let newChannels = [...channels];
|
|
||||||
newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
|
||||||
setChannels(newChannels);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearching(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPaginationChange = (event, activePage) => {
|
const handleChangePage = (event, newPage) => {
|
||||||
(async () => {
|
setPage(newPage);
|
||||||
if (activePage === Math.ceil(channels.length / ITEMS_PER_PAGE)) {
|
};
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
await loadChannels(activePage);
|
const handleChangeRowsPerPage = (event) => {
|
||||||
}
|
setPage(0);
|
||||||
setActivePage(activePage);
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchChannels = async (event) => {
|
const searchChannels = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (searchKeyword === '') {
|
const formData = new FormData(event.target);
|
||||||
await loadChannels(0);
|
setPage(0);
|
||||||
setActivePage(0);
|
setSearchKeyword(formData.get('keyword'));
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSearching(true);
|
|
||||||
try {
|
|
||||||
const res = await API.get(`/api/channel/search?keyword=${searchKeyword}`);
|
|
||||||
const { success, message, data } = res.data;
|
|
||||||
if (success) {
|
|
||||||
setChannels(data);
|
|
||||||
setActivePage(0);
|
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
setSearching(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchKeyword = (event) => {
|
|
||||||
setSearchKeyword(event.target.value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const manageChannel = async (id, action, value) => {
|
const manageChannel = async (id, action, value) => {
|
||||||
@ -141,7 +111,9 @@ export default function ChannelPage() {
|
|||||||
|
|
||||||
// 处理刷新
|
// 处理刷新
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
await loadChannels(activePage);
|
setOrderBy('id');
|
||||||
|
setOrder('desc');
|
||||||
|
setRefreshFlag(!refreshFlag);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理测试所有启用渠道
|
// 处理测试所有启用渠道
|
||||||
@ -210,14 +182,36 @@ export default function ChannelPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||||
loadChannels(0)
|
setSearching(true);
|
||||||
.then()
|
try {
|
||||||
.catch((reason) => {
|
if (orderBy) {
|
||||||
showError(reason);
|
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||||
|
}
|
||||||
|
const res = await API.get(`/api/channel/`, {
|
||||||
|
params: {
|
||||||
|
page: page + 1,
|
||||||
|
size: rowsPerPage,
|
||||||
|
keyword: keyword,
|
||||||
|
order: orderBy
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const { success, message, data } = res.data;
|
||||||
}, []);
|
if (success) {
|
||||||
|
setListCount(data.total_count);
|
||||||
|
setChannels(data.data);
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
setSearching(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||||
|
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -237,7 +231,7 @@ export default function ChannelPage() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Card>
|
<Card>
|
||||||
<Box component="form" onSubmit={searchChannels} noValidate>
|
<Box component="form" onSubmit={searchChannels} noValidate>
|
||||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索渠道的 ID,名称和密钥 ...'} />
|
<TableToolBar placeholder={'搜索渠道的 ID,名称和密钥 ...'} />
|
||||||
</Box>
|
</Box>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
@ -292,9 +286,24 @@ export default function ChannelPage() {
|
|||||||
<PerfectScrollbar component="div">
|
<PerfectScrollbar component="div">
|
||||||
<TableContainer sx={{ overflow: 'unset' }}>
|
<TableContainer sx={{ overflow: 'unset' }}>
|
||||||
<Table sx={{ minWidth: 800 }}>
|
<Table sx={{ minWidth: 800 }}>
|
||||||
<ChannelTableHead />
|
<KeywordTableHead
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onRequestSort={handleSort}
|
||||||
|
headLabel={[
|
||||||
|
{ id: 'id', label: 'ID', disableSort: false },
|
||||||
|
{ id: 'name', label: '名称', disableSort: false },
|
||||||
|
{ id: 'group', label: '分组', disableSort: true },
|
||||||
|
{ id: 'type', label: '类型', disableSort: false },
|
||||||
|
{ id: 'status', label: '状态', disableSort: false },
|
||||||
|
{ id: 'response_time', label: '响应时间', disableSort: false },
|
||||||
|
{ id: 'balance', label: '余额', disableSort: false },
|
||||||
|
{ id: 'priority', label: '优先级', disableSort: false },
|
||||||
|
{ id: 'action', label: '操作', disableSort: true }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{channels.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
{channels.map((row) => (
|
||||||
<ChannelTableRow
|
<ChannelTableRow
|
||||||
item={row}
|
item={row}
|
||||||
manageChannel={manageChannel}
|
manageChannel={manageChannel}
|
||||||
@ -308,12 +317,15 @@ export default function ChannelPage() {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
page={activePage}
|
page={page}
|
||||||
component="div"
|
component="div"
|
||||||
count={channels.length + (channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
count={listCount}
|
||||||
rowsPerPage={ITEMS_PER_PAGE}
|
rowsPerPage={rowsPerPage}
|
||||||
onPageChange={onPaginationChange}
|
onPageChange={handleChangePage}
|
||||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
rowsPerPageOptions={[10, 25, 30]}
|
||||||
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} channelId={editChannelId} />
|
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} channelId={editChannelId} />
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
|
||||||
|
|
||||||
const LogTableHead = ({ userIsAdmin }) => {
|
|
||||||
return (
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>时间</TableCell>
|
|
||||||
{userIsAdmin && <TableCell>渠道</TableCell>}
|
|
||||||
{userIsAdmin && <TableCell>用户</TableCell>}
|
|
||||||
<TableCell>令牌</TableCell>
|
|
||||||
<TableCell>类型</TableCell>
|
|
||||||
<TableCell>模型</TableCell>
|
|
||||||
<TableCell>耗时</TableCell>
|
|
||||||
<TableCell>提示</TableCell>
|
|
||||||
<TableCell>补全</TableCell>
|
|
||||||
<TableCell>额度</TableCell>
|
|
||||||
<TableCell>详情</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LogTableHead;
|
|
||||||
|
|
||||||
LogTableHead.propTypes = {
|
|
||||||
userIsAdmin: PropTypes.bool
|
|
||||||
};
|
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { showError } from 'utils/common';
|
import { showError } from 'utils/common';
|
||||||
|
|
||||||
import Table from '@mui/material/Table';
|
import Table from '@mui/material/Table';
|
||||||
@ -12,12 +12,13 @@ import Toolbar from '@mui/material/Toolbar';
|
|||||||
|
|
||||||
import { Button, Card, Stack, Container, Typography, Box } from '@mui/material';
|
import { Button, Card, Stack, Container, Typography, Box } from '@mui/material';
|
||||||
import LogTableRow from './component/TableRow';
|
import LogTableRow from './component/TableRow';
|
||||||
import LogTableHead from './component/TableHead';
|
import KeywordTableHead from 'ui-component/TableHead';
|
||||||
import TableToolBar from './component/TableToolBar';
|
import TableToolBar from './component/TableToolBar';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { isAdmin } from 'utils/common';
|
import { isAdmin } from 'utils/common';
|
||||||
import { ITEMS_PER_PAGE } from 'constants';
|
import { ITEMS_PER_PAGE } from 'constants';
|
||||||
import { IconRefresh, IconSearch } from '@tabler/icons-react';
|
import { IconRefresh, IconSearch } from '@tabler/icons-react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
export default function Log() {
|
export default function Log() {
|
||||||
const originalKeyword = {
|
const originalKeyword = {
|
||||||
@ -26,86 +27,98 @@ export default function Log() {
|
|||||||
token_name: '',
|
token_name: '',
|
||||||
model_name: '',
|
model_name: '',
|
||||||
start_timestamp: 0,
|
start_timestamp: 0,
|
||||||
end_timestamp: new Date().getTime() / 1000 + 3600,
|
end_timestamp: dayjs().unix() + 3600,
|
||||||
type: 0,
|
type: 0,
|
||||||
channel: ''
|
channel: ''
|
||||||
};
|
};
|
||||||
const [logs, setLogs] = useState([]);
|
|
||||||
const [activePage, setActivePage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
const [order, setOrder] = useState('desc');
|
||||||
|
const [orderBy, setOrderBy] = useState('created_at');
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||||
|
const [listCount, setListCount] = useState(0);
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
|
const [toolBarValue, setToolBarValue] = useState(originalKeyword);
|
||||||
const [searchKeyword, setSearchKeyword] = useState(originalKeyword);
|
const [searchKeyword, setSearchKeyword] = useState(originalKeyword);
|
||||||
const [initPage, setInitPage] = useState(true);
|
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||||
|
|
||||||
|
const [logs, setLogs] = useState([]);
|
||||||
const userIsAdmin = isAdmin();
|
const userIsAdmin = isAdmin();
|
||||||
|
|
||||||
const loadLogs = async (startIdx) => {
|
const handleSort = (event, id) => {
|
||||||
setSearching(true);
|
const isAsc = orderBy === id && order === 'asc';
|
||||||
const url = userIsAdmin ? '/api/log/' : '/api/log/self/';
|
if (id !== '') {
|
||||||
const query = searchKeyword;
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
|
setOrderBy(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
query.p = startIdx;
|
const handleChangePage = (event, newPage) => {
|
||||||
|
setPage(newPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeRowsPerPage = (event) => {
|
||||||
|
setPage(0);
|
||||||
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchLogs = async () => {
|
||||||
|
setPage(0);
|
||||||
|
setSearchKeyword(toolBarValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToolBarValue = (event) => {
|
||||||
|
setToolBarValue({ ...toolBarValue, [event.target.name]: event.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = useCallback(
|
||||||
|
async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||||
|
setSearching(true);
|
||||||
|
try {
|
||||||
|
if (orderBy) {
|
||||||
|
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||||
|
}
|
||||||
|
const url = userIsAdmin ? '/api/log/' : '/api/log/self/';
|
||||||
if (!userIsAdmin) {
|
if (!userIsAdmin) {
|
||||||
delete query.username;
|
delete keyword.username;
|
||||||
delete query.channel;
|
delete keyword.channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const res = await API.get(url, {
|
||||||
const res = await API.get(url, { params: query });
|
params: {
|
||||||
|
page: page + 1,
|
||||||
|
size: rowsPerPage,
|
||||||
|
order: orderBy,
|
||||||
|
...keyword
|
||||||
|
}
|
||||||
|
});
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
if (startIdx === 0) {
|
setListCount(data.total_count);
|
||||||
setLogs(data);
|
setLogs(data.data);
|
||||||
} else {
|
|
||||||
let newLogs = [...logs];
|
|
||||||
newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
|
||||||
setLogs(newLogs);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
};
|
},
|
||||||
|
[userIsAdmin]
|
||||||
const onPaginationChange = (event, activePage) => {
|
);
|
||||||
(async () => {
|
|
||||||
if (activePage === Math.ceil(logs.length / ITEMS_PER_PAGE)) {
|
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
await loadLogs(activePage);
|
|
||||||
}
|
|
||||||
setActivePage(activePage);
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchLogs = async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
await loadLogs(0);
|
|
||||||
setActivePage(0);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchKeyword = (event) => {
|
|
||||||
setSearchKeyword({ ...searchKeyword, [event.target.name]: event.target.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理刷新
|
// 处理刷新
|
||||||
const handleRefresh = () => {
|
const handleRefresh = async () => {
|
||||||
setInitPage(true);
|
setOrderBy('created_at');
|
||||||
|
setOrder('desc');
|
||||||
|
setToolBarValue(originalKeyword);
|
||||||
|
setSearchKeyword(originalKeyword);
|
||||||
|
setRefreshFlag(!refreshFlag);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSearchKeyword(originalKeyword);
|
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||||
setActivePage(0);
|
}, [page, rowsPerPage, searchKeyword, order, orderBy, fetchData, refreshFlag]);
|
||||||
loadLogs(0)
|
|
||||||
.then()
|
|
||||||
.catch((reason) => {
|
|
||||||
showError(reason);
|
|
||||||
});
|
|
||||||
setInitPage(false);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [initPage]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -113,8 +126,8 @@ export default function Log() {
|
|||||||
<Typography variant="h4">日志</Typography>
|
<Typography variant="h4">日志</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Card>
|
<Card>
|
||||||
<Box component="form" onSubmit={searchLogs} noValidate>
|
<Box component="form" noValidate>
|
||||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} userIsAdmin={userIsAdmin} />
|
<TableToolBar filterName={toolBarValue} handleFilterName={handleToolBarValue} userIsAdmin={userIsAdmin} />
|
||||||
</Box>
|
</Box>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
@ -141,9 +154,72 @@ export default function Log() {
|
|||||||
<PerfectScrollbar component="div">
|
<PerfectScrollbar component="div">
|
||||||
<TableContainer sx={{ overflow: 'unset' }}>
|
<TableContainer sx={{ overflow: 'unset' }}>
|
||||||
<Table sx={{ minWidth: 800 }}>
|
<Table sx={{ minWidth: 800 }}>
|
||||||
<LogTableHead userIsAdmin={userIsAdmin} />
|
<KeywordTableHead
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onRequestSort={handleSort}
|
||||||
|
headLabel={[
|
||||||
|
{
|
||||||
|
id: 'created_at',
|
||||||
|
label: '时间',
|
||||||
|
disableSort: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'channel_id',
|
||||||
|
label: '渠道',
|
||||||
|
disableSort: false,
|
||||||
|
hide: !userIsAdmin
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user_id',
|
||||||
|
label: '用户',
|
||||||
|
disableSort: false,
|
||||||
|
hide: !userIsAdmin
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'token_name',
|
||||||
|
label: '令牌',
|
||||||
|
disableSort: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
label: '类型',
|
||||||
|
disableSort: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'model_name',
|
||||||
|
label: '模型',
|
||||||
|
disableSort: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'duration',
|
||||||
|
label: '耗时',
|
||||||
|
disableSort: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'message',
|
||||||
|
label: '提示',
|
||||||
|
disableSort: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'completion',
|
||||||
|
label: '补全',
|
||||||
|
disableSort: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quota',
|
||||||
|
label: '额度',
|
||||||
|
disableSort: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'detail',
|
||||||
|
label: '详情',
|
||||||
|
disableSort: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{logs.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row, index) => (
|
{logs.map((row, index) => (
|
||||||
<LogTableRow item={row} key={`${row.id}_${index}`} userIsAdmin={userIsAdmin} />
|
<LogTableRow item={row} key={`${row.id}_${index}`} userIsAdmin={userIsAdmin} />
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
@ -151,12 +227,15 @@ export default function Log() {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
page={activePage}
|
page={page}
|
||||||
component="div"
|
component="div"
|
||||||
count={logs.length + (logs.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
count={listCount}
|
||||||
rowsPerPage={ITEMS_PER_PAGE}
|
rowsPerPage={rowsPerPage}
|
||||||
onPageChange={onPaginationChange}
|
onPageChange={handleChangePage}
|
||||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
rowsPerPageOptions={[10, 25, 30]}
|
||||||
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
|
||||||
|
|
||||||
const RedemptionTableHead = () => {
|
|
||||||
return (
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>ID</TableCell>
|
|
||||||
<TableCell>名称</TableCell>
|
|
||||||
<TableCell>状态</TableCell>
|
|
||||||
<TableCell>额度</TableCell>
|
|
||||||
<TableCell>创建时间</TableCell>
|
|
||||||
<TableCell>兑换时间</TableCell>
|
|
||||||
<TableCell>操作</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RedemptionTableHead;
|
|
@ -12,7 +12,7 @@ import Toolbar from '@mui/material/Toolbar';
|
|||||||
|
|
||||||
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
||||||
import RedemptionTableRow from './component/TableRow';
|
import RedemptionTableRow from './component/TableRow';
|
||||||
import RedemptionTableHead from './component/TableHead';
|
import KeywordTableHead from 'ui-component/TableHead';
|
||||||
import TableToolBar from 'ui-component/TableToolBar';
|
import TableToolBar from 'ui-component/TableToolBar';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { ITEMS_PER_PAGE } from 'constants';
|
import { ITEMS_PER_PAGE } from 'constants';
|
||||||
@ -21,74 +21,81 @@ import EditeModal from './component/EditModal';
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
export default function Redemption() {
|
export default function Redemption() {
|
||||||
const [redemptions, setRedemptions] = useState([]);
|
const [page, setPage] = useState(0);
|
||||||
const [activePage, setActivePage] = useState(0);
|
const [order, setOrder] = useState('desc');
|
||||||
const [searching, setSearching] = useState(false);
|
const [orderBy, setOrderBy] = useState('id');
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||||
|
const [listCount, setListCount] = useState(0);
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
const [redemptions, setRedemptions] = useState([]);
|
||||||
|
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [editRedemptionId, setEditRedemptionId] = useState(0);
|
const [editRedemptionId, setEditRedemptionId] = useState(0);
|
||||||
|
|
||||||
const loadRedemptions = async (startIdx) => {
|
const handleSort = (event, id) => {
|
||||||
setSearching(true);
|
const isAsc = orderBy === id && order === 'asc';
|
||||||
try {
|
if (id !== '') {
|
||||||
const res = await API.get(`/api/redemption/?p=${startIdx}`);
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
const { success, message, data } = res.data;
|
setOrderBy(id);
|
||||||
if (success) {
|
|
||||||
if (startIdx === 0) {
|
|
||||||
setRedemptions(data);
|
|
||||||
} else {
|
|
||||||
let newRedemptions = [...redemptions];
|
|
||||||
newRedemptions.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
|
||||||
setRedemptions(newRedemptions);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
setSearching(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPaginationChange = (event, activePage) => {
|
const handleChangePage = (event, newPage) => {
|
||||||
(async () => {
|
setPage(newPage);
|
||||||
if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE)) {
|
};
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
await loadRedemptions(activePage);
|
const handleChangeRowsPerPage = (event) => {
|
||||||
}
|
setPage(0);
|
||||||
setActivePage(activePage);
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchRedemptions = async (event) => {
|
const searchRedemptions = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (searchKeyword === '') {
|
const formData = new FormData(event.target);
|
||||||
await loadRedemptions(0);
|
setPage(0);
|
||||||
setActivePage(0);
|
setSearchKeyword(formData.get('keyword'));
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
setSearching(true);
|
|
||||||
|
|
||||||
|
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||||
|
setSearching(true);
|
||||||
try {
|
try {
|
||||||
const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`);
|
if (orderBy) {
|
||||||
|
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||||
|
}
|
||||||
|
const res = await API.get(`/api/redemption/`, {
|
||||||
|
params: {
|
||||||
|
page: page + 1,
|
||||||
|
size: rowsPerPage,
|
||||||
|
keyword: keyword,
|
||||||
|
order: orderBy
|
||||||
|
}
|
||||||
|
});
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setRedemptions(data);
|
setListCount(data.total_count);
|
||||||
setActivePage(0);
|
setRedemptions(data.data);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchKeyword = (event) => {
|
// 处理刷新
|
||||||
setSearchKeyword(event.target.value);
|
const handleRefresh = async () => {
|
||||||
|
setOrderBy('id');
|
||||||
|
setOrder('desc');
|
||||||
|
setRefreshFlag(!refreshFlag);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||||
|
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||||
|
|
||||||
const manageRedemptions = async (id, action, value) => {
|
const manageRedemptions = async (id, action, value) => {
|
||||||
const url = '/api/redemption/';
|
const url = '/api/redemption/';
|
||||||
let data = { id };
|
let data = { id };
|
||||||
@ -110,7 +117,7 @@ export default function Redemption() {
|
|||||||
if (success) {
|
if (success) {
|
||||||
showSuccess('操作成功完成!');
|
showSuccess('操作成功完成!');
|
||||||
if (action === 'delete') {
|
if (action === 'delete') {
|
||||||
await loadRedemptions(0);
|
await handleRefresh();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
@ -122,13 +129,6 @@ export default function Redemption() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理刷新
|
|
||||||
const handleRefresh = async () => {
|
|
||||||
await loadRedemptions(0);
|
|
||||||
setActivePage(0);
|
|
||||||
setSearchKeyword('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenModal = (redemptionId) => {
|
const handleOpenModal = (redemptionId) => {
|
||||||
setEditRedemptionId(redemptionId);
|
setEditRedemptionId(redemptionId);
|
||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
@ -146,15 +146,6 @@ export default function Redemption() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadRedemptions(0)
|
|
||||||
.then()
|
|
||||||
.catch((reason) => {
|
|
||||||
showError(reason);
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
||||||
@ -166,7 +157,7 @@ export default function Redemption() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Card>
|
<Card>
|
||||||
<Box component="form" onSubmit={searchRedemptions} noValidate>
|
<Box component="form" onSubmit={searchRedemptions} noValidate>
|
||||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索兑换码的ID和名称...'} />
|
<TableToolBar placeholder={'搜索兑换码的ID和名称...'} />
|
||||||
</Box>
|
</Box>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
@ -189,9 +180,22 @@ export default function Redemption() {
|
|||||||
<PerfectScrollbar component="div">
|
<PerfectScrollbar component="div">
|
||||||
<TableContainer sx={{ overflow: 'unset' }}>
|
<TableContainer sx={{ overflow: 'unset' }}>
|
||||||
<Table sx={{ minWidth: 800 }}>
|
<Table sx={{ minWidth: 800 }}>
|
||||||
<RedemptionTableHead />
|
<KeywordTableHead
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onRequestSort={handleSort}
|
||||||
|
headLabel={[
|
||||||
|
{ id: 'id', label: 'ID', disableSort: false },
|
||||||
|
{ id: 'name', label: '名称', disableSort: false },
|
||||||
|
{ id: 'status', label: '状态', disableSort: false },
|
||||||
|
{ id: 'quota', label: '额度', disableSort: false },
|
||||||
|
{ id: 'created_time', label: '创建时间', disableSort: false },
|
||||||
|
{ id: 'redeemed_time', label: '兑换时间', disableSort: false },
|
||||||
|
{ id: 'action', label: '操作', disableSort: true }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{redemptions.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
{redemptions.map((row) => (
|
||||||
<RedemptionTableRow
|
<RedemptionTableRow
|
||||||
item={row}
|
item={row}
|
||||||
manageRedemption={manageRedemptions}
|
manageRedemption={manageRedemptions}
|
||||||
@ -205,12 +209,15 @@ export default function Redemption() {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
page={activePage}
|
page={page}
|
||||||
component="div"
|
component="div"
|
||||||
count={redemptions.length + (redemptions.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
count={listCount}
|
||||||
rowsPerPage={ITEMS_PER_PAGE}
|
rowsPerPage={rowsPerPage}
|
||||||
onPageChange={onPaginationChange}
|
onPageChange={handleChangePage}
|
||||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
rowsPerPageOptions={[10, 25, 30]}
|
||||||
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} redemptiondId={editRedemptionId} />
|
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} redemptiondId={editRedemptionId} />
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
|
||||||
|
|
||||||
const TokenTableHead = () => {
|
|
||||||
return (
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>名称</TableCell>
|
|
||||||
<TableCell>状态</TableCell>
|
|
||||||
<TableCell>已用额度</TableCell>
|
|
||||||
<TableCell>剩余额度</TableCell>
|
|
||||||
<TableCell>创建时间</TableCell>
|
|
||||||
<TableCell>过期时间</TableCell>
|
|
||||||
<TableCell>操作</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TokenTableHead;
|
|
@ -13,94 +13,91 @@ import Toolbar from '@mui/material/Toolbar';
|
|||||||
|
|
||||||
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
||||||
import TokensTableRow from './component/TableRow';
|
import TokensTableRow from './component/TableRow';
|
||||||
import TokenTableHead from './component/TableHead';
|
import KeywordTableHead from 'ui-component/TableHead';
|
||||||
import TableToolBar from 'ui-component/TableToolBar';
|
import TableToolBar from 'ui-component/TableToolBar';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { ITEMS_PER_PAGE } from 'constants';
|
|
||||||
import { IconRefresh, IconPlus } from '@tabler/icons-react';
|
import { IconRefresh, IconPlus } from '@tabler/icons-react';
|
||||||
import EditeModal from './component/EditModal';
|
import EditeModal from './component/EditModal';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { ITEMS_PER_PAGE } from 'constants';
|
||||||
|
|
||||||
export default function Token() {
|
export default function Token() {
|
||||||
const [tokens, setTokens] = useState([]);
|
const [page, setPage] = useState(0);
|
||||||
const [activePage, setActivePage] = useState(0);
|
const [order, setOrder] = useState('desc');
|
||||||
|
const [orderBy, setOrderBy] = useState('id');
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||||
|
const [listCount, setListCount] = useState(0);
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
|
const [tokens, setTokens] = useState([]);
|
||||||
|
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [editTokenId, setEditTokenId] = useState(0);
|
const [editTokenId, setEditTokenId] = useState(0);
|
||||||
const siteInfo = useSelector((state) => state.siteInfo);
|
const siteInfo = useSelector((state) => state.siteInfo);
|
||||||
|
|
||||||
const loadTokens = async (startIdx) => {
|
const handleSort = (event, id) => {
|
||||||
setSearching(true);
|
const isAsc = orderBy === id && order === 'asc';
|
||||||
try {
|
if (id !== '') {
|
||||||
const res = await API.get(`/api/token/?p=${startIdx}`);
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
const { success, message, data } = res.data;
|
setOrderBy(id);
|
||||||
if (success) {
|
|
||||||
if (startIdx === 0) {
|
|
||||||
setTokens(data);
|
|
||||||
} else {
|
|
||||||
let newTokens = [...tokens];
|
|
||||||
newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
|
||||||
setTokens(newTokens);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearching(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleChangePage = (event, newPage) => {
|
||||||
loadTokens(0)
|
setPage(newPage);
|
||||||
.then()
|
};
|
||||||
.catch((reason) => {
|
|
||||||
showError(reason);
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onPaginationChange = (event, activePage) => {
|
const handleChangeRowsPerPage = (event) => {
|
||||||
(async () => {
|
setPage(0);
|
||||||
if (activePage === Math.ceil(tokens.length / ITEMS_PER_PAGE)) {
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
await loadTokens(activePage);
|
|
||||||
}
|
|
||||||
setActivePage(activePage);
|
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchTokens = async (event) => {
|
const searchTokens = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (searchKeyword === '') {
|
const formData = new FormData(event.target);
|
||||||
await loadTokens(0);
|
setPage(0);
|
||||||
setActivePage(0);
|
setSearchKeyword(formData.get('keyword'));
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
setSearching(true);
|
|
||||||
|
|
||||||
|
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||||
|
setSearching(true);
|
||||||
try {
|
try {
|
||||||
const res = await API.get(`/api/token/search?keyword=${searchKeyword}`);
|
if (orderBy) {
|
||||||
|
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||||
|
}
|
||||||
|
const res = await API.get(`/api/token/`, {
|
||||||
|
params: {
|
||||||
|
page: page + 1,
|
||||||
|
size: rowsPerPage,
|
||||||
|
keyword: keyword,
|
||||||
|
order: orderBy
|
||||||
|
}
|
||||||
|
});
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setTokens(data);
|
setListCount(data.total_count);
|
||||||
setActivePage(0);
|
setTokens(data.data);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return;
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchKeyword = (event) => {
|
// 处理刷新
|
||||||
setSearchKeyword(event.target.value);
|
const handleRefresh = async () => {
|
||||||
|
setOrderBy('id');
|
||||||
|
setOrder('desc');
|
||||||
|
setRefreshFlag(!refreshFlag);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||||
|
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||||
|
|
||||||
const manageToken = async (id, action, value) => {
|
const manageToken = async (id, action, value) => {
|
||||||
const url = '/api/token/';
|
const url = '/api/token/';
|
||||||
let data = { id };
|
let data = { id };
|
||||||
@ -133,11 +130,6 @@ export default function Token() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理刷新
|
|
||||||
const handleRefresh = async () => {
|
|
||||||
await loadTokens(activePage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenModal = (tokenId) => {
|
const handleOpenModal = (tokenId) => {
|
||||||
setEditTokenId(tokenId);
|
setEditTokenId(tokenId);
|
||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
@ -178,7 +170,7 @@ export default function Token() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Card>
|
<Card>
|
||||||
<Box component="form" onSubmit={searchTokens} noValidate>
|
<Box component="form" onSubmit={searchTokens} noValidate>
|
||||||
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索令牌的名称...'} />
|
<TableToolBar placeholder={'搜索令牌的名称...'} />
|
||||||
</Box>
|
</Box>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
@ -201,9 +193,22 @@ export default function Token() {
|
|||||||
<PerfectScrollbar component="div">
|
<PerfectScrollbar component="div">
|
||||||
<TableContainer sx={{ overflow: 'unset' }}>
|
<TableContainer sx={{ overflow: 'unset' }}>
|
||||||
<Table sx={{ minWidth: 800 }}>
|
<Table sx={{ minWidth: 800 }}>
|
||||||
<TokenTableHead />
|
<KeywordTableHead
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onRequestSort={handleSort}
|
||||||
|
headLabel={[
|
||||||
|
{ id: 'name', label: '名称', disableSort: false },
|
||||||
|
{ id: 'status', label: '状态', disableSort: false },
|
||||||
|
{ id: 'used_quota', label: '已用额度', disableSort: false },
|
||||||
|
{ id: 'remain_quota', label: '剩余额度', disableSort: false },
|
||||||
|
{ id: 'created_time', label: '创建时间', disableSort: false },
|
||||||
|
{ id: 'expired_time', label: '过期时间', disableSort: false },
|
||||||
|
{ id: 'action', label: '操作', disableSort: true }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{tokens.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
{tokens.map((row) => (
|
||||||
<TokensTableRow
|
<TokensTableRow
|
||||||
item={row}
|
item={row}
|
||||||
manageToken={manageToken}
|
manageToken={manageToken}
|
||||||
@ -217,12 +222,15 @@ export default function Token() {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
page={activePage}
|
page={page}
|
||||||
component="div"
|
component="div"
|
||||||
count={tokens.length + (tokens.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
count={listCount}
|
||||||
rowsPerPage={ITEMS_PER_PAGE}
|
rowsPerPage={rowsPerPage}
|
||||||
onPageChange={onPaginationChange}
|
onPageChange={handleChangePage}
|
||||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
rowsPerPageOptions={[10, 25, 30]}
|
||||||
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} tokenId={editTokenId} />
|
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} tokenId={editTokenId} />
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { TableCell, TableHead, TableRow } from '@mui/material';
|
|
||||||
|
|
||||||
const UsersTableHead = () => {
|
|
||||||
return (
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>ID</TableCell>
|
|
||||||
<TableCell>用户名</TableCell>
|
|
||||||
<TableCell>分组</TableCell>
|
|
||||||
<TableCell>统计信息</TableCell>
|
|
||||||
<TableCell>用户角色</TableCell>
|
|
||||||
<TableCell>绑定</TableCell>
|
|
||||||
<TableCell>状态</TableCell>
|
|
||||||
<TableCell>操作</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UsersTableHead;
|
|
@ -19,7 +19,7 @@ import {
|
|||||||
|
|
||||||
import Label from 'ui-component/Label';
|
import Label from 'ui-component/Label';
|
||||||
import TableSwitch from 'ui-component/Switch';
|
import TableSwitch from 'ui-component/Switch';
|
||||||
import { renderQuota, renderNumber } from 'utils/common';
|
import { renderQuota, renderNumber, timestamp2string } from 'utils/common';
|
||||||
import { IconDotsVertical, IconEdit, IconTrash, IconUser, IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
|
import { IconDotsVertical, IconEdit, IconTrash, IconUser, IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ export default function UsersTableRow({ item, manageUser, handleOpenModal, setMo
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>{item.created_time === 0 ? '未知' : timestamp2string(item.created_time)}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{' '}
|
{' '}
|
||||||
<TableSwitch id={`switch-${item.id}`} checked={statusSwitch === 1} onChange={handleStatus} />
|
<TableSwitch id={`switch-${item.id}`} checked={statusSwitch === 1} onChange={handleStatus} />
|
||||||
|
@ -12,7 +12,7 @@ import Toolbar from '@mui/material/Toolbar';
|
|||||||
|
|
||||||
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
|
||||||
import UsersTableRow from './component/TableRow';
|
import UsersTableRow from './component/TableRow';
|
||||||
import UsersTableHead from './component/TableHead';
|
import KeywordTableHead from 'ui-component/TableHead';
|
||||||
import TableToolBar from 'ui-component/TableToolBar';
|
import TableToolBar from 'ui-component/TableToolBar';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { ITEMS_PER_PAGE } from 'constants';
|
import { ITEMS_PER_PAGE } from 'constants';
|
||||||
@ -21,74 +21,81 @@ import EditeModal from './component/EditModal';
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
const [users, setUsers] = useState([]);
|
const [page, setPage] = useState(0);
|
||||||
const [activePage, setActivePage] = useState(0);
|
const [order, setOrder] = useState('desc');
|
||||||
|
const [orderBy, setOrderBy] = useState('id');
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
|
||||||
|
const [listCount, setListCount] = useState(0);
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const [refreshFlag, setRefreshFlag] = useState(false);
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [editUserId, setEditUserId] = useState(0);
|
const [editUserId, setEditUserId] = useState(0);
|
||||||
|
|
||||||
const loadUsers = async (startIdx) => {
|
const handleSort = (event, id) => {
|
||||||
setSearching(true);
|
const isAsc = orderBy === id && order === 'asc';
|
||||||
try {
|
if (id !== '') {
|
||||||
const res = await API.get(`/api/user/?p=${startIdx}`);
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
const { success, message, data } = res.data;
|
setOrderBy(id);
|
||||||
if (success) {
|
|
||||||
if (startIdx === 0) {
|
|
||||||
setUsers(data);
|
|
||||||
} else {
|
|
||||||
let newUsers = [...users];
|
|
||||||
newUsers.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
|
||||||
setUsers(newUsers);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
setSearching(false);
|
|
||||||
} catch (error) {
|
|
||||||
setSearching(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPaginationChange = (event, activePage) => {
|
const handleChangePage = (event, newPage) => {
|
||||||
(async () => {
|
setPage(newPage);
|
||||||
if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE)) {
|
};
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
await loadUsers(activePage);
|
const handleChangeRowsPerPage = (event) => {
|
||||||
}
|
setPage(0);
|
||||||
setActivePage(activePage);
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchUsers = async (event) => {
|
const searchUsers = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (searchKeyword === '') {
|
const formData = new FormData(event.target);
|
||||||
await loadUsers(0);
|
setPage(0);
|
||||||
setActivePage(0);
|
setSearchKeyword(formData.get('keyword'));
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
try {
|
try {
|
||||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
|
if (orderBy) {
|
||||||
|
orderBy = order === 'desc' ? '-' + orderBy : orderBy;
|
||||||
|
}
|
||||||
|
const res = await API.get(`/api/user/`, {
|
||||||
|
params: {
|
||||||
|
page: page + 1,
|
||||||
|
size: rowsPerPage,
|
||||||
|
keyword: keyword,
|
||||||
|
order: orderBy
|
||||||
|
}
|
||||||
|
});
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setUsers(data);
|
setListCount(data.total_count);
|
||||||
setActivePage(0);
|
setUsers(data.data);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
setSearching(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSearching(false);
|
console.error(error);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
setSearching(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchKeyword = (event) => {
|
// 处理刷新
|
||||||
setSearchKeyword(event.target.value);
|
const handleRefresh = async () => {
|
||||||
|
setOrderBy('id');
|
||||||
|
setOrder('desc');
|
||||||
|
setRefreshFlag(!refreshFlag);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
|
||||||
|
}, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
|
||||||
|
|
||||||
const manageUser = async (username, action, value) => {
|
const manageUser = async (username, action, value) => {
|
||||||
const url = '/api/user/manage';
|
const url = '/api/user/manage';
|
||||||
let data = { username: username, action: '' };
|
let data = { username: username, action: '' };
|
||||||
@ -110,7 +117,7 @@ export default function Users() {
|
|||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess('操作成功完成!');
|
showSuccess('操作成功完成!');
|
||||||
await loadUsers(activePage);
|
await handleRefresh();
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
@ -121,11 +128,6 @@ export default function Users() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理刷新
|
|
||||||
const handleRefresh = async () => {
|
|
||||||
await loadUsers(activePage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenModal = (userId) => {
|
const handleOpenModal = (userId) => {
|
||||||
setEditUserId(userId);
|
setEditUserId(userId);
|
||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
@ -143,15 +145,6 @@ export default function Users() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadUsers(0)
|
|
||||||
.then()
|
|
||||||
.catch((reason) => {
|
|
||||||
showError(reason);
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
|
||||||
@ -163,11 +156,7 @@ export default function Users() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Card>
|
<Card>
|
||||||
<Box component="form" onSubmit={searchUsers} noValidate>
|
<Box component="form" onSubmit={searchUsers} noValidate>
|
||||||
<TableToolBar
|
<TableToolBar placeholder={'搜索用户的ID,用户名,显示名称,以及邮箱地址...'} />
|
||||||
filterName={searchKeyword}
|
|
||||||
handleFilterName={handleSearchKeyword}
|
|
||||||
placeholder={'搜索用户的ID,用户名,显示名称,以及邮箱地址...'}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
@ -190,9 +179,24 @@ export default function Users() {
|
|||||||
<PerfectScrollbar component="div">
|
<PerfectScrollbar component="div">
|
||||||
<TableContainer sx={{ overflow: 'unset' }}>
|
<TableContainer sx={{ overflow: 'unset' }}>
|
||||||
<Table sx={{ minWidth: 800 }}>
|
<Table sx={{ minWidth: 800 }}>
|
||||||
<UsersTableHead />
|
<KeywordTableHead
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onRequestSort={handleSort}
|
||||||
|
headLabel={[
|
||||||
|
{ id: 'id', label: 'ID', disableSort: false },
|
||||||
|
{ id: 'username', label: '用户名', disableSort: false },
|
||||||
|
{ id: 'group', label: '分组', disableSort: true },
|
||||||
|
{ id: 'stats', label: '统计信息', disableSort: true },
|
||||||
|
{ id: 'role', label: '用户角色', disableSort: false },
|
||||||
|
{ id: 'bind', label: '绑定', disableSort: true },
|
||||||
|
{ id: 'created_time', label: '创建时间', disableSort: false },
|
||||||
|
{ id: 'status', label: '状态', disableSort: false },
|
||||||
|
{ id: 'action', label: '操作', disableSort: true }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
|
{users.map((row) => (
|
||||||
<UsersTableRow
|
<UsersTableRow
|
||||||
item={row}
|
item={row}
|
||||||
manageUser={manageUser}
|
manageUser={manageUser}
|
||||||
@ -206,12 +210,15 @@ export default function Users() {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
page={activePage}
|
page={page}
|
||||||
component="div"
|
component="div"
|
||||||
count={users.length + (users.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
|
count={listCount}
|
||||||
rowsPerPage={ITEMS_PER_PAGE}
|
rowsPerPage={rowsPerPage}
|
||||||
onPageChange={onPaginationChange}
|
onPageChange={handleChangePage}
|
||||||
rowsPerPageOptions={[ITEMS_PER_PAGE]}
|
rowsPerPageOptions={[10, 25, 30]}
|
||||||
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} userId={editUserId} />
|
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} userId={editUserId} />
|
||||||
|
Loading…
Reference in New Issue
Block a user