mirror of
https://github.com/andreicscs/HoneyWire
synced 2026-06-26 12:39:53 +00:00
updated to api/v2
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:**
|
||||
|
||||
@@ -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".
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ---
|
||||
|
||||
@@ -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' }
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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')}")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user