8000 feat: add name and nameoverride func to clients page by barnabasbusa · Pull Request #67 · ethpandaops/spamoor · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: add name and nameoverride func to clients page #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions README.md
10000
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@ All scenarios require:
- `--privkey` - Private key for the sending wallet
- `--rpchost` - RPC endpoint(s) to send transactions to

### RPC Host Configuration

RPC hosts support additional configuration parameters through URL prefixes:

- `headers(key:value|key2:value2)` - Sets custom HTTP headers
- `group(name)` - Assigns the client to a named group (can be used multiple times)
- `group(name1,name2,name3)` - Assigns the client to multiple groups (comma-separated)
- `name(custom_name)` - Sets a custom display name override for the client

**Examples:**
```bash
# Basic RPC endpoint
--rpchost="http://localhost:8545"

# With custom headers and groups
--rpchost="headers(Authorization:Bearer token|User-Agent:MyApp)group(mainnet)group(primary)http://localhost:8545"

# With custom name and multiple groups
--rpchost="group(mainnet,primary,backup)name(MainNet Primary)http://localhost:8545"

# Full configuration example
--rpchost="headers(Authorization:Bearer token)group(mainnet)name(My Custom Node)http://localhost:8545"
```

## Scenarios

Spamoor provides multiple scenarios for different transaction types:
Expand Down Expand Up @@ -75,8 +99,14 @@ The web interface runs on `http://localhost:8080` by default and provides:
- Start/pause/delete functionality

### API
The daemon exposes a REST API for programmatic control.
See the API Documentation in the spamoor web interface for details.
The daemon exposes a REST API for programmatic control, including:

- **Client Management**: Get client information, update client groups, enable/disable clients
- **Client Name Override**: Set custom display names for RPC clients via `PUT /api/client/{index}/name`
- **Spammer Control**: Create, start, pause, and delete spammers
- **Export/Import**: Export and import spammer configurations

See the API Documentation in the spamoor web interface for complete details.

