mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-22 07:31:05 +08:00
Go CLI: add admin stats commands (#17090)
### Summary ``` RAGFlow(admin)> STATS USERS TOP 5 FROM '2026-01-01' TO '2026-02-01'; +-----------------------+----------------------------------------------+------------+---------------------+-----+ | command | error | from_date | to_date | top | +-----------------------+----------------------------------------------+------------+---------------------+-----+ | get_token_users_stats | 'Get API token users stats' is not supported | 2026-01-01 | 2026-02-01 23:59:59 | 5 | +-----------------------+----------------------------------------------+------------+---------------------+-----+ RAGFlow(admin)> STATS USER 'aaa@aaa.com' FROM '2026-01-01' TO '2026-02-01' MONTH; +-----------------+----------------------------------------+------------+-------------+---------------------+-------------+ | command | error | from_date | granularity | to_date | user_name | +-----------------+----------------------------------------+------------+-------------+---------------------+-------------+ | get_token_stats | 'Get API token stats' is not supported | 2026-01-01 | month | 2026-02-01 23:59:59 | aaa@aaa.com | +-----------------+----------------------------------------+------------+-------------+---------------------+-------------+ RAGFlow(admin)> STATS SUMMARY FROM '2026-01-01' TO '2026-02-01' MONTH; +-----------+------------------------------------------------+ | field | value | +-----------+------------------------------------------------+ | to_date | 2026-02-01 23:59:59 | | command | get_token_stats_summary | | error | 'Get API token stats summary' is not supported | | from_date | 2026-01-01 | +-----------+------------------------------------------------+ ``` --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
@@ -19,6 +19,7 @@ package cli
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"ragflow/internal/common"
|
||||
)
|
||||
|
||||
@@ -1733,6 +1734,102 @@ func (c *CLI) AdminShowUsersPlanQuotaCommand(cmd *Command) (ResponseIf, error) {
|
||||
return HandleCommonDataResponse(resp, "get users plan quota")
|
||||
}
|
||||
|
||||
// AdminStatsUserCommand stats user token usage
|
||||
func (c *CLI) AdminStatsUserCommand(cmd *Command) (ResponseIf, error) {
|
||||
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
|
||||
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
|
||||
}
|
||||
|
||||
userName, ok := cmd.Params["user_name"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("user name not provided")
|
||||
}
|
||||
fromTime, _ := cmd.Params["from"].(string)
|
||||
toTime, _ := cmd.Params["to"].(string)
|
||||
granularity, ok := cmd.Params["granularity"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("granularity not provided")
|
||||
}
|
||||
|
||||
q := url.Values{}
|
||||
q.Set("user_name", userName)
|
||||
if fromTime != "" {
|
||||
q.Set("from", fromTime)
|
||||
}
|
||||
if toTime != "" {
|
||||
q.Set("to", toTime)
|
||||
}
|
||||
q.Set("granularity", granularity)
|
||||
|
||||
baseUrl := fmt.Sprintf("/admin/stats/token?%s", q.Encode())
|
||||
|
||||
resp, err := c.AdminServerClient.Request("GET", baseUrl, "admin", nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get stats user: %w", err)
|
||||
}
|
||||
|
||||
return HandleCommonResponse(resp, "get stats user")
|
||||
}
|
||||
|
||||
// AdminStatsUsersCommand stats users token usage
|
||||
func (c *CLI) AdminStatsUsersCommand(cmd *Command) (ResponseIf, error) {
|
||||
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
|
||||
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
|
||||
}
|
||||
|
||||
top, ok := cmd.Params["top"].(int)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("top not provided")
|
||||
}
|
||||
fromTime, _ := cmd.Params["from"].(string)
|
||||
toTime, _ := cmd.Params["to"].(string)
|
||||
|
||||
q := url.Values{}
|
||||
q.Set("top", fmt.Sprintf("%d", top))
|
||||
if fromTime != "" {
|
||||
q.Set("from", fromTime)
|
||||
}
|
||||
if toTime != "" {
|
||||
q.Set("to", toTime)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("/admin/stats/token/users?%s", q.Encode())
|
||||
|
||||
resp, err := c.AdminServerClient.Request("GET", url, "web", nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get stats: %w", err)
|
||||
}
|
||||
|
||||
return HandleCommonResponse(resp, "get stats")
|
||||
}
|
||||
|
||||
// AdminStatsSummaryCommand stats summary token usage
|
||||
func (c *CLI) AdminStatsSummaryCommand(cmd *Command) (ResponseIf, error) {
|
||||
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
|
||||
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
|
||||
}
|
||||
|
||||
fromTime, _ := cmd.Params["from"].(string)
|
||||
toTime, _ := cmd.Params["to"].(string)
|
||||
|
||||
q := url.Values{}
|
||||
if fromTime != "" {
|
||||
q.Set("from", fromTime)
|
||||
}
|
||||
if toTime != "" {
|
||||
q.Set("to", toTime)
|
||||
}
|
||||
|
||||
baseUrl := fmt.Sprintf("/admin/stats/token/summary?%s", q.Encode())
|
||||
|
||||
resp, err := c.AdminServerClient.Request("GET", baseUrl, "admin", nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get stats summary: %w", err)
|
||||
}
|
||||
|
||||
return HandleCommonDataResponse(resp, "get stats summary")
|
||||
}
|
||||
|
||||
// ListUsers lists all users (admin mode only)
|
||||
// Returns (result_map, error) - result_map is non-nil for benchmark mode
|
||||
func (c *CLI) AdminListUsersCommand(cmd *Command) (ResponseIf, error) {
|
||||
|
||||
@@ -949,6 +949,151 @@ func (p *Parser) parseCommonShowPoolModel() (*Command, error) {
|
||||
|
||||
// endregion SHOW commands
|
||||
|
||||
// region STATS commands
|
||||
|
||||
// STATS USER 'user_name' FROM <start_time> TO <end_time> HOUR, DAY, MONTH
|
||||
// STATS USER 'user_name' FROM <start_time> TO <end_time>
|
||||
// STATS USERS TOP <n> FROM <start_time> TO <end_time>
|
||||
// STATS SUMMARY FROM <start_time> TO <end_time>
|
||||
func (p *Parser) parseAdminStatsCommands() (*Command, error) {
|
||||
p.nextToken() // consume STATS
|
||||
|
||||
switch p.curToken.Type {
|
||||
case TokenUser:
|
||||
return p.parseAdminStatsUser()
|
||||
case TokenUsers:
|
||||
return p.parseAdminStatsUsers()
|
||||
case TokenSummary:
|
||||
return p.parseAdminStatsSummary()
|
||||
default:
|
||||
return nil, fmt.Errorf("expected USER or MODEL or SUMMARY after STATS")
|
||||
}
|
||||
}
|
||||
|
||||
// STATS USER 'user_name' FROM <start_time> TO <end_time> HOUR, DAY, MONTH
|
||||
// STATS USER 'user_name' FROM <start_time> TO <end_time>
|
||||
func (p *Parser) parseAdminStatsUser() (*Command, error) {
|
||||
p.nextToken() // consume USER
|
||||
|
||||
cmd := NewCommand("admin_stats_user")
|
||||
|
||||
userName, err := p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.Params["user_name"] = userName
|
||||
p.nextToken()
|
||||
|
||||
// Parse time range
|
||||
if p.curToken.Type == TokenFrom {
|
||||
p.nextToken()
|
||||
cmd.Params["from"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
if p.curToken.Type == TokenTo {
|
||||
p.nextToken()
|
||||
cmd.Params["to"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
switch p.curToken.Type {
|
||||
case TokenHour:
|
||||
p.nextToken()
|
||||
cmd.Params["granularity"] = "HOUR"
|
||||
case TokenDay:
|
||||
p.nextToken()
|
||||
cmd.Params["granularity"] = "DAY"
|
||||
case TokenMonth:
|
||||
p.nextToken()
|
||||
cmd.Params["granularity"] = "MONTH"
|
||||
default:
|
||||
p.nextToken()
|
||||
cmd.Params["granularity"] = "HOUR"
|
||||
}
|
||||
|
||||
// Semicolon is optional
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// STATS USERS TOP <n> FROM <start_time> TO <end_time>
|
||||
func (p *Parser) parseAdminStatsUsers() (*Command, error) {
|
||||
p.nextToken() // consume USERS
|
||||
cmd := NewCommand("admin_stats_users")
|
||||
|
||||
cmd.Params["top"] = 10
|
||||
|
||||
var err error
|
||||
if p.curToken.Type == TokenTop {
|
||||
p.nextToken()
|
||||
cmd.Params["top"], err = p.parseNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
if p.curToken.Type == TokenFrom {
|
||||
p.nextToken()
|
||||
cmd.Params["from"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
if p.curToken.Type == TokenTo {
|
||||
p.nextToken()
|
||||
cmd.Params["to"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// STATS SUMMARY FROM <start_time> TO <end_time>
|
||||
func (p *Parser) parseAdminStatsSummary() (*Command, error) {
|
||||
p.nextToken() // consume SUMMARY
|
||||
cmd := NewCommand("admin_stats_summary")
|
||||
|
||||
var err error
|
||||
if p.curToken.Type == TokenFrom {
|
||||
p.nextToken()
|
||||
cmd.Params["from"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
if p.curToken.Type == TokenTo {
|
||||
p.nextToken()
|
||||
cmd.Params["to"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// endregion STATS commands
|
||||
|
||||
// CHECK LICENSE
|
||||
// CHECK PROVIDER 'provider_name' REGION 'region_name' KEY 'api_key' [URL 'base_url'];
|
||||
// CHECK PROVIDER 'provider_name' INSTANCE 'instance_name';
|
||||
|
||||
@@ -207,6 +207,12 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) {
|
||||
return c.AdminShowUsersPlanSummaryCommand(cmd)
|
||||
case "admin_show_users_plan_quota":
|
||||
return c.AdminShowUsersPlanQuotaCommand(cmd)
|
||||
case "admin_stats_user":
|
||||
return c.AdminStatsUserCommand(cmd)
|
||||
case "admin_stats_users":
|
||||
return c.AdminStatsUsersCommand(cmd)
|
||||
case "admin_stats_summary":
|
||||
return c.AdminStatsSummaryCommand(cmd)
|
||||
case "admin_list_users_command":
|
||||
return c.AdminListUsersCommand(cmd)
|
||||
case "admin_list_users_condition_command":
|
||||
|
||||
@@ -513,6 +513,8 @@ func (l *Lexer) lookupIdent(ident string) Token {
|
||||
return Token{Type: TokenAnalyze, Value: ident}
|
||||
case "SUMMARY":
|
||||
return Token{Type: TokenSummary, Value: ident}
|
||||
case "STATS":
|
||||
return Token{Type: TokenStats, Value: ident}
|
||||
case "STORAGE":
|
||||
return Token{Type: TokenStorage, Value: ident}
|
||||
case "QUOTA":
|
||||
@@ -523,6 +525,12 @@ func (l *Lexer) lookupIdent(ident string) Token {
|
||||
return Token{Type: TokenOrphan, Value: ident}
|
||||
case "DAYS":
|
||||
return Token{Type: TokenDays, Value: ident}
|
||||
case "HOUR":
|
||||
return Token{Type: TokenHour, Value: ident}
|
||||
case "DAY":
|
||||
return Token{Type: TokenDay, Value: ident}
|
||||
case "MONTH":
|
||||
return Token{Type: TokenMonth, Value: ident}
|
||||
case "WINDOW":
|
||||
return Token{Type: TokenWindow, Value: ident}
|
||||
case "ACTIVITY":
|
||||
|
||||
@@ -94,6 +94,8 @@ func (p *Parser) parseAdminCommand() (*Command, error) {
|
||||
return p.parseAdminListCommands()
|
||||
case TokenShow:
|
||||
return p.parseAdminShowCommands()
|
||||
case TokenStats:
|
||||
return p.parseAdminStatsCommands()
|
||||
case TokenCheck:
|
||||
return p.parseAdminCheck()
|
||||
case TokenCreate:
|
||||
|
||||
@@ -187,10 +187,14 @@ const (
|
||||
TokenNoACK
|
||||
TokenAnalyze
|
||||
TokenSummary
|
||||
TokenStats
|
||||
TokenStorage
|
||||
TokenQuota
|
||||
TokenTree
|
||||
TokenOrphan
|
||||
TokenHour
|
||||
TokenDay
|
||||
TokenMonth
|
||||
TokenDays
|
||||
TokenWindow
|
||||
TokenActivity
|
||||
|
||||
Reference in New Issue
Block a user