diff --git a/Docs/architecture/dataContracts.md b/Docs/architecture/dataContracts.md index 3ad5af7..0b00f1e 100644 --- a/Docs/architecture/dataContracts.md +++ b/Docs/architecture/dataContracts.md @@ -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 ` ```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 ` ```json diff --git a/Docs/architecture/hub/backend/API.md b/Docs/architecture/hub/backend/API.md index d9152d5..85678e0 100644 --- a/Docs/architecture/hub/backend/API.md +++ b/Docs/architecture/hub/backend/API.md @@ -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 ## 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. diff --git a/Docs/architecture/hub/backend/overview.md b/Docs/architecture/hub/backend/overview.md index 1c5bc97..4fc48be 100644 --- a/Docs/architecture/hub/backend/overview.md +++ b/Docs/architecture/hub/backend/overview.md @@ -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. diff --git a/Docs/architecture/hub/backend/projections.md b/Docs/architecture/hub/backend/projections.md index 9cdd789..c4fcec2 100644 --- a/Docs/architecture/hub/backend/projections.md +++ b/Docs/architecture/hub/backend/projections.md @@ -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. diff --git a/Docs/architecture/hub/backend/services.md b/Docs/architecture/hub/backend/services.md index 37d0175..6f00e69 100644 --- a/Docs/architecture/hub/backend/services.md +++ b/Docs/architecture/hub/backend/services.md @@ -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:** diff --git a/Docs/architecture/sensors/overview.md b/Docs/architecture/sensors/overview.md index 2d30682..bbd442b 100644 --- a/Docs/architecture/sensors/overview.md +++ b/Docs/architecture/sensors/overview.md @@ -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". \ No newline at end of file +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". \ No newline at end of file diff --git a/Docs/development/maintainer-workflow.md b/Docs/development/maintainer-workflow.md index 877838d..cea6ce1 100644 --- a/Docs/development/maintainer-workflow.md +++ b/Docs/development/maintainer-workflow.md @@ -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. diff --git a/Hub/internal/api/analytics.go b/Hub/internal/api/analytics.go index d8df28a..b21454d 100644 --- a/Hub/internal/api/analytics.go +++ b/Hub/internal/api/analytics.go @@ -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") diff --git a/Hub/internal/api/nodes.go b/Hub/internal/api/nodes.go index f7a4c2a..2872e83 100644 --- a/Hub/internal/api/nodes.go +++ b/Hub/internal/api/nodes.go @@ -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) { diff --git a/Hub/internal/api/router.go b/Hub/internal/api/router.go index 2dbb511..8ec8e12 100644 --- a/Hub/internal/api/router.go +++ b/Hub/internal/api/router.go @@ -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 --- diff --git a/Hub/ui/src/components/layout/Sidebar.vue b/Hub/ui/src/components/layout/Sidebar.vue index d42c5a8..2d77ef5 100644 --- a/Hub/ui/src/components/layout/Sidebar.vue +++ b/Hub/ui/src/components/layout/Sidebar.vue @@ -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' } }) diff --git a/Hub/ui/src/services/ws.ts b/Hub/ui/src/services/ws.ts index 9182800..7a56b69 100644 --- a/Hub/ui/src/services/ws.ts +++ b/Hub/ui/src/services/ws.ts @@ -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); diff --git a/Hub/ui/src/stores/Config/config.ts b/Hub/ui/src/stores/Config/config.ts index bf1ad8b..01136d5 100644 --- a/Hub/ui/src/stores/Config/config.ts +++ b/Hub/ui/src/stores/Config/config.ts @@ -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 => { 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>): Promise => { try { - await api.patch('/api/v1/config', updates) + await api.patch('/api/v2/config', updates) Object.assign(state.value, updates) return true diff --git a/Hub/ui/src/stores/Events/events.ts b/Hub/ui/src/stores/Events/events.ts index 85c2b94..0f3d0e0 100644 --- a/Hub/ui/src/stores/Events/events.ts +++ b/Hub/ui/src/stores/Events/events.ts @@ -144,7 +144,7 @@ export const useEventsStore = defineStore('events', () => { const refreshUnreadCount = async (): Promise => { 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) diff --git a/Hub/ui/src/stores/Fleet/fleet.ts b/Hub/ui/src/stores/Fleet/fleet.ts index 09ccf92..64056a3 100644 --- a/Hub/ui/src/stores/Fleet/fleet.ts +++ b/Hub/ui/src/stores/Fleet/fleet.ts @@ -378,7 +378,7 @@ export const useFleetStore = defineStore('fleet', () => { // --- ACTIONS: STRUCTURAL INGESTION --- const fetchFleet = async (): Promise => { 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 => { 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 => { 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 => { 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 => { 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 => { 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 }): Promise => { 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, }) )) diff --git a/Hub/ui/src/stores/System/app.ts b/Hub/ui/src/stores/System/app.ts index 1a44dfb..4196429 100644 --- a/Hub/ui/src/stores/System/app.ts +++ b/Hub/ui/src/stores/System/app.ts @@ -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 => { - 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 => { 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) diff --git a/Hub/ui/src/views/NodeDetails.vue b/Hub/ui/src/views/NodeDetails.vue index a5fc319..9737dd1 100644 --- a/Hub/ui/src/views/NodeDetails.vue +++ b/Hub/ui/src/views/NodeDetails.vue @@ -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) diff --git a/README.md b/README.md index c51f771..30c904c 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file diff --git a/SDKs/go-honeywire/sdk.go b/SDKs/go-honeywire/sdk.go index 9d91342..dc54e6a 100644 --- a/SDKs/go-honeywire/sdk.go +++ b/SDKs/go-honeywire/sdk.go @@ -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) diff --git a/SDKs/go-honeywire/sdk_test.go b/SDKs/go-honeywire/sdk_test.go index 240da21..d1bdd52 100644 --- a/SDKs/go-honeywire/sdk_test.go +++ b/SDKs/go-honeywire/sdk_test.go @@ -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: diff --git a/SDKs/python-honeywire/honeywire/__init__.py b/SDKs/python-honeywire/honeywire/__init__.py index 046e0fa..fdbebac 100644 --- a/SDKs/python-honeywire/honeywire/__init__.py +++ b/SDKs/python-honeywire/honeywire/__init__.py @@ -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 diff --git a/scripts/mock_hub.py b/scripts/mock_hub.py index 7d3bf69..4e4a0c9 100644 --- a/scripts/mock_hub.py +++ b/scripts/mock_hub.py @@ -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')}") diff --git a/wizard/core/api/client.go b/wizard/core/api/client.go index 4b6049a..f4c8659 100644 --- a/wizard/core/api/client.go +++ b/wizard/core/api/client.go @@ -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 {