### Export/Import Functionality
Spamoor supports exporting and importing spammer configurations as YAML files:
Expand Down
27 changes: 25 additions & 2 deletions spamoor/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Client struct {

clientGroups []string
enabled bool
nameOverride string

gasSuggestionMutex sync.Mutex
lastGasSuggestion time.Time
Expand All @@ -47,12 +48,14 @@ type Client struct {
// - headers(key:value|key2:value2) - sets custom HTTP headers
// - group(name) - assigns the client to a named group (can be used multiple times)
// - group(name1,name2,name3) - assigns the client to multiple groups (comma-separated)
// - name(custom_name) - sets a custom display name override
//
// Example: "headers(Authorization:Bearer token|User-Agent:MyApp)group(mainnet)group(primary)http://localhost:8545"
// Example: "group(mainnet,primary,backup)http://localhost:8545"
// Example: "headers(Authorization:Bearer token|User-Agent:MyApp)group(mainnet)group(primary)name(My Custom Node)http://localhost:8545"
// Example: "group(mainnet,primary,backup)name(MainNet Primary)http://localhost:8545"
func NewClient(rpchost string) (*Client, error) {
headers := map[string]string{}
clientGroups := []string{"default"}
nameOverride := ""

for {
if strings.HasPrefix(rpchost, "headers(") {
Expand Down Expand Up @@ -87,6 +90,10 @@ func NewClient(rpchost string) (*Client, error) {
}
}
}
} else if strings.HasPrefix(rpchost, "name(") {
nameEnd := strings.Index(rpchost, ")")
nameOverride = rpchost[5:nameEnd]
rpchost = rpchost[nameEnd+1:]
} else {
break
}
Expand All @@ -109,12 +116,17 @@ func NewClient(rpchost string) (*Client, error) {
logger: logrus.WithField("rpc", rpchost),
clientGroups: clientGroups,
enabled: true,
nameOverride: nameOverride,
}, nil
}

// GetName returns a shortened name for the client derived from the RPC host URL,
// removing common suffixes like ".ethpandaops.io".
// If a name override is set, it returns the override instead.
func (client *Client) GetName() string {
if client.nameOverride != "" {
return client.nameOverride
}
url, _ := url.Parse(client.rpchost)
name := strings.TrimSuffix(url.Host, ".ethpandaops.io")
return name
Expand Down Expand Up @@ -221,6 +233,17 @@ func (client *Client) SetEnabled(enabled bool) {
client.enabled = enabled
}

// GetNameOverride returns the name override for the client.
func (client *Client) GetNameOverride() string {
return client.nameOverride
}

// SetNameOverride sets a custom name override for the client.
// If set, this name will be used instead of the auto-generated name from the RPC host.
func (client *Client) SetNameOverride(name string) {
client.nameOverride = name
}

func (client *Client) getContext(ctx context.Context) (context.Context, context.CancelFunc) {
if client.Timeout > 0 {
return context.WithTimeout(ctx, client.Timeout)
Expand Down
Binary file added spamoor/spamoor-daemon
Binary file not shown.
81 changes: 63 additions & 18 deletions webui/handlers/api/api.go
9E81
Original file line number Diff line number Diff line change
Expand Up @@ -545,15 +545,16 @@ func (ah *APIHandler) StreamSpammerLogs(w http.ResponseWriter, r *http.Request)

// ClientEntry represents a client in the API response
type ClientEntry struct {
Index int `json:"index"`
Name string `json:"name"`
Group string `json:"group"` // First group for backward compatibility
Groups []string `json:"groups"` // All groups
Version string `json:"version"`
BlockHeight uint64 `json:"block_height"`
IsReady bool `json:"ready"`
RpcHost string `json:"rpc_host"`
Enabled bool `json:"enabled"`
Index int `json:"index"`
Name string `json:"name"`
Group string `json:"group"` // First group for backward compatibility
Groups []string `json:"groups"` // All groups
Version string `json:"version"`
BlockHeight uint64 `json:"block_height"`
IsReady bool `json:"ready"`
RpcHost string `json:"rpc_host"`
Enabled bool `json:"enabled"`
NameOverride string `json:"name_override,omitempty"`
}

// UpdateClientGroupRequest represents the request body for updating a client group
Expand All @@ -567,6 +568,11 @@ type UpdateClientEnabledRequest struct {
Enabled bool `json:"enabled"`
}

// UpdateClientNameRequest represents the request body for updating a client's name override
type UpdateClientNameRequest struct {
NameOverride string `json:"name_override"`
}

// GetClients godoc
// @Id getClients
// @Summary Get all clients
Expand All @@ -591,15 +597,16 @@ func (ah *APIHandler) GetClient 6D47 s(w http.ResponseWriter, r *http.Request) {
}

response[i] = ClientEntry{
Index: i,
Name: client.GetName(),
Group: client.GetClientGroup(),
Groups: client.GetClientGroups(),
Version: version,
BlockHeight: blockHeight,
IsReady: slices.Contains(goodClients, client),
RpcHost: client.GetRPCHost(),
Enabled: client.IsEnabled(),
Index: i,
Name: client.GetName(),
Group: client.GetClientGroup(),
Groups: client.GetClientGroups(),
Version: version,
BlockHeight: blockHeight,
IsReady: slices.Contains(goodClients, client),
RpcHost: client.GetRPCHost(),
Enabled: client.IsEnabled(),
NameOverride: client.GetNameOverride(),
}
}

Expand Down Expand Up @@ -692,6 +699,44 @@ func (ah *APIHandler) UpdateClientEnabled(w http.ResponseWriter, r *http.Request
w.WriteHeader(http.StatusOK)
}

// UpdateClientName godoc
// @Id updateClientName
// @Summary Update client name override
// @Tags Client
// @Description Updates the name override for a specific client
// @Accept json
// @Param index path int true "Client index"
// @Param request body UpdateClientNameRequest true "New name override"
// @Success 200 {object} Response "Success"
// @Failure 400 {object} Response "Invalid client index"
// @Failure 404 {object} Response "Client not found"
// @Router /api/client/{index}/name [put]
func (ah *APIHandler) UpdateClientName(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
index, err := strconv.Atoi(vars["index"])
if err != nil {
http.Error(w, "Invalid client index", http.StatusBadRequest)
return
}

var req UpdateClientNameRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

allClients := ah.daemon.GetClientPool().GetAllClients()
if index < 0 || index >= len(allClients) {
http.Error(w, "Client not found", http.StatusNotFound)
return
}

client := allClients[index]
client.SetNameOverride(req.NameOverride)

w.WriteHeader(http.StatusOK)
}

// ExportSpammersRequest represents the request body for exporting spammers
type ExportSpammersRequest struct {
SpammerIDs []int64 `json:"spammer_ids,omitempty"` // If empty, exports all spammers
Expand Down
32 changes: 17 additions & 15 deletions webui/handlers/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ type ClientsPage struct {
}

type ClientsPageClient struct {
Index int `json:"index"`
Name string `json:"name"`
Group string `json:"group"` // First group for backward compatibility
Groups []string `json:"groups"` // All groups
Version string `json:"version"`
BlockHeight uint64 `json:"block_height"`
IsReady bool `json:"ready"`
Enabled bool `json:"enabled"`
Index int `json:"index"`
Name string `json:"name"`
Group string `json:"group"` // First group for backward compatibility
Groups []string `json:"groups"` // All groups
Version string `json:"version"`
BlockHeight uint64 `json:"block_height"`
IsReady bool `json:"ready"`
Enabled bool `json:"enabled"`
NameOverride string `json:"name_override,omitempty"`
}

// Clients will return the "clients" page using a go template
Expand Down Expand Up @@ -64,13 +65,14 @@ func (fh *FrontendHandler) getClientsPageData(ctx context.Context) (*ClientsPage
blockHeight, _ := client.GetLastBlockHeight()

clientData := &ClientsPageClient{
Index: idx,
Name: client.GetName(),
Group: client.GetClientGroup(),
Groups: client.GetClientGroups(),
BlockHeight: blockHeight,
IsReady: slices.Contains(goodClients, client),
Enabled: client.IsEnabled(),
Index: idx,
Name: client.GetName(),
Group: client.GetClientGroup(),
Groups: client.GetClientGroups(),
BlockHeight: blockHeight,
IsReady: slices.Contains(goodClients, client),
Enabled: client.IsEnabled(),
NameOverride: client.GetNameOverride(),
}

wg.Add(1)
Expand Down
69 changes: 69 additions & 0 deletions webui/handlers/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,57 @@ const docTemplate = `{
}
}
},
"/api/client/{index}/name": {
"put": {
"description": "Updates the name override for a specific client",
"consumes": [
"application/json"
],
"tags": [
"Client"
],
"summary": "Update client name override",
"operationId": "updateClientName",
"parameters": [
{
"type": "integer",
"description": "Client index",
"name": "index",
"in": "path",
"required": true
},
{
"description": "New name override",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/api.UpdateClientNameRequest"
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/api.Response"
}
},
"400": {
"description": "Invalid client index",
"schema": {
"$ref": "#/definitions/api.Response"
}
},
"404": {
"description": "Client not found",
"schema": {
"$ref": "#/definitions/api.Response"
}
}
}
}
},
"/api/clients": {
"get": {
"description": "Returns a list of all clients with their details",
Expand Down Expand Up @@ -869,6 +920,9 @@ const docTemplate = `{
"name": {
"type": "string"
},
"name_override": {
"type": "string"
},
"ready": {
"type": "boolean"
},
Expand Down Expand Up @@ -1032,6 +1086,21 @@ const docTemplate = `{
}
}
},
"api.UpdateClientNameRequest": {
"type": "object",
"properties": {
"name_override": {
"type": "string"
},
"groups": {
"description": "Multiple groups",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"api.UpdateSpammerRequest": {
"type": "object",
"properties": {
Expand Down
Loading
0