265 lines
6.5 KiB
Elixir
265 lines
6.5 KiB
Elixir
defmodule ToweropsWeb.Api.V1.MibController do
|
|
@moduledoc """
|
|
API controller for managing SNMP MIB files.
|
|
|
|
Allows uploading MIB files to the persistent storage for use with MIB name translation.
|
|
|
|
All endpoints require superuser API token authentication.
|
|
"""
|
|
|
|
use ToweropsWeb, :controller
|
|
|
|
require Logger
|
|
|
|
@mib_dir Application.compile_env(:towerops, :mib_dir, "/app/mibs")
|
|
|
|
@doc """
|
|
Upload a MIB file or archive (.tar.gz, .zip) containing MIB files.
|
|
|
|
Requires superuser API token.
|
|
|
|
## Request
|
|
|
|
POST /api/v1/mibs
|
|
Content-Type: multipart/form-data
|
|
|
|
file: <mib_file>
|
|
vendor: <vendor_name> (optional)
|
|
|
|
## Response
|
|
|
|
201 Created
|
|
{
|
|
"status": "ok",
|
|
"message": "Successfully uploaded MIB file",
|
|
"vendor": "mikrotik",
|
|
"files_count": 15
|
|
}
|
|
"""
|
|
def upload(conn, params) do
|
|
with :ok <- require_superuser(conn) do
|
|
case params["file"] do
|
|
%Plug.Upload{} = upload ->
|
|
vendor = params["vendor"] || "custom"
|
|
handle_upload(conn, upload, vendor)
|
|
|
|
_ ->
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Missing file parameter"})
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
List all available MIB vendors.
|
|
|
|
Requires superuser API token.
|
|
|
|
## Response
|
|
|
|
200 OK
|
|
{
|
|
"vendors": ["mikrotik", "cisco", "ubiquiti", ...],
|
|
"total_files": 1234
|
|
}
|
|
"""
|
|
def index(conn, _params) do
|
|
with :ok <- require_superuser(conn) do
|
|
case list_mib_vendors() do
|
|
{:ok, vendors, total_files} ->
|
|
json(conn, %{
|
|
vendors: vendors,
|
|
total_files: total_files
|
|
})
|
|
|
|
{:error, reason} ->
|
|
conn
|
|
|> put_status(:internal_server_error)
|
|
|> json(%{error: inspect(reason)})
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Delete MIB files for a specific vendor.
|
|
|
|
Requires superuser API token.
|
|
|
|
## Request
|
|
|
|
DELETE /api/v1/mibs/:vendor
|
|
|
|
## Response
|
|
|
|
200 OK
|
|
{
|
|
"status": "ok",
|
|
"message": "Deleted MIB files for vendor: mikrotik"
|
|
}
|
|
"""
|
|
def delete(conn, %{"vendor" => vendor}) do
|
|
with :ok <- require_superuser(conn) do
|
|
vendor_dir = Path.join(@mib_dir, vendor)
|
|
|
|
if File.exists?(vendor_dir) do
|
|
case File.rm_rf(vendor_dir) do
|
|
{:ok, _files} ->
|
|
Logger.info("Deleted MIB files for vendor: #{vendor}")
|
|
|
|
json(conn, %{
|
|
status: "ok",
|
|
message: "Deleted MIB files for vendor: #{vendor}"
|
|
})
|
|
|
|
{:error, reason, file} ->
|
|
Logger.error("Failed to delete MIB files for #{vendor}: #{file} - #{inspect(reason)}")
|
|
|
|
conn
|
|
|> put_status(:internal_server_error)
|
|
|> json(%{error: "Failed to delete MIB files"})
|
|
end
|
|
else
|
|
conn
|
|
|> put_status(:not_found)
|
|
|> json(%{error: "Vendor not found: #{vendor}"})
|
|
end
|
|
end
|
|
end
|
|
|
|
# Private functions
|
|
|
|
defp require_superuser(conn) do
|
|
user = conn.assigns[:current_user]
|
|
|
|
if !user || !user.is_superuser do
|
|
Logger.warning("MIB management attempted by non-superuser: #{inspect(user && user.email)}")
|
|
|
|
conn
|
|
|> put_status(:forbidden)
|
|
|> json(%{error: "Superuser access required. Only API tokens created by superusers can manage MIB files."})
|
|
|> halt()
|
|
else
|
|
:ok
|
|
end
|
|
end
|
|
|
|
defp handle_upload(conn, upload, vendor) do
|
|
vendor_dir = Path.join(@mib_dir, vendor)
|
|
File.mkdir_p!(vendor_dir)
|
|
|
|
cond do
|
|
String.ends_with?(upload.filename, ".tar.gz") or String.ends_with?(upload.filename, ".tgz") ->
|
|
extract_tarball(conn, upload, vendor_dir, vendor)
|
|
|
|
String.ends_with?(upload.filename, ".zip") ->
|
|
extract_zip(conn, upload, vendor_dir, vendor)
|
|
|
|
true ->
|
|
copy_single_file(conn, upload, vendor_dir, vendor)
|
|
end
|
|
end
|
|
|
|
defp extract_tarball(conn, upload, vendor_dir, vendor) do
|
|
case System.cmd("tar", ["-xzf", upload.path, "-C", vendor_dir]) do
|
|
{_output, 0} ->
|
|
files_count = count_files(vendor_dir)
|
|
Logger.info("Extracted #{files_count} MIB files for vendor: #{vendor}")
|
|
|
|
conn
|
|
|> put_status(:created)
|
|
|> json(%{
|
|
status: "ok",
|
|
message: "Successfully extracted MIB archive",
|
|
vendor: vendor,
|
|
files_count: files_count
|
|
})
|
|
|
|
{error, exit_code} ->
|
|
Logger.error("Failed to extract tarball: #{error}")
|
|
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Failed to extract archive (exit code: #{exit_code})"})
|
|
end
|
|
end
|
|
|
|
defp extract_zip(conn, upload, vendor_dir, vendor) do
|
|
case System.cmd("unzip", ["-o", upload.path, "-d", vendor_dir]) do
|
|
{_output, 0} ->
|
|
files_count = count_files(vendor_dir)
|
|
Logger.info("Extracted #{files_count} MIB files for vendor: #{vendor}")
|
|
|
|
conn
|
|
|> put_status(:created)
|
|
|> json(%{
|
|
status: "ok",
|
|
message: "Successfully extracted MIB archive",
|
|
vendor: vendor,
|
|
files_count: files_count
|
|
})
|
|
|
|
{error, exit_code} ->
|
|
Logger.error("Failed to extract zip: #{error}")
|
|
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Failed to extract archive (exit code: #{exit_code})"})
|
|
end
|
|
end
|
|
|
|
defp copy_single_file(conn, upload, vendor_dir, vendor) do
|
|
target_path = Path.join(vendor_dir, upload.filename)
|
|
|
|
case File.cp(upload.path, target_path) do
|
|
:ok ->
|
|
Logger.info("Uploaded MIB file: #{upload.filename} for vendor: #{vendor}")
|
|
|
|
conn
|
|
|> put_status(:created)
|
|
|> json(%{
|
|
status: "ok",
|
|
message: "Successfully uploaded MIB file",
|
|
vendor: vendor,
|
|
filename: upload.filename
|
|
})
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Failed to copy MIB file: #{inspect(reason)}")
|
|
|
|
conn
|
|
|> put_status(:internal_server_error)
|
|
|> json(%{error: "Failed to upload file"})
|
|
end
|
|
end
|
|
|
|
defp list_mib_vendors do
|
|
if File.exists?(@mib_dir) do
|
|
vendors =
|
|
@mib_dir
|
|
|> File.ls!()
|
|
|> Enum.filter(fn name ->
|
|
path = Path.join(@mib_dir, name)
|
|
File.dir?(path) and name != "lost+found"
|
|
end)
|
|
|> Enum.sort()
|
|
|
|
total_files = count_files(@mib_dir)
|
|
|
|
{:ok, vendors, total_files}
|
|
else
|
|
{:ok, [], 0}
|
|
end
|
|
rescue
|
|
e ->
|
|
Logger.error("Failed to list MIB vendors: #{inspect(e)}")
|
|
{:error, e}
|
|
end
|
|
|
|
defp count_files(dir) do
|
|
dir
|
|
|> Path.join("**/*")
|
|
|> Path.wildcard()
|
|
|> Enum.count(&File.regular?/1)
|
|
end
|
|
end
|