Add admin api for deleting invalid metrics (#32666)

This commit is contained in:
andig 2026-08-09 16:30:34 +02:00 • committed by GitHub
parent 5d1f1193bb
commit 5ab5cfc2ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 589 additions and 12 deletions

View file

@ -438,9 +438,11 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *uti
api.Use(ensureDbAuth(auth))
routes := map[string]route{
"backup": {"GET", "/backup", getBackup()},
"restore": {"POST", "/restore", restoreDatabase(shutdown)},
"reset": {"POST", "/reset", resetDatabase(shutdown)},
"backup": {"GET", "/backup", getBackup()},
"restore": {"POST", "/restore", restoreDatabase(shutdown)},
"reset": {"POST", "/reset", resetDatabase(shutdown)},
"deleteenergy": {"DELETE", "/metrics/energy", deleteEnergyHandler},
"deletetariffs": {"DELETE", "/metrics/tariffs", deleteTariffsHandler},
}
for _, r := range routes {

View file

@ -0,0 +1,88 @@
package server
import (
"errors"
"net/http"
"time"
"github.com/evcc-io/evcc/core/metrics"
"github.com/evcc-io/evcc/server/db"
)
// deleteResult reports the number of affected rows
type deleteResult struct {
Deleted int64 `json:"deleted"`
}
// timeRange parses the mandatory from/to query parameters
func timeRange(r *http.Request) (time.Time, time.Time, error) {
q := r.URL.Query()
from, err := time.Parse(time.RFC3339, q.Get("from"))
if err != nil {
return time.Time{}, time.Time{}, errors.New("invalid 'from' parameter")
}
to, err := time.Parse(time.RFC3339, q.Get("to"))
if err != nil {
return time.Time{}, time.Time{}, errors.New("invalid 'to' parameter")
}
return from, to, nil
}
// deleteEnergyHandler removes energy metrics. Parameters match
// /api/history/energy so the same request can be previewed before deleting.
func deleteEnergyHandler(w http.ResponseWriter, r *http.Request) {
if db.Instance == nil {
jsonError(w, http.StatusBadRequest, errors.New("database offline"))
return
}
from, to, err := timeRange(r)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
q := r.URL.Query()
filter := metrics.EnergyFilter{
Group: q.Get("group"),
Name: q.Get("name"),
Title: q.Get("title"),
}
rows, err := metrics.DeleteEnergy(from, to, filter)
if err != nil {
jsonError(w, http.StatusInternalServerError, err)
return
}
jsonWrite(w, deleteResult{rows})
}
// deleteTariffsHandler removes persisted tariff values
func deleteTariffsHandler(w http.ResponseWriter, r *http.Request) {
if db.Instance == nil {
jsonError(w, http.StatusBadRequest, errors.New("database offline"))
return
}
from, to, err := timeRange(r)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
rows, err := metrics.DeleteTariffs(from, to, r.URL.Query().Get("usage"))
if err != nil {
status := http.StatusInternalServerError
if errors.Is(err, metrics.ErrInvalidUsage) {
status = http.StatusBadRequest
}
jsonError(w, status, err)
return
}
jsonWrite(w, deleteResult{rows})
}

181
server/mcp/openapi.json generated
View file

@ -2244,6 +2244,171 @@
}
}
},
"/db/metrics/energy": {
"delete": {
"operationId": "deleteEnergyMetrics",
"summary": "Delete energy metrics",
"description": "Deletes recorded energy slots in the given time range, optionally narrowed by entity. Filter parameters match /history/energy, so the same request can be previewed there before deleting. Session users must supply the admin password in the X-Admin-Password header. API key holders via Bearer token are exempt.",
"tags": [
"db"
],
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/adminPassword"
},
{
"name": "from",
"in": "query",
"required": true,
"description": "Start time, inclusive (RFC3339)",
"schema": {
"type": "string",
"format": "date-time",
"example": "2026-07-01T00:00:00Z"
}
},
{
"name": "to",
"in": "query",
"required": true,
"description": "End time, exclusive (RFC3339)",
"schema": {
"type": "string",
"format": "date-time",
"example": "2026-07-02T00:00:00Z"
}
},
{
"name": "group",
"in": "query",
"description": "Filter by entity group",
"schema": {
"type": "string",
"example": "forecast",
"enum": [
"forecast",
"battery",
"grid",
"pv",
"home",
"loadpoint",
"meter",
"consumer",
"temperature"
]
}
},
{
"name": "name",
"in": "query",
"description": "Filter by entity name",
"schema": {
"type": "string",
"example": "db:8"
}
},
{
"name": "title",
"in": "query",
"description": "Filter by entity title",
"schema": {
"type": "string",
"example": "Battery"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/Deleted"
},
"400": {
"description": "Invalid parameters or database offline"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
}
}
}
},
"/db/metrics/tariffs": {
"delete": {
"operationId": "deleteTariffMetrics",
"summary": "Delete tariff metrics",
"description": "Deletes recorded tariff values in the given time range. Without usage the entire record is removed, otherwise only that usage is cleared and records left without any value are removed. Session users must supply the admin password in the X-Admin-Password header. API key holders via Bearer token are exempt.",
"tags": [
"db"
],
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/adminPassword"
},
{
"name": "from",
"in": "query",
"required": true,
"description": "Start time, inclusive (RFC3339)",
"schema": {
"type": "string",
"format": "date-time",
"example": "2026-07-01T00:00:00Z"
}
},
{
"name": "to",
"in": "query",
"required": true,
"description": "End time, exclusive (RFC3339)",
"schema": {
"type": "string",
"format": "date-time",
"example": "2026-07-02T00:00:00Z"
}
},
{
"name": "usage",
"in": "query",
"description": "Limit deletion to a single tariff usage",
"schema": {
"type": "string",
"example": "co2",
"enum": [
"grid",
"feedin",
"co2",
"temperature"
]
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/Deleted"
},
"400": {
"description": "Invalid parameters or database offline"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
}
}
}
},
"/history/energy": {
"get": {
"operationId": "getEnergyHistory",
@ -5296,6 +5461,22 @@
}
},
"responses": {
"Deleted": {
"description": "Success - number of deleted records",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"deleted": {
"type": "integer",
"example": 96
}
}
}
}
}
},
"NanoSecondsResult": {
"description": "Success - Number result - Unit is **nanoseconds**",
"content": {

View file

@ -1353,6 +1353,115 @@ paths:
description: Reset successful, instance is restarting
"401":
$ref: "#/components/responses/Unauthorized"
/db/metrics/energy:
delete:
operationId: deleteEnergyMetrics
summary: Delete energy metrics
description: "Deletes recorded energy slots in the given time range, optionally narrowed by entity. Filter parameters match /history/energy, so the same request can be previewed there before deleting. Session users must supply the admin password in the X-Admin-Password header. API key holders via Bearer token are exempt."
tags:
- db
security:
- cookieAuth: []
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/adminPassword"
- name: from
in: query
required: true
description: Start time, inclusive (RFC3339)
schema:
type: string
format: date-time
example: "2026-07-01T00:00:00Z"
- name: to
in: query
required: true
description: End time, exclusive (RFC3339)
schema:
type: string
format: date-time
example: "2026-07-02T00:00:00Z"
- name: group
in: query
description: Filter by entity group
schema:
type: string
example: forecast
enum:
- forecast
- battery
- grid
- pv
- home
- loadpoint
- meter
- consumer
- temperature
- name: name
in: query
description: Filter by entity name
schema:
type: string
example: db:8
- name: title
in: query
description: Filter by entity title
schema:
type: string
example: Battery
responses:
"200":
$ref: "#/components/responses/Deleted"
"400":
description: Invalid parameters or database offline
"401":
$ref: "#/components/responses/Unauthorized"
/db/metrics/tariffs:
delete:
operationId: deleteTariffMetrics
summary: Delete tariff metrics
description: "Deletes recorded tariff values in the given time range. Without usage the entire record is removed, otherwise only that usage is cleared and records left without any value are removed. Session users must supply the admin password in the X-Admin-Password header. API key holders via Bearer token are exempt."
tags:
- db
security:
- cookieAuth: []
- bearerAuth: []
parameters:
- $ref: "#/components/parameters/adminPassword"
- name: from
in: query
required: true
description: Start time, inclusive (RFC3339)
schema:
type: string
format: date-time
example: "2026-07-01T00:00:00Z"
- name: to
in: query
required: true
description: End time, exclusive (RFC3339)
schema:
type: string
format: date-time
example: "2026-07-02T00:00:00Z"
- name: usage
in: query
description: Limit deletion to a single tariff usage
schema:
type: string
example: co2
enum:
- grid
- feedin
- co2
- temperature
responses:
"200":
$ref: "#/components/responses/Deleted"
"400":
description: Invalid parameters or database offline
"401":
$ref: "#/components/responses/Unauthorized"
/history/energy:
get:
operationId: getEnergyHistory
@ -1816,6 +1925,16 @@ components:
- "1"
- "0"
responses:
Deleted:
description: Success - number of deleted records
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
example: 96
NanoSecondsResult:
description: Success - Number result - Unit is **nanoseconds**
content: