updated to api/v2

This commit is contained in:
AndReicscs
2026-06-22 12:21:52 +00:00
parent a9a918f67b
commit 8589ce8e8e
23 changed files with 163 additions and 163 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ This document serves as the **single source of truth** for all cross-component d
This is the standard telemetry payload that all sensors (official or community) must POST to the Hub when an intrusion is detected. The Hub's frontend dynamically parses, syntax-highlights, and renders this JSON.
**Endpoint:** `POST /api/v1/event`
**Endpoint:** `POST /api/v2/event`
**Authentication:** `Authorization: Bearer <HW_NODE_KEY>`
```json
@@ -43,7 +43,7 @@ This is the standard telemetry payload that all sensors (official or community)
This payload is emitted continuously (typically every 30 seconds) by sensors to prove they are alive and running the expected configuration.
**Endpoint:** `POST /api/v1/heartbeat`
**Endpoint:** `POST /api/v2/heartbeat`
**Authentication:** `Authorization: Bearer <HW_NODE_KEY>`
```json
+38 -38
View File
@@ -1,6 +1,6 @@
# HoneyWire Hub — API Reference
All Hub routes are prefixed with `/api/v1` unless otherwise noted.
All Hub routes are prefixed with `/api/v2` unless otherwise noted.
## Authentication
@@ -21,7 +21,7 @@ Authorization: Bearer <HW_NODE_KEY>
## Public Endpoints
### GET /api/v1/version
### GET /api/v2/version
Returns the running Hub version.
@@ -49,7 +49,7 @@ Invalidates the current dashboard session and redirects the client to `/`.
**Response:** `303 See Other`
### GET /api/v1/setup/status
### GET /api/v2/setup/status
Returns whether the Hub requires initial setup.
When `HW_DASHBOARD_PASSWORD` is set, setup is considered locked and this endpoint returns `requires_setup: false`.
@@ -59,7 +59,7 @@ When `HW_DASHBOARD_PASSWORD` is set, setup is considered locked and this endpoin
{ "requiresSetup": true }
```
### POST /api/v1/setup
### POST /api/v2/setup
Completes first-time hub setup and stores the hashed admin password, hub endpoint, and hub API key.
If `HW_DASHBOARD_PASSWORD` is set, setup is blocked.
@@ -81,7 +81,7 @@ If `HW_DASHBOARD_PASSWORD` is set, setup is blocked.
These endpoints require a valid `hw_auth` session cookie.
### GET /api/v1/ws
### GET /api/v2/ws
Upgrades the connection to a WebSocket for realtime dashboard updates.
@@ -100,18 +100,18 @@ Common message types:
- `SILENCE_SENSOR` — a sensor silence state changed.
- `SYNC_CHARTS` — instructs UIs to refresh chart data.
### GET /api/v1/manifests
### GET /api/v2/manifests
Fetches the sensor manifest catalog from `RegistryURL` or the default public manifest registry.
### GET /api/v1/manifests/{sensorId}/versions
### GET /api/v2/manifests/{sensorId}/versions
Fetches a specific historical version schema of a sensor manifest from the registry.
**Query parameters:**
- `version` — The exact version string to fetch (e.g. `2.0.6`).
### POST /api/v1/compose/generate
### POST /api/v2/compose/generate
Generates a `docker-compose.yml` preview from the selected sensor manifests and UI-provided environment values.
@@ -136,7 +136,7 @@ Generates a `docker-compose.yml` preview from the selected sensor manifests and
### Nodes and Sensors
#### POST /api/v1/nodes
#### POST /api/v2/nodes
Creates a new node entry and returns the generated node credentials.
@@ -156,7 +156,7 @@ Creates a new node entry and returns the generated node credentials.
}
```
#### GET /api/v1/nodes
#### GET /api/v2/nodes
Returns all registered nodes and their installed sensors. Each node includes current status and pending config state.
@@ -182,11 +182,11 @@ Returns all registered nodes and their installed sensors. Each node includes cur
]
```
#### GET /api/v1/nodes/{nodeId}
#### GET /api/v2/nodes/{nodeId}
Returns details for a single node, including installed sensors and per-sensor event counts.
#### PATCH /api/v1/nodes/{nodeId}
#### PATCH /api/v2/nodes/{nodeId}
Updates a node's alias, tags, public IP, or private IP.
@@ -200,15 +200,15 @@ Updates a node's alias, tags, public IP, or private IP.
}
```
#### POST /api/v1/nodes/{nodeId}/upgrade
#### POST /api/v2/nodes/{nodeId}/upgrade
Triggers a fleet-wide sync command over WebSockets to force the node to pull the newest compose definitions and automatically pull the latest manifest versions for all of its installed sensors.
#### DELETE /api/v1/nodes/{nodeId}
#### DELETE /api/v2/nodes/{nodeId}
Deletes a node and cascades to remove its sensors, event history, and heartbeat records.
#### POST /api/v1/nodes/{nodeId}/sensors
#### POST /api/v2/nodes/{nodeId}/sensors
Adds a sensor to a node and flags the node as pending configuration sync.
@@ -223,19 +223,19 @@ Adds a sensor to a node and flags the node as pending configuration sync.
}
```
#### PUT /api/v1/nodes/{nodeId}/sensors/{sensorId}
#### PUT /api/v2/nodes/{nodeId}/sensors/{sensorId}
Updates a sensor's custom name and config values and marks the parent node pending sync.
#### DELETE /api/v1/nodes/{nodeId}/sensors/{sensorId}
#### DELETE /api/v2/nodes/{nodeId}/sensors/{sensorId}
Removes a sensor from the node and marks the node pending sync.
#### POST /api/v1/nodes/{nodeId}/sensors/{sensorId}/upgrade
#### POST /api/v2/nodes/{nodeId}/sensors/{sensorId}/upgrade
Upgrades a specific sensor to the latest available manifest version listed in the registry, and marks the node pending sync.
#### PATCH /api/v1/nodes/{nodeId}/sensors/{sensorId}/silence
#### PATCH /api/v2/nodes/{nodeId}/sensors/{sensorId}/silence
Toggles a sensor's silence state. Silenced sensors still log events but suppress push notifications.
@@ -255,7 +255,7 @@ Toggles a sensor's silence state. Silenced sensors still log events but suppress
### Configuration and System Settings
#### GET /api/v1/config
#### GET /api/v2/config
Returns runtime configuration loaded from SQLite.
@@ -285,7 +285,7 @@ Returns runtime configuration loaded from SQLite.
}
```
#### PATCH /api/v1/config
#### PATCH /api/v2/config
Updates runtime settings. Only supported fields are applied. This endpoint also hot-reloads webhook and SIEM configuration.
@@ -309,7 +309,7 @@ Updates runtime settings. Only supported fields are applied. This endpoint also
}
```
#### GET /api/v1/system/state
#### GET /api/v2/system/state
Returns whether the Hub is armed.
@@ -318,7 +318,7 @@ Returns whether the Hub is armed.
{ "isArmed": true }
```
#### PATCH /api/v1/system/state
#### PATCH /api/v2/system/state
Sets the armed/disarmed state.
@@ -327,7 +327,7 @@ Sets the armed/disarmed state.
{ "isArmed": false }
```
#### PATCH /api/v1/system/password
#### PATCH /api/v2/system/password
Changes the dashboard admin password. When `HW_DASHBOARD_PASSWORD` is set, this operation is forbidden.
@@ -339,7 +339,7 @@ Changes the dashboard admin password. When `HW_DASHBOARD_PASSWORD` is set, this
}
```
#### POST /api/v1/system/reset
#### POST /api/v2/system/reset
Performs a full factory reset of the runtime database, clearing events, sensors, heartbeats, and config.
Requires the current admin password.
@@ -353,7 +353,7 @@ Requires the current admin password.
## Fleet and Events
### GET /api/v1/events/unread
### GET /api/v2/events/unread
Returns the count of active, unread events.
@@ -362,7 +362,7 @@ Returns the count of active, unread events.
{ "count": 12 }
```
### GET /api/v1/events
### GET /api/v2/events
Returns a list of events, newest first.
@@ -371,23 +371,23 @@ Returns a list of events, newest first.
- `nodeId` — filter by node ID
- `sensorId` — filter by sensor ID
### PATCH /api/v1/events/read
### PATCH /api/v2/events/read
Marks all active events as read.
### PATCH /api/v1/events/{eventId}/read
### PATCH /api/v2/events/{eventId}/read
Marks a single event as read.
### PATCH /api/v1/events/{eventId}/archive
### PATCH /api/v2/events/{eventId}/archive
Archives a single event and marks it as read.
### PATCH /api/v1/events/archive-all
### PATCH /api/v2/events/archive-all
Archives all active events.
### DELETE /api/v1/events
### DELETE /api/v2/events
Deletes all events permanently.
@@ -408,7 +408,7 @@ Deletes all events permanently.
{ "status": "success", "dryrun": false }
```
### GET /api/v1/events/severity
### GET /api/v2/events/severity
Returns severity distribution analytics for the fleet.
@@ -431,7 +431,7 @@ Returns severity distribution analytics for the fleet.
}
```
### GET /api/v1/events/velocity
### GET /api/v2/events/velocity
Returns time-bucketed velocity analytics for the fleet.
@@ -463,7 +463,7 @@ Returns time-bucketed velocity analytics for the fleet.
## Uptime and Health
### GET /api/v1/uptime
### GET /api/v2/uptime
Returns uptime blocks for fleet health charts.
@@ -532,7 +532,7 @@ Returns uptime blocks for fleet health charts.
## Node Compose
### GET /api/v1/nodes/compose
### GET /api/v2/nodes/compose
Returns a generated `docker-compose.yml` for a node based on the node's current installed sensors.
@@ -544,7 +544,7 @@ This endpoint is read-only for node deployment: it returns the current or reques
These endpoints are used by sensor agents and require the node API key bearer token.
### POST /api/v1/heartbeat
### POST /api/v2/heartbeat
Reports a sensor heartbeat and updates its versioning state.
@@ -561,7 +561,7 @@ Reports a sensor heartbeat and updates its versioning state.
{ "status": "alive" }
```
### POST /api/v1/event
### POST /api/v2/event
Reports an intrusion event to the Hub.
+1 -1
View File
@@ -82,4 +82,4 @@ HoneyWire employs a multi-tiered authentication strategy depending on the actor:
1. **UI Dashboard (Humans):** Secured via short-lived Sessions and HTTP-Only, Secure, SameSite `hw_auth` Cookies. Handled via `UIAuthMiddleware`.
2. **Sensors/Agents (Machines):** Secured via statically generated, cryptographically random API Keys (`hw_key_...`). Passed via `Authorization: Bearer` or `X-Api-Key` headers and validated by `AgentAuthMiddleware`. These are aggressively cached in memory to prevent database bottlenecks during heartbeat storms.
3. **Dual-Auth Endpoints:** Shared endpoints (like `/api/v1/manifests`) are protected by `DualAuthMiddleware`, which attempts Bearer authentication first before falling back to Cookie validation.
3. **Dual-Auth Endpoints:** Shared endpoints (like `/api/v2/manifests`) are protected by `DualAuthMiddleware`, which attempts Bearer authentication first before falling back to Cookie validation.
+2 -2
View File
@@ -36,7 +36,7 @@ The Severity Projection provides a comprehensive breakdown of event severities b
### API Endpoint
`GET /api/v1/events/severity`
`GET /api/v2/events/severity`
**Query Parameters:**
- `timeframe`: The time window (`alltime`, `24H`, etc.) (default: `alltime`)
@@ -63,7 +63,7 @@ To maintain real-time reactivity without frontend recomputation:
1. A new event triggers a WebSocket broadcast (`NEW_EVENT`).
2. The frontend Vue store receives the event and checks if it falls within the currently active filter context.
3. If relevant, the frontend simply **invalidates** the current projection and re-fetches `/api/v1/events/severity`.
3. If relevant, the frontend simply **invalidates** the current projection and re-fetches `/api/v2/events/severity`.
4. The Vue Store replaces the snapshot reference, and the chart re-renders effortlessly.
This guarantees that the backend always remains the single source of truth for analytics.
+1 -1
View File
@@ -17,7 +17,7 @@ Workers are decoupled from the HTTP transport layer and managed exclusively via
## Data Flow Example (Event Processing)
To understand how the layers interact, consider the lifecycle of an intrusion event when a sensor POSTs to `/api/v1/event`:
To understand how the layers interact, consider the lifecycle of an intrusion event when a sensor POSTs to `/api/v2/event`:
1. **Router (`internal/api/router.go`):** Matches the route and invokes `AgentAuthMiddleware`.
2. **Middleware:**
+3 -3
View File
@@ -33,7 +33,7 @@ Sensor (SDK)
Before a sensor begins monitoring, it must successfully establish a baseline with the Hub.
1. **Environment Validation:** The sensor strictly requires `HW_HUB_ENDPOINT`, `HW_HUB_KEY`, and `HW_SENSOR_ID` to be present.
2. **Version Synchronization (`syncHubVersion`):** The sensor performs a blocking handshake with the Hub (`GET /api/v1/version`). It retrieves the Hub's contract version. If the Hub's major version does not match the SDK's agent version, the sensor refuses to start.
2. **Version Synchronization (`syncHubVersion`):** The sensor performs a blocking handshake with the Hub (`GET /api/v2/version`). It retrieves the Hub's contract version. If the Hub's major version does not match the SDK's agent version, the sensor refuses to start.
3. **Pipeline Boot:** If successful, the SDK spins up the Event Loop and Heartbeat Loop as isolated background workers (Goroutines in Go, Daemon Threads in Python).
---
@@ -79,7 +79,7 @@ The Event Worker handles intrusion alerts. It prioritizes guaranteed delivery wi
### Pipeline B: Heartbeat Worker
The Heartbeat Worker proves the sensor is alive.
1. **Stateless Pulse:** Every 30 seconds, it POSTs to `/api/v1/heartbeat` with its Sensor ID, Agent Version, and Contract Version.
1. **Stateless Pulse:** Every 30 seconds, it POSTs to `/api/v2/heartbeat` with its Sensor ID, Agent Version, and Contract Version.
2. **Lossy:** Unlike events, heartbeats are not queued or retried. If a heartbeat fails, the worker simply sleeps and tries again on the next tick.
---
@@ -90,4 +90,4 @@ When a sensor container receives a termination signal (SIGTERM):
1. **Stop Signal:** The SDK stops accepting new events into the buffer.
2. **Drain Queue:** The Event Worker attempts a best-effort, rapid flush of any remaining events in its buffer directly to the Hub.
3. **Offline Notification (`GoOffline`):** The sensor fires a final, fast-timeout POST to `/api/v1/offline` with the reason `graceful_shutdown`. This allows the Hub's UI to immediately mark the sensor as "Offline" rather than waiting 60 seconds for the heartbeat monitor to declare it "Down".
3. **Offline Notification (`GoOffline`):** The sensor fires a final, fast-timeout POST to `/api/v2/offline` with the reason `graceful_shutdown`. This allows the Hub's UI to immediately mark the sensor as "Offline" rather than waiting 60 seconds for the heartbeat monitor to declare it "Down".
+1 -1
View File
@@ -135,4 +135,4 @@ To sync the catalog:
1. Run `honeywire status` via the edge node CLI, OR
2. go into Fleet Management or Node Details view in the Hub Dashboard.
This triggers an on-demand registry fetch. The Hub will compare the latest compatible versions against deployed sensors and flag nodes with an **"Update Available"** indicator. Users must manually trigger the `/api/v1/nodes/{id}/upgrade` endpoint (via the UI) or run `honeywire apply` to instruct the node to pull the new version schema and execute a compose restart.
This triggers an on-demand registry fetch. The Hub will compare the latest compatible versions against deployed sensors and flag nodes with an **"Update Available"** indicator. Users must manually trigger the `/api/v2/nodes/{id}/upgrade` endpoint (via the UI) or run `honeywire apply` to instruct the node to pull the new version schema and execute a compose restart.
+1 -1
View File
@@ -95,7 +95,7 @@ func (h *AnalyticsHandler) GetSeverityAnalytics(w http.ResponseWriter, r *http.R
SendJSON(w, http.StatusOK, projection)
}
// GetUptime handles GET /api/v1/uptime and returns the fleet uptime projection
// GetUptime handles GET /api/v2/uptime and returns the fleet uptime projection
func (h *AnalyticsHandler) GetUptime(w http.ResponseWriter, r *http.Request) {
// Parse timeframe from query string (default to 24H)
timeframe := r.URL.Query().Get("timeframe")
+11 -11
View File
@@ -10,7 +10,7 @@ import (
)
// CreateNode handles UI-first node creation
// Route: POST /api/v1/nodes
// Route: POST /api/v2/nodes
type NodeHandler struct {
service *node.Service
}
@@ -49,7 +49,7 @@ func (h *NodeHandler) CreateNode(w http.ResponseWriter, r *http.Request) {
}
// UpdateNode handles UI requests to edit a Node's metadata
// Route: PATCH /api/v1/nodes/{id}
// Route: PATCH /api/v2/nodes/{id}
func (h *NodeHandler) UpdateNode(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
@@ -78,7 +78,7 @@ func (h *NodeHandler) UpdateNode(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, map[string]string{"status": "success"})
}
// GetNodes handles GET /api/v1/nodes
// GetNodes handles GET /api/v2/nodes
func (h *NodeHandler) GetNodes(w http.ResponseWriter, r *http.Request) {
nodes, err := h.service.GetNodes()
if err != nil {
@@ -90,7 +90,7 @@ func (h *NodeHandler) GetNodes(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, nodes)
}
// GetNodeDetails handles GET /api/v1/nodes/{id}
// GetNodeDetails handles GET /api/v2/nodes/{id}
func (h *NodeHandler) GetNodeDetails(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
@@ -117,7 +117,7 @@ func (h *NodeHandler) GetNodeDetails(w http.ResponseWriter, r *http.Request) {
})
}
// AddNodeSensor handles POST /api/v1/nodes/{id}/sensors
// AddNodeSensor handles POST /api/v2/nodes/{id}/sensors
func (h *NodeHandler) AddNodeSensor(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
@@ -145,7 +145,7 @@ func (h *NodeHandler) AddNodeSensor(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, map[string]string{"status": "success", "message": "Sensor added, node pending sync"})
}
// EditNodeSensor handles PUT /api/v1/nodes/{id}/sensors/{sensorId}
// EditNodeSensor handles PUT /api/v2/nodes/{id}/sensors/{sensorId}
func (h *NodeHandler) EditNodeSensor(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
sensorID := chi.URLParam(r, "sensorId")
@@ -168,7 +168,7 @@ func (h *NodeHandler) EditNodeSensor(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, map[string]string{"status": "success"})
}
// UpgradeNodeSensor handles POST /api/v1/nodes/{id}/sensors/{sensorId}/upgrade
// UpgradeNodeSensor handles POST /api/v2/nodes/{id}/sensors/{sensorId}/upgrade
func (h *NodeHandler) UpgradeNodeSensor(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
sensorID := chi.URLParam(r, "sensorId")
@@ -181,7 +181,7 @@ func (h *NodeHandler) UpgradeNodeSensor(w http.ResponseWriter, r *http.Request)
SendJSON(w, http.StatusOK, map[string]string{"status": "success", "message": "Sensor upgraded, node pending sync"})
}
// DeleteNodeSensor handles DELETE /api/v1/nodes/{id}/sensors/{sensorId}
// DeleteNodeSensor handles DELETE /api/v2/nodes/{id}/sensors/{sensorId}
func (h *NodeHandler) DeleteNodeSensor(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
sensorID := chi.URLParam(r, "sensorId")
@@ -194,7 +194,7 @@ func (h *NodeHandler) DeleteNodeSensor(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, map[string]string{"status": "success"})
}
// DeleteNode handles DELETE /api/v1/nodes/{id}
// DeleteNode handles DELETE /api/v2/nodes/{id}
func (h *NodeHandler) DeleteNode(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
@@ -206,7 +206,7 @@ func (h *NodeHandler) DeleteNode(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, map[string]string{"status": "success"})
}
// UpgradeNode handles POST /api/v1/nodes/{id}/upgrade
// UpgradeNode handles POST /api/v2/nodes/{id}/upgrade
func (h *NodeHandler) UpgradeNode(w http.ResponseWriter, r *http.Request) {
nodeID := chi.URLParam(r, "nodeId")
@@ -218,7 +218,7 @@ func (h *NodeHandler) UpgradeNode(w http.ResponseWriter, r *http.Request) {
SendJSON(w, http.StatusOK, map[string]string{"status": "success", "message": "Node upgraded, pending sync"})
}
// GetCurrentNode handles GET /api/v1/nodes/me
// GetCurrentNode handles GET /api/v2/nodes/me
// Used by wizard agents authenticated via Bearer token
func (h *NodeHandler) GetCurrentNode(w http.ResponseWriter, r *http.Request) {
+40 -40
View File
@@ -50,73 +50,73 @@ func SetupRouter(cfg RouterConfig) (*chi.Mux, error) {
rateLimiter := NewRateLimiter()
// Public Endpoints
r.Get("/api/v1/version", cfg.Config.HandleVersion)
r.Get("/api/v2/version", cfg.Config.HandleVersion)
r.Post("/login", cfg.Auth.Login)
r.Post("/logout", cfg.Auth.Logout)
r.Get("/api/v1/setup/status", cfg.Config.GetSetupStatus)
r.Post("/api/v1/setup", cfg.Config.CompleteSetup)
r.Get("/api/v2/setup/status", cfg.Config.GetSetupStatus)
r.Post("/api/v2/setup", cfg.Config.CompleteSetup)
// UI Endpoints (Protected by Cookies)
r.Group(func(r chi.Router) {
r.Use(UIAuthMiddleware(cfg.SessionValidator))
r.Get("/api/v1/ws", cfg.WSService.HandleWS)
r.Get("/api/v2/ws", cfg.WSService.HandleWS)
// UI Compose Preview
r.Post("/api/v1/compose/generate", cfg.Compose.GenerateCompose) // Used by UI modal for live preview
r.Post("/api/v2/compose/generate", cfg.Compose.GenerateCompose) // Used by UI modal for live preview
// --- Node & Fleet Management ---
r.Post("/api/v1/nodes", cfg.Nodes.CreateNode)
r.Get("/api/v1/nodes", cfg.Nodes.GetNodes)
r.Get("/api/v1/nodes/{nodeId}", cfg.Nodes.GetNodeDetails)
r.Patch("/api/v1/nodes/{nodeId}", cfg.Nodes.UpdateNode)
r.Post("/api/v1/nodes/{nodeId}/upgrade", cfg.Nodes.UpgradeNode)
r.Delete("/api/v1/nodes/{nodeId}", cfg.Nodes.DeleteNode)
r.Post("/api/v2/nodes", cfg.Nodes.CreateNode)
r.Get("/api/v2/nodes", cfg.Nodes.GetNodes)
r.Get("/api/v2/nodes/{nodeId}", cfg.Nodes.GetNodeDetails)
r.Patch("/api/v2/nodes/{nodeId}", cfg.Nodes.UpdateNode)
r.Post("/api/v2/nodes/{nodeId}/upgrade", cfg.Nodes.UpgradeNode)
r.Delete("/api/v2/nodes/{nodeId}", cfg.Nodes.DeleteNode)
// --- Sensor Management ---
r.Post("/api/v1/nodes/{nodeId}/sensors", cfg.Nodes.AddNodeSensor)
r.Put("/api/v1/nodes/{nodeId}/sensors/{sensorId}", cfg.Nodes.EditNodeSensor)
r.Post("/api/v1/nodes/{nodeId}/sensors/{sensorId}/upgrade", cfg.Nodes.UpgradeNodeSensor)
r.Delete("/api/v1/nodes/{nodeId}/sensors/{sensorId}", cfg.Nodes.DeleteNodeSensor)
r.Patch("/api/v1/nodes/{nodeId}/sensors/{sensorId}/silence", cfg.Sensors.ToggleSilence)
r.Post("/api/v2/nodes/{nodeId}/sensors", cfg.Nodes.AddNodeSensor)
r.Put("/api/v2/nodes/{nodeId}/sensors/{sensorId}", cfg.Nodes.EditNodeSensor)
r.Post("/api/v2/nodes/{nodeId}/sensors/{sensorId}/upgrade", cfg.Nodes.UpgradeNodeSensor)
r.Delete("/api/v2/nodes/{nodeId}/sensors/{sensorId}", cfg.Nodes.DeleteNodeSensor)
r.Patch("/api/v2/nodes/{nodeId}/sensors/{sensorId}/silence", cfg.Sensors.ToggleSilence)
// System Configuration & Danger Zone
r.Get("/api/v1/config", cfg.Config.GetConfig)
r.Patch("/api/v1/config", cfg.Config.UpdateConfig)
r.Patch("/api/v1/system/password", cfg.Config.ChangePassword)
r.Post("/api/v1/system/reset", cfg.Config.FactoryReset)
r.Get("/api/v2/config", cfg.Config.GetConfig)
r.Patch("/api/v2/config", cfg.Config.UpdateConfig)
r.Patch("/api/v2/system/password", cfg.Config.ChangePassword)
r.Post("/api/v2/system/reset", cfg.Config.FactoryReset)
// Telemetry & State (For UI Dashboards)
r.Get("/api/v1/events/severity", cfg.Analytics.GetSeverityAnalytics)
r.Get("/api/v1/events/velocity", cfg.Analytics.GetVelocityAnalytics)
r.Get("/api/v1/events/summary", cfg.Analytics.GetSummaryAnalytics)
r.Get("/api/v1/events", cfg.Events.GetEvents)
r.Get("/api/v1/uptime", cfg.Analytics.GetUptime)
r.Get("/api/v1/system/state", cfg.Config.GetSystemState)
r.Patch("/api/v1/system/state", cfg.Config.SetSystemState)
r.Get("/api/v2/events/severity", cfg.Analytics.GetSeverityAnalytics)
r.Get("/api/v2/events/velocity", cfg.Analytics.GetVelocityAnalytics)
r.Get("/api/v2/events/summary", cfg.Analytics.GetSummaryAnalytics)
r.Get("/api/v2/events", cfg.Events.GetEvents)
r.Get("/api/v2/uptime", cfg.Analytics.GetUptime)
r.Get("/api/v2/system/state", cfg.Config.GetSystemState)
r.Patch("/api/v2/system/state", cfg.Config.SetSystemState)
// Event Management
r.Get("/api/v1/events/unread", cfg.Events.GetUnreadCount)
r.Patch("/api/v1/events/read", cfg.Events.MarkEventsRead)
r.Patch("/api/v1/events/{eventId}/read", cfg.Events.MarkSingleEventRead)
r.Delete("/api/v1/events", cfg.Events.ClearEvents)
r.Patch("/api/v1/events/{eventId}/archive", cfg.Events.ArchiveEvent)
r.Patch("/api/v1/events/archive-all", cfg.Events.ArchiveAll)
r.Get("/api/v2/events/unread", cfg.Events.GetUnreadCount)
r.Patch("/api/v2/events/read", cfg.Events.MarkEventsRead)
r.Patch("/api/v2/events/{eventId}/read", cfg.Events.MarkSingleEventRead)
r.Delete("/api/v2/events", cfg.Events.ClearEvents)
r.Patch("/api/v2/events/{eventId}/archive", cfg.Events.ArchiveEvent)
r.Patch("/api/v2/events/archive-all", cfg.Events.ArchiveAll)
})
// --- Wizard & Telemetry Endpoints ---
r.Group(func(r chi.Router) {
r.Use(AgentAuthMiddleware(cfg.NodeAuthenticator, rateLimiter))
r.Get("/api/v1/nodes/me", cfg.Nodes.GetCurrentNode)
r.Get("/api/v1/nodes/compose", cfg.Compose.GetNodeCompose)
r.Post("/api/v1/heartbeat", cfg.Sensors.ReceiveHeartbeat)
r.Post("/api/v1/offline", cfg.Sensors.ReceiveOffline)
r.Post("/api/v1/event", cfg.Events.ReceiveEvent)
r.Get("/api/v2/nodes/me", cfg.Nodes.GetCurrentNode)
r.Get("/api/v2/nodes/compose", cfg.Compose.GetNodeCompose)
r.Post("/api/v2/heartbeat", cfg.Sensors.ReceiveHeartbeat)
r.Post("/api/v2/offline", cfg.Sensors.ReceiveOffline)
r.Post("/api/v2/event", cfg.Events.ReceiveEvent)
})
r.With(DualAuthMiddleware(cfg.SessionValidator, cfg.NodeAuthenticator, rateLimiter)).Group(func(r chi.Router) {
r.Get("/api/v1/manifests", cfg.Sensors.GetManifests)
r.Get("/api/v1/manifests/{sensorId}/versions", cfg.Sensors.GetSpecificManifest)
r.Get("/api/v2/manifests", cfg.Sensors.GetManifests)
r.Get("/api/v2/manifests/{sensorId}/versions", cfg.Sensors.GetSpecificManifest)
})
// --- Serve the Vue Frontend ---
+2 -2
View File
@@ -16,7 +16,7 @@ const { sidebarOpen, viewingArchive, version } = storeToRefs(appStore)
const clearLogs = async () => {
try {
const dryRes = await fetch('/api/v1/events?dryrun=true', { method: 'DELETE' })
const dryRes = await fetch('/api/v2/events?dryrun=true', { method: 'DELETE' })
if (!dryRes.ok) throw new Error("Failed to fetch dryrun data")
const dryData = await dryRes.json()
@@ -28,7 +28,7 @@ const clearLogs = async () => {
}
if (confirm(`Confirm Database Purge?\n\nThis will permanently delete ${count} active and archived event logs.\n\nThis action cannot be undone.`)) {
const response = await fetch('/api/v1/events?dryrun=false', {
const response = await fetch('/api/v2/events?dryrun=false', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
+1 -1
View File
@@ -102,7 +102,7 @@ export class HoneyWireWS {
const protocol = this.baseUrl.startsWith('https') ? 'wss:' : 'ws:';
const host = this.baseUrl.replace(/^https?:\/\//, '');
const wsUrl = `${protocol}//${host}/api/v1/ws`;
const wsUrl = `${protocol}//${host}/api/v2/ws`;
this.ws = new WebSocket(wsUrl);
+3 -3
View File
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
import { ref, readonly } from 'vue'
import { api } from '../../api/client'
// Represents the exact JSON schema returned by GET /api/v1/config
// Represents the exact JSON schema returned by GET /api/v2/config
export interface ConfigApiResponse {
hubEndpoint: string
registryUrl: string
@@ -48,7 +48,7 @@ export const useConfigStore = defineStore('config', () => {
const fetchConfig = async (): Promise<void> => {
try {
const res = await api.get('/api/v1/config')
const res = await api.get('/api/v2/config')
const data = (await res.json()) as ConfigApiResponse
state.value.hubEndpoint = data.hubEndpoint || window.location.origin
@@ -75,7 +75,7 @@ export const useConfigStore = defineStore('config', () => {
const patchConfig = async (updates: Partial<Omit<ConfigState, 'isLoaded'>>): Promise<boolean> => {
try {
await api.patch('/api/v1/config', updates)
await api.patch('/api/v2/config', updates)
Object.assign(state.value, updates)
return true
+9 -9
View File
@@ -144,7 +144,7 @@ export const useEventsStore = defineStore('events', () => {
const refreshUnreadCount = async (): Promise<void> => {
try {
const res = await api.get('/api/v1/events/unread')
const res = await api.get('/api/v2/events/unread')
const data = await res.json()
state.value.unreadCount = data.count
} catch {
@@ -160,7 +160,7 @@ export const useEventsStore = defineStore('events', () => {
if (nodeId) params.append('nodeId', nodeId)
if (sensorId) params.append('sensorId', sensorId)
const res = await api.get(`/api/v1/events?${params.toString()}`)
const res = await api.get(`/api/v2/events?${params.toString()}`)
state.value.events = (await res.json() as any[]).map(normalizeEvent)
await refreshUnreadCount()
@@ -180,7 +180,7 @@ export const useEventsStore = defineStore('events', () => {
if (nodeId) params.append('node', nodeId)
if (sensorId) params.append('sensor', sensorId)
const response = await api.get(`/api/v1/events/severity?${params.toString()}`, { signal: severityAbortController.signal })
const response = await api.get(`/api/v2/events/severity?${params.toString()}`, { signal: severityAbortController.signal })
state.value.severityProjection = (await response.json()) as SeverityProjection
} catch (e: any) {
if (e.name !== 'AbortError') console.error('Failed to fetch severity projection', e)
@@ -197,7 +197,7 @@ export const useEventsStore = defineStore('events', () => {
if (nodeId) params.append('nodeId', nodeId)
if (sensorId) params.append('sensorId', sensorId)
const response = await api.get(`/api/v1/events/velocity?${params.toString()}`, { signal: velocityAbortController.signal })
const response = await api.get(`/api/v2/events/velocity?${params.toString()}`, { signal: velocityAbortController.signal })
state.value.threatVelocityProjection = (await response.json()) as ThreatVelocityProjection
} catch (e: any) {
if (e.name !== 'AbortError') console.error('Velocity fetch failed:', e)
@@ -216,7 +216,7 @@ export const useEventsStore = defineStore('events', () => {
if (nodeId) params.append('nodeId', nodeId)
if (sensorId) params.append('sensorId', sensorId)
const response = await api.get(`/api/v1/events/summary?${params.toString()}`, { signal: summaryAbortController.signal })
const response = await api.get(`/api/v2/events/summary?${params.toString()}`, { signal: summaryAbortController.signal })
state.value.summaryProjection = (await response.json()) as SummaryProjection
} catch (e: any) {
if (e.name !== 'AbortError') console.error('Summary fetch failed:', e)
@@ -231,7 +231,7 @@ export const useEventsStore = defineStore('events', () => {
const markAllRead = async (): Promise<{ success: boolean; error?: string }> => {
try {
await api.patch('/api/v1/events/read')
await api.patch('/api/v2/events/read')
state.value.events.forEach(e => (e.isRead = true))
state.value.unreadCount = 0
return { success: true }
@@ -250,7 +250,7 @@ export const useEventsStore = defineStore('events', () => {
state.value.unreadCount = Math.max(0, state.value.unreadCount - 1)
try {
await api.patch(`/api/v1/events/${eventId}/read`)
await api.patch(`/api/v2/events/${eventId}/read`)
return { success: true }
} catch (err) {
ev.isRead = wasRead
@@ -263,7 +263,7 @@ export const useEventsStore = defineStore('events', () => {
const archiveEvent = async (eventId: string): Promise<{ success: boolean; error?: string }> => {
const originalEvents = [...state.value.events]
try {
await api.patch(`/api/v1/events/${eventId}/archive`)
await api.patch(`/api/v2/events/${eventId}/archive`)
state.value.events = state.value.events.filter(e => e.id !== eventId)
state.value.activeEvent = null
await refreshUnreadCount()
@@ -281,7 +281,7 @@ export const useEventsStore = defineStore('events', () => {
const archiveAll = async (): Promise<{ success: boolean; error?: string }> => {
try {
await api.patch('/api/v1/events/archive-all')
await api.patch('/api/v2/events/archive-all')
await fetchEvents(false, fleetStore.selectedNode?.id, fleetStore.selectedSensor?.sensorId)
// Refetch analytics projections so the charts zero out
fetchSeverityProjection('alltime', fleetStore.selectedNode?.id, fleetStore.selectedSensor?.sensorId)
+16 -16
View File
@@ -378,7 +378,7 @@ export const useFleetStore = defineStore('fleet', () => {
// --- ACTIONS: STRUCTURAL INGESTION ---
const fetchFleet = async (): Promise<void> => {
try {
const res = await api.get('/api/v1/nodes')
const res = await api.get('/api/v2/nodes')
const raw = await res.json() || []
const incoming = Array.isArray(raw) ? raw : raw.nodes || []
@@ -410,7 +410,7 @@ export const useFleetStore = defineStore('fleet', () => {
abortControllers.set(nodeId, controller)
try {
const res = await api.get(`/api/v1/nodes/${encodeURIComponent(nodeId)}`, { signal: controller.signal })
const res = await api.get(`/api/v2/nodes/${encodeURIComponent(nodeId)}`, { signal: controller.signal })
const rawNode = await res.json()
const node = normalizeNodeData(rawNode)
if (!node) return
@@ -504,7 +504,7 @@ export const useFleetStore = defineStore('fleet', () => {
const fetchUptime = async (timeframe?: string): Promise<void> => {
const target = timeframe || state.value.activeTimeframe
try {
const res = await api.get(`/api/v1/uptime?timeframe=${target}`)
const res = await api.get(`/api/v2/uptime?timeframe=${target}`)
state.value.uptimeData = (await res.json()) as UptimeData
} catch (e) {
console.error('Failed to fetch uptime', e)
@@ -513,7 +513,7 @@ export const useFleetStore = defineStore('fleet', () => {
const fetchManifests = async (): Promise<any[]> => {
try {
const res = await api.get('/api/v1/manifests')
const res = await api.get('/api/v2/manifests')
const data = await res.json()
// The API returns the raw index.json which has a top-level { sensors: [] } wrapper
const sensorsArray = Array.isArray(data) ? data : (data?.sensors || [])
@@ -528,7 +528,7 @@ export const useFleetStore = defineStore('fleet', () => {
// --- ACTIONS: COMPOSE / SYNC ---
const fetchCompose = async (apiKey: string): Promise<string> => {
try {
const res = await api.request('/api/v1/nodes/compose', {
const res = await api.request('/api/v2/nodes/compose', {
headers: { Authorization: `Bearer ${apiKey}` },
})
return await res.text()
@@ -540,7 +540,7 @@ export const useFleetStore = defineStore('fleet', () => {
const generateCompose = async (payload: any): Promise<string> => {
try {
const res = await api.post('/api/v1/compose/generate', payload)
const res = await api.post('/api/v2/compose/generate', payload)
return await res.text()
} catch (err) {
console.error('Failed to generate compose:', err)
@@ -566,7 +566,7 @@ export const useFleetStore = defineStore('fleet', () => {
// --- ACTIONS: NODE MUTATIONS ---
const createNode = async (alias: string, tags: string[] = []): Promise<{ nodeId: string, apiKey: string }> => {
try {
const res = await api.post('/api/v1/nodes', { alias, tags })
const res = await api.post('/api/v2/nodes', { alias, tags })
const data = await res.json()
await fetchFleet()
@@ -584,7 +584,7 @@ export const useFleetStore = defineStore('fleet', () => {
let previousState: FleetNode | null = null
try {
previousState = patchNode(nodeId, payload)
await api.patch(`/api/v1/nodes/${nodeId}`, payload)
await api.patch(`/api/v2/nodes/${nodeId}`, payload)
} catch (err) {
if (previousState) patchNode(nodeId, previousState)
console.error('Failed to update node:', err)
@@ -594,7 +594,7 @@ export const useFleetStore = defineStore('fleet', () => {
const upgradeNode = async (nodeId: string): Promise<void> => {
try {
await api.post(`/api/v1/nodes/${nodeId}/upgrade`)
await api.post(`/api/v2/nodes/${nodeId}/upgrade`)
fetchNodeDetails(nodeId)
} catch (err: any) {
console.error('Node Upgrade Failed:', err.message || 'Failed to upgrade node')
@@ -606,7 +606,7 @@ export const useFleetStore = defineStore('fleet', () => {
markNodeAction(nodeId, 'deleting')
try {
await api.delete(`/api/v1/nodes/${nodeId}`)
await api.delete(`/api/v2/nodes/${nodeId}`)
if (state.value.selectedNodeId === nodeId) {
state.value.selectedNodeId = null
state.value.selectedSensorId = null
@@ -625,7 +625,7 @@ export const useFleetStore = defineStore('fleet', () => {
// --- ACTIONS: SENSOR MUTATIONS ---
const addSensor = async (nodeId: string, { sensorId: rawSensorId, customName, configValues }: { sensorId: string, customName?: string, configValues?: Record<string, string> }): Promise<void> => {
try {
await api.post(`/api/v1/nodes/${encodeURIComponent(nodeId)}/sensors`, {
await api.post(`/api/v2/nodes/${encodeURIComponent(nodeId)}/sensors`, {
sensorId: rawSensorId,
customName: customName || rawSensorId,
configValues: configValues,
@@ -641,7 +641,7 @@ export const useFleetStore = defineStore('fleet', () => {
if (!nodeId || !rawSensorId) throw new Error('Missing required parameters')
try {
await api.post(`/api/v1/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}/upgrade`)
await api.post(`/api/v2/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}/upgrade`)
fetchNodeDetails(nodeId)
} catch (err: any) {
console.error('Sensor Upgrade Failed:', err.message || 'Failed to upgrade sensor')
@@ -653,7 +653,7 @@ export const useFleetStore = defineStore('fleet', () => {
const sensor = getSensor(nodeId, rawSensorId)
if (!sensor) return
try {
await api.put(`/api/v1/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}`, {
await api.put(`/api/v2/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}`, {
customName: customName || rawSensorId,
configValues: configValues,
})
@@ -669,7 +669,7 @@ export const useFleetStore = defineStore('fleet', () => {
if (!sensor) return { success: false, error: 'Sensor not found.' }
try {
await api.delete(`/api/v1/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}`)
await api.delete(`/api/v2/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}`)
await fetchNodeDetails(nodeId)
const compositeId = `${nodeId}:${rawSensorId}`
if (state.value.selectedSensorId === compositeId) state.value.selectedSensorId = null
@@ -688,7 +688,7 @@ export const useFleetStore = defineStore('fleet', () => {
const previousState = patchSensor(sensor.id, { isSilenced: targetState })
try {
await api.patch(`/api/v1/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}/silence`, {
await api.patch(`/api/v2/nodes/${encodeURIComponent(nodeId)}/sensors/${encodeURIComponent(rawSensorId)}/silence`, {
isSilenced: targetState,
})
} catch (err) {
@@ -713,7 +713,7 @@ export const useFleetStore = defineStore('fleet', () => {
try {
await Promise.all(nodeSensors.map(s =>
api.patch(`/api/v1/nodes/${nodeId}/sensors/${encodeURIComponent(s.sensorId)}/silence`, {
api.patch(`/api/v2/nodes/${nodeId}/sensors/${encodeURIComponent(s.sensorId)}/silence`, {
isSilenced: targetState,
})
))
+8 -8
View File
@@ -103,7 +103,7 @@ export const useAppStore = defineStore('app', () => {
const fetchSystemState = async (): Promise<{ success: boolean; status?: number }> => {
try {
const res = await api.get('/api/v1/system/state')
const res = await api.get('/api/v2/system/state')
const data = (await res.json()) as SystemStatePayload
commitSystemState(data)
return { success: true }
@@ -124,7 +124,7 @@ export const useAppStore = defineStore('app', () => {
const targetState = !state.value.isArmed
try {
await api.patch('/api/v1/system/state', { isArmed: targetState })
await api.patch('/api/v2/system/state', { isArmed: targetState })
await fetchSystemState()
return { success: true }
} catch (err) {
@@ -136,7 +136,7 @@ export const useAppStore = defineStore('app', () => {
const changePassword = async (currentPassword: string, newPassword: string): Promise<{ success: boolean; error?: string }> => {
try {
await api.patch('/api/v1/system/password', { currentPassword, newPassword })
await api.patch('/api/v2/system/password', { currentPassword, newPassword })
return { success: true }
} catch (err: any) {
return {
@@ -148,7 +148,7 @@ export const useAppStore = defineStore('app', () => {
const factoryReset = async (password: string, dryrun: boolean = false): Promise<{ success: boolean; error?: string; stats?: any }> => {
try {
const url = dryrun ? '/api/v1/system/reset?dryrun=true' : '/api/v1/system/reset'
const url = dryrun ? '/api/v2/system/reset?dryrun=true' : '/api/v2/system/reset'
const response = await api.post(url, { password })
let stats = undefined
@@ -194,7 +194,7 @@ export const useAppStore = defineStore('app', () => {
const completeSetup = async (password: string, hubEndpoint: string): Promise<{ success: boolean; error?: string }> => {
state.value.setupError = null
try {
await api.post('/api/v1/setup', { password, hubEndpoint })
await api.post('/api/v2/setup', { password, hubEndpoint })
state.value.requiresSetup = false
transitionSession('unauthenticated')
return { success: true }
@@ -207,15 +207,15 @@ export const useAppStore = defineStore('app', () => {
// --- ACTIONS: BOOTSTRAP ---
const checkRequiresSetup = async (): Promise<void> => {
const res = await api.get('/api/v1/setup/status')
const res = await api.get('/api/v2/setup/status')
const data = (await res.json()) as { requiresSetup: boolean }
state.value.requiresSetup = data.requiresSetup || false
}
const checkCoreConfiguration = async (): Promise<void> => {
const [stateRes, verRes] = await Promise.allSettled([
api.get('/api/v1/system/state').then(r => r.json()),
api.get('/api/v1/version').then(r => r.json())
api.get('/api/v2/system/state').then(r => r.json()),
api.get('/api/v2/version').then(r => r.json())
])
if (verRes.status === 'fulfilled') commitVersionState(verRes.value as VersionPayload)
+1 -1
View File
@@ -258,7 +258,7 @@ const editSensor = async (installedSensor: any) => {
if (installedSensor.deployedVersion) {
try {
const res = await api.get(`/api/v1/manifests/${encodeURIComponent(installedSensor.sensorId || installedSensor.id || installedSensor.name)}/versions?version=${encodeURIComponent(installedSensor.deployedVersion)}`)
const res = await api.get(`/api/v2/manifests/${encodeURIComponent(installedSensor.sensorId || installedSensor.id || installedSensor.name)}/versions?version=${encodeURIComponent(installedSensor.deployedVersion)}`)
manifest = await res.json()
} catch (err) {
console.error("Failed to fetch deployed manifest version schema:", err)
+1 -1
View File
@@ -168,5 +168,5 @@ Run `honeywire firedrill` to make the HoneyWires send a mock event to the hub to
- HoneyWire versions are managed via Git Tags.
- `Hub` endpoint:
- `GET /api/v1/version` → returns `{ "version": "v2.0.0" }`
- `GET /api/v2/version` → returns `{ "version": "v2.0.0" }`
- API docs file: [API.md](./Docs/architecture/hub/backend/API.md) with full backend route reference and sample payloads.
+5 -5
View File
@@ -248,7 +248,7 @@ func (s *Sensor) RunTestMode() bool {
"details": details,
}
resp, err := s.postToHub("/api/v1/event", payload)
resp, err := s.postToHub("/api/v2/event", payload)
if err != nil {
log.Printf("[-] Test mode failed to send event: %v", err)
return false
@@ -321,7 +321,7 @@ func (s *Sensor) eventLoop() {
func (s *Sensor) processEvent(event map[string]any) {
for attempt := 0; attempt < MaxRetriesPerEvent; attempt++ {
resp, err := s.postToHub("/api/v1/event", event)
resp, err := s.postToHub("/api/v2/event", event)
fact := classify(err, resp)
if resp != nil {
@@ -357,7 +357,7 @@ func (s *Sensor) drainQueue() {
for {
select {
case event := <-s.eventCh:
if resp, err := s.postToHub("/api/v1/event", event); err == nil && resp != nil {
if resp, err := s.postToHub("/api/v2/event", event); err == nil && resp != nil {
resp.Body.Close()
}
default:
@@ -405,7 +405,7 @@ func (s *Sensor) sendHeartbeat() (*http.Response, error) {
"sensorId": s.SensorID,
"configRev": s.ConfigRev,
}
return s.postToHub("/api/v1/heartbeat", payload)
return s.postToHub("/api/v2/heartbeat", payload)
}
// ==========================================
@@ -419,7 +419,7 @@ func (s *Sensor) GoOffline(reason string) {
payload := map[string]any{"sensorId": s.SensorID, "reason": reason}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", s.HubEndpoint+"/api/v1/offline", bytes.NewReader(jsonData))
req, _ := http.NewRequest("POST", s.HubEndpoint+"/api/v2/offline", bytes.NewReader(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+s.HubKey)
+3 -3
View File
@@ -202,7 +202,7 @@ func TestReportEvent_Serialization(t *testing.T) {
payloadReceived := make(chan map[string]any, 1)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/event" {
if r.URL.Path != "/api/v2/event" {
w.WriteHeader(http.StatusNotFound)
return
}
@@ -277,10 +277,10 @@ func TestDrainQueueOnStop(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/event":
case "/api/v2/event":
eventRequests <- struct{}{}
w.WriteHeader(http.StatusOK)
case "/api/v1/offline":
case "/api/v2/offline":
// Acknowledge the offline message sent by s.Stop()
w.WriteHeader(http.StatusOK)
default:
+5 -5
View File
@@ -206,7 +206,7 @@ class HoneyWireSensor(ABC):
}
try:
resp = self.client.post(f"{self.hub_endpoint}/api/v1/event", json=payload, timeout=10)
resp = self.client.post(f"{self.hub_endpoint}/api/v2/event", json=payload, timeout=10)
if resp.status_code >= 400:
print(f"[-] Test mode failed to send event: HTTP {resp.status_code}")
return False
@@ -258,7 +258,7 @@ class HoneyWireSensor(ABC):
for attempt in range(MAX_RETRIES_PER_EVENT):
resp, exc = None, None
try:
resp = self.client.post(f"{self.hub_endpoint}/api/v1/event", json=event, timeout=10)
resp = self.client.post(f"{self.hub_endpoint}/api/v2/event", json=event, timeout=10)
except Exception as e:
exc = e
@@ -285,7 +285,7 @@ class HoneyWireSensor(ABC):
while not self._event_queue.empty():
try:
event = self._event_queue.get_nowait()
self.client.post(f"{self.hub_endpoint}/api/v1/event", json=event, timeout=2)
self.client.post(f"{self.hub_endpoint}/api/v2/event", json=event, timeout=2)
except Exception:
pass
print("[*] Event queue gracefully drained.")
@@ -309,7 +309,7 @@ class HoneyWireSensor(ABC):
resp, exc = None, None
try:
resp = self.client.post(f"{self.hub_endpoint}/api/v1/heartbeat", json=payload, timeout=10)
resp = self.client.post(f"{self.hub_endpoint}/api/v2/heartbeat", json=payload, timeout=10)
except Exception as e:
exc = e
@@ -329,7 +329,7 @@ class HoneyWireSensor(ABC):
"reason": reason
}
try:
self.client.post(f"{self.hub_endpoint}/api/v1/offline", json=payload, timeout=2)
self.client.post(f"{self.hub_endpoint}/api/v2/offline", json=payload, timeout=2)
except Exception:
pass
+4 -4
View File
@@ -6,7 +6,7 @@ import os
class MockHubHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
"""Handles the HoneyWire SDK synchronization handshake."""
if self.path == '/api/v1/version':
if self.path == '/api/v2/version':
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
@@ -27,7 +27,7 @@ class MockHubHandler(http.server.BaseHTTPRequestHandler):
try:
payload = json.loads(post_data.decode('utf-8'))
if self.path == '/api/v1/event':
if self.path == '/api/v2/event':
# 1. Enforce the updated V1.0 Contract
required_keys = [
"eventTrigger", "source", "target", "details"
@@ -54,12 +54,12 @@ class MockHubHandler(http.server.BaseHTTPRequestHandler):
self.send_response(200)
self.end_headers()
elif self.path == '/api/v1/heartbeat':
elif self.path == '/api/v2/heartbeat':
self.send_response(200)
self.end_headers()
print(f"[HEARTBEAT] OK | Sensor: {payload.get('sensorId', 'unknown')}")
elif self.path == '/api/v1/offline':
elif self.path == '/api/v2/offline':
self.send_response(200)
self.end_headers()
print(f"[OFFLINE] OK | Sensor: {payload.get('sensorId', 'unknown')}")
+5 -5
View File
@@ -134,7 +134,7 @@ func (c *HubClient) CreateNode(ctx context.Context, alias string, tags []string,
return "", fmt.Errorf("failed to marshal create node request: %w", err)
}
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/nodes", bytes.NewReader(body), map[string]string{
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v2/nodes", bytes.NewReader(body), map[string]string{
"Content-Type": "application/json",
"Cookie": "hw_auth=" + cookie,
})
@@ -173,7 +173,7 @@ func (c *HubClient) CreateNode(ctx context.Context, alias string, tags []string,
}
func (c *HubClient) GetCurrentNode(ctx context.Context, apiKey string) (*NodeInfo, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/nodes/me", nil, map[string]string{
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v2/nodes/me", nil, map[string]string{
"Authorization": "Bearer " + apiKey,
})
if err != nil {
@@ -208,7 +208,7 @@ func (c *HubClient) AddSensor(ctx context.Context, nodeID, cookie, sensorID, cus
return fmt.Errorf("failed to marshal add sensor request: %w", err)
}
path := fmt.Sprintf("/api/v1/nodes/%s/sensors", nodeID)
path := fmt.Sprintf("/api/v2/nodes/%s/sensors", nodeID)
resp, err := c.doRequest(ctx, http.MethodPost, path, bytes.NewReader(body), map[string]string{
"Content-Type": "application/json",
"Cookie": "hw_auth=" + cookie,
@@ -225,7 +225,7 @@ func (c *HubClient) AddSensor(ctx context.Context, nodeID, cookie, sensorID, cus
}
func (c *HubClient) FetchCompose(ctx context.Context, apiKey string) ([]byte, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/nodes/compose", nil, map[string]string{
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v2/nodes/compose", nil, map[string]string{
"Authorization": "Bearer " + apiKey,
})
if err != nil {
@@ -284,7 +284,7 @@ type addSensorRequest struct {
}
func (c *HubClient) FetchManifests(ctx context.Context, apiKey string) ([]*schema.SensorManifest, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/manifests", nil, map[string]string{
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v2/manifests", nil, map[string]string{
"Authorization": "Bearer " + apiKey,
})
if err != nil {