diff --git a/internal/admin/handler_ee.go b/internal/admin/handler_ee.go index 5ae201fa00..ee299b77a7 100644 --- a/internal/admin/handler_ee.go +++ b/internal/admin/handler_ee.go @@ -823,6 +823,47 @@ func (h *Handler) UpdateSystemLicenseConfig(c *gin.Context) { common.SuccessWithData(c, result, "System license config updated successfully") } +type SetSoftFingerprintRequest struct { + SoftFingerprint string `json:"fingerprint" binding:"required"` +} + +func (h *Handler) SetSoftFingerprint(c *gin.Context) { + var req SetSoftFingerprintRequest + if err := c.ShouldBindJSON(&req); err != nil { + println("JSON bind error: %v (type: %T)", err, err) + common.ErrorWithCode(c, common.CodeBadRequest, err.Error()) + return + } + + err := h.service.SetSoftFingerprint(req.SoftFingerprint) + if err != nil { + common.ErrorWithCode(c, common.CodeServerError, err.Error()) + return + } + + common.SuccessNoData(c, "SUCCESS") +} + +func (h *Handler) ShowSoftFingerprint(c *gin.Context) { + softFingerprint, err := h.service.GetSoftFingerprint() + if err != nil { + common.ErrorWithCode(c, common.CodeServerError, err.Error()) + return + } + + common.SuccessWithData(c, softFingerprint, "") +} + +func (h *Handler) DeleteSoftFingerprint(c *gin.Context) { + err := h.service.DeleteSoftFingerprint() + if err != nil { + common.ErrorWithCode(c, common.CodeServerError, err.Error()) + return + } + + common.SuccessNoData(c, "SUCCESS") +} + type ShowUserActivityRequest struct { Days int `json:"days"` Email string `json:"email"` diff --git a/internal/admin/router_ee.go b/internal/admin/router_ee.go index 5adb82b2ad..e0586f02ac 100644 --- a/internal/admin/router_ee.go +++ b/internal/admin/router_ee.go @@ -70,6 +70,10 @@ func RegisterEERouter(protected *gin.RouterGroup, r *Router) { protected.GET("/system/license", r.handler.ShowSystemLicense) protected.PUT("/system/license/config", r.handler.UpdateSystemLicenseConfig) + protected.POST("/system/soft-fingerprint", r.handler.SetSoftFingerprint) + protected.GET("/system/soft-fingerprint", r.handler.ShowSoftFingerprint) + protected.DELETE("/system/soft-fingerprint", r.handler.DeleteSoftFingerprint) + protected.GET("/fingerprint", r.handler.GetFingerprint) protected.POST("/license", r.handler.SetLicense) protected.POST("/license/config", r.handler.UpdateLicenseConfig) diff --git a/internal/admin/service_ee.go b/internal/admin/service_ee.go index 9702e2a982..d3264393b4 100644 --- a/internal/admin/service_ee.go +++ b/internal/admin/service_ee.go @@ -402,6 +402,23 @@ func (s *Service) UpdateSystemLicenseConfig(timeRecordSaveInterval, timeRecordTa return result, nil } +func (s *Service) SetSoftFingerprint(softFingerprint string) error { + return errors.New("set soft fingerprint is not supported") +} + +func (s *Service) GetSoftFingerprint() (map[string]interface{}, error) { + result := map[string]interface{}{ + "command": "show_soft_fingerprint", + "error": "'show soft fingerprint' is not supported", + } + + return result, nil +} + +func (s *Service) DeleteSoftFingerprint() error { + return errors.New("delete soft fingerprint is not supported") +} + // ShowUserActivity show user activity for enterprise edition func (s *Service) ShowUserActivity(email string, days int) (map[string]interface{}, error) { // Query user by email diff --git a/internal/cli/admin_command.go b/internal/cli/admin_command.go index 8d11b80706..c847e70555 100644 --- a/internal/cli/admin_command.go +++ b/internal/cli/admin_command.go @@ -808,6 +808,27 @@ func (c *CLI) AdminSetLicenseCommand(cmd *Command) (ResponseIf, error) { return HandleCommonDataResponse(resp, "set license") } +func (c *CLI) AdminSetSoftFingerprintCommand(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") + } + + softFingerprint, ok := cmd.Params["soft_fingerprint"].(string) + if !ok { + return nil, fmt.Errorf("soft_fingerprint not provided") + } + + payload := map[string]interface{}{ + "fingerprint": softFingerprint, + } + resp, err := c.AdminServerClient.Request("POST", "/admin/system/soft-fingerprint", "admin", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to set soft fingerprint: %w", err) + } + + return HandleSimpleResponse(resp, "set soft fingerprint") +} + func (c *CLI) AdminSetLicenseConfigCommand(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") @@ -1348,6 +1369,22 @@ func (c *CLI) AdminShowFingerprintCommand(cmd *Command) (ResponseIf, error) { return HandleCommonDataResponse(resp, "show fingerprint") } +// AdminShowSoftFingerprintCommand show soft fingerprint command (admin mode only) +func (c *CLI) AdminShowSoftFingerprintCommand(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") + } + + apiURL := fmt.Sprintf("/admin/system/soft-fingerprint") + + resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to show soft fingerprint: %w", err) + } + + return HandleCommonDataResponse(resp, "show soft fingerprint") +} + // AdminShowLicenseCommand show license command (admin mode only) func (c *CLI) AdminShowLicenseCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { @@ -2826,6 +2863,23 @@ func (c *CLI) AdminDeleteModelsCommand(cmd *Command) (ResponseIf, error) { return HandleCommonDataResponse(resp, fmt.Sprintf("remove model %s", modelNames)) } +// AdminDeleteSoftFingerprintCommand delete soft fingerprint +func (c *CLI) AdminDeleteSoftFingerprintCommand(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") + } + + apiURL := fmt.Sprintf("/admin/system/soft-fingerprint") + + resp, err := c.AdminServerClient.Request("DELETE", apiURL, "admin", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to remove soft fingerprint: %w", err) + } + + return HandleSimpleResponse(resp, fmt.Sprintf("remove soft fingerprint")) +} + func (c *CLI) AdminShowLogLevelCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != AdminMode { diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index 448893870d..f3a6d18054 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -408,6 +408,8 @@ func (p *Parser) parseAdminShowCommands() (*Command, error) { return p.parseAdminShowCurrent() case TokenFingerprint: return p.parseAdminShowFingerprint() + case TokenSoft: + return p.parseAdminShowSoftFingerprint() case TokenLicense: return p.parseAdminShowLicense() case TokenProvider: @@ -736,6 +738,23 @@ func (p *Parser) parseAdminShowFingerprint() (*Command, error) { return NewCommand("admin_show_fingerprint"), nil } +// SHOW SOFT FINGERPRINT; +func (p *Parser) parseAdminShowSoftFingerprint() (*Command, error) { + p.nextToken() // consume SOFT + + if p.curToken.Type != TokenFingerprint { + return nil, fmt.Errorf("expected FINGERPRINT") + } + p.nextToken() + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("admin_show_soft_fingerprint"), nil +} + // SHOW LICENSE; func (p *Parser) parseAdminShowLicense() (*Command, error) { p.nextToken() // consume LICENSE @@ -1836,6 +1855,8 @@ func (p *Parser) parseAdminSetCommand() (*Command, error) { switch p.curToken.Type { case TokenLicense: return p.parseAdminSetLicense() + case TokenSoft: + return p.parseAdminSetSoftFingerprint() case TokenVar: return p.parseAdminSetVariable() case TokenRole: @@ -1885,6 +1906,30 @@ func (p *Parser) parseAdminSetLicense() (*Command, error) { return cmd, nil } +func (p *Parser) parseAdminSetSoftFingerprint() (*Command, error) { + p.nextToken() // consume SOFT + + if p.curToken.Type != TokenFingerprint { + return nil, fmt.Errorf("expected FINGERPRINT") + } + p.nextToken() + + softFingerprint, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() + + cmd := NewCommand("admin_set_soft_fingerprint") + cmd.Params["soft_fingerprint"] = softFingerprint + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + func (p *Parser) parseAdminSetVariable() (*Command, error) { p.nextToken() // consume VAR @@ -2280,6 +2325,8 @@ func (p *Parser) parseAdminDeleteCommands() (*Command, error) { return p.parseAPIDeleteAdminServer() case TokenProvider: return p.parseAdminDeleteProvider() + case TokenSoft: + return p.parseAdminDeleteSoftFingerprint() default: return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value) } @@ -2359,6 +2406,25 @@ func (p *Parser) parseAdminDeleteModel(providerName, instanceName string) (*Comm return cmd, nil } +// syntax: delete soft fingerprint; +func (p *Parser) parseAdminDeleteSoftFingerprint() (*Command, error) { + p.nextToken() // consume SOFT + + if p.curToken.Type != TokenFingerprint { + return nil, fmt.Errorf("expected FINGERPRINT after SOFT") + } + p.nextToken() + + cmd := NewCommand("admin_delete_soft_fingerprint") + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + func (p *Parser) parseAdminEnableCommand() (*Command, error) { p.nextToken() // consume ENABLE switch p.curToken.Type { diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go index abfc3bd4e5..51961d5297 100644 --- a/internal/cli/cli_http.go +++ b/internal/cli/cli_http.go @@ -111,6 +111,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.AdminShowVariable(cmd) case "admin_set_license": return c.AdminSetLicenseCommand(cmd) + case "admin_set_soft_fingerprint": + return c.AdminSetSoftFingerprintCommand(cmd) case "admin_set_license_config": return c.AdminSetLicenseConfigCommand(cmd) case "admin_set_variable": @@ -177,6 +179,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.CommonCheckProviderConnectionCommand(cmd) case "admin_show_fingerprint": return c.AdminShowFingerprintCommand(cmd) + case "admin_show_soft_fingerprint": + return c.AdminShowSoftFingerprintCommand(cmd) case "admin_show_license": return c.AdminShowLicenseCommand(cmd) case "admin_show_user": @@ -277,6 +281,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.AdminDeleteInstancesCommand(cmd) case "admin_delete_model": return c.AdminDeleteModelsCommand(cmd) + case "admin_delete_soft_fingerprint": + return c.AdminDeleteSoftFingerprintCommand(cmd) case "admin_enable_model": return c.CommonEnableOrDisableModelCommand(cmd, "enable") case "admin_disable_model": diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index ac08933ff2..207438c8c0 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -377,6 +377,8 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenRetrieve, Value: ident} case "CURRENT": return Token{Type: TokenCurrent, Value: ident} + case "SOFT": + return Token{Type: TokenSoft, Value: ident} case "FINGERPRINT": return Token{Type: TokenFingerprint, Value: ident} case "LICENSE": diff --git a/internal/cli/types.go b/internal/cli/types.go index 3d3232602c..af258d6d1c 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -108,6 +108,7 @@ const ( TokenMemory TokenRetrieve TokenCurrent + TokenSoft TokenFingerprint TokenLicense TokenVision