feat: show human-readable sync status messages for integrations
- Added last_sync_message field to integrations schema - Gaiia sync: shows item counts on success, friendly error on failure - Preseem sync: shows AP/device counts on success, friendly error on failure - Error messages translated from technical errors to user-friendly text - Message displayed on integration card next to status badge - Exposed in REST API response
This commit is contained in:
parent
ddae90f06c
commit
db40a3e5b7
7 changed files with 60 additions and 7 deletions
|
|
@ -32,7 +32,11 @@ defmodule Towerops.Gaiia.Sync do
|
|||
items_count = upsert_nodes(org_id, items, :inventory_item),
|
||||
{:ok, subs} <- Client.list_billing_subscriptions(api_key, opts) do
|
||||
subs_count = upsert_nodes(org_id, subs, :billing_subscription)
|
||||
Integrations.update_sync_status(integration, "success")
|
||||
|
||||
message =
|
||||
"Synced #{accounts_count} accounts, #{sites_count} sites, #{items_count} inventory items, #{subs_count} subscriptions"
|
||||
|
||||
Integrations.update_sync_status(integration, "success", message)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
|
|
@ -43,7 +47,7 @@ defmodule Towerops.Gaiia.Sync do
|
|||
}}
|
||||
else
|
||||
{:error, reason} ->
|
||||
Integrations.update_sync_status(integration, "failed")
|
||||
Integrations.update_sync_status(integration, "failed", humanize_sync_error(reason))
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
|
@ -170,4 +174,19 @@ defmodule Towerops.Gaiia.Sync do
|
|||
endpoint -> [endpoint: endpoint]
|
||||
end
|
||||
end
|
||||
|
||||
defp humanize_sync_error(:unauthorized), do: "Authentication failed — check your API key"
|
||||
|
||||
defp humanize_sync_error(:forbidden), do: "Access denied — your API key may not have sufficient permissions"
|
||||
|
||||
defp humanize_sync_error(:not_found), do: "API endpoint not found — the Gaiia API may have changed"
|
||||
|
||||
defp humanize_sync_error(:timeout), do: "Connection timed out — Gaiia may be temporarily unavailable"
|
||||
|
||||
defp humanize_sync_error(%Req.TransportError{reason: :econnrefused}), do: "Connection refused — unable to reach Gaiia"
|
||||
|
||||
defp humanize_sync_error(%Req.TransportError{reason: reason}), do: "Connection error: #{reason}"
|
||||
|
||||
defp humanize_sync_error(reason) when is_binary(reason), do: reason
|
||||
defp humanize_sync_error(_reason), do: "An unexpected error occurred during sync"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,11 +37,12 @@ defmodule Towerops.Integrations do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
def update_sync_status(%Integration{} = integration, status) do
|
||||
def update_sync_status(%Integration{} = integration, status, message \\ nil) do
|
||||
integration
|
||||
|> Integration.changeset(%{
|
||||
last_sync_status: status,
|
||||
last_synced_at: DateTime.truncate(DateTime.utc_now(), :second)
|
||||
last_synced_at: DateTime.truncate(DateTime.utc_now(), :second),
|
||||
last_sync_message: message
|
||||
})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ defmodule Towerops.Integrations.Integration do
|
|||
field :sync_interval_minutes, :integer, default: 10
|
||||
field :last_synced_at, :utc_datetime
|
||||
field :last_sync_status, :string, default: "never"
|
||||
field :last_sync_message, :string
|
||||
|
||||
belongs_to :organization, Towerops.Organizations.Organization
|
||||
|
||||
|
|
@ -39,7 +40,8 @@ defmodule Towerops.Integrations.Integration do
|
|||
:credentials,
|
||||
:sync_interval_minutes,
|
||||
:last_synced_at,
|
||||
:last_sync_status
|
||||
:last_sync_status,
|
||||
:last_sync_message
|
||||
])
|
||||
|> validate_required([:organization_id, :provider])
|
||||
|> validate_inclusion(:provider, @valid_providers)
|
||||
|
|
|
|||
|
|
@ -117,8 +117,9 @@ defmodule Towerops.Preseem.Sync do
|
|||
|
||||
status = if synced_count == length(ap_list), do: "success", else: "partial"
|
||||
|
||||
message = "Synced #{synced_count} access points, matched #{matched_count} devices"
|
||||
log_sync(integration, status, synced_count, %{}, duration_ms)
|
||||
Integrations.update_sync_status(integration, status)
|
||||
Integrations.update_sync_status(integration, status, message)
|
||||
|
||||
{:ok, %{synced: synced_count, matched: matched_count}}
|
||||
end
|
||||
|
|
@ -126,8 +127,9 @@ defmodule Towerops.Preseem.Sync do
|
|||
defp handle_failed_sync(integration, reason, start_time) do
|
||||
duration_ms = System.monotonic_time(:millisecond) - start_time
|
||||
|
||||
message = humanize_sync_error(reason)
|
||||
log_sync(integration, "failed", 0, %{error: inspect(reason)}, duration_ms)
|
||||
Integrations.update_sync_status(integration, "failed")
|
||||
Integrations.update_sync_status(integration, "failed", message)
|
||||
|
||||
{:error, reason}
|
||||
end
|
||||
|
|
@ -167,4 +169,11 @@ defmodule Towerops.Preseem.Sync do
|
|||
defp cast_to_float(nil), do: nil
|
||||
defp cast_to_float(val) when is_float(val), do: val
|
||||
defp cast_to_float(val) when is_integer(val), do: val / 1
|
||||
|
||||
defp humanize_sync_error(:unauthorized), do: "Authentication failed — check your API key"
|
||||
|
||||
defp humanize_sync_error(:timeout), do: "Connection timed out — Preseem may be temporarily unavailable"
|
||||
|
||||
defp humanize_sync_error(reason) when is_binary(reason), do: reason
|
||||
defp humanize_sync_error(_reason), do: "An unexpected error occurred during sync"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ defmodule ToweropsWeb.Api.V1.IntegrationsController do
|
|||
sync_interval_minutes: integration.sync_interval_minutes,
|
||||
last_synced_at: integration.last_synced_at,
|
||||
last_sync_status: integration.last_sync_status,
|
||||
last_sync_message: integration.last_sync_message,
|
||||
inserted_at: integration.inserted_at,
|
||||
updated_at: integration.updated_at
|
||||
}
|
||||
|
|
|
|||
|
|
@ -866,6 +866,18 @@
|
|||
</span>
|
||||
<% end %>
|
||||
|
||||
<%= if integration.last_sync_message && integration.last_sync_message != "" do %>
|
||||
<span class={[
|
||||
"text-xs",
|
||||
if(integration.last_sync_status == "failed",
|
||||
do: "text-red-600 dark:text-red-400",
|
||||
else: "text-gray-500 dark:text-gray-400"
|
||||
)
|
||||
]}>
|
||||
{integration.last_sync_message}
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<%= if integration.last_synced_at do %>
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
||||
· Next sync in ~{next_sync_minutes(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Towerops.Repo.Migrations.AddLastSyncMessageToIntegrations do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:integrations) do
|
||||
add :last_sync_message, :text
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue