Files
ragflow/internal/cli/client.go
chanx 0da9c4618d feat(cli): Enhance CLI functionality and add administrator mode support (#13539)
### What problem does this PR solve?

feat(cli): Enhance CLI functionality and add administrator mode support

- Modify `parseActivateUser` in `parser.go` to support 'on'/'off' states
- Add administrator mode switching and host port settings functionality
to `cli.go`
- Implement user management API calls in `client.go`

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2026-03-12 13:33:13 +08:00

953 lines
26 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cli
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"unsafe"
)
// PasswordPromptFunc is a function type for password input
type PasswordPromptFunc func(prompt string) (string, error)
// RAGFlowClient handles API interactions with the RAGFlow server
type RAGFlowClient struct {
HTTPClient *HTTPClient
ServerType string // "admin" or "user"
PasswordPrompt PasswordPromptFunc // Function for password input
}
// NewRAGFlowClient creates a new RAGFlow client
func NewRAGFlowClient(serverType string) *RAGFlowClient {
httpClient := NewHTTPClient()
// Set port from configuration file based on server type
if serverType == "admin" {
httpClient.Port = 9381
} else {
httpClient.Port = 9380
}
return &RAGFlowClient{
HTTPClient: httpClient,
ServerType: serverType,
}
}
// LoginUserInteractive performs interactive login with username and password
func (c *RAGFlowClient) LoginUserInteractive(username, password string) error {
// First, ping the server to check if it's available
// For admin mode, use /admin/ping with useAPIBase=true
// For user mode, use /system/ping with useAPIBase=false
var pingPath string
var useAPIBase bool
if c.ServerType == "admin" {
pingPath = "/admin/ping"
useAPIBase = true
} else {
pingPath = "/system/ping"
useAPIBase = false
}
resp, err := c.HTTPClient.Request("GET", pingPath, useAPIBase, "web", nil, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
if resp.StatusCode != 200 {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
// Check response - admin returns JSON with message "PONG", user returns plain "pong"
resJSON, err := resp.JSON()
if err == nil {
// Admin mode returns {"code":0,"message":"PONG"}
if msg, ok := resJSON["message"].(string); !ok || msg != "PONG" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
} else {
// User mode returns plain "pong"
if string(resp.Body) != "pong" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
}
// If password is not provided, prompt for it
if password == "" {
fmt.Printf("password for %s: ", username)
var err error
password, err = readPassword()
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = strings.TrimSpace(password)
}
// Login
token, err := c.loginUser(username, password)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
c.HTTPClient.LoginToken = token
fmt.Printf("Login user %s successfully\n", username)
return nil
}
// LoginUser performs user login
func (c *RAGFlowClient) LoginUser(cmd *Command) error {
// First, ping the server to check if it's available
// For admin mode, use /admin/ping with useAPIBase=true
// For user mode, use /system/ping with useAPIBase=false
var pingPath string
var useAPIBase bool
if c.ServerType == "admin" {
pingPath = "/admin/ping"
useAPIBase = true
} else {
pingPath = "/system/ping"
useAPIBase = false
}
resp, err := c.HTTPClient.Request("GET", pingPath, useAPIBase, "web", nil, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
if resp.StatusCode != 200 {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
// Check response - admin returns JSON with message "PONG", user returns plain "pong"
resJSON, err := resp.JSON()
if err == nil {
// Admin mode returns {"code":0,"message":"PONG"}
if msg, ok := resJSON["message"].(string); !ok || msg != "PONG" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
} else {
// User mode returns plain "pong"
if string(resp.Body) != "pong" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
}
email, ok := cmd.Params["email"].(string)
if !ok {
return fmt.Errorf("email not provided")
}
// Get password from user input (hidden)
var password string
if c.PasswordPrompt != nil {
pwd, err := c.PasswordPrompt(fmt.Sprintf("password for %s: ", email))
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = pwd
} else {
fmt.Printf("password for %s: ", email)
pwd, err := readPassword()
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = pwd
}
password = strings.TrimSpace(password)
// Login
token, err := c.loginUser(email, password)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
c.HTTPClient.LoginToken = token
fmt.Printf("Login user %s successfully\n", email)
return nil
}
// loginUser performs the actual login request
func (c *RAGFlowClient) loginUser(email, password string) (string, error) {
// Encrypt password using scrypt (same as Python implementation)
encryptedPassword, err := EncryptPassword(password)
if err != nil {
return "", fmt.Errorf("failed to encrypt password: %w", err)
}
payload := map[string]interface{}{
"email": email,
"password": encryptedPassword,
}
var path string
if c.ServerType == "admin" {
path = "/admin/login"
} else {
path = "/user/login"
}
resp, err := c.HTTPClient.Request("POST", path, c.ServerType == "admin", "", nil, payload)
if err != nil {
return "", err
}
resJSON, err := resp.JSON()
if err != nil {
return "", fmt.Errorf("login failed: invalid JSON response (%w)", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return "", fmt.Errorf("login failed: %s", msg)
}
token := resp.Headers.Get("Authorization")
if token == "" {
return "", fmt.Errorf("login failed: missing Authorization header")
}
return token, nil
}
// PingServer pings the server to check if it's alive
// Returns benchmark result map if iterations > 1, otherwise prints status
func (c *RAGFlowClient) PingServer(cmd *Command) (map[string]interface{}, error) {
// Get iterations from command params (for benchmark)
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
if iterations > 1 {
// Benchmark mode: multiple iterations
result, err := c.HTTPClient.RequestWithIterations("GET", "/system/ping", false, "web", nil, nil, iterations)
if err != nil {
return nil, err
}
return result, nil
}
// Single ping mode
resp, err := c.HTTPClient.Request("GET", "/system/ping", false, "web", nil, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Server is down")
return nil, err
}
if resp.StatusCode == 200 && string(resp.Body) == "pong" {
fmt.Println("Server is alive")
} else {
fmt.Printf("Error: %d\n", resp.StatusCode)
}
return nil, nil
}
// ListUserDatasets lists datasets for current user (user mode)
// Returns (result_map, error) - result_map is non-nil for benchmark mode
func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (map[string]interface{}, error) {
if c.ServerType != "user" {
return nil, fmt.Errorf("this command is only allowed in USER mode")
}
// Check for benchmark iterations
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
if iterations > 1 {
// Benchmark mode - return raw result for benchmark stats
return c.HTTPClient.RequestWithIterations("POST", "/kb/list", false, "web", nil, nil, iterations)
}
// Normal mode
resp, err := c.HTTPClient.Request("POST", "/kb/list", false, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list datasets: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to list datasets: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return nil, fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return nil, fmt.Errorf("failed to list datasets: %s", msg)
}
data, ok := resJSON["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
kbs, ok := data["kbs"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format: kbs not found")
}
// Convert to slice of maps
tableData := make([]map[string]interface{}, 0, len(kbs))
for _, kb := range kbs {
if kbMap, ok := kb.(map[string]interface{}); ok {
// Remove avatar field
delete(kbMap, "avatar")
tableData = append(tableData, kbMap)
}
}
PrintTableSimple(tableData)
return nil, nil
}
// ListDatasets lists datasets for a specific user (admin mode)
// Returns (result_map, error) - result_map is non-nil for benchmark mode
func (c *RAGFlowClient) ListDatasets(cmd *Command) (map[string]interface{}, error) {
if c.ServerType != "admin" {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return nil, fmt.Errorf("user_name not provided")
}
// Check for benchmark iterations
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
if iterations > 1 {
// Benchmark mode - return raw result for benchmark stats
return c.HTTPClient.RequestWithIterations("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), true, "admin", nil, nil, iterations)
}
fmt.Printf("Listing all datasets of user: %s\n", userName)
resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), true, "admin", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list datasets: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to list datasets: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return nil, fmt.Errorf("invalid JSON response: %w", err)
}
data, ok := resJSON["data"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
// Convert to slice of maps and remove avatar
tableData := make([]map[string]interface{}, 0, len(data))
for _, item := range data {
if itemMap, ok := item.(map[string]interface{}); ok {
delete(itemMap, "avatar")
tableData = append(tableData, itemMap)
}
}
PrintTableSimple(tableData)
return nil, nil
}
// readPassword reads password from terminal without echoing
func readPassword() (string, error) {
// Check if stdin is a terminal by trying to get terminal size
if isTerminal() {
// Use stty to disable echo
cmd := exec.Command("stty", "-echo")
cmd.Stdin = os.Stdin
if err := cmd.Run(); err != nil {
// Fallback: read normally
return readPasswordFallback()
}
defer func() {
// Re-enable echo
cmd := exec.Command("stty", "echo")
cmd.Stdin = os.Stdin
cmd.Run()
}()
reader := bufio.NewReader(os.Stdin)
password, err := reader.ReadString('\n')
fmt.Println() // New line after password input
if err != nil {
return "", err
}
return strings.TrimSpace(password), nil
}
// Fallback for non-terminal input (e.g., piped input)
return readPasswordFallback()
}
// isTerminal checks if stdin is a terminal
func isTerminal() bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, os.Stdin.Fd(), syscall.TCGETS, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}
// readPasswordFallback reads password as plain text (fallback mode)
func readPasswordFallback() (string, error) {
reader := bufio.NewReader(os.Stdin)
password, err := reader.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(password), nil
}
// getDatasetID gets dataset ID by name
func (c *RAGFlowClient) getDatasetID(datasetName string) (string, error) {
resp, err := c.HTTPClient.Request("POST", "/kb/list", false, "web", nil, nil)
if err != nil {
return "", fmt.Errorf("failed to list datasets: %w", err)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("failed to list datasets: HTTP %d", resp.StatusCode)
}
resJSON, err := resp.JSON()
if err != nil {
return "", fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return "", fmt.Errorf("failed to list datasets: %s", msg)
}
data, ok := resJSON["data"].(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid response format")
}
kbs, ok := data["kbs"].([]interface{})
if !ok {
return "", fmt.Errorf("invalid response format: kbs not found")
}
for _, kb := range kbs {
if kbMap, ok := kb.(map[string]interface{}); ok {
if name, _ := kbMap["name"].(string); name == datasetName {
if id, _ := kbMap["id"].(string); id != "" {
return id, nil
}
}
}
}
return "", fmt.Errorf("dataset '%s' not found", datasetName)
}
// SearchOnDatasets searches for chunks in specified datasets
// Returns (result_map, error) - result_map is non-nil for benchmark mode
func (c *RAGFlowClient) SearchOnDatasets(cmd *Command) (map[string]interface{}, error) {
if c.ServerType != "user" {
return nil, fmt.Errorf("this command is only allowed in USER mode")
}
question, ok := cmd.Params["question"].(string)
if !ok {
return nil, fmt.Errorf("question not provided")
}
datasets, ok := cmd.Params["datasets"].(string)
if !ok {
return nil, fmt.Errorf("datasets not provided")
}
// Parse dataset names (comma-separated) and convert to IDs
datasetNames := strings.Split(datasets, ",")
datasetIDs := make([]string, 0, len(datasetNames))
for _, name := range datasetNames {
name = strings.TrimSpace(name)
id, err := c.getDatasetID(name)
if err != nil {
return nil, err
}
datasetIDs = append(datasetIDs, id)
}
// Check for benchmark iterations
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
payload := map[string]interface{}{
"kb_id": datasetIDs,
"question": question,
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
}
if iterations > 1 {
// Benchmark mode - return raw result for benchmark stats
return c.HTTPClient.RequestWithIterations("POST", "/chunk/retrieval_test", false, "web", nil, payload, iterations)
}
// Normal mode
resp, err := c.HTTPClient.Request("POST", "/chunk/retrieval_test", false, "web", nil, payload)
if err != nil {
return nil, fmt.Errorf("failed to search on datasets: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to search on datasets: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return nil, fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return nil, fmt.Errorf("failed to search on datasets: %s", msg)
}
data, ok := resJSON["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
chunks, ok := data["chunks"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format: chunks not found")
}
// Convert to slice of maps for printing
tableData := make([]map[string]interface{}, 0, len(chunks))
for _, chunk := range chunks {
if chunkMap, ok := chunk.(map[string]interface{}); ok {
row := map[string]interface{}{
"id": chunkMap["chunk_id"],
"content": chunkMap["content_with_weight"],
"document_id": chunkMap["doc_id"],
"dataset_id": chunkMap["kb_id"],
"docnm_kwd": chunkMap["docnm_kwd"],
"image_id": chunkMap["image_id"],
"similarity": chunkMap["similarity"],
"term_similarity": chunkMap["term_similarity"],
"vector_similarity": chunkMap["vector_similarity"],
}
tableData = append(tableData, row)
}
}
PrintTableSimple(tableData)
return nil, nil
}
// ExecuteCommand executes a parsed command
// Returns benchmark result map for commands that support it (e.g., ping_server with iterations > 1)
func (c *RAGFlowClient) ExecuteCommand(cmd *Command) (map[string]interface{}, error) {
switch cmd.Type {
case "login_user":
return nil, c.LoginUser(cmd)
case "ping_server":
return c.PingServer(cmd)
case "benchmark":
return nil, c.RunBenchmark(cmd)
case "list_user_datasets":
return c.ListUserDatasets(cmd)
case "list_datasets":
return c.ListDatasets(cmd)
case "search_on_datasets":
return c.SearchOnDatasets(cmd)
case "list_users":
return c.ListUsers(cmd)
case "grant_admin":
return nil, c.GrantAdmin(cmd)
case "revoke_admin":
return nil, c.RevokeAdmin(cmd)
case "show_current_user":
return c.ShowCurrentUser(cmd)
case "create_user":
return nil, c.CreateUser(cmd)
case "activate_user":
return nil, c.ActivateUser(cmd)
case "alter_user":
return nil, c.AlterUserPassword(cmd)
case "drop_user":
return nil, c.DropUser(cmd)
// TODO: Implement other commands
default:
return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type)
}
}
// ListUsers lists all users (admin mode only)
// Returns (result_map, error) - result_map is non-nil for benchmark mode
func (c *RAGFlowClient) ListUsers(cmd *Command) (map[string]interface{}, error) {
if c.ServerType != "admin" {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode")
}
// Check for benchmark iterations
iterations := 1
if val, ok := cmd.Params["iterations"].(int); ok && val > 1 {
iterations = val
}
if iterations > 1 {
// Benchmark mode - return raw result for benchmark stats
return c.HTTPClient.RequestWithIterations("GET", "/admin/users", true, "admin", nil, nil, iterations)
}
resp, err := c.HTTPClient.Request("GET", "/admin/users", true, "admin", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list users: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to list users: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return nil, fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return nil, fmt.Errorf("failed to list users: %s", msg)
}
data, ok := resJSON["data"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
// Convert to slice of maps and remove sensitive fields
tableData := make([]map[string]interface{}, 0, len(data))
for _, item := range data {
if itemMap, ok := item.(map[string]interface{}); ok {
// Remove sensitive fields
delete(itemMap, "password")
delete(itemMap, "access_token")
tableData = append(tableData, itemMap)
}
}
PrintTableSimple(tableData)
return nil, nil
}
// GrantAdmin grants admin privileges to a user (admin mode only)
func (c *RAGFlowClient) GrantAdmin(cmd *Command) error {
if c.ServerType != "admin" {
return fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return fmt.Errorf("user_name not provided")
}
resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/admin", userName), true, "admin", nil, nil)
if err != nil {
return fmt.Errorf("failed to grant admin: %w", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to grant admin: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return fmt.Errorf("failed to grant admin: %s", msg)
}
fmt.Printf("Admin role granted to user: %s\n", userName)
return nil
}
// RevokeAdmin revokes admin privileges from a user (admin mode only)
func (c *RAGFlowClient) RevokeAdmin(cmd *Command) error {
if c.ServerType != "admin" {
return fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return fmt.Errorf("user_name not provided")
}
resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/users/%s/admin", userName), true, "admin", nil, nil)
if err != nil {
return fmt.Errorf("failed to revoke admin: %w", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to revoke admin: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return fmt.Errorf("failed to revoke admin: %s", msg)
}
fmt.Printf("Admin role revoked from user: %s\n", userName)
return nil
}
// ShowCurrentUser shows the current logged-in user information
// TODO: Implement showing current user information when API is available
func (c *RAGFlowClient) ShowCurrentUser(cmd *Command) (map[string]interface{}, error) {
// TODO: Call the appropriate API to get current user information
// Currently there is no /admin/user/info or /user/info API available
// The /admin/auth API only verifies authorization, does not return user info
return nil, fmt.Errorf("command 'SHOW CURRENT USER' is not yet implemented")
}
// CreateUser creates a new user (admin mode only)
func (c *RAGFlowClient) CreateUser(cmd *Command) error {
if c.ServerType != "admin" {
return fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return fmt.Errorf("user_name not provided")
}
password, ok := cmd.Params["password"].(string)
if !ok {
return fmt.Errorf("password not provided")
}
// Encrypt password using RSA
encryptedPassword, err := EncryptPassword(password)
if err != nil {
return fmt.Errorf("failed to encrypt password: %w", err)
}
payload := map[string]interface{}{
"username": userName,
"password": encryptedPassword,
"role": "user",
}
resp, err := c.HTTPClient.Request("POST", "/admin/users", true, "admin", nil, payload)
if err != nil {
return fmt.Errorf("failed to create user: %w", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to create user: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return fmt.Errorf("failed to create user: %s", msg)
}
fmt.Printf("User created successfully: %s\n", userName)
return nil
}
// ActivateUser activates or deactivates a user (admin mode only)
func (c *RAGFlowClient) ActivateUser(cmd *Command) error {
if c.ServerType != "admin" {
return fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return fmt.Errorf("user_name not provided")
}
activateStatus, ok := cmd.Params["activate_status"].(string)
if !ok {
return fmt.Errorf("activate_status not provided")
}
// Validate activate_status
if activateStatus != "on" && activateStatus != "off" {
return fmt.Errorf("activate_status must be 'on' or 'off'")
}
payload := map[string]interface{}{
"activate_status": activateStatus,
}
resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/activate", userName), true, "admin", nil, payload)
if err != nil {
return fmt.Errorf("failed to update user activate status: %w", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to update user activate status: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return fmt.Errorf("failed to update user activate status: %s", msg)
}
fmt.Printf("User '%s' activate status set to '%s'\n", userName, activateStatus)
return nil
}
// AlterUserPassword changes a user's password (admin mode only)
func (c *RAGFlowClient) AlterUserPassword(cmd *Command) error {
if c.ServerType != "admin" {
return fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return fmt.Errorf("user_name not provided")
}
password, ok := cmd.Params["password"].(string)
if !ok {
return fmt.Errorf("password not provided")
}
// Encrypt password using RSA
encryptedPassword, err := EncryptPassword(password)
if err != nil {
return fmt.Errorf("failed to encrypt password: %w", err)
}
payload := map[string]interface{}{
"new_password": encryptedPassword,
}
resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/password", userName), true, "admin", nil, payload)
if err != nil {
return fmt.Errorf("failed to change user password: %w", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to change user password: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return fmt.Errorf("failed to change user password: %s", msg)
}
fmt.Printf("Password changed for user: %s\n", userName)
return nil
}
// DropUser deletes a user (admin mode only)
func (c *RAGFlowClient) DropUser(cmd *Command) error {
if c.ServerType != "admin" {
return fmt.Errorf("this command is only allowed in ADMIN mode")
}
userName, ok := cmd.Params["user_name"].(string)
if !ok {
return fmt.Errorf("user_name not provided")
}
resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/users/%s", userName), true, "admin", nil, nil)
if err != nil {
return fmt.Errorf("failed to delete user: %w", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to delete user: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
resJSON, err := resp.JSON()
if err != nil {
return fmt.Errorf("invalid JSON response: %w", err)
}
code, ok := resJSON["code"].(float64)
if !ok || code != 0 {
msg, _ := resJSON["message"].(string)
return fmt.Errorf("failed to delete user: %s", msg)
}
fmt.Printf("User deleted: %s\n", userName)
return nil
}