towerops/lib/towerops_web/controllers/api/v1/profiles_controller.ex
Graham McIntire e0bcd4feda
feat: add profile export/import workflow via API
Add export and import functionality for device SNMP profiles:

Export (local):
- New mix task: mix export_profiles --librenms-path ~/dev/librenms --output profiles.json
- Exports all profiles to JSON format
- Can filter specific profiles with --profiles flag

Import (production):
- New API endpoint: POST /api/v1/admin/profiles/import
- Requires superuser authentication via browser session
- Processes imports in background via Exq worker (maintenance queue)
- Returns job ID for tracking

New modules:
- Mix.Tasks.ExportProfiles - Export profiles to JSON
- ProfileImportWorker - Background job for importing profiles
- ProfilesController - API endpoint for bulk import
- RequireSuperuser plug - Restricts access to superusers
- Importer.import_profile_from_data/2 - Import from data structures

This enables bulk profile management without SSH access to production.
2026-01-18 09:23:38 -06:00

75 lines
2 KiB
Elixir

defmodule ToweropsWeb.Api.V1.ProfilesController do
@moduledoc """
API controller for managing device SNMP profiles.
All endpoints require API token authentication.
"""
use ToweropsWeb, :controller
require Logger
@doc """
POST /api/v1/profiles/import
Imports device profiles from JSON data exported via `mix export_profiles`.
Request body:
[
{
"os": "epmp",
"detection": { ... }, # YAML detection data
"discovery": { ... } # YAML discovery data (optional)
},
...
]
Response:
{
"success": 15,
"failed": 2,
"errors": [
{"os": "profile_name", "reason": "error message"}
]
}
Example:
curl -X POST https://towerops.net/api/v1/profiles/import \\
-H "Authorization: Bearer YOUR_TOKEN" \\
-H "Content-Type: application/json" \\
-d @profiles.json
"""
def import_bulk(conn, %{"_json" => profiles}) when is_list(profiles) do
Logger.info("Queueing import for #{length(profiles)} device profiles via API")
# Generate unique job ID for tracking
job_id = Ecto.UUID.generate()
# Enqueue background job
case Exq.enqueue(Exq, "maintenance", Towerops.Workers.ProfileImportWorker, [profiles, job_id]) do
{:ok, _job_jid} ->
Logger.info("Profile import job #{job_id} queued successfully")
json(conn, %{
status: "queued",
job_id: job_id,
profile_count: length(profiles),
message: "Import job queued successfully. #{length(profiles)} profiles will be imported in the background."
})
{:error, reason} ->
Logger.error("Failed to queue profile import: #{inspect(reason)}")
conn
|> put_status(:internal_server_error)
|> json(%{error: "Failed to queue import job", reason: inspect(reason)})
end
end
def import_bulk(conn, _params) do
conn
|> put_status(:bad_request)
|> json(%{error: "Invalid request format. Expected array of profile objects."})
end
# Private functions (removed - logic moved to ProfileImportWorker)
end