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:
Jin Hai
2026-07-20 14:24:28 +08:00
committed by GitHub
parent 27b6cd1a65
commit 7ad27adecb
9 changed files with 383 additions and 0 deletions

View File

@@ -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';