diff --git a/assets/static/images/news/2025/05-11-map-active-characters/cover.png b/assets/static/images/news/2025/05-11-map-active-characters/cover.png new file mode 100644 index 00000000..3f366b1a Binary files /dev/null and b/assets/static/images/news/2025/05-11-map-active-characters/cover.png differ diff --git a/assets/static/images/news/2025/05-11-map-active-characters/map.png b/assets/static/images/news/2025/05-11-map-active-characters/map.png new file mode 100644 index 00000000..2394e773 Binary files /dev/null and b/assets/static/images/news/2025/05-11-map-active-characters/map.png differ diff --git a/assets/static/images/news/2025/05-11-map-active-characters/page.png b/assets/static/images/news/2025/05-11-map-active-characters/page.png new file mode 100644 index 00000000..6ea63153 Binary files /dev/null and b/assets/static/images/news/2025/05-11-map-active-characters/page.png differ diff --git a/lib/wanderer_app/character/tracking_utils.ex b/lib/wanderer_app/character/tracking_utils.ex index f88ff112..6cc4f8db 100644 --- a/lib/wanderer_app/character/tracking_utils.ex +++ b/lib/wanderer_app/character/tracking_utils.ex @@ -161,12 +161,12 @@ defmodule WandererApp.Character.TrackingUtils do end # Helper functions for character tracking - def track(_, _, false, _), do: :ok + def track([], _map_id, _is_track_character?, _), do: :ok - def track([character | characters], map_id, true, caller_pid) do - with :ok <- track_character(character, map_id, caller_pid) do - track(characters, map_id, true, caller_pid) + def track([character | characters], map_id, is_track_allowed, caller_pid) do + with :ok <- track_character(character, map_id, is_track_allowed, caller_pid) do + track(characters, map_id, is_track_allowed, caller_pid) end end @@ -176,28 +176,55 @@ defmodule WandererApp.Character.TrackingUtils do eve_id: eve_id }, map_id, + is_track_allowed, caller_pid - ) do - with false <- is_nil(caller_pid) do - WandererAppWeb.Presence.track(caller_pid, map_id, character_id, %{}) + ) + when not is_nil(caller_pid) do + WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{ + tracked: is_track_allowed, + from: DateTime.utc_now() + }) + |> case do + {:ok, _} -> + :ok - cache_key = "#{inspect(caller_pid)}_map_#{map_id}:character_#{character_id}:tracked" + {:error, :nopresence} -> + WandererAppWeb.Presence.track(caller_pid, map_id, character_id, %{ + tracked: is_track_allowed, + from: DateTime.utc_now() + }) - case WandererApp.Cache.lookup!(cache_key, false) do - true -> - :ok - - _ -> - :ok = Phoenix.PubSub.subscribe(WandererApp.PubSub, "character:#{eve_id}") - :ok = WandererApp.Cache.put(cache_key, true) - end - - :ok = WandererApp.Character.TrackerManager.start_tracking(character_id) - else - true -> - Logger.error("caller_pid is required for tracking characters") - {:error, "caller_pid is required"} + error -> + Logger.error("Failed to update presence: #{inspect(error)}") + {:error, "Failed to update presence"} end + + cache_key = "#{inspect(caller_pid)}_map_#{map_id}:character_#{character_id}:tracked" + + case WandererApp.Cache.lookup!(cache_key, false) do + true -> + :ok + + _ -> + :ok = Phoenix.PubSub.subscribe(WandererApp.PubSub, "character:#{eve_id}") + :ok = WandererApp.Cache.put(cache_key, true) + end + + if is_track_allowed do + :ok = WandererApp.Character.TrackerManager.start_tracking(character_id) + end + + :ok + end + + defp track_character( + _character, + _map_id, + _is_track_allowed, + _caller_pid + ) do + Logger.error("caller_pid is required for tracking characters") + {:error, "caller_pid is required"} end def untrack(characters, map_id, caller_pid) do @@ -206,7 +233,10 @@ defmodule WandererApp.Character.TrackingUtils do characters |> Enum.each(fn character -> - WandererAppWeb.Presence.untrack(caller_pid, map_id, character.id) + WandererAppWeb.Presence.update(caller_pid, map_id, character.id, %{ + tracked: false, + from: DateTime.utc_now() + }) end) WandererApp.Map.Server.untrack_characters(map_id, character_ids) diff --git a/lib/wanderer_app_web/live/map/components/map_characters.ex b/lib/wanderer_app_web/live/map/components/map_characters.ex index f462d468..43055567 100644 --- a/lib/wanderer_app_web/live/map/components/map_characters.ex +++ b/lib/wanderer_app_web/live/map/components/map_characters.ex @@ -17,24 +17,26 @@ defmodule WandererAppWeb.MapCharacters do |> handle_info_or_assign(assigns)} end - # attr(:groups, :any, required: true) - # attr(:character_settings, :any, required: true) - @impl true def render(assigns) do ~H"""
-
diff --git a/lib/wanderer_app_web/presence.ex b/lib/wanderer_app_web/presence.ex index f08327f1..8f5754d1 100644 --- a/lib/wanderer_app_web/presence.ex +++ b/lib/wanderer_app_web/presence.ex @@ -10,19 +10,39 @@ defmodule WandererAppWeb.Presence do end def handle_metas(map_id, %{joins: _joins, leaves: _leaves}, presences, state) do - presence_character_ids = + presence_data = presences - |> Enum.map(fn {character_id, _} -> character_id end) + |> Enum.map(fn {character_id, meta} -> + from = + meta + |> Enum.map(& &1.from) + |> Enum.sort(&(DateTime.compare(&1, &2) != :gt)) + |> List.first() + + any_tracked = Enum.any?(meta, fn %{tracked: tracked} -> tracked end) + + %{character_id: character_id, tracked: any_tracked, from: from} + end) + + presence_tracked_character_ids = + presence_data + |> Enum.filter(fn %{tracked: tracked} -> tracked end) + |> Enum.map(fn %{character_id: character_id} -> + character_id + end) WandererApp.Cache.insert("map_#{map_id}:presence_updated", true) - WandererApp.Cache.insert("map_#{map_id}:presence_character_ids", presence_character_ids) + + WandererApp.Cache.insert( + "map_#{map_id}:presence_character_ids", + presence_tracked_character_ids + ) + + WandererApp.Cache.insert( + "map_#{map_id}:presence_data", + presence_data + ) {:ok, state} end - - def presence_character_ids(map_id) do - map_id - |> list() - |> Enum.map(fn {character_id, _} -> character_id end) - end end diff --git a/priv/posts/2025/05-11-map-active-characters.md b/priv/posts/2025/05-11-map-active-characters.md new file mode 100644 index 00000000..ab2e958d --- /dev/null +++ b/priv/posts/2025/05-11-map-active-characters.md @@ -0,0 +1,77 @@ +%{ +title: "Map Active Characters Page — Interface Guide", +author: "Wanderer Team", +cover_image_uri: "/images/news/2025/05-11-map-active-characters/cover.png", +tags: ~w(characters interface guide map security), +description: "This interface is essential for managing access and tracking behavior on shared maps, especially in large organizations." +} + +--- + +### Introduction + +![Maps Icon](/images/news/2025/05-11-map-active-characters/cover.png "Maps Icon") +![Map Icon](/images/news/2025/05-11-map-active-characters/map.png "Map Icon") + +This page displays **only currently active characters** — those who have the map page open in an active browser tab or window. + +### Key Use Cases: +- Identify active pilots on your map +- Monitor user activity and access level +- Manage tracked status to stay within subscription limits + +--- + +## 👤 Character Grouping by User + +Each user may have multiple EVE Online characters authorized. On this page: +- Characters are **grouped under their owning user** +- Admins can easily see which characters belong to the same person +- Useful for distinguishing between multiboxers or corp mates sharing access + +--- + +## 📋 Character Info Displayed + +![Page](/images/news/2025/05-11-map-active-characters/page.png "Page") + +Each tracked character on this page includes: + +| Field | Description | +|--------------------|-----------------------------------------------------------------------------| +| **Active From** | Timestamp indicating when the character opened the map (based on real-time browser tab activity) | +| **Character Info** | Character Name, Corporation, and Alliance | +| **Tracked Status** | Whether the character is being actively tracked on the map | +| **Online Status** | Online/offline status (in-game) | + +--- + +## 🔧 Admin Actions + +Map **owners and administrators** can: + +- ✅ **See viewer-only access**: Identify characters who can view but are not being tracked. +- 🚫 **Force Untrack** characters: + - Stop tracking & remove characters from map. + - Useful to stay within your character limit or reset tracking manually. + - Note: The user should re-enable tracking later if needed manually in map tracking settings. + +--- + +## 🛑 Notes & Recommendations + +- A character is **counted toward the Characters Limit** of the map's subscription **only when tracked**. +- Tracking begins **as soon as the character opens the map page** in any tab/browser. +- Closing all tabs with the map **automatically stops tracking** for that character (after a small period about 15 minutes). +- This system ensures you always have a live (tracking data automatically updated every 30 seconds), accurate picture of map usage across your team. + +--- + +By using the **Map Active Characters** page, admins can efficiently manage map activity, maintain security, and optimize performance across their team or alliance. + +--- + +Fly safe, +**The Wanderer Team** + +---