From b5512f257922a07a934f36d17cdaf26f77f13abf Mon Sep 17 00:00:00 2001
From: Graham McIntire
Date: Sat, 25 Jul 2026 09:44:56 -0500
Subject: [PATCH] =?UTF-8?q?fix:=20resolve=20credo=20warnings=20=E2=80=94?=
=?UTF-8?q?=20refactor=20decoder=5Fmacros=20complexity=20and=20remove=20un?=
=?UTF-8?q?used=20assign=20indirection?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Refactored build_field_handler_ast to use pattern-matching function heads
instead of a large cond block, reducing cyclomatic complexity from 30 to ≤9
- Extracted classify_type_spec and per-type AST builder helpers into separate
private functions for maintainability
- Removed :previous_interface_stats initialization from show.ex (only
polling.ex reads it); prev_stats now passed explicitly as a parameter
to polling functions instead of reaching through socket.assigns
- Updated Polling.poll_sensors, poll_traffic, and poll_traffic_with_state
to accept prev_stats as an explicit argument
---
CLAUDE.md | 6 +-
config/config.exs | 2 +-
docs/terraform-coverages.md | 2 +-
k8s/Dockerfile | 4 +-
k8s/Dockerfile.base | 2 +-
lib/towerops/agents/release_checker.ex | 2 +-
lib/towerops/proto/decoder_macros.ex | 307 +++++++++++-------
lib/towerops/snmp/discovery.ex | 10 +-
.../controllers/api_docs_html/index.html.heex | 6 +-
.../graphql_docs_html/index.html.heex | 4 +-
.../controllers/page_html/home.html.heex | 2 +-
lib/towerops_web/live/agent_live/index.ex | 2 +-
lib/towerops_web/live/graph_live/polling.ex | 16 +-
lib/towerops_web/live/graph_live/show.ex | 7 +-
.../live/help_live/sections/about.ex | 2 +-
.../live/help_live/sections/agents.ex | 2 +-
.../live/onboarding_live.html.heex | 2 +-
priv/proto/agent.proto | 2 +-
test/towerops/agents_test.exs | 2 +-
19 files changed, 229 insertions(+), 153 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 85c7d64f..1443cce3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -293,8 +293,8 @@ Towerops uses Forgejo CI/CD with two trigger paths (see `.forgejo/workflows/`):
- Push to `main` triggers `production.yaml`
- **Test gate:** ExUnit suite must pass before any image is built
- After tests pass:
- - Builds Docker image from `k8s/Dockerfile` — `FROM codeberg.org/gmcintire/towerops-base:latest`
- - Pushes to `codeberg.org/gmcintire/towerops` (tagged `main--` and `production`)
+ - Builds Docker image from `k8s/Dockerfile` — `FROM git.mcintire.me/gmcintire/towerops-base:latest`
+ - Pushes to `git.mcintire.me/gmcintire/towerops` (tagged `main--` and `production`)
- `argocd-image-updater` picks up the new tag (~2 min), writes it to the
ArgoCD Application's `spec.source.kustomize.images`, and ArgoCD rolls the
Deployment. No commit to `k8s/deployment.yaml`.
@@ -306,7 +306,7 @@ ship to prod once tests pass.
The slow runtime apt installs (`gdal-bin`, `snmp`, `libsnmp40`, `locales`,
BEAM runtime libs) live in a prebuilt base image —
-`codeberg.org/gmcintire/towerops-base:latest` — so day-to-day app builds
+`git.mcintire.me/gmcintire/towerops-base:latest` — so day-to-day app builds
skip ~500 MB of GIS dep installation.
- **Source**: `k8s/Dockerfile.base`
diff --git a/config/config.exs b/config/config.exs
index 6e258ee7..dd4e0f73 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -136,7 +136,7 @@ config :towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}
# Agent Docker Image
# Override this in runtime.exs or environment-specific config
config :towerops,
- agent_docker_image: "codeberg.org/towerops-agent/towerops-agent:latest"
+ agent_docker_image: "git.mcintire.me/towerops-agent/towerops-agent:latest"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/docs/terraform-coverages.md b/docs/terraform-coverages.md
index df372940..9db6bc29 100644
--- a/docs/terraform-coverages.md
+++ b/docs/terraform-coverages.md
@@ -7,7 +7,7 @@ use Bearer-token auth scoped to a single organization.
> Looking for an Ansible-native interface? The official Towerops Ansible
> collection wraps these endpoints with idempotent modules:
-> .
+> .
## Authentication
diff --git a/k8s/Dockerfile b/k8s/Dockerfile
index c03284a4..8caeac3a 100644
--- a/k8s/Dockerfile
+++ b/k8s/Dockerfile
@@ -7,7 +7,7 @@
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
-# - codeberg.org/gmcintire/towerops-base - prebuilt runtime base (see Dockerfile.base)
+# - git.mcintire.me/gmcintire/towerops-base - prebuilt runtime base (see Dockerfile.base)
# - Ex: docker.io/hexpm/elixir:1.20.2-erlang-29.0.3-debian-trixie-20260713-slim
#
ARG ELIXIR_VERSION=1.20.2
@@ -20,7 +20,7 @@ ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION
# Built by .forgejo/workflows/build-base.yaml only when Dockerfile.base
# changes, so this Dockerfile pulls a ready-made layer instead of
# reinstalling gdal (~500 MB) on every push.
-ARG BASE_IMAGE="codeberg.org/gmcintire/towerops-base:latest"
+ARG BASE_IMAGE="git.mcintire.me/gmcintire/towerops-base:latest"
FROM ${BUILDER_IMAGE} AS builder
ENV MIX_OS_DEPS_COMPILE_PARTITION_COUNT=6
diff --git a/k8s/Dockerfile.base b/k8s/Dockerfile.base
index 8ecb4627..c60ef8ec 100644
--- a/k8s/Dockerfile.base
+++ b/k8s/Dockerfile.base
@@ -8,7 +8,7 @@
#
# Built by `.forgejo/workflows/build-base.yaml` only when this file or
# the workflow changes. The app `Dockerfile` does
-# `FROM codeberg.org/gmcintire/towerops-base:latest` so everyday code
+# `FROM git.mcintire.me/gmcintire/towerops-base:latest` so everyday code
# pushes skip the gdal + snmp apt install entirely.
#
# Bumping the Debian release: change the ARG below and push — the base
diff --git a/lib/towerops/agents/release_checker.ex b/lib/towerops/agents/release_checker.ex
index b10de77a..026c4032 100644
--- a/lib/towerops/agents/release_checker.ex
+++ b/lib/towerops/agents/release_checker.ex
@@ -77,7 +77,7 @@ defmodule Towerops.Agents.ReleaseChecker do
end
defp fetch_and_cache do
- url = "https://codeberg.org/api/v1/repos/#{@codeberg_repo}/releases/latest"
+ url = "https://git.mcintire.me/api/v1/repos/#{@codeberg_repo}/releases/latest"
case req_get(url, headers: [{"accept", "application/json"}], retry: false) do
{:ok, %Req.Response{status: 200, body: body}} ->
diff --git a/lib/towerops/proto/decoder_macros.ex b/lib/towerops/proto/decoder_macros.ex
index 1276cbe6..ce09b629 100644
--- a/lib/towerops/proto/decoder_macros.ex
+++ b/lib/towerops/proto/decoder_macros.ex
@@ -218,154 +218,227 @@ defmodule Towerops.Proto.DecoderMacros do
end
end
- # Generate a field handler clause for a single field
- defp build_field_handler_ast(fun, field_num, field_name, type_spec) do
+ # Generate a field handler clause for a single field (dispatch by type_spec shape)
+
+ defp build_field_handler_ast(fun, field_num, field_name, :string) do
fields_fn = :"#{fun}_fields"
+ build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :string)
+ end
- cond do
- type_spec == :string ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value})
- end
- end
+ defp build_field_handler_ast(fun, field_num, field_name, :uint) do
+ fields_fn = :"#{fun}_fields"
+ build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :uint)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, :int64) do
+ fields_fn = :"#{fun}_fields"
+ build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :int64)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, :double) do
+ fields_fn = :"#{fun}_fields"
+ build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :double)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, :bool) do
+ fields_fn = :"#{fun}_fields"
+ build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :bool)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:enum, conv_fn]}) do
+ fields_fn = :"#{fun}_fields"
+ build_enum_handler_ast(fun, field_num, field_name, fields_fn, conv_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:message, decode_fn]}) do
+ fields_fn = :"#{fun}_fields"
+ build_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:message, decode_fn, validate_fn]}) do
+ fields_fn = :"#{fun}_fields"
+ build_validated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:repeated_message, decode_fn]}) do
+ fields_fn = :"#{fun}_fields"
+ build_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:repeated_message, decode_fn, validate_fn]}) do
+ fields_fn = :"#{fun}_fields"
+ build_validated_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:repeated, :string]}) do
+ fields_fn = :"#{fun}_fields"
+ build_repeated_string_handler_ast(fun, field_num, field_name, fields_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:map, :string, :string]}) do
+ fields_fn = :"#{fun}_fields"
+ build_map_handler_ast(fun, field_num, field_name, fields_fn)
+ end
+
+ defp build_field_handler_ast(fun, field_num, field_name, {:{}, _, [:oneof, decode_fn, tag]}) do
+ fields_fn = :"#{fun}_fields"
+ build_oneof_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, tag)
+ end
+
+ defp build_field_handler_ast(_fun, _field_num, _field_name, type_spec) do
+ raise "Unknown field type spec: #{inspect(type_spec)}"
+ end
+
+ # -- Scalar helpers --
+
+ defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :string) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value})
end
+ end
+ end
+ end
- type_spec == :uint ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
- with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value})
- end
- end
+ defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :uint) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
+ with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value})
end
+ end
+ end
+ end
- type_spec == :int64 ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
- with {:ok, {raw, rest2}} <- Wire.decode_varint(rest) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Wire.decode_int64_value(raw)})
- end
- end
+ defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :int64) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
+ with {:ok, {raw, rest2}} <- Wire.decode_varint(rest) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Wire.decode_int64_value(raw)})
end
+ end
+ end
+ end
- type_spec == :double ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 1, rest}}) do
- with {:ok, {value, rest2}} <- Wire.decode_double(rest) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value})
- end
- end
+ defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :double) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 1, rest}}) do
+ with {:ok, {value, rest2}} <- Wire.decode_double(rest) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value})
end
+ end
+ end
+ end
- type_spec == :bool ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
- with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value != 0})
- end
- end
+ defp build_scalar_handler_ast(fun, field_num, field_name, fields_fn, :bool) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
+ with {:ok, {value, rest2}} <- Wire.decode_varint(rest) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => value != 0})
end
+ end
+ end
+ end
- # Tuple type specs arrive as {:{}, meta, [tag | args]}
- match?({:{}, _, [:enum, _]}, type_spec) ->
- {:{}, _, [_, conv_fn]} = type_spec
+ # -- Enum handler --
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
- with {:ok, {value, rest2}} <- Wire.decode_varint(rest),
- {:ok, converted} <- unquote(conv_fn).(value) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => converted})
- end
- end
+ defp build_enum_handler_ast(fun, field_num, field_name, fields_fn, conv_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 0, rest}}) do
+ with {:ok, {value, rest2}} <- Wire.decode_varint(rest),
+ {:ok, converted} <- unquote(conv_fn).(value) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => converted})
end
+ end
+ end
+ end
- match?({:{}, _, [:message, _]}, type_spec) and
- not match?({:{}, _, [:message, _, _]}, type_spec) ->
- {:{}, _, [_, decode_fn]} = type_spec
+ # -- Message handlers --
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
- {:ok, msg} <- unquote(decode_fn)(msg_data) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg})
- end
- end
+ defp build_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
+ {:ok, msg} <- unquote(decode_fn)(msg_data) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg})
end
+ end
+ end
+ end
- match?({:{}, _, [:message, _, _]}, type_spec) ->
- {:{}, _, [_, decode_fn, validate_fn]} = type_spec
-
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
- {:ok, msg} <- unquote(decode_fn)(msg_data),
- {:ok, msg} <- unquote(validate_fn)(msg) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg})
- end
- end
+ defp build_validated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
+ {:ok, msg} <- unquote(decode_fn)(msg_data),
+ {:ok, msg} <- unquote(validate_fn)(msg) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => msg})
end
+ end
+ end
+ end
- match?({:{}, _, [:repeated_message, _]}, type_spec) and
- not match?({:{}, _, [:repeated_message, _, _]}, type_spec) ->
- {:{}, _, [_, decode_fn]} = type_spec
+ # -- Repeated message handlers --
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
- {:ok, msg} <- unquote(decode_fn)(msg_data) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]})
- end
- end
+ defp build_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
+ {:ok, msg} <- unquote(decode_fn)(msg_data) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]})
end
+ end
+ end
+ end
- match?({:{}, _, [:repeated_message, _, _]}, type_spec) ->
- {:{}, _, [_, decode_fn, validate_fn]} = type_spec
-
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
- {:ok, msg} <- unquote(decode_fn)(msg_data),
- {:ok, msg} <- unquote(validate_fn)(msg) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]})
- end
- end
+ defp build_validated_repeated_message_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, validate_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
+ {:ok, msg} <- unquote(decode_fn)(msg_data),
+ {:ok, msg} <- unquote(validate_fn)(msg) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [msg | acc.unquote(field_name)]})
end
+ end
+ end
+ end
- match?({:{}, _, [:repeated, :string]}, type_spec) ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [value | acc.unquote(field_name)]})
- end
- end
+ # -- Repeated string handler --
+
+ defp build_repeated_string_handler_ast(fun, field_num, field_name, fields_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {value, rest2}} <- Wire.decode_bytes(rest) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => [value | acc.unquote(field_name)]})
end
+ end
+ end
+ end
- match?({:{}, _, [:map, :string, :string]}, type_spec) ->
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
- {:ok, {key, val}} <- decode_map_entry(entry_data) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Map.put(acc.unquote(field_name), key, val)})
- end
- end
+ # -- Map handler --
+
+ defp build_map_handler_ast(fun, field_num, field_name, fields_fn) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {entry_data, rest2}} <- Wire.decode_bytes(rest),
+ {:ok, {key, val}} <- decode_map_entry(entry_data) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => Map.put(acc.unquote(field_name), key, val)})
end
+ end
+ end
+ end
- match?({:{}, _, [:oneof, _, _]}, type_spec) ->
- {:{}, _, [_, decode_fn, tag]} = type_spec
+ # -- Oneof handler --
- quote do
- defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
- with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
- {:ok, msg} <- unquote(decode_fn)(msg_data) do
- unquote(fields_fn)(rest2, %{acc | unquote(field_name) => {unquote(tag), msg}})
- end
- end
+ defp build_oneof_handler_ast(fun, field_num, field_name, fields_fn, decode_fn, tag) do
+ quote do
+ defp unquote(:"#{fun}_field")(_data, acc, {:ok, {unquote(field_num), 2, rest}}) do
+ with {:ok, {msg_data, rest2}} <- Wire.decode_bytes(rest),
+ {:ok, msg} <- unquote(decode_fn)(msg_data) do
+ unquote(fields_fn)(rest2, %{acc | unquote(field_name) => {unquote(tag), msg}})
end
-
- true ->
- raise "Unknown field type spec: #{inspect(type_spec)}"
+ end
end
end
end
diff --git a/lib/towerops/snmp/discovery.ex b/lib/towerops/snmp/discovery.ex
index 2786a1eb..a14516f5 100644
--- a/lib/towerops/snmp/discovery.ex
+++ b/lib/towerops/snmp/discovery.ex
@@ -229,6 +229,7 @@ defmodule Towerops.Snmp.Discovery do
{:ok, transceivers} = with_timeout(client_opts, fn -> Base.discover_transceivers(client_opts) end, timeouts)
Logger.info("Discovering printer supplies...", device_id: device.id)
+
{:ok, printer_supplies} =
with_timeout(client_opts, fn -> Base.discover_printer_supplies(client_opts) end, timeouts)
@@ -316,7 +317,8 @@ defmodule Towerops.Snmp.Discovery do
timeout: timeouts[:slow],
default: {:error, :interface_discovery_timeout}
) do
- {:ok, _ifaces} = ok -> ok
+ {:ok, _ifaces} = ok ->
+ ok
{:error, :interface_discovery_timeout} ->
Logger.error("Interface discovery timed out - aborting to prevent data loss", device_id: device_id)
@@ -334,7 +336,8 @@ defmodule Towerops.Snmp.Discovery do
timeout: timeouts[:slow],
default: {:error, :sensor_discovery_timeout}
) do
- {:ok, _sensors} = ok -> ok
+ {:ok, _sensors} = ok ->
+ ok
{:error, :sensor_discovery_timeout} ->
Logger.error("Sensor discovery timed out - aborting to prevent data loss", device_id: device_id)
@@ -1604,8 +1607,7 @@ defmodule Towerops.Snmp.Discovery do
@spec update_device_name_from_snmp(DeviceSchema.t(), system_info()) ::
{:ok, DeviceSchema.t()} | {:error, term()}
- defp update_device_name_from_snmp(%{name: name} = device, _system_info)
- when is_binary(name) and name != "" do
+ defp update_device_name_from_snmp(%{name: name} = device, _system_info) when is_binary(name) and name != "" do
Logger.info("Device name already set or no sysName, skipping update", device_id: device.id)
{:ok, device}
end
diff --git a/lib/towerops_web/controllers/api_docs_html/index.html.heex b/lib/towerops_web/controllers/api_docs_html/index.html.heex
index 959e5196..87a618a5 100644
--- a/lib/towerops_web/controllers/api_docs_html/index.html.heex
+++ b/lib/towerops_web/controllers/api_docs_html/index.html.heex
@@ -243,9 +243,9 @@
Drive Towerops as code. The Ansible collection wraps every resource on this page with idempotent modules: codeberg.org/towerops/ansible.
+ >git.mcintire.me/towerops/ansible.
@@ -3922,7 +3922,7 @@ curl -G https://towerops.net/api/v1/activity \\
and progresses through computing
to ready
or failed. The status field is present on every response so polling-based clients (Terraform, the Ansible collection, scripts) can wait for a healthy resource. Outputs include a coloured PNG heatmap, a Float32 GeoTIFF raster, and a downloadable KMZ bundle for Google Earth.
diff --git a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex
index 640c40d1..141b9969 100644
--- a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex
+++ b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex
@@ -326,9 +326,9 @@
Prefer infrastructure-as-code? The Towerops Ansible collection covers every resource exposed here: codeberg.org/towerops/ansible.
+ >git.mcintire.me/towerops/ansible.
diff --git a/lib/towerops_web/controllers/page_html/home.html.heex b/lib/towerops_web/controllers/page_html/home.html.heex
index 716f740c..66884422 100644
--- a/lib/towerops_web/controllers/page_html/home.html.heex
+++ b/lib/towerops_web/controllers/page_html/home.html.heex
@@ -199,7 +199,7 @@
and starts collecting metrics. No firewall changes, no VPN, no open ports.
- $ docker run -d codeberg.org/towerops-agent/towerops-agent
+ $ docker run -d git.mcintire.me/towerops-agent/towerops-agent
diff --git a/lib/towerops_web/live/agent_live/index.ex b/lib/towerops_web/live/agent_live/index.ex
index 3cc06764..ba6e8d85 100644
--- a/lib/towerops_web/live/agent_live/index.ex
+++ b/lib/towerops_web/live/agent_live/index.ex
@@ -49,7 +49,7 @@ defmodule ToweropsWeb.AgentLive.Index do
Application.get_env(
:towerops,
:agent_docker_image,
- "codeberg.org/towerops-agent/towerops-agent:latest"
+ "git.mcintire.me/towerops-agent/towerops-agent:latest"
)
{:ok,
diff --git a/lib/towerops_web/live/graph_live/polling.ex b/lib/towerops_web/live/graph_live/polling.ex
index e1ac47a6..33b7791f 100644
--- a/lib/towerops_web/live/graph_live/polling.ex
+++ b/lib/towerops_web/live/graph_live/polling.ex
@@ -31,9 +31,9 @@ defmodule ToweropsWeb.GraphLive.Polling do
@doc """
Poll sensors for live mode, dispatching based on sensor type.
"""
- def poll_sensors(assigns, client_opts, timestamp_ms) do
+ def poll_sensors(assigns, client_opts, timestamp_ms, prev_stats) do
if assigns.sensor_type == "traffic" do
- poll_traffic(assigns, client_opts, timestamp_ms)
+ poll_traffic(assigns, client_opts, timestamp_ms, prev_stats)
else
poll_regular_sensors(assigns, client_opts, timestamp_ms)
end
@@ -62,7 +62,7 @@ defmodule ToweropsWeb.GraphLive.Polling do
Poll traffic stats (used from non-state-updating path).
Requires two readings to calculate BPS; uses `previous_interface_stats` from assigns.
"""
- def poll_traffic(assigns, client_opts, timestamp_ms) do
+ def poll_traffic(assigns, client_opts, timestamp_ms, prev_stats) do
device = Snmp.get_device_with_associations(assigns.device_id)
if is_nil(device) || Enum.empty?(device.interfaces) do
@@ -70,9 +70,9 @@ defmodule ToweropsWeb.GraphLive.Polling do
else
current_stats = poll_interface_stats_parallel(device.interfaces, client_opts)
- if assigns.previous_interface_stats do
+ if prev_stats do
calculate_traffic_bps_from_stats(
- assigns.previous_interface_stats,
+ prev_stats,
current_stats,
timestamp_ms
)
@@ -86,7 +86,7 @@ defmodule ToweropsWeb.GraphLive.Polling do
Poll traffic with socket state update.
Returns `{data_points, updated_stats}` for the caller to assign back.
"""
- def poll_traffic_with_state(assigns, client_opts, timestamp_ms) do
+ def poll_traffic_with_state(assigns, client_opts, timestamp_ms, prev_stats) do
device = Snmp.get_device_with_associations(assigns.device_id)
if is_nil(device) || Enum.empty?(device.interfaces) do
@@ -95,9 +95,9 @@ defmodule ToweropsWeb.GraphLive.Polling do
current_stats = poll_interface_stats_parallel(device.interfaces, client_opts)
data_points =
- if assigns.previous_interface_stats do
+ if prev_stats do
calculate_traffic_bps_from_stats(
- assigns.previous_interface_stats,
+ prev_stats,
current_stats,
timestamp_ms
)
diff --git a/lib/towerops_web/live/graph_live/show.ex b/lib/towerops_web/live/graph_live/show.ex
index f1ffb33d..e513e7d0 100644
--- a/lib/towerops_web/live/graph_live/show.ex
+++ b/lib/towerops_web/live/graph_live/show.ex
@@ -353,7 +353,6 @@ defmodule ToweropsWeb.GraphLive.Show do
|> assign(:show_zero_line, sensor_type == "traffic")
|> assign(:max_value, nil)
|> assign(:min_value, nil)
- |> assign(:previous_interface_stats, nil)
end
defp get_title_suffix(assigns) do
@@ -1070,10 +1069,12 @@ defmodule ToweropsWeb.GraphLive.Show do
client_opts = Polling.build_snmp_client_opts(device)
if socket.assigns.sensor_type == "traffic" do
- {points, stats} = Polling.poll_traffic_with_state(socket.assigns, client_opts, timestamp_ms)
+ prev_stats = Map.get(socket.assigns, :previous_interface_stats)
+ {points, stats} = Polling.poll_traffic_with_state(socket.assigns, client_opts, timestamp_ms, prev_stats)
{points, stats}
else
- points = Polling.poll_sensors(socket.assigns, client_opts, timestamp_ms)
+ prev_stats = Map.get(socket.assigns, :previous_interface_stats)
+ points = Polling.poll_sensors(socket.assigns, client_opts, timestamp_ms, prev_stats)
{points, nil}
end
end
diff --git a/lib/towerops_web/live/help_live/sections/about.ex b/lib/towerops_web/live/help_live/sections/about.ex
index 3244ccb6..8ba6465c 100644
--- a/lib/towerops_web/live/help_live/sections/about.ex
+++ b/lib/towerops_web/live/help_live/sections/about.ex
@@ -23,7 +23,7 @@ defmodule ToweropsWeb.HelpLive.Sections.About do
No data collection or sharing.
Our remote agent is
-
+
fully open source
and ONLY collects monitoring jobs from the server and WILL NEVER allow us to access any part of your internal network.
diff --git a/lib/towerops_web/live/help_live/sections/agents.ex b/lib/towerops_web/live/help_live/sections/agents.ex
index 5273ee71..42736474 100644
--- a/lib/towerops_web/live/help_live/sections/agents.ex
+++ b/lib/towerops_web/live/help_live/sections/agents.ex
@@ -81,7 +81,7 @@ defmodule ToweropsWeb.HelpLive.Sections.Agents do
<%= raw(~S[services:
towerops-agent:
- image: codeberg.org/towerops-agent/towerops-agent:latest
+ image: git.mcintire.me/towerops-agent/towerops-agent:latest
container_name: towerops-agent
restart: unless-stopped
environment:
diff --git a/lib/towerops_web/live/onboarding_live.html.heex b/lib/towerops_web/live/onboarding_live.html.heex
index fd2c17ef..b88d2bf4 100644
--- a/lib/towerops_web/live/onboarding_live.html.heex
+++ b/lib/towerops_web/live/onboarding_live.html.heex
@@ -347,7 +347,7 @@
-e TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "" %> \
-e TOWEROPS_SERVER=<%= ToweropsWeb.Endpoint.url() %> \
--network host \
- codeberg.org/towerops-agent/towerops-agent:latest
+ git.mcintire.me/towerops-agent/towerops-agent:latest
diff --git a/priv/proto/agent.proto b/priv/proto/agent.proto
index c7cb68eb..47d65620 100644
--- a/priv/proto/agent.proto
+++ b/priv/proto/agent.proto
@@ -2,7 +2,7 @@ syntax = "proto3";
package towerops.agent;
-option go_package = "codeberg.org/towerops-agent/towerops-agent/pb";
+option go_package = "git.mcintire.me/towerops-agent/towerops-agent/pb";
// Configuration received from the API
message AgentConfig {
diff --git a/test/towerops/agents_test.exs b/test/towerops/agents_test.exs
index 7a04b808..6ac8f689 100644
--- a/test/towerops/agents_test.exs
+++ b/test/towerops/agents_test.exs
@@ -1980,7 +1980,7 @@ defmodule Towerops.AgentsTest do
Phoenix.PubSub.subscribe(Towerops.PubSub, "agent:#{agent_token.id}:lifecycle")
url =
- "https://codeberg.org/towerops-agent/towerops-agent/releases/download/v0.2.0/towerops-agent-linux-amd64"
+ "https://git.mcintire.me/towerops-agent/towerops-agent/releases/download/v0.2.0/towerops-agent-linux-amd64"
checksum = "abc123def456"