mirror of
				https://github.com/h44z/wg-portal.git
				synced 2025-11-03 23:56:18 +00:00 
			
		
		
		
	@@ -295,6 +295,30 @@ func (r *SqlRepo) GetAllInterfaces(ctx context.Context) ([]domain.Interface, err
 | 
			
		||||
	return interfaces, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *SqlRepo) GetInterfaceStats(ctx context.Context, id domain.InterfaceIdentifier) (
 | 
			
		||||
	*domain.InterfaceStatus,
 | 
			
		||||
	error,
 | 
			
		||||
) {
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var stats []domain.InterfaceStatus
 | 
			
		||||
 | 
			
		||||
	err := r.db.WithContext(ctx).Where("identifier = ?", id).Find(&stats).Error
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(stats) == 0 {
 | 
			
		||||
		return nil, domain.ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stat := stats[0]
 | 
			
		||||
 | 
			
		||||
	return &stat, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *SqlRepo) FindInterfaces(ctx context.Context, search string) ([]domain.Interface, error) {
 | 
			
		||||
	var users []domain.Interface
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -309,6 +309,180 @@
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "/metrics/by-interface/{id}": {
 | 
			
		||||
            "get": {
 | 
			
		||||
                "security": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "BasicAuth": []
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "produces": [
 | 
			
		||||
                    "application/json"
 | 
			
		||||
                ],
 | 
			
		||||
                "tags": [
 | 
			
		||||
                    "Metrics"
 | 
			
		||||
                ],
 | 
			
		||||
                "summary": "Get all metrics for a WireGuard Portal interface.",
 | 
			
		||||
                "operationId": "metrics_handleMetricsForInterfaceGet",
 | 
			
		||||
                "parameters": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "type": "string",
 | 
			
		||||
                        "description": "The WireGuard interface identifier.",
 | 
			
		||||
                        "name": "id",
 | 
			
		||||
                        "in": "path",
 | 
			
		||||
                        "required": true
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "responses": {
 | 
			
		||||
                    "200": {
 | 
			
		||||
                        "description": "OK",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.InterfaceMetrics"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "400": {
 | 
			
		||||
                        "description": "Bad Request",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "401": {
 | 
			
		||||
                        "description": "Unauthorized",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "404": {
 | 
			
		||||
                        "description": "Not Found",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "500": {
 | 
			
		||||
                        "description": "Internal Server Error",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "/metrics/by-peer/{id}": {
 | 
			
		||||
            "get": {
 | 
			
		||||
                "security": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "BasicAuth": []
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "produces": [
 | 
			
		||||
                    "application/json"
 | 
			
		||||
                ],
 | 
			
		||||
                "tags": [
 | 
			
		||||
                    "Metrics"
 | 
			
		||||
                ],
 | 
			
		||||
                "summary": "Get all metrics for a WireGuard Portal peer.",
 | 
			
		||||
                "operationId": "metrics_handleMetricsForPeerGet",
 | 
			
		||||
                "parameters": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "type": "string",
 | 
			
		||||
                        "description": "The peer identifier (public key).",
 | 
			
		||||
                        "name": "id",
 | 
			
		||||
                        "in": "path",
 | 
			
		||||
                        "required": true
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "responses": {
 | 
			
		||||
                    "200": {
 | 
			
		||||
                        "description": "OK",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.PeerMetrics"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "400": {
 | 
			
		||||
                        "description": "Bad Request",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "401": {
 | 
			
		||||
                        "description": "Unauthorized",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "404": {
 | 
			
		||||
                        "description": "Not Found",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "500": {
 | 
			
		||||
                        "description": "Internal Server Error",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "/metrics/by-user/{id}": {
 | 
			
		||||
            "get": {
 | 
			
		||||
                "security": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "BasicAuth": []
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "produces": [
 | 
			
		||||
                    "application/json"
 | 
			
		||||
                ],
 | 
			
		||||
                "tags": [
 | 
			
		||||
                    "Metrics"
 | 
			
		||||
                ],
 | 
			
		||||
                "summary": "Get all metrics for a WireGuard Portal user.",
 | 
			
		||||
                "operationId": "metrics_handleMetricsForUserGet",
 | 
			
		||||
                "parameters": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "type": "string",
 | 
			
		||||
                        "description": "The user identifier.",
 | 
			
		||||
                        "name": "id",
 | 
			
		||||
                        "in": "path",
 | 
			
		||||
                        "required": true
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "responses": {
 | 
			
		||||
                    "200": {
 | 
			
		||||
                        "description": "OK",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.UserMetrics"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "400": {
 | 
			
		||||
                        "description": "Bad Request",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "401": {
 | 
			
		||||
                        "description": "Unauthorized",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "404": {
 | 
			
		||||
                        "description": "Not Found",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "500": {
 | 
			
		||||
                        "description": "Internal Server Error",
 | 
			
		||||
                        "schema": {
 | 
			
		||||
                            "$ref": "#/definitions/models.Error"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "/peer/by-id/{id}": {
 | 
			
		||||
            "get": {
 | 
			
		||||
                "security": [
 | 
			
		||||
@@ -1523,6 +1697,26 @@
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "models.InterfaceMetrics": {
 | 
			
		||||
            "type": "object",
 | 
			
		||||
            "properties": {
 | 
			
		||||
                "BytesReceived": {
 | 
			
		||||
                    "description": "The number of bytes received by the interface.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 123456789
 | 
			
		||||
                },
 | 
			
		||||
                "BytesTransmitted": {
 | 
			
		||||
                    "description": "The number of bytes transmitted by the interface.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 123456789
 | 
			
		||||
                },
 | 
			
		||||
                "InterfaceIdentifier": {
 | 
			
		||||
                    "description": "The unique identifier of the interface.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "wg0"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "models.Peer": {
 | 
			
		||||
            "type": "object",
 | 
			
		||||
            "required": [
 | 
			
		||||
@@ -1728,6 +1922,51 @@
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "models.PeerMetrics": {
 | 
			
		||||
            "type": "object",
 | 
			
		||||
            "properties": {
 | 
			
		||||
                "BytesReceived": {
 | 
			
		||||
                    "description": "The number of bytes received by the peer.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 123456789
 | 
			
		||||
                },
 | 
			
		||||
                "BytesTransmitted": {
 | 
			
		||||
                    "description": "The number of bytes transmitted by the peer.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 123456789
 | 
			
		||||
                },
 | 
			
		||||
                "Endpoint": {
 | 
			
		||||
                    "description": "The current endpoint address of the peer.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "12.34.56.78"
 | 
			
		||||
                },
 | 
			
		||||
                "IsPingable": {
 | 
			
		||||
                    "description": "If this field is set, the peer is pingable.",
 | 
			
		||||
                    "type": "boolean",
 | 
			
		||||
                    "example": true
 | 
			
		||||
                },
 | 
			
		||||
                "LastHandshake": {
 | 
			
		||||
                    "description": "The last time the peer initiated a handshake.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "2021-01-01T12:00:00Z"
 | 
			
		||||
                },
 | 
			
		||||
                "LastPing": {
 | 
			
		||||
                    "description": "The last time the peer responded to a ICMP ping request.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "2021-01-01T12:00:00Z"
 | 
			
		||||
                },
 | 
			
		||||
                "LastSessionStart": {
 | 
			
		||||
                    "description": "The last time the peer initiated a session.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "2021-01-01T12:00:00Z"
 | 
			
		||||
                },
 | 
			
		||||
                "PeerIdentifier": {
 | 
			
		||||
                    "description": "The unique identifier of the peer.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "models.ProvisioningRequest": {
 | 
			
		||||
            "type": "object",
 | 
			
		||||
            "required": [
 | 
			
		||||
@@ -1922,6 +2161,38 @@
 | 
			
		||||
                    "example": true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "models.UserMetrics": {
 | 
			
		||||
            "type": "object",
 | 
			
		||||
            "properties": {
 | 
			
		||||
                "BytesReceived": {
 | 
			
		||||
                    "description": "The total number of bytes received by the user. This is the sum of all bytes received by the peers linked to the user.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 123456789
 | 
			
		||||
                },
 | 
			
		||||
                "BytesTransmitted": {
 | 
			
		||||
                    "description": "The total number of bytes transmitted by the user. This is the sum of all bytes transmitted by the peers linked to the user.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 123456789
 | 
			
		||||
                },
 | 
			
		||||
                "PeerCount": {
 | 
			
		||||
                    "description": "PeerCount represents the number of peers linked to the user.",
 | 
			
		||||
                    "type": "integer",
 | 
			
		||||
                    "example": 2
 | 
			
		||||
                },
 | 
			
		||||
                "PeerMetrics": {
 | 
			
		||||
                    "description": "PeerMetrics represents the metrics of the peers linked to the user.",
 | 
			
		||||
                    "type": "array",
 | 
			
		||||
                    "items": {
 | 
			
		||||
                        "$ref": "#/definitions/models.PeerMetrics"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "UserIdentifier": {
 | 
			
		||||
                    "description": "The unique identifier of the user.",
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "example": "uid-1234567"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "securityDefinitions": {
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,21 @@ definitions:
 | 
			
		||||
    - PrivateKey
 | 
			
		||||
    - PublicKey
 | 
			
		||||
    type: object
 | 
			
		||||
  models.InterfaceMetrics:
 | 
			
		||||
    properties:
 | 
			
		||||
      BytesReceived:
 | 
			
		||||
        description: The number of bytes received by the interface.
 | 
			
		||||
        example: 123456789
 | 
			
		||||
        type: integer
 | 
			
		||||
      BytesTransmitted:
 | 
			
		||||
        description: The number of bytes transmitted by the interface.
 | 
			
		||||
        example: 123456789
 | 
			
		||||
        type: integer
 | 
			
		||||
      InterfaceIdentifier:
 | 
			
		||||
        description: The unique identifier of the interface.
 | 
			
		||||
        example: wg0
 | 
			
		||||
        type: string
 | 
			
		||||
    type: object
 | 
			
		||||
  models.Peer:
 | 
			
		||||
    properties:
 | 
			
		||||
      Addresses:
 | 
			
		||||
@@ -383,6 +398,41 @@ definitions:
 | 
			
		||||
    - InterfaceIdentifier
 | 
			
		||||
    - PrivateKey
 | 
			
		||||
    type: object
 | 
			
		||||
  models.PeerMetrics:
 | 
			
		||||
    properties:
 | 
			
		||||
      BytesReceived:
 | 
			
		||||
        description: The number of bytes received by the peer.
 | 
			
		||||
        example: 123456789
 | 
			
		||||
        type: integer
 | 
			
		||||
      BytesTransmitted:
 | 
			
		||||
        description: The number of bytes transmitted by the peer.
 | 
			
		||||
        example: 123456789
 | 
			
		||||
        type: integer
 | 
			
		||||
      Endpoint:
 | 
			
		||||
        description: The current endpoint address of the peer.
 | 
			
		||||
        example: 12.34.56.78
 | 
			
		||||
        type: string
 | 
			
		||||
      IsPingable:
 | 
			
		||||
        description: If this field is set, the peer is pingable.
 | 
			
		||||
        example: true
 | 
			
		||||
        type: boolean
 | 
			
		||||
      LastHandshake:
 | 
			
		||||
        description: The last time the peer initiated a handshake.
 | 
			
		||||
        example: "2021-01-01T12:00:00Z"
 | 
			
		||||
        type: string
 | 
			
		||||
      LastPing:
 | 
			
		||||
        description: The last time the peer responded to a ICMP ping request.
 | 
			
		||||
        example: "2021-01-01T12:00:00Z"
 | 
			
		||||
        type: string
 | 
			
		||||
      LastSessionStart:
 | 
			
		||||
        description: The last time the peer initiated a session.
 | 
			
		||||
        example: "2021-01-01T12:00:00Z"
 | 
			
		||||
        type: string
 | 
			
		||||
      PeerIdentifier:
 | 
			
		||||
        description: The unique identifier of the peer.
 | 
			
		||||
        example: xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
 | 
			
		||||
        type: string
 | 
			
		||||
    type: object
 | 
			
		||||
  models.ProvisioningRequest:
 | 
			
		||||
    properties:
 | 
			
		||||
      InterfaceIdentifier:
 | 
			
		||||
@@ -547,6 +597,33 @@ definitions:
 | 
			
		||||
        example: true
 | 
			
		||||
        type: boolean
 | 
			
		||||
    type: object
 | 
			
		||||
  models.UserMetrics:
 | 
			
		||||
    properties:
 | 
			
		||||
      BytesReceived:
 | 
			
		||||
        description: The total number of bytes received by the user. This is the sum
 | 
			
		||||
          of all bytes received by the peers linked to the user.
 | 
			
		||||
        example: 123456789
 | 
			
		||||
        type: integer
 | 
			
		||||
      BytesTransmitted:
 | 
			
		||||
        description: The total number of bytes transmitted by the user. This is the
 | 
			
		||||
          sum of all bytes transmitted by the peers linked to the user.
 | 
			
		||||
        example: 123456789
 | 
			
		||||
        type: integer
 | 
			
		||||
      PeerCount:
 | 
			
		||||
        description: PeerCount represents the number of peers linked to the user.
 | 
			
		||||
        example: 2
 | 
			
		||||
        type: integer
 | 
			
		||||
      PeerMetrics:
 | 
			
		||||
        description: PeerMetrics represents the metrics of the peers linked to the
 | 
			
		||||
          user.
 | 
			
		||||
        items:
 | 
			
		||||
          $ref: '#/definitions/models.PeerMetrics'
 | 
			
		||||
        type: array
 | 
			
		||||
      UserIdentifier:
 | 
			
		||||
        description: The unique identifier of the user.
 | 
			
		||||
        example: uid-1234567
 | 
			
		||||
        type: string
 | 
			
		||||
    type: object
 | 
			
		||||
info:
 | 
			
		||||
  contact:
 | 
			
		||||
    name: WireGuard Portal Project
 | 
			
		||||
@@ -749,6 +826,117 @@ paths:
 | 
			
		||||
      summary: Create a new interface record.
 | 
			
		||||
      tags:
 | 
			
		||||
      - Interfaces
 | 
			
		||||
  /metrics/by-interface/{id}:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: metrics_handleMetricsForInterfaceGet
 | 
			
		||||
      parameters:
 | 
			
		||||
      - description: The WireGuard interface identifier.
 | 
			
		||||
        in: path
 | 
			
		||||
        name: id
 | 
			
		||||
        required: true
 | 
			
		||||
        type: string
 | 
			
		||||
      produces:
 | 
			
		||||
      - application/json
 | 
			
		||||
      responses:
 | 
			
		||||
        "200":
 | 
			
		||||
          description: OK
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.InterfaceMetrics'
 | 
			
		||||
        "400":
 | 
			
		||||
          description: Bad Request
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "401":
 | 
			
		||||
          description: Unauthorized
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "404":
 | 
			
		||||
          description: Not Found
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "500":
 | 
			
		||||
          description: Internal Server Error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
      security:
 | 
			
		||||
      - BasicAuth: []
 | 
			
		||||
      summary: Get all metrics for a WireGuard Portal interface.
 | 
			
		||||
      tags:
 | 
			
		||||
      - Metrics
 | 
			
		||||
  /metrics/by-peer/{id}:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: metrics_handleMetricsForPeerGet
 | 
			
		||||
      parameters:
 | 
			
		||||
      - description: The peer identifier (public key).
 | 
			
		||||
        in: path
 | 
			
		||||
        name: id
 | 
			
		||||
        required: true
 | 
			
		||||
        type: string
 | 
			
		||||
      produces:
 | 
			
		||||
      - application/json
 | 
			
		||||
      responses:
 | 
			
		||||
        "200":
 | 
			
		||||
          description: OK
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.PeerMetrics'
 | 
			
		||||
        "400":
 | 
			
		||||
          description: Bad Request
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "401":
 | 
			
		||||
          description: Unauthorized
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "404":
 | 
			
		||||
          description: Not Found
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "500":
 | 
			
		||||
          description: Internal Server Error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
      security:
 | 
			
		||||
      - BasicAuth: []
 | 
			
		||||
      summary: Get all metrics for a WireGuard Portal peer.
 | 
			
		||||
      tags:
 | 
			
		||||
      - Metrics
 | 
			
		||||
  /metrics/by-user/{id}:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: metrics_handleMetricsForUserGet
 | 
			
		||||
      parameters:
 | 
			
		||||
      - description: The user identifier.
 | 
			
		||||
        in: path
 | 
			
		||||
        name: id
 | 
			
		||||
        required: true
 | 
			
		||||
        type: string
 | 
			
		||||
      produces:
 | 
			
		||||
      - application/json
 | 
			
		||||
      responses:
 | 
			
		||||
        "200":
 | 
			
		||||
          description: OK
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.UserMetrics'
 | 
			
		||||
        "400":
 | 
			
		||||
          description: Bad Request
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "401":
 | 
			
		||||
          description: Unauthorized
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "404":
 | 
			
		||||
          description: Not Found
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
        "500":
 | 
			
		||||
          description: Internal Server Error
 | 
			
		||||
          schema:
 | 
			
		||||
            $ref: '#/definitions/models.Error'
 | 
			
		||||
      security:
 | 
			
		||||
      - BasicAuth: []
 | 
			
		||||
      summary: Get all metrics for a WireGuard Portal user.
 | 
			
		||||
      tags:
 | 
			
		||||
      - Metrics
 | 
			
		||||
  /peer/by-id/{id}:
 | 
			
		||||
    delete:
 | 
			
		||||
      operationId: peers_handleDelete
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										131
									
								
								internal/app/api/v1/backend/metrics_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								internal/app/api/v1/backend/metrics_service.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
package backend
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/config"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MetricsServiceDatabaseRepo interface {
 | 
			
		||||
	GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error)
 | 
			
		||||
	GetInterfaceStats(ctx context.Context, id domain.InterfaceIdentifier) (
 | 
			
		||||
		*domain.InterfaceStatus,
 | 
			
		||||
		error,
 | 
			
		||||
	)
 | 
			
		||||
	GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetricsServiceUserManagerRepo interface {
 | 
			
		||||
	GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetricsServicePeerManagerRepo interface {
 | 
			
		||||
	GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetricsService struct {
 | 
			
		||||
	cfg *config.Config
 | 
			
		||||
 | 
			
		||||
	db    MetricsServiceDatabaseRepo
 | 
			
		||||
	users MetricsServiceUserManagerRepo
 | 
			
		||||
	peers MetricsServicePeerManagerRepo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMetricsService(
 | 
			
		||||
	cfg *config.Config,
 | 
			
		||||
	db MetricsServiceDatabaseRepo,
 | 
			
		||||
	users MetricsServiceUserManagerRepo,
 | 
			
		||||
	peers MetricsServicePeerManagerRepo,
 | 
			
		||||
) *MetricsService {
 | 
			
		||||
	return &MetricsService{
 | 
			
		||||
		cfg:   cfg,
 | 
			
		||||
		db:    db,
 | 
			
		||||
		users: users,
 | 
			
		||||
		peers: peers,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m MetricsService) GetForInterface(ctx context.Context, id domain.InterfaceIdentifier) (
 | 
			
		||||
	*domain.InterfaceStatus,
 | 
			
		||||
	error,
 | 
			
		||||
) {
 | 
			
		||||
	if !m.cfg.Statistics.CollectInterfaceData {
 | 
			
		||||
		return nil, fmt.Errorf("interface statistics collection is disabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// validate admin rights
 | 
			
		||||
	if err := domain.ValidateAdminAccessRights(ctx); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	interfaceStats, err := m.db.GetInterfaceStats(ctx, id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to fetch stats for interface %s: %w", id, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return interfaceStats, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m MetricsService) GetForUser(ctx context.Context, id domain.UserIdentifier) (
 | 
			
		||||
	*domain.User,
 | 
			
		||||
	[]domain.PeerStatus,
 | 
			
		||||
	error,
 | 
			
		||||
) {
 | 
			
		||||
	if !m.cfg.Statistics.CollectPeerData {
 | 
			
		||||
		return nil, nil, fmt.Errorf("statistics collection is disabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := domain.ValidateUserAccessRights(ctx, id); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := m.users.GetUser(ctx, id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peers, err := m.db.GetUserPeers(ctx, user.Identifier)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("failed to fetch peers for user %s: %w", user.Identifier, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peerIds := make([]domain.PeerIdentifier, len(peers))
 | 
			
		||||
	for i, peer := range peers {
 | 
			
		||||
		peerIds[i] = peer.Identifier
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peerStats, err := m.db.GetPeersStats(ctx, peerIds...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("failed to fetch peer stats for user %s: %w", user.Identifier, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return user, peerStats, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m MetricsService) GetForPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.PeerStatus, error) {
 | 
			
		||||
	if !m.cfg.Statistics.CollectPeerData {
 | 
			
		||||
		return nil, fmt.Errorf("peer statistics collection is disabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peer, err := m.peers.GetPeer(ctx, id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peerStats, err := m.db.GetPeersStats(ctx, peer.Identifier)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to fetch stats for peer %s: %w", peer.Identifier, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(peerStats) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("no stats found for peer %s: %w", peer.Identifier, domain.ErrNotFound)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &peerStats[0], nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								internal/app/api/v1/handlers/endpoint_metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								internal/app/api/v1/handlers/endpoint_metrics.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/app/api/v1/models"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MetricsEndpointStatisticsService interface {
 | 
			
		||||
	GetForInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.InterfaceStatus, error)
 | 
			
		||||
	GetForUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, []domain.PeerStatus, error)
 | 
			
		||||
	GetForPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.PeerStatus, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetricsEndpoint struct {
 | 
			
		||||
	metrics MetricsEndpointStatisticsService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMetricsEndpoint(metrics MetricsEndpointStatisticsService) *MetricsEndpoint {
 | 
			
		||||
	return &MetricsEndpoint{
 | 
			
		||||
		metrics: metrics,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e MetricsEndpoint) GetName() string {
 | 
			
		||||
	return "MetricsEndpoint"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e MetricsEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
 | 
			
		||||
	apiGroup := g.Group("/metrics", authenticator.LoggedIn())
 | 
			
		||||
 | 
			
		||||
	apiGroup.GET("/by-interface/:id", authenticator.LoggedIn(ScopeAdmin), e.handleMetricsForInterfaceGet())
 | 
			
		||||
	apiGroup.GET("/by-user/:id", authenticator.LoggedIn(), e.handleMetricsForUserGet())
 | 
			
		||||
	apiGroup.GET("/by-peer/:id", authenticator.LoggedIn(), e.handleMetricsForPeerGet())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleMetricsForInterfaceGet returns a gorm Handler function.
 | 
			
		||||
//
 | 
			
		||||
// @ID metrics_handleMetricsForInterfaceGet
 | 
			
		||||
// @Tags Metrics
 | 
			
		||||
// @Summary Get all metrics for a WireGuard Portal interface.
 | 
			
		||||
// @Param id path string true "The WireGuard interface identifier."
 | 
			
		||||
// @Produce json
 | 
			
		||||
// @Success 200 {object} models.InterfaceMetrics
 | 
			
		||||
// @Failure 400 {object} models.Error
 | 
			
		||||
// @Failure 401 {object} models.Error
 | 
			
		||||
// @Failure 404 {object} models.Error
 | 
			
		||||
// @Failure 500 {object} models.Error
 | 
			
		||||
// @Router /metrics/by-interface/{id} [get]
 | 
			
		||||
// @Security BasicAuth
 | 
			
		||||
func (e MetricsEndpoint) handleMetricsForInterfaceGet() gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		ctx := domain.SetUserInfoFromGin(c)
 | 
			
		||||
 | 
			
		||||
		id := c.Param("id")
 | 
			
		||||
		if id == "" {
 | 
			
		||||
			c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		interfaceMetrics, err := e.metrics.GetForInterface(ctx, domain.InterfaceIdentifier(id))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(ParseServiceError(err))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.JSON(http.StatusOK, models.NewInterfaceMetrics(interfaceMetrics))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleMetricsForUserGet returns a gorm Handler function.
 | 
			
		||||
//
 | 
			
		||||
// @ID metrics_handleMetricsForUserGet
 | 
			
		||||
// @Tags Metrics
 | 
			
		||||
// @Summary Get all metrics for a WireGuard Portal user.
 | 
			
		||||
// @Param id path string true "The user identifier."
 | 
			
		||||
// @Produce json
 | 
			
		||||
// @Success 200 {object} models.UserMetrics
 | 
			
		||||
// @Failure 400 {object} models.Error
 | 
			
		||||
// @Failure 401 {object} models.Error
 | 
			
		||||
// @Failure 404 {object} models.Error
 | 
			
		||||
// @Failure 500 {object} models.Error
 | 
			
		||||
// @Router /metrics/by-user/{id} [get]
 | 
			
		||||
// @Security BasicAuth
 | 
			
		||||
func (e MetricsEndpoint) handleMetricsForUserGet() gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		ctx := domain.SetUserInfoFromGin(c)
 | 
			
		||||
 | 
			
		||||
		id := c.Param("id")
 | 
			
		||||
		if id == "" {
 | 
			
		||||
			c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		user, userMetrics, err := e.metrics.GetForUser(ctx, domain.UserIdentifier(id))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(ParseServiceError(err))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.JSON(http.StatusOK, models.NewUserMetrics(user, userMetrics))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleMetricsForPeerGet returns a gorm Handler function.
 | 
			
		||||
//
 | 
			
		||||
// @ID metrics_handleMetricsForPeerGet
 | 
			
		||||
// @Tags Metrics
 | 
			
		||||
// @Summary Get all metrics for a WireGuard Portal peer.
 | 
			
		||||
// @Param id path string true "The peer identifier (public key)."
 | 
			
		||||
// @Produce json
 | 
			
		||||
// @Success 200 {object} models.PeerMetrics
 | 
			
		||||
// @Failure 400 {object} models.Error
 | 
			
		||||
// @Failure 401 {object} models.Error
 | 
			
		||||
// @Failure 404 {object} models.Error
 | 
			
		||||
// @Failure 500 {object} models.Error
 | 
			
		||||
// @Router /metrics/by-peer/{id} [get]
 | 
			
		||||
// @Security BasicAuth
 | 
			
		||||
func (e MetricsEndpoint) handleMetricsForPeerGet() gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		ctx := domain.SetUserInfoFromGin(c)
 | 
			
		||||
 | 
			
		||||
		id := c.Param("id")
 | 
			
		||||
		if id == "" {
 | 
			
		||||
			c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		peerMetrics, err := e.metrics.GetForPeer(ctx, domain.PeerIdentifier(id))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(ParseServiceError(err))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.JSON(http.StatusOK, models.NewPeerMetrics(peerMetrics))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								internal/app/api/v1/models/models_metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								internal/app/api/v1/models/models_metrics.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PeerMetrics represents the metrics of a WireGuard peer.
 | 
			
		||||
type PeerMetrics struct {
 | 
			
		||||
	// The unique identifier of the peer.
 | 
			
		||||
	PeerIdentifier string `json:"PeerIdentifier" example:"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="`
 | 
			
		||||
 | 
			
		||||
	// If this field is set, the peer is pingable.
 | 
			
		||||
	IsPingable bool `json:"IsPingable" example:"true"`
 | 
			
		||||
	// The last time the peer responded to a ICMP ping request.
 | 
			
		||||
	LastPing *time.Time `json:"LastPing" example:"2021-01-01T12:00:00Z"`
 | 
			
		||||
 | 
			
		||||
	// The number of bytes received by the peer.
 | 
			
		||||
	BytesReceived uint64 `json:"BytesReceived" example:"123456789"`
 | 
			
		||||
	// The number of bytes transmitted by the peer.
 | 
			
		||||
	BytesTransmitted uint64 `json:"BytesTransmitted" example:"123456789"`
 | 
			
		||||
 | 
			
		||||
	// The last time the peer initiated a handshake.
 | 
			
		||||
	LastHandshake *time.Time `json:"LastHandshake" example:"2021-01-01T12:00:00Z"`
 | 
			
		||||
	// The current endpoint address of the peer.
 | 
			
		||||
	Endpoint string `json:"Endpoint" example:"12.34.56.78"`
 | 
			
		||||
	// The last time the peer initiated a session.
 | 
			
		||||
	LastSessionStart *time.Time `json:"LastSessionStart" example:"2021-01-01T12:00:00Z"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewPeerMetrics(src *domain.PeerStatus) *PeerMetrics {
 | 
			
		||||
	return &PeerMetrics{
 | 
			
		||||
		PeerIdentifier:   string(src.PeerId),
 | 
			
		||||
		IsPingable:       src.IsPingable,
 | 
			
		||||
		LastPing:         src.LastPing,
 | 
			
		||||
		BytesReceived:    src.BytesReceived,
 | 
			
		||||
		BytesTransmitted: src.BytesTransmitted,
 | 
			
		||||
		LastHandshake:    src.LastHandshake,
 | 
			
		||||
		Endpoint:         src.Endpoint,
 | 
			
		||||
		LastSessionStart: src.LastSessionStart,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InterfaceMetrics represents the metrics of a WireGuard interface.
 | 
			
		||||
type InterfaceMetrics struct {
 | 
			
		||||
	// The unique identifier of the interface.
 | 
			
		||||
	InterfaceIdentifier string `json:"InterfaceIdentifier" example:"wg0"`
 | 
			
		||||
 | 
			
		||||
	// The number of bytes received by the interface.
 | 
			
		||||
	BytesReceived uint64 `json:"BytesReceived" example:"123456789"`
 | 
			
		||||
	// The number of bytes transmitted by the interface.
 | 
			
		||||
	BytesTransmitted uint64 `json:"BytesTransmitted" example:"123456789"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewInterfaceMetrics(src *domain.InterfaceStatus) *InterfaceMetrics {
 | 
			
		||||
	return &InterfaceMetrics{
 | 
			
		||||
		InterfaceIdentifier: string(src.InterfaceId),
 | 
			
		||||
		BytesReceived:       src.BytesReceived,
 | 
			
		||||
		BytesTransmitted:    src.BytesTransmitted,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UserMetrics represents the metrics of a WireGuard user.
 | 
			
		||||
type UserMetrics struct {
 | 
			
		||||
	// The unique identifier of the user.
 | 
			
		||||
	UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
 | 
			
		||||
 | 
			
		||||
	// PeerCount represents the number of peers linked to the user.
 | 
			
		||||
	PeerCount int `json:"PeerCount" example:"2"`
 | 
			
		||||
 | 
			
		||||
	// The total number of bytes received by the user. This is the sum of all bytes received by the peers linked to the user.
 | 
			
		||||
	BytesReceived uint64 `json:"BytesReceived" example:"123456789"`
 | 
			
		||||
	// The total number of bytes transmitted by the user. This is the sum of all bytes transmitted by the peers linked to the user.
 | 
			
		||||
	BytesTransmitted uint64 `json:"BytesTransmitted" example:"123456789"`
 | 
			
		||||
 | 
			
		||||
	// PeerMetrics represents the metrics of the peers linked to the user.
 | 
			
		||||
	PeerMetrics []PeerMetrics `json:"PeerMetrics"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewUserMetrics(srcUser *domain.User, src []domain.PeerStatus) *UserMetrics {
 | 
			
		||||
	if srcUser == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	um := &UserMetrics{
 | 
			
		||||
		UserIdentifier: string(srcUser.Identifier),
 | 
			
		||||
		PeerCount:      srcUser.LinkedPeerCount,
 | 
			
		||||
		PeerMetrics:    []PeerMetrics{},
 | 
			
		||||
 | 
			
		||||
		BytesReceived:    0,
 | 
			
		||||
		BytesTransmitted: 0,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peerMetrics := make([]PeerMetrics, len(src))
 | 
			
		||||
	for i, peer := range src {
 | 
			
		||||
		peerMetrics[i] = *NewPeerMetrics(&peer)
 | 
			
		||||
 | 
			
		||||
		um.BytesReceived += peer.BytesReceived
 | 
			
		||||
		um.BytesTransmitted += peer.BytesTransmitted
 | 
			
		||||
	}
 | 
			
		||||
	um.PeerMetrics = peerMetrics
 | 
			
		||||
 | 
			
		||||
	return um
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user