Feat/auto group (#6590)
* feat(token): support custom auto group order * feat(keys): enhance auto group presentation * fix(keys): rework Auto flow border and compact inherited order The Auto group highlight previously tinted the whole control surface with a gradient and animated only a 1px top sweep, which read as a background color rather than a flowing border. Replace it with a border-only effect: an aria-hidden, pointer-events-none overlay whose conic gradient is masked down to a thin ring hugging the rounded perimeter, so the highlight travels around all four edges and corners every 3.2s. The interior stays neutral with a restrained static primary border and glow; prefers-reduced-motion hides the moving layer while keeping the static emphasis. The inherited global Auto order also rendered as spacious two-line rows with circular sequence markers, wasting drawer space. Render it as a compact wrapping strip of one-line chips (index, name, ratio badge) with descriptions kept accessible via title and sr-only text, scrolling only past a much smaller max height. Custom add/remove/reorder editing, empty-array inheritance semantics, and the submit payload are unchanged. * fix(keys): preserve Auto inheritance and unify effects * refactor(keys): temporarily disable AutoGroupBadge in api-key-group-cell
This commit is contained in:
+124
-8
@@ -7,30 +7,115 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/constant"
|
||||
"github.com/QuantumNous/new-api/i18n"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/QuantumNous/new-api/setting"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func buildMaskedTokenResponse(token *model.Token) *model.Token {
|
||||
type tokenAutoGroupsInput struct {
|
||||
Set bool
|
||||
Groups []string
|
||||
}
|
||||
|
||||
func (input *tokenAutoGroupsInput) UnmarshalJSON(data []byte) error {
|
||||
input.Set = true
|
||||
if strings.TrimSpace(string(data)) == "null" {
|
||||
input.Groups = nil
|
||||
return nil
|
||||
}
|
||||
return common.Unmarshal(data, &input.Groups)
|
||||
}
|
||||
|
||||
type tokenRequest struct {
|
||||
model.Token
|
||||
AutoGroups tokenAutoGroupsInput `json:"auto_groups"`
|
||||
}
|
||||
|
||||
type tokenResponse struct {
|
||||
*model.Token
|
||||
AutoGroups []string `json:"auto_groups"`
|
||||
}
|
||||
|
||||
func buildMaskedTokenResponse(token *model.Token) *tokenResponse {
|
||||
if token == nil {
|
||||
return nil
|
||||
}
|
||||
maskedToken := *token
|
||||
maskedToken.Key = token.GetMaskedKey()
|
||||
return &maskedToken
|
||||
autoGroups, err := token.GetAutoGroups()
|
||||
if err != nil {
|
||||
common.SysError(fmt.Sprintf("failed to parse auto groups for token %d: %v", token.Id, err))
|
||||
autoGroups = nil
|
||||
}
|
||||
if len(autoGroups) == 0 {
|
||||
autoGroups = nil
|
||||
}
|
||||
return &tokenResponse{Token: &maskedToken, AutoGroups: autoGroups}
|
||||
}
|
||||
|
||||
func buildMaskedTokenResponses(tokens []*model.Token) []*model.Token {
|
||||
maskedTokens := make([]*model.Token, 0, len(tokens))
|
||||
func buildMaskedTokenResponses(tokens []*model.Token) []*tokenResponse {
|
||||
maskedTokens := make([]*tokenResponse, 0, len(tokens))
|
||||
for _, token := range tokens {
|
||||
maskedTokens = append(maskedTokens, buildMaskedTokenResponse(token))
|
||||
}
|
||||
return maskedTokens
|
||||
}
|
||||
|
||||
func getTokenRequestUserGroup(c *gin.Context) (string, error) {
|
||||
if userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup); userGroup != "" {
|
||||
return userGroup, nil
|
||||
}
|
||||
if userGroup := c.GetString("group"); userGroup != "" {
|
||||
return userGroup, nil
|
||||
}
|
||||
return model.GetUserGroup(c.GetInt("id"), false)
|
||||
}
|
||||
|
||||
func setTokenAutoGroups(c *gin.Context, token *model.Token, groups []string) bool {
|
||||
if len(groups) == 0 {
|
||||
if err := token.SetAutoGroups(nil); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
maxCount := setting.GetMaxTokenAutoGroups()
|
||||
if len(groups) > maxCount {
|
||||
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsTooMany, map[string]any{"Max": maxCount})
|
||||
return false
|
||||
}
|
||||
|
||||
userGroup, err := getTokenRequestUserGroup(c)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return false
|
||||
}
|
||||
seen := make(map[string]struct{}, len(groups))
|
||||
for _, group := range groups {
|
||||
if _, ok := seen[group]; ok {
|
||||
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsDuplicate, map[string]any{"Group": group})
|
||||
return false
|
||||
}
|
||||
seen[group] = struct{}{}
|
||||
if !service.IsUserSelectableGroup(userGroup, group) {
|
||||
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsInvalid, map[string]any{"Group": group})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if err := token.SetAutoGroups(groups); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetAllTokens(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
pageInfo := common.GetPageQuery(c)
|
||||
@@ -77,6 +162,18 @@ func GetToken(c *gin.Context) {
|
||||
common.ApiSuccess(c, buildMaskedTokenResponse(token))
|
||||
}
|
||||
|
||||
func GetTokenAutoGroups(c *gin.Context) {
|
||||
userGroup, err := getTokenRequestUserGroup(c)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"groups": service.GetUserAutoGroup(userGroup),
|
||||
"max_count": setting.GetMaxTokenAutoGroups(),
|
||||
})
|
||||
}
|
||||
|
||||
func GetTokenKey(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
userId := c.GetInt("id")
|
||||
@@ -165,12 +262,13 @@ func GetTokenUsage(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddToken(c *gin.Context) {
|
||||
token := model.Token{}
|
||||
err := c.ShouldBindJSON(&token)
|
||||
request := tokenRequest{}
|
||||
err := c.ShouldBindJSON(&request)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
token := request.Token
|
||||
if len(token.Name) > 50 {
|
||||
common.ApiErrorI18n(c, i18n.MsgTokenNameTooLong)
|
||||
return
|
||||
@@ -201,6 +299,14 @@ func AddToken(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
if token.Group == "auto" {
|
||||
if !setTokenAutoGroups(c, &token, request.AutoGroups.Groups) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
token.CrossGroupRetry = false
|
||||
_ = token.SetAutoGroups(nil)
|
||||
}
|
||||
key, err := common.GenerateKey()
|
||||
if err != nil {
|
||||
common.ApiErrorI18n(c, i18n.MsgTokenGenerateFailed)
|
||||
@@ -221,6 +327,7 @@ func AddToken(c *gin.Context) {
|
||||
AllowIps: token.AllowIps,
|
||||
Group: token.Group,
|
||||
CrossGroupRetry: token.CrossGroupRetry,
|
||||
AutoGroups: token.AutoGroups,
|
||||
}
|
||||
err = cleanToken.Insert()
|
||||
if err != nil {
|
||||
@@ -250,12 +357,13 @@ func DeleteToken(c *gin.Context) {
|
||||
func UpdateToken(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
statusOnly := c.Query("status_only")
|
||||
token := model.Token{}
|
||||
err := c.ShouldBindJSON(&token)
|
||||
request := tokenRequest{}
|
||||
err := c.ShouldBindJSON(&request)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
token := request.Token
|
||||
if len(token.Name) > 50 {
|
||||
common.ApiErrorI18n(c, i18n.MsgTokenNameTooLong)
|
||||
return
|
||||
@@ -299,6 +407,14 @@ func UpdateToken(c *gin.Context) {
|
||||
cleanToken.AllowIps = token.AllowIps
|
||||
cleanToken.Group = token.Group
|
||||
cleanToken.CrossGroupRetry = token.CrossGroupRetry
|
||||
if token.Group != "auto" {
|
||||
cleanToken.CrossGroupRetry = false
|
||||
_ = cleanToken.SetAutoGroups(nil)
|
||||
} else if request.AutoGroups.Set {
|
||||
if !setTokenAutoGroups(c, cleanToken, request.AutoGroups.Groups) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = cleanToken.Update()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user