diff --git a/internal/admin/enterprise_handler.go b/internal/admin/enterprise_handler.go index d6cc22981c..9e0481dbbf 100644 --- a/internal/admin/enterprise_handler.go +++ b/internal/admin/enterprise_handler.go @@ -395,6 +395,108 @@ func (h *Handler) ShowUserPermission(c *gin.Context) { success(c, permissions, "") } +// ListUserDatasets handle show user datasets +func (h *Handler) ListUserDatasets(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + datasets, err := h.service.ListUserDatasets(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, datasets, "") +} + +// ListUserAgents handle show user agents +func (h *Handler) ListUserAgents(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + agents, err := h.service.ListUserAgents(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, agents, "") +} + +// ListUserChats handle show user chats +func (h *Handler) ListUserChats(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + chats, err := h.service.ListUserChats(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, chats, "") +} + +// ListUserSearches handle show user searches +func (h *Handler) ListUserSearches(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + searches, err := h.service.ListUserSearches(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, searches, "") +} + +// ListUserModels handle show user models +func (h *Handler) ListUserModels(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + models, err := h.service.ListUserModels(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, models, "") +} + +// ListUserFiles handle show user files +func (h *Handler) ListUserFiles(c *gin.Context) { + username := c.Param("username") + if username == "" { + errorResponse(c, "Username is required", 400) + return + } + + files, err := h.service.ListUserFiles(username) + if err != nil { + errorResponse(c, err.Error(), 500) + return + } + + success(c, files, "") +} + // ShowUsersSummary handle show users summary func (h *Handler) ShowUsersSummary(c *gin.Context) { usersSummary, err := h.service.ShowUsersSummary() diff --git a/internal/admin/enterprise_service.go b/internal/admin/enterprise_service.go index 51a685ddcb..4d2f996d9a 100644 --- a/internal/admin/enterprise_service.go +++ b/internal/admin/enterprise_service.go @@ -222,6 +222,132 @@ func (s *Service) ShowUserPermission(email string) (map[string]interface{}, erro return result, nil } +// ListUserDatasets show user datasets for enterprise edition +func (s *Service) ListUserDatasets(email string) ([]map[string]interface{}, error) { + // Query user by email + var user entity.User + err := dao.DB.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, common.ErrUserNotFound + } + + result := []map[string]interface{}{ + { + "command": "list_user_datasets", + "email": user.Email, + "nickname": user.Nickname, + "error": "'list user datasets' is implemented in enterprise edition", + }, + } + + return result, nil +} + +// ListUserAgents show user agents for enterprise edition +func (s *Service) ListUserAgents(email string) ([]map[string]interface{}, error) { + // Query user by email + var user entity.User + err := dao.DB.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, common.ErrUserNotFound + } + + result := []map[string]interface{}{ + { + "command": "list_user_agents", + "email": user.Email, + "nickname": user.Nickname, + "error": "'list user agents' is implemented in enterprise edition", + }, + } + + return result, nil +} + +// ListUserChats show user chats for enterprise edition +func (s *Service) ListUserChats(email string) ([]map[string]interface{}, error) { + // Query user by email + var user entity.User + err := dao.DB.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, common.ErrUserNotFound + } + + result := []map[string]interface{}{ + { + "command": "list_user_chats", + "email": user.Email, + "nickname": user.Nickname, + "error": "'list user chats' is implemented in enterprise edition", + }, + } + + return result, nil +} + +// ListUserSearches show user searches for enterprise edition +func (s *Service) ListUserSearches(email string) ([]map[string]interface{}, error) { + // Query user by email + var user entity.User + err := dao.DB.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, common.ErrUserNotFound + } + + result := []map[string]interface{}{ + { + "command": "list_user_searches", + "email": user.Email, + "nickname": user.Nickname, + "error": "'list user searches' is implemented in enterprise edition", + }, + } + + return result, nil +} + +// ListUserModels show user models for enterprise edition +func (s *Service) ListUserModels(email string) ([]map[string]interface{}, error) { + // Query user by email + var user entity.User + err := dao.DB.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, common.ErrUserNotFound + } + + result := []map[string]interface{}{ + { + "command": "list_user_models", + "email": user.Email, + "nickname": user.Nickname, + "error": "'list user models' is implemented in enterprise edition", + }, + } + + return result, nil +} + +// ListUserFiles show user files for enterprise edition +func (s *Service) ListUserFiles(email string) ([]map[string]interface{}, error) { + // Query user by email + var user entity.User + err := dao.DB.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, common.ErrUserNotFound + } + + result := []map[string]interface{}{ + { + "command": "list_user_files", + "email": user.Email, + "nickname": user.Nickname, + "error": "'list user files' is implemented in enterprise edition", + }, + } + + return result, nil +} + // ShowUsersSummary show users summary for enterprise edition func (s *Service) ShowUsersSummary() (map[string]interface{}, error) { result := map[string]interface{}{ diff --git a/internal/admin/handler.go b/internal/admin/handler.go index 51f1a2afa2..21f9c7a666 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -443,40 +443,6 @@ func (h *Handler) RevokeAdmin(c *gin.Context) { successNoData(c, "Admin role revoked") } -// GetUserDatasets handle get user datasets -func (h *Handler) GetUserDatasets(c *gin.Context) { - username := c.Param("username") - if username == "" { - errorResponse(c, "Username is required", 400) - return - } - - datasets, err := h.service.GetUserDatasets(username) - if err != nil { - errorResponse(c, err.Error(), 500) - return - } - - success(c, datasets, "") -} - -// GetUserAgents handle get user agents -func (h *Handler) GetUserAgents(c *gin.Context) { - username := c.Param("username") - if username == "" { - errorResponse(c, "Username is required", 400) - return - } - - agents, err := h.service.GetUserAgents(username) - if err != nil { - errorResponse(c, err.Error(), 500) - return - } - - success(c, agents, "") -} - // ListUserAPITokens handle get user API keys func (h *Handler) ListUserAPITokens(c *gin.Context) { username := c.Param("username") diff --git a/internal/admin/router.go b/internal/admin/router.go index 9af7b179d5..f7c7a82bbd 100644 --- a/internal/admin/router.go +++ b/internal/admin/router.go @@ -68,8 +68,6 @@ func (r *Router) Setup(engine *gin.Engine) { protected.PUT("/users/:username/activate", r.handler.UpdateUserActivateStatus) protected.PUT("/users/:username/admin", r.handler.GrantAdmin) protected.DELETE("/users/:username/admin", r.handler.RevokeAdmin) - protected.GET("/users/:username/datasets", r.handler.GetUserDatasets) - protected.GET("/users/:username/agents", r.handler.GetUserAgents) // For enterprise edition protected.GET("/users/:username/activity", r.handler.ShowUserActivity) @@ -80,6 +78,12 @@ func (r *Router) Setup(engine *gin.Engine) { protected.GET("/users/:username/index", r.handler.ShowUserIndex) protected.PUT("/users/:username/role", r.handler.UpdateUserRole) protected.GET("/users/:username/permission", r.handler.ShowUserPermission) + protected.GET("/users/:username/datasets", r.handler.ListUserDatasets) + protected.GET("/users/:username/agents", r.handler.ListUserAgents) + protected.GET("/users/:username/chats", r.handler.ListUserChats) + protected.GET("/users/:username/searches", r.handler.ListUserSearches) + protected.GET("/users/:username/models", r.handler.ListUserModels) + protected.GET("/users/:username/files", r.handler.ListUserFiles) protected.GET("/users/summary", r.handler.ShowUsersSummary) protected.GET("/users/activity", r.handler.ShowUsersActivity) protected.GET("/users/reports", r.handler.ListUsersReports) diff --git a/internal/cli/admin_command.go b/internal/cli/admin_command.go index f53d5f9d99..6f49dee5e6 100644 --- a/internal/cli/admin_command.go +++ b/internal/cli/admin_command.go @@ -2393,6 +2393,211 @@ func (c *CLI) AdminListUserIngestionTasksCommand(cmd *Command) (ResponseIf, erro return &result, nil } +func (c *CLI) AdminListUserDatasetsCommand(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") + } + + apiURL := fmt.Sprintf("/admin/users/%s/datasets", userName) + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list user %s datasets: %w", userName, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list user %s datasets: HTTP %d, body: %s", userName, resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list user %s datasets failed: invalid JSON (%w)", userName, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} +func (c *CLI) AdminListUserAgentsCommand(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") + } + + apiURL := fmt.Sprintf("/admin/users/%s/agents", userName) + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list user %s agents: %w", userName, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list user %s agents: HTTP %d, body: %s", userName, resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list user %s agents failed: invalid JSON (%w)", userName, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} +func (c *CLI) AdminListUserChatsCommand(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") + } + + apiURL := fmt.Sprintf("/admin/users/%s/chats", userName) + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list user %s chats: %w", userName, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list user %s chats: HTTP %d, body: %s", userName, resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list user %s chats failed: invalid JSON (%w)", userName, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} +func (c *CLI) AdminListUserSearchesCommand(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") + } + + apiURL := fmt.Sprintf("/admin/users/%s/searches", userName) + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list user %s searches: %w", userName, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list user %s searches: HTTP %d, body: %s", userName, resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list user %s searches failed: invalid JSON (%w)", userName, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} +func (c *CLI) AdminListUserModelsCommand(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") + } + + apiURL := fmt.Sprintf("/admin/users/%s/models", userName) + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list user %s models: %w", userName, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list user %s models: HTTP %d, body: %s", userName, resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list user %s models failed: invalid JSON (%w)", userName, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} +func (c *CLI) AdminListUserFilesCommand(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") + } + + apiURL := fmt.Sprintf("/admin/users/%s/files", userName) + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list user %s files: %w", userName, err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list user %s files: HTTP %d, body: %s", userName, resp.StatusCode, string(resp.Body)) + } + + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list user %s files failed: invalid JSON (%w)", userName, err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + + result.Duration = resp.Duration + return &result, nil +} + func (c *CLI) AdminStopUserIngestionTasksCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index e411ec8e46..3d58435aca 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -2578,8 +2578,31 @@ func (p *Parser) parseAdminListUserCommand() (*Command, error) { cmd.Params["status"] = status p.nextToken() } + case TokenDatasets: + p.nextToken() + cmd = NewCommand("admin_list_user_datasets_command") + case TokenAgents: + p.nextToken() + cmd = NewCommand("admin_list_user_agents_command") + case TokenChats: + p.nextToken() + cmd = NewCommand("admin_list_user_chats_command") + case TokenSearches: + p.nextToken() + cmd = NewCommand("admin_list_user_searches_command") + case TokenModels: + p.nextToken() + cmd = NewCommand("admin_list_user_models_command") + case TokenFiles: + p.nextToken() + cmd = NewCommand("admin_list_user_files_command") default: - return nil, fmt.Errorf("expected INGESTION after USER") + return nil, fmt.Errorf("expected INGESTION or DATASETS or AGENTS or CHATS or SEARCHES or MODELS or FILES after USER") + } + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() } cmd.Params["user_name"] = userName diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go index 13587abe8f..e0c763cdfc 100644 --- a/internal/cli/cli_http.go +++ b/internal/cli/cli_http.go @@ -163,6 +163,18 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.AdminPurgeUsersCommand(cmd) case "admin_list_user_ingestion_tasks_command": return c.AdminListUserIngestionTasksCommand(cmd) + case "admin_list_user_datasets_command": + return c.AdminListUserDatasetsCommand(cmd) + case "admin_list_user_agents_command": + return c.AdminListUserAgentsCommand(cmd) + case "admin_list_user_chats_command": + return c.AdminListUserChatsCommand(cmd) + case "admin_list_user_searches_command": + return c.AdminListUserSearchesCommand(cmd) + case "admin_list_user_models_command": + return c.AdminListUserModelsCommand(cmd) + case "admin_list_user_files_command": + return c.AdminListUserFilesCommand(cmd) case "admin_stop_user_ingestion_tasks_command": return c.AdminStopUserIngestionTasksCommand(cmd) case "admin_remove_user_ingestion_tasks_command": diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index 4308342a30..07c03ab936 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -243,6 +243,8 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenOf, Value: ident} case "AGENTS": return Token{Type: TokenAgents, Value: ident} + case "SEARCHES": + return Token{Type: TokenSearches, Value: ident} case "ROLE": return Token{Type: TokenRole, Value: ident} case "ROLES": diff --git a/internal/cli/types.go b/internal/cli/types.go index c67f385424..ad38489903 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -54,6 +54,7 @@ const ( TokenChunkStore TokenOf TokenAgents + TokenSearches TokenRole TokenRoles TokenDescription