mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-10-24 10:23:41 +08:00
Compare commits
10 Commits
v0.3.0-alp
...
v0.3.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc1ef2ffd5 | ||
|
|
7201bd1c97 | ||
|
|
73d5e0f283 | ||
|
|
efc744ca35 | ||
|
|
e8da98139f | ||
|
|
519cb030f7 | ||
|
|
58fe923c85 | ||
|
|
c9ac5e391f | ||
|
|
69cf1de7bd | ||
|
|
4d6172a242 |
@@ -54,6 +54,7 @@ var QuotaForNewUser = 0
|
|||||||
var ChannelDisableThreshold = 5.0
|
var ChannelDisableThreshold = 5.0
|
||||||
var AutomaticDisableChannelEnabled = false
|
var AutomaticDisableChannelEnabled = false
|
||||||
var QuotaRemindThreshold = 1000
|
var QuotaRemindThreshold = 1000
|
||||||
|
var PreConsumedQuota = 500
|
||||||
|
|
||||||
var RootUserEmail = ""
|
var RootUserEmail = ""
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ const (
|
|||||||
var ChannelBaseURLs = []string{
|
var ChannelBaseURLs = []string{
|
||||||
"", // 0
|
"", // 0
|
||||||
"https://api.openai.com", // 1
|
"https://api.openai.com", // 1
|
||||||
"https://openai.api2d.net", // 2
|
"https://oa.api2d.net", // 2
|
||||||
"", // 3
|
"", // 3
|
||||||
"https://api.openai-asia.com", // 4
|
"https://api.openai-asia.com", // 4
|
||||||
"https://api.openai-sb.com", // 5
|
"https://api.openai-sb.com", // 5
|
||||||
|
|||||||
@@ -210,11 +210,12 @@ func testChannel(channel *model.Channel, request *ChatRequest) error {
|
|||||||
func buildTestRequest(c *gin.Context) *ChatRequest {
|
func buildTestRequest(c *gin.Context) *ChatRequest {
|
||||||
model_ := c.Query("model")
|
model_ := c.Query("model")
|
||||||
testRequest := &ChatRequest{
|
testRequest := &ChatRequest{
|
||||||
Model: model_,
|
Model: model_,
|
||||||
|
MaxTokens: 1,
|
||||||
}
|
}
|
||||||
testMessage := Message{
|
testMessage := Message{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: "echo hi",
|
Content: "hi",
|
||||||
}
|
}
|
||||||
testRequest.Messages = append(testRequest.Messages, testMessage)
|
testRequest.Messages = append(testRequest.Messages, testMessage)
|
||||||
return testRequest
|
return testRequest
|
||||||
|
|||||||
153
controller/model.go
Normal file
153
controller/model.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://platform.openai.com/docs/api-reference/models/list
|
||||||
|
|
||||||
|
type OpenAIModelPermission struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Created int `json:"created"`
|
||||||
|
AllowCreateEngine bool `json:"allow_create_engine"`
|
||||||
|
AllowSampling bool `json:"allow_sampling"`
|
||||||
|
AllowLogprobs bool `json:"allow_logprobs"`
|
||||||
|
AllowSearchIndices bool `json:"allow_search_indices"`
|
||||||
|
AllowView bool `json:"allow_view"`
|
||||||
|
AllowFineTuning bool `json:"allow_fine_tuning"`
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Group *string `json:"group"`
|
||||||
|
IsBlocking bool `json:"is_blocking"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenAIModels struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Created int `json:"created"`
|
||||||
|
OwnedBy string `json:"owned_by"`
|
||||||
|
Permission OpenAIModelPermission `json:"permission"`
|
||||||
|
Root string `json:"root"`
|
||||||
|
Parent *string `json:"parent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var openAIModels []OpenAIModels
|
||||||
|
var openAIModelsMap map[string]OpenAIModels
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
permission := OpenAIModelPermission{
|
||||||
|
Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ",
|
||||||
|
Object: "model_permission",
|
||||||
|
Created: 1626777600,
|
||||||
|
AllowCreateEngine: true,
|
||||||
|
AllowSampling: true,
|
||||||
|
AllowLogprobs: true,
|
||||||
|
AllowSearchIndices: false,
|
||||||
|
AllowView: true,
|
||||||
|
AllowFineTuning: false,
|
||||||
|
Organization: "*",
|
||||||
|
Group: nil,
|
||||||
|
IsBlocking: false,
|
||||||
|
}
|
||||||
|
// https://platform.openai.com/docs/models/model-endpoint-compatibility
|
||||||
|
openAIModels = []OpenAIModels{
|
||||||
|
{
|
||||||
|
Id: "gpt-3.5-turbo",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-3.5-turbo",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "gpt-3.5-turbo-0301",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-3.5-turbo-0301",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "gpt-4",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-4",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "gpt-4-0314",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-4-0314",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "gpt-4-32k",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-4-32k",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "gpt-4-32k-0314",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-4-32k-0314",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "gpt-3.5-turbo",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "gpt-3.5-turbo",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "text-embedding-ada-002",
|
||||||
|
Object: "model",
|
||||||
|
Created: 1677649963,
|
||||||
|
OwnedBy: "openai",
|
||||||
|
Permission: permission,
|
||||||
|
Root: "text-embedding-ada-002",
|
||||||
|
Parent: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
openAIModelsMap = make(map[string]OpenAIModels)
|
||||||
|
for _, model := range openAIModels {
|
||||||
|
openAIModelsMap[model.Id] = model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListModels(c *gin.Context) {
|
||||||
|
c.JSON(200, openAIModels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RetrieveModel(c *gin.Context) {
|
||||||
|
modelId := c.Param("model")
|
||||||
|
if model, ok := openAIModelsMap[modelId]; ok {
|
||||||
|
c.JSON(200, model)
|
||||||
|
} else {
|
||||||
|
openAIError := OpenAIError{
|
||||||
|
Message: fmt.Sprintf("The model '%s' does not exist", modelId),
|
||||||
|
Type: "invalid_request_error",
|
||||||
|
Param: "model",
|
||||||
|
Code: "model_not_found",
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"error": openAIError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,14 +21,16 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChatRequest struct {
|
type ChatRequest struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Messages []Message `json:"messages"`
|
Messages []Message `json:"messages"`
|
||||||
|
MaxTokens int `json:"max_tokens"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TextRequest struct {
|
type TextRequest struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Messages []Message `json:"messages"`
|
Messages []Message `json:"messages"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
|
MaxTokens int `json:"max_tokens"`
|
||||||
//Stream bool `json:"stream"`
|
//Stream bool `json:"stream"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +130,17 @@ func relayHelper(c *gin.Context) error {
|
|||||||
model_ = strings.TrimSuffix(model_, "-0314")
|
model_ = strings.TrimSuffix(model_, "-0314")
|
||||||
fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/%s", baseURL, model_, task)
|
fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/%s", baseURL, model_, task)
|
||||||
}
|
}
|
||||||
preConsumedQuota := 500 // TODO: make this configurable, take ratio into account
|
var promptText string
|
||||||
|
for _, message := range textRequest.Messages {
|
||||||
|
promptText += fmt.Sprintf("%s: %s\n", message.Role, message.Content)
|
||||||
|
}
|
||||||
|
promptTokens := countToken(promptText) + 3
|
||||||
|
preConsumedTokens := common.PreConsumedQuota
|
||||||
|
if textRequest.MaxTokens != 0 {
|
||||||
|
preConsumedTokens = promptTokens + textRequest.MaxTokens
|
||||||
|
}
|
||||||
|
ratio := common.GetModelRatio(textRequest.Model)
|
||||||
|
preConsumedQuota := int(float64(preConsumedTokens) * ratio)
|
||||||
if consumeQuota {
|
if consumeQuota {
|
||||||
err := model.PreConsumeTokenQuota(tokenId, preConsumedQuota)
|
err := model.PreConsumeTokenQuota(tokenId, preConsumedQuota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -163,7 +175,7 @@ func relayHelper(c *gin.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var textResponse TextResponse
|
var textResponse TextResponse
|
||||||
isStream := resp.Header.Get("Content-Type") == "text/event-stream"
|
isStream := strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream")
|
||||||
var streamResponseText string
|
var streamResponseText string
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -175,16 +187,11 @@ func relayHelper(c *gin.Context) error {
|
|||||||
completionRatio = 2
|
completionRatio = 2
|
||||||
}
|
}
|
||||||
if isStream {
|
if isStream {
|
||||||
var promptText string
|
|
||||||
for _, message := range textRequest.Messages {
|
|
||||||
promptText += fmt.Sprintf("%s: %s\n", message.Role, message.Content)
|
|
||||||
}
|
|
||||||
completionText := fmt.Sprintf("%s: %s\n", "assistant", streamResponseText)
|
completionText := fmt.Sprintf("%s: %s\n", "assistant", streamResponseText)
|
||||||
quota = countToken(promptText) + countToken(completionText)*completionRatio + 3
|
quota = promptTokens + countToken(completionText)*completionRatio
|
||||||
} else {
|
} else {
|
||||||
quota = textResponse.Usage.PromptTokens + textResponse.Usage.CompletionTokens*completionRatio
|
quota = textResponse.Usage.PromptTokens + textResponse.Usage.CompletionTokens*completionRatio
|
||||||
}
|
}
|
||||||
ratio := common.GetModelRatio(textRequest.Model)
|
|
||||||
quota = int(float64(quota) * ratio)
|
quota = int(float64(quota) * ratio)
|
||||||
quotaDelta := quota - preConsumedQuota
|
quotaDelta := quota - preConsumedQuota
|
||||||
err := model.PostConsumeTokenQuota(tokenId, quotaDelta)
|
err := model.PostConsumeTokenQuota(tokenId, quotaDelta)
|
||||||
|
|||||||
@@ -467,6 +467,13 @@ func CreateUser(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := common.Validate.Struct(&user); err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "输入不合法 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
if user.DisplayName == "" {
|
if user.DisplayName == "" {
|
||||||
user.DisplayName = user.Username
|
user.DisplayName = user.Username
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ func RootAuth() func(c *gin.Context) {
|
|||||||
func TokenAuth() func(c *gin.Context) {
|
func TokenAuth() func(c *gin.Context) {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
key := c.Request.Header.Get("Authorization")
|
key := c.Request.Header.Get("Authorization")
|
||||||
|
key = strings.TrimPrefix(key, "Bearer ")
|
||||||
|
key = strings.TrimPrefix(key, "sk-")
|
||||||
parts := strings.Split(key, "-")
|
parts := strings.Split(key, "-")
|
||||||
key = parts[0]
|
key = parts[0]
|
||||||
token, err := model.ValidateUserToken(key)
|
token, err := model.ValidateUserToken(key)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func InitOptionMap() {
|
|||||||
common.OptionMap["TurnstileSecretKey"] = ""
|
common.OptionMap["TurnstileSecretKey"] = ""
|
||||||
common.OptionMap["QuotaForNewUser"] = strconv.Itoa(common.QuotaForNewUser)
|
common.OptionMap["QuotaForNewUser"] = strconv.Itoa(common.QuotaForNewUser)
|
||||||
common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
|
common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
|
||||||
|
common.OptionMap["PreConsumedQuota"] = strconv.Itoa(common.PreConsumedQuota)
|
||||||
common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
|
common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
|
||||||
common.OptionMap["TopUpLink"] = common.TopUpLink
|
common.OptionMap["TopUpLink"] = common.TopUpLink
|
||||||
common.OptionMapRWMutex.Unlock()
|
common.OptionMapRWMutex.Unlock()
|
||||||
@@ -159,6 +160,8 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
common.QuotaForNewUser, _ = strconv.Atoi(value)
|
common.QuotaForNewUser, _ = strconv.Atoi(value)
|
||||||
case "QuotaRemindThreshold":
|
case "QuotaRemindThreshold":
|
||||||
common.QuotaRemindThreshold, _ = strconv.Atoi(value)
|
common.QuotaRemindThreshold, _ = strconv.Atoi(value)
|
||||||
|
case "PreConsumedQuota":
|
||||||
|
common.PreConsumedQuota, _ = strconv.Atoi(value)
|
||||||
case "ModelRatio":
|
case "ModelRatio":
|
||||||
err = common.UpdateModelRatioByJSONString(value)
|
err = common.UpdateModelRatioByJSONString(value)
|
||||||
case "TopUpLink":
|
case "TopUpLink":
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
_ "gorm.io/driver/sqlite"
|
_ "gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
@@ -38,7 +37,6 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
|||||||
if key == "" {
|
if key == "" {
|
||||||
return nil, errors.New("未提供 token")
|
return nil, errors.New("未提供 token")
|
||||||
}
|
}
|
||||||
key = strings.Replace(key, "Bearer ", "", 1)
|
|
||||||
token = &Token{}
|
token = &Token{}
|
||||||
err = DB.Where("`key` = ?", key).First(token).Error
|
err = DB.Where("`key` = ?", key).First(token).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ func SetRelayRouter(router *gin.Engine) {
|
|||||||
relayV1Router := router.Group("/v1")
|
relayV1Router := router.Group("/v1")
|
||||||
relayV1Router.Use(middleware.TokenAuth(), middleware.Distribute())
|
relayV1Router.Use(middleware.TokenAuth(), middleware.Distribute())
|
||||||
{
|
{
|
||||||
relayV1Router.GET("/models", controller.Relay)
|
relayV1Router.GET("/models", controller.ListModels)
|
||||||
relayV1Router.GET("/models/:model", controller.Relay)
|
relayV1Router.GET("/models/:model", controller.RetrieveModel)
|
||||||
relayV1Router.POST("/completions", controller.RelayNotImplemented)
|
relayV1Router.POST("/completions", controller.RelayNotImplemented)
|
||||||
relayV1Router.POST("/chat/completions", controller.Relay)
|
relayV1Router.POST("/chat/completions", controller.Relay)
|
||||||
relayV1Router.POST("/edits", controller.RelayNotImplemented)
|
relayV1Router.POST("/edits", controller.RelayNotImplemented)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const SystemSetting = () => {
|
|||||||
RegisterEnabled: '',
|
RegisterEnabled: '',
|
||||||
QuotaForNewUser: 0,
|
QuotaForNewUser: 0,
|
||||||
QuotaRemindThreshold: 0,
|
QuotaRemindThreshold: 0,
|
||||||
|
PreConsumedQuota: 0,
|
||||||
ModelRatio: '',
|
ModelRatio: '',
|
||||||
TopUpLink: '',
|
TopUpLink: '',
|
||||||
AutomaticDisableChannelEnabled: '',
|
AutomaticDisableChannelEnabled: '',
|
||||||
@@ -98,6 +99,7 @@ const SystemSetting = () => {
|
|||||||
name === 'TurnstileSecretKey' ||
|
name === 'TurnstileSecretKey' ||
|
||||||
name === 'QuotaForNewUser' ||
|
name === 'QuotaForNewUser' ||
|
||||||
name === 'QuotaRemindThreshold' ||
|
name === 'QuotaRemindThreshold' ||
|
||||||
|
name === 'PreConsumedQuota' ||
|
||||||
name === 'ModelRatio' ||
|
name === 'ModelRatio' ||
|
||||||
name === 'TopUpLink'
|
name === 'TopUpLink'
|
||||||
) {
|
) {
|
||||||
@@ -119,6 +121,9 @@ const SystemSetting = () => {
|
|||||||
if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
|
if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
|
||||||
await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
|
await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
|
||||||
}
|
}
|
||||||
|
if (originInputs['PreConsumedQuota'] !== inputs.PreConsumedQuota) {
|
||||||
|
await updateOption('PreConsumedQuota', inputs.PreConsumedQuota);
|
||||||
|
}
|
||||||
if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
|
if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
|
||||||
if (!verifyJSON(inputs.ModelRatio)) {
|
if (!verifyJSON(inputs.ModelRatio)) {
|
||||||
showError('模型倍率不是合法的 JSON 字符串');
|
showError('模型倍率不是合法的 JSON 字符串');
|
||||||
@@ -272,7 +277,7 @@ const SystemSetting = () => {
|
|||||||
<Header as='h3'>
|
<Header as='h3'>
|
||||||
运营设置
|
运营设置
|
||||||
</Header>
|
</Header>
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={4}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='新用户初始配额'
|
label='新用户初始配额'
|
||||||
name='QuotaForNewUser'
|
name='QuotaForNewUser'
|
||||||
@@ -302,6 +307,16 @@ const SystemSetting = () => {
|
|||||||
min='0'
|
min='0'
|
||||||
placeholder='低于此额度时将发送邮件提醒用户'
|
placeholder='低于此额度时将发送邮件提醒用户'
|
||||||
/>
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='请求预扣费额度'
|
||||||
|
name='PreConsumedQuota'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.PreConsumedQuota}
|
||||||
|
type='number'
|
||||||
|
min='0'
|
||||||
|
placeholder='请求结束后多退少补'
|
||||||
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
@@ -321,7 +336,7 @@ const SystemSetting = () => {
|
|||||||
</Header>
|
</Header>
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='最长回应时间'
|
label='最长响应时间'
|
||||||
name='ChannelDisableThreshold'
|
name='ChannelDisableThreshold'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { API, showError, showSuccess } from '../helpers';
|
import { API, showError, showSuccess } from '../helpers';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE } from '../constants';
|
import { ITEMS_PER_PAGE } from '../constants';
|
||||||
|
import { renderText } from '../helpers/render';
|
||||||
|
|
||||||
function renderRole(role) {
|
function renderRole(role) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@@ -64,7 +65,7 @@ const UsersTable = () => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
const res = await API.post('/api/user/manage', {
|
const res = await API.post('/api/user/manage', {
|
||||||
username,
|
username,
|
||||||
action,
|
action
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -161,18 +162,18 @@ const UsersTable = () => {
|
|||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
sortUser('username');
|
sortUser('id');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
用户名
|
ID
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
sortUser('display_name');
|
sortUser('username');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
显示名称
|
用户名
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
@@ -220,9 +221,17 @@ const UsersTable = () => {
|
|||||||
if (user.deleted) return <></>;
|
if (user.deleted) return <></>;
|
||||||
return (
|
return (
|
||||||
<Table.Row key={user.id}>
|
<Table.Row key={user.id}>
|
||||||
<Table.Cell>{user.username}</Table.Cell>
|
<Table.Cell>{user.id}</Table.Cell>
|
||||||
<Table.Cell>{user.display_name}</Table.Cell>
|
<Table.Cell>
|
||||||
<Table.Cell>{user.email ? user.email : '无'}</Table.Cell>
|
<Popup
|
||||||
|
content={user.email ? user.email : '未绑定邮箱地址'}
|
||||||
|
key={user.display_name}
|
||||||
|
header={user.display_name ? user.display_name : user.username}
|
||||||
|
trigger={<span>{renderText(user.username, 10)}</span>}
|
||||||
|
hoverable
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>{user.email ? renderText(user.email, 30) : '无'}</Table.Cell>
|
||||||
<Table.Cell>{user.quota}</Table.Cell>
|
<Table.Cell>{user.quota}</Table.Cell>
|
||||||
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
<Table.Cell>{renderRole(user.role)}</Table.Cell>
|
||||||
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
||||||
@@ -234,6 +243,7 @@ const UsersTable = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
manageUser(user.username, 'promote', idx);
|
manageUser(user.username, 'promote', idx);
|
||||||
}}
|
}}
|
||||||
|
disabled={user.role === 100}
|
||||||
>
|
>
|
||||||
提升
|
提升
|
||||||
</Button>
|
</Button>
|
||||||
@@ -243,12 +253,13 @@ const UsersTable = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
manageUser(user.username, 'demote', idx);
|
manageUser(user.username, 'demote', idx);
|
||||||
}}
|
}}
|
||||||
|
disabled={user.role === 100}
|
||||||
>
|
>
|
||||||
降级
|
降级
|
||||||
</Button>
|
</Button>
|
||||||
<Popup
|
<Popup
|
||||||
trigger={
|
trigger={
|
||||||
<Button size='small' negative>
|
<Button size='small' negative disabled={user.role === 100}>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -274,6 +285,7 @@ const UsersTable = () => {
|
|||||||
idx
|
idx
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
disabled={user.role === 100}
|
||||||
>
|
>
|
||||||
{user.status === 1 ? '禁用' : '启用'}
|
{user.status === 1 ? '禁用' : '启用'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -281,6 +293,7 @@ const UsersTable = () => {
|
|||||||
size={'small'}
|
size={'small'}
|
||||||
as={Link}
|
as={Link}
|
||||||
to={'/user/edit/' + user.id}
|
to={'/user/edit/' + user.id}
|
||||||
|
disabled={user.role === 100}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
6
web/src/helpers/render.js
Normal file
6
web/src/helpers/render.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function renderText(text, limit) {
|
||||||
|
if (text.length > limit) {
|
||||||
|
return text.slice(0, limit - 3) + '...';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user