Compare commits

..

26 Commits

Author SHA1 Message Date
CI
45eb08fc3a chore: release version v1.70.0
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-11 16:30:02 +00:00
Dmitry Popov
fbcfae0200 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 18:29:36 +02:00
Dmitry Popov
9c4ce013ec feat(Core): Fix admin page error 2025-06-11 18:29:32 +02:00
CI
5dba7c12f0 chore: release version v1.69.1 2025-06-11 11:52:01 +00:00
Dmitry Popov
db793c80c8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 13:51:32 +02:00
Dmitry Popov
aa4a3f1aa9 chore: release version v1.68.6 2025-06-11 13:51:29 +02:00
CI
3424639309 chore: release version v1.69.0 2025-06-11 11:43:54 +00:00
Dmitry Popov
0f309a29ba Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 13:43:18 +02:00
Dmitry Popov
e13b8846b8 feat(Core): Added multiple tracking pools support 2025-06-11 13:43:13 +02:00
CI
d5ea4d6129 chore: release version v1.68.6
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-10 22:35:04 +00:00
Dmitry Popov
9d50bfedbd Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 00:34:36 +02:00
Dmitry Popov
b03410083c Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 00:34:27 +02:00
CI
a314b1e448 chore: release version v1.68.5 2025-06-10 22:34:26 +00:00
Dmitry Popov
e8a51a85c4 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 00:33:59 +02:00
Dmitry Popov
d4074f966c fix(Core): Fixed updating map options 2025-06-11 00:33:56 +02:00
CI
1413b41824 chore: release version v1.68.4
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-10 07:45:48 +00:00
Dmitry Popov
379c1edec3 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-10 09:45:17 +02:00
Dmitry Popov
58b5bade9e chore: release version v1.68.2 2025-06-10 09:45:14 +02:00
CI
71aee4cd3e chore: release version v1.68.3
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-09 17:58:52 +00:00
Dmitry Popov
10bab0cfa1 chore: release version v1.68.2 2025-06-09 19:55:36 +02:00
CI
698350b0f7 chore: release version v1.68.2
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-09 14:56:49 +00:00
Dmitry Popov
a97cf25031 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-09 16:56:11 +02:00
Dmitry Popov
8302d088bd fix(Core): Fixed character auth with wallet (on characters page) 2025-06-09 16:56:08 +02:00
CI
64788e73de chore: release version v1.68.1 2025-06-09 12:25:10 +00:00
Dmitry Popov
114fd471e8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-09 14:24:37 +02:00
Dmitry Popov
b24a3120d3 fix(Core): Fixed auth from welcome page if invites disabled 2025-06-09 14:24:35 +02:00
22 changed files with 790 additions and 71 deletions

View File

@@ -2,6 +2,71 @@
<!-- changelog -->
## [v1.70.0](https://github.com/wanderer-industries/wanderer/compare/v1.69.1...v1.70.0) (2025-06-11)
### Features:
* Core: Fix admin page error
## [v1.69.1](https://github.com/wanderer-industries/wanderer/compare/v1.69.0...v1.69.1) (2025-06-11)
## [v1.69.0](https://github.com/wanderer-industries/wanderer/compare/v1.68.6...v1.69.0) (2025-06-11)
### Features:
* Core: Added multiple tracking pools support
## [v1.68.6](https://github.com/wanderer-industries/wanderer/compare/v1.68.5...v1.68.6) (2025-06-10)
## [v1.68.5](https://github.com/wanderer-industries/wanderer/compare/v1.68.4...v1.68.5) (2025-06-10)
### Bug Fixes:
* Core: Fixed updating map options
## [v1.68.4](https://github.com/wanderer-industries/wanderer/compare/v1.68.3...v1.68.4) (2025-06-10)
## [v1.68.3](https://github.com/wanderer-industries/wanderer/compare/v1.68.2...v1.68.3) (2025-06-09)
## [v1.68.2](https://github.com/wanderer-industries/wanderer/compare/v1.68.1...v1.68.2) (2025-06-09)
### Bug Fixes:
* Core: Fixed character auth with wallet (on characters page)
## [v1.68.1](https://github.com/wanderer-industries/wanderer/compare/v1.68.0...v1.68.1) (2025-06-09)
### Bug Fixes:
* Core: Fixed auth from welcome page if invites disabled
## [v1.68.0](https://github.com/wanderer-industries/wanderer/compare/v1.67.5...v1.68.0) (2025-06-09)

View File

@@ -117,8 +117,9 @@ config :wanderer_app,
admins: admins,
corp_id: System.get_env("WANDERER_CORP_ID", "-1") |> String.to_integer(),
corp_wallet: System.get_env("WANDERER_CORP_WALLET", ""),
corp_wallet_eve_id: System.get_env("WANDERER_CORP_WALLET_EVE_ID", "-1") |> String.to_integer(),
corp_wallet_eve_id: System.get_env("WANDERER_CORP_WALLET_EVE_ID", "-1"),
public_api_disabled: public_api_disabled,
active_tracking_pool: System.get_env("WANDERER_ACTIVE_TRACKING_POOL", "default"),
character_tracking_pause_disabled:
System.get_env("WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED", "true")
|> String.to_existing_atom(),
@@ -174,11 +175,31 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
client_id: {WandererApp.Ueberauth, :client_id},
client_secret: {WandererApp.Ueberauth, :client_secret},
client_id_default: System.get_env("EVE_CLIENT_ID", "<EVE_CLIENT_ID>"),
client_id_1: System.get_env("EVE_CLIENT_ID_1", ""),
client_id_2: System.get_env("EVE_CLIENT_ID_2", ""),
client_id_3: System.get_env("EVE_CLIENT_ID_3", ""),
client_id_4: System.get_env("EVE_CLIENT_ID_4", ""),
client_id_5: System.get_env("EVE_CLIENT_ID_5", ""),
client_id_6: System.get_env("EVE_CLIENT_ID_6", ""),
client_id_7: System.get_env("EVE_CLIENT_ID_7", ""),
client_id_8: System.get_env("EVE_CLIENT_ID_8", ""),
client_id_9: System.get_env("EVE_CLIENT_ID_9", ""),
client_id_10: System.get_env("EVE_CLIENT_ID_10", ""),
client_id_with_wallet:
System.get_env("EVE_CLIENT_WITH_WALLET_ID", "<EVE_CLIENT_WITH_WALLET_ID>"),
client_id_with_corp_wallet:
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_ID", "<EVE_CLIENT_WITH_CORP_WALLET_ID>"),
client_secret_default: System.get_env("EVE_CLIENT_SECRET", "<EVE_CLIENT_SECRET>"),
client_secret_1: System.get_env("EVE_CLIENT_SECRET_1", ""),
client_secret_2: System.get_env("EVE_CLIENT_SECRET_2", ""),
client_secret_3: System.get_env("EVE_CLIENT_SECRET_3", ""),
client_secret_4: System.get_env("EVE_CLIENT_SECRET_4", ""),
client_secret_5: System.get_env("EVE_CLIENT_SECRET_5", ""),
client_secret_6: System.get_env("EVE_CLIENT_SECRET_6", ""),
client_secret_7: System.get_env("EVE_CLIENT_SECRET_7", ""),
client_secret_8: System.get_env("EVE_CLIENT_SECRET_8", ""),
client_secret_9: System.get_env("EVE_CLIENT_SECRET_9", ""),
client_secret_10: System.get_env("EVE_CLIENT_SECRET_10", ""),
client_secret_with_wallet:
System.get_env("EVE_CLIENT_WITH_WALLET_SECRET", "<EVE_CLIENT_WITH_WALLET_SECRET>"),
client_secret_with_corp_wallet:

View File

@@ -24,6 +24,7 @@ defmodule WandererApp.Api.Character do
define(:update_alliance, action: :update_alliance)
define(:update_wallet_balance, action: :update_wallet_balance)
define(:mark_as_deleted, action: :mark_as_deleted)
define(:last_active, action: :last_active)
define(:by_id,
get_by: [:id],
@@ -47,7 +48,8 @@ defmodule WandererApp.Api.Character do
:access_token,
:refresh_token,
:expires_at,
:scopes
:scopes,
:tracking_pool
]
defaults [:create, :read, :destroy]
@@ -72,6 +74,12 @@ defmodule WandererApp.Api.Character do
filter(expr(user_id == ^arg(:user_id) and deleted == false))
end
read :last_active do
argument(:from, :utc_datetime, allow_nil?: false)
filter(expr(updated_at > ^arg(:from)))
end
update :assign do
accept []
require_atomic? false
@@ -85,7 +93,7 @@ defmodule WandererApp.Api.Character do
update :update do
require_atomic? false
accept([:name, :access_token, :refresh_token, :expires_at, :scopes])
accept([:name, :access_token, :refresh_token, :expires_at, :scopes, :tracking_pool])
change(set_attribute(:deleted, false))
end
@@ -198,6 +206,7 @@ defmodule WandererApp.Api.Character do
attribute :alliance_name, :string
attribute :alliance_ticker, :string
attribute :eve_wallet_balance, :float
attribute :tracking_pool, :string
create_timestamp(:inserted_at)
update_timestamp(:updated_at)

View File

@@ -27,6 +27,7 @@ defmodule WandererApp.Application do
}
},
WandererApp.Cache,
Supervisor.child_spec({Cachex, name: :esi_auth_cache}, id: :esi_auth_cache_worker),
Supervisor.child_spec({Cachex, name: :api_cache}, id: :api_cache_worker),
Supervisor.child_spec({Cachex, name: :system_static_info_cache},
id: :system_static_info_cache_worker
@@ -40,6 +41,7 @@ defmodule WandererApp.Application do
Supervisor.child_spec({Cachex, name: :tracked_characters},
id: :tracked_characters_cache_worker
),
WandererApp.Esi.InitClientsTask,
WandererApp.Scheduler,
{Registry, keys: :unique, name: WandererApp.MapRegistry},
{Registry, keys: :unique, name: WandererApp.Character.TrackerRegistry},

View File

@@ -213,6 +213,21 @@ defmodule WandererApp.Character do
def can_track_corp_wallet?(_), do: false
@decorate cacheable(
cache: WandererApp.Cache,
key: "can_pause_tracking-#{character_id}"
)
def can_pause_tracking?(character_id) do
case get_character(character_id) do
{:ok, character} when not is_nil(character) ->
not WandererApp.Env.character_tracking_pause_disabled?() &&
not can_track_corp_wallet?(character)
_ ->
true
end
end
def get_ship(%{ship: ship_type_id, ship_name: ship_name} = _character)
when not is_nil(ship_type_id) and is_integer(ship_type_id) do
ship_type_id

View File

@@ -110,7 +110,7 @@ defmodule WandererApp.Character.Tracker do
end
defp pause_tracking(character_id) do
if not WandererApp.Env.character_tracking_pause_disabled?() &&
if WandererApp.Character.can_pause_tracking?(character_id) &&
not WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused") do
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
WandererApp.Cache.delete("character:#{character_id}:online_error_time")

View File

@@ -236,13 +236,17 @@ defmodule WandererApp.Character.TrackerPool do
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(
WandererApp.Character.Tracker,
:check_offline,
[
character_id
]
)
if WandererApp.Character.can_pause_tracking?(character_id) do
WandererApp.TaskWrapper.start_link(
WandererApp.Character.Tracker,
:check_offline,
[
character_id
]
)
else
:ok
end
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),

View File

@@ -16,6 +16,12 @@ defmodule WandererApp.Env do
def invites, do: get_key(:invites, false)
def map_subscriptions_enabled?, do: get_key(:map_subscriptions_enabled, false)
def public_api_disabled?, do: get_key(:public_api_disabled, false)
@decorate cacheable(
cache: WandererApp.Cache,
key: "active_tracking_pool"
)
def active_tracking_pool, do: get_key(:active_tracking_pool, "default")
def character_tracking_pause_disabled?, do: get_key(:character_tracking_pause_disabled, true)
def character_api_disabled?, do: get_key(:character_api_disabled, false)
def zkill_preload_disabled?, do: get_key(:zkill_preload_disabled, false)
@@ -24,7 +30,7 @@ defmodule WandererApp.Env do
def admin_username, do: get_key(:admin_username)
def admin_password, do: get_key(:admin_password)
def corp_wallet, do: get_key(:corp_wallet, "")
def corp_wallet_eve_id, do: get_key(:corp_wallet_eve_id, -1)
def corp_wallet_eve_id, do: get_key(:corp_wallet_eve_id, "-1")
def corp_eve_id, do: get_key(:corp_id, -1)
def subscription_settings, do: get_key(:subscription_settings)

View File

@@ -671,13 +671,20 @@ defmodule WandererApp.Esi.ApiClient do
end
defp refresh_token(character_id) do
{:ok, %{expires_at: expires_at, refresh_token: refresh_token, scopes: scopes} = character} =
{:ok,
%{
expires_at: expires_at,
refresh_token: refresh_token,
scopes: scopes,
tracking_pool: tracking_pool
} = character} =
WandererApp.Character.get_character(character_id)
refresh_token_result =
WandererApp.Ueberauth.Strategy.Eve.OAuth.get_refresh_token([],
with_wallet: WandererApp.Character.can_track_wallet?(character),
is_admin?: WandererApp.Character.can_track_corp_wallet?(character),
tracking_pool: tracking_pool,
token: %OAuth2.AccessToken{refresh_token: refresh_token}
)

View File

@@ -294,14 +294,16 @@ defmodule WandererApp.Map do
map_id
|> update_map(%{characters_limit: characters_limit, hubs_limit: hubs_limit})
map
map_id
|> get_map!()
end
def update_options!(%{map_id: map_id} = map, options) do
map_id
|> update_map(%{options: options})
map
map_id
|> get_map!()
end
def add_systems!(map, []), do: map

View File

@@ -212,7 +212,7 @@ defmodule WandererApp.Map.Server do
do:
map_id
|> map_pid!
|> GenServer.call({&Impl.update_subscription_settings/2, [settings]})
|> GenServer.cast({&Impl.update_subscription_settings/2, [settings]})
def delete_connection(map_id, connection_info) when is_binary(map_id),
do:

View File

@@ -192,10 +192,13 @@ defmodule WandererApp.Ueberauth.Strategy.Eve do
end
defp oauth_client_options_from_conn(conn, with_wallet, is_admin?) do
tracking_pool = WandererApp.Env.active_tracking_pool()
base_options = [
redirect_uri: callback_url(conn),
with_wallet: with_wallet,
is_admin?: is_admin?
is_admin?: is_admin?,
tracking_pool: tracking_pool
]
request_options = conn.private[:ueberauth_request_options].options
@@ -213,11 +216,11 @@ defmodule WandererApp.Ueberauth.Strategy.Eve do
defp check_invite_valid(invite_token) do
case invite_token do
nil ->
{false, nil}
token ->
token when not is_nil(token) and token != "" ->
check_token_valid(token)
_ ->
{not WandererApp.Env.invites(), :user}
end
end

View File

@@ -0,0 +1,84 @@
defmodule WandererApp.Esi.InitClientsTask do
use Task
require Logger
def start_link(arg) do
Task.start_link(__MODULE__, :run, [arg])
end
def run(_arg) do
Logger.info("starting")
Cachex.put(
:esi_auth_cache,
:active_config,
"config_#{WandererApp.Env.active_tracking_pool()}"
)
cache_clients()
end
defp cache_clients() do
config = Application.get_env(:ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth, [])
cache_client("default", %{
client_id: config[:client_id_default],
client_secret: config[:client_secret_default]
})
Enum.each(1..10, fn index ->
client_id = config["client_id_#{index}" |> String.to_atom()]
client_secret = config["client_secret_#{index}" |> String.to_atom()]
if client_id != "" && client_secret != "" do
cache_client(index, %{
client_id: client_id,
client_secret: client_secret
})
end
end)
end
defp cache_client(id, config) do
config_uuid = UUID.uuid4()
config =
config
|> Map.merge(%{
id: id,
uuid: config_uuid
})
Cachex.put(
:esi_auth_cache,
"config_#{id}",
config
)
Cachex.put(
:esi_auth_cache,
config_uuid,
config
)
# Cachex.put(
# :esi_auth_cache,
# "config_uuid_#{id}",
# config_uuid
# )
configs_total_count =
if id == "default" do
0
else
id
end
Cachex.put(
:esi_auth_cache,
"configs_total_count",
configs_total_count
)
end
end

View File

@@ -2,26 +2,50 @@ defmodule WandererApp.Ueberauth do
@moduledoc false
def client_id(opts \\ []) do
config = _get_config()
config = get_config()
tracking_pool = Keyword.get(opts, :tracking_pool)
cond do
Keyword.get(opts, :is_admin?) -> config[:client_id_with_corp_wallet]
Keyword.get(opts, :with_wallet) -> config[:client_id_with_wallet]
not is_nil(tracking_pool) -> get_settings(tracking_pool)[:client_id]
true -> config[:client_id_default]
end
end
def client_secret(opts \\ []) do
config = _get_config()
config = get_config()
tracking_pool = Keyword.get(opts, :tracking_pool)
cond do
Keyword.get(opts, :is_admin?) -> config[:client_secret_with_corp_wallet]
Keyword.get(opts, :with_wallet) -> config[:client_secret_with_wallet]
not is_nil(tracking_pool) -> get_settings(tracking_pool)[:client_secret]
true -> config[:client_secret_default]
end
end
defp _get_config() do
defp get_settings(nil) do
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_default"
)
esi_config
end
defp get_settings(tracking_pool) do
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_#{tracking_pool}"
)
esi_config
end
defp get_config() do
Application.get_env(:ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth, [])
end
end

View File

@@ -14,7 +14,8 @@ defmodule WandererAppWeb.AuthController do
access_token: auth.credentials.token,
refresh_token: auth.credentials.refresh_token,
expires_at: auth.credentials.expires_at,
scopes: auth.credentials.scopes
scopes: auth.credentials.scopes,
tracking_pool: WandererApp.Env.active_tracking_pool()
}
%{
@@ -29,7 +30,8 @@ defmodule WandererAppWeb.AuthController do
access_token: auth.credentials.token,
refresh_token: auth.credentials.refresh_token,
expires_at: auth.credentials.expires_at,
scopes: auth.credentials.scopes
scopes: auth.credentials.scopes,
tracking_pool: WandererApp.Env.active_tracking_pool()
}
{:ok, character} =

View File

@@ -62,6 +62,7 @@ defmodule WandererAppWeb.AdminLive do
user_character_ids: user_character_ids,
user_id: user_id,
invite_link: nil,
tracker_stats: [],
map_subscriptions_enabled?: WandererApp.Env.map_subscriptions_enabled?(),
restrict_maps_creation?: WandererApp.Env.restrict_maps_creation?()
)}
@@ -294,6 +295,8 @@ defmodule WandererAppWeb.AdminLive do
defp apply_action(socket, :index, _params, uri) do
{:ok, invites} = WandererApp.Api.MapInvite.read()
{:ok, tracker_stats} = load_tracker_stats()
socket
|> assign(:active_page, :admin)
|> assign(:uri, URI.parse(uri))
@@ -308,6 +311,7 @@ defmodule WandererAppWeb.AdminLive do
|> assign(:form, to_form(%{"amount" => 500_000_000}))
|> assign(:unlink_character_form, to_form(%{}))
|> assign(:invites, invites)
|> assign(:tracker_stats, tracker_stats)
end
defp apply_action(socket, :add_invite_link, _params, uri) do
@@ -344,6 +348,70 @@ defmodule WandererAppWeb.AdminLive do
|> assign(:invites, [])
end
defp load_tracker_stats() do
{:ok, characters} =
WandererApp.Api.Character.last_active(%{
from:
DateTime.utc_now()
|> DateTime.add(-1 * 60 * 24 * 7, :minute)
})
admins_count =
characters |> Enum.filter(&WandererApp.Character.can_track_corp_wallet?/1) |> Enum.count()
with_wallets_count =
characters
|> Enum.filter(
&(WandererApp.Character.can_track_wallet?(&1) and
not WandererApp.Character.can_track_corp_wallet?(&1))
)
|> Enum.count()
default_count =
characters
|> Enum.filter(
&(is_nil(&1.tracking_pool) and not WandererApp.Character.can_track_wallet?(&1) and
not WandererApp.Character.can_track_corp_wallet?(&1))
)
|> Enum.count()
result = [
%{title: "Admins", value: admins_count},
%{title: "With Wallet", value: with_wallets_count},
%{title: "Default", value: default_count}
]
{:ok, pools_count} =
Cachex.get(
:esi_auth_cache,
"configs_total_count"
)
result =
if not is_nil(pools_count) && pools_count != 0 do
pools =
1..pools_count
|> Enum.map(fn pool_id ->
pools_character_count =
characters
|> Enum.filter(
&(&1.tracking_pool == "#{pool_id}" and
not WandererApp.Character.can_track_wallet?(&1) and
not WandererApp.Character.can_track_corp_wallet?(&1))
)
|> Enum.count()
%{title: "Pool #{pool_id}", value: pools_character_count}
end)
result ++ pools
else
result
end
{:ok, result}
end
defp get_invite_link(uri, token) do
uri
|> Map.put(:path, "/auth/eve")

View File

@@ -121,50 +121,69 @@
<.icon name="hero-plus-solid" class="w-6 h-6" />
<h3 class="card-title text-center text-md">New Invite</h3>
</.link>
</h4>
<.table
id="invites"
rows={@invites}
class="!max-h-[40vh] !overflow-y-auto"
>
<:col :let={invite} label="Link">
<div class="flex items-center">
<div class="w-[200px] no-wrap truncate"><%= get_invite_link(@uri, invite.token) %></div>
<.button
phx-hook="CopyToClipboard"
id="copy-to-clipboard"
class="copy-link btn btn-neutral rounded-none"
data-url={get_invite_link(@uri, invite.token)}
>
Copy
<div class="absolute w-[100px] !mr-[-170px] link-copied hidden">
Link copied
</div>
</.button>
</div>
</:col>
<:col :let={invite} label="Type">
<%= invite.type %>
</:col>
<:col :let={invite} label="Valid Until">
<div>
<p class="mb-0 text-xs text-gray-600 dark:text-zinc-100 whitespace-nowrap">
<.local_time id={invite.id} at={invite.valid_until} />
</p>
</div>
</:col>
<:action :let={invite}>
<.button
phx-click="delete-invite"
phx-value-id={invite.id}
data={[confirm: "Please confirm to delete invite!"]}
class="hover:text-white"
>
<.icon name="hero-trash-solid" class="w-4 h-4" />
</.button>
</:action>
</.table>
</div>
</div>
</div>
<div class="card dark:bg-zinc-800 dark:border-zinc-600">
<div class="card-body">
<div class="col-span-6">
<span class="text-gray-400 dark:text-gray-400">Tracking Pools</span>
<.table
id="invites"
rows={@invites}
id="tracking_pools"
rows={@tracker_stats}
class="!max-h-[40vh] !overflow-y-auto"
>
<:col :let={invite} label="Link">
<div class="flex items-center">
<div class="w-[200px] no-wrap truncate"><%= get_invite_link(@uri, invite.token) %></div>
<.button
phx-hook="CopyToClipboard"
id="copy-to-clipboard"
class="copy-link btn btn-neutral rounded-none"
data-url={get_invite_link(@uri, invite.token)}
>
Copy
<div class="absolute w-[100px] !mr-[-170px] link-copied hidden">
Link copied
</div>
</.button>
</div>
<:col :let={stat} label="Pool">
<div class="w-[200px] no-wrap truncate">{stat.title}</div>
</:col>
<:col :let={invite} label="Type">
<%= invite.type %>
<:col :let={stat} label="Count">
{stat.value}
</:col>
<:col :let={invite} label="Valid Until">
<div>
<p class="mb-0 text-xs text-gray-600 dark:text-zinc-100 whitespace-nowrap">
<.local_time id={invite.id} at={invite.valid_until} />
</p>
</div>
</:col>
<:action :let={invite}>
<.button
phx-click="delete-invite"
phx-value-id={invite.id}
data={[confirm: "Please confirm to delete invite!"]}
class="hover:text-white"
>
<.icon name="hero-trash-solid" class="w-4 h-4" />
</.button>
</:action>
</.table>
</h4>
</div>
</div>
</div>
@@ -293,6 +312,7 @@
show
on_cancel={JS.patch(~p"/admin")}
>
<.form :let={f} for={@form} phx-change="validate" phx-submit={@live_action}>
<.input
type="select"

View File

@@ -5,6 +5,8 @@ defmodule WandererAppWeb.CharactersLive do
alias BetterNumber, as: Number
@active_tracking_pool 1
def mount(_params, %{"user_id" => user_id} = _session, socket)
when not is_nil(user_id) do
{:ok, characters} = WandererApp.Api.Character.active_by_user(%{user_id: user_id})
@@ -56,17 +58,25 @@ defmodule WandererAppWeb.CharactersLive do
@impl true
def handle_event("authorize", form, socket) do
track_wallet = form |> Map.get("track_wallet", false)
token = UUID.uuid4(:default)
WandererApp.Cache.put("invite_#{token}", true, ttl: :timer.minutes(30))
{:noreply, socket |> push_navigate(to: ~p"/auth/eve?invite=#{token}&w=#{track_wallet}")}
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_#{WandererApp.Env.active_tracking_pool()}"
)
WandererApp.Cache.put("invite_#{esi_config.uuid}", true, ttl: :timer.minutes(30))
{:noreply,
socket |> push_navigate(to: ~p"/auth/eve?invite=#{esi_config.uuid}&w=#{track_wallet}")}
end
@impl true
def handle_event("delete", %{"character_id" => character_id}, socket) do
WandererApp.Character.TrackerManager.stop_tracking(character_id)
{:ok, map_user_settings} = WandererApp.Api.MapCharacterSettings.tracked_by_character(%{character_id: character_id})
{:ok, map_user_settings} =
WandererApp.Api.MapCharacterSettings.tracked_by_character(%{character_id: character_id})
map_user_settings
|> Enum.each(fn settings ->
@@ -87,6 +97,15 @@ defmodule WandererAppWeb.CharactersLive do
{:noreply, socket |> assign(characters: characters |> Enum.map(&map_ui_character/1))}
end
@impl true
def handle_event(
"validate",
params,
socket
) do
{:noreply, assign(socket, form: params)}
end
@impl true
def handle_event("show_table", %{"value" => "on"}, socket) do
{:noreply, socket |> assign(mode: :table)}
@@ -148,7 +167,7 @@ defmodule WandererAppWeb.CharactersLive do
socket
|> assign(:active_page, :characters)
|> assign(:page_title, "Authorize Character - Characters")
|> assign(:form, to_form(%{"track_wallet" => false}))
|> assign(:form, to_form(%{}))
end
defp map_ui_character(character) do

View File

@@ -242,10 +242,13 @@
show
on_cancel={JS.patch(~p"/characters")}
>
<.form :let={f} for={@form} phx-submit="authorize">
<div :if={@wallet_tracking_enabled?} class="pb-2 -mt-8">
<div class="flex flex-col gap-3">
<.form :let={f} for={@form} phx-submit="authorize" phx-change="validate">
<div :if={@wallet_tracking_enabled?} class="pb-2">
<.input
type="checkbox"
field={f[:track_wallet]}
label="Access to character wallet information"
/>
@@ -254,5 +257,6 @@
<.button type="submit">AUTHORIZE</.button>
</div>
</.form>
</div>
</.modal>
</main>

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.68.0"
@version "1.70.0"
def project do
[

View File

@@ -0,0 +1,21 @@
defmodule WandererApp.Repo.Migrations.AddCharacterTrackingPool do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:character_v1) do
add :tracking_pool, :text
end
end
def down do
alter table(:character_v1) do
remove :tracking_pool
end
end
end

View File

@@ -0,0 +1,343 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_item_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "tracking_pool",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_eve_wallet_balance",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_location",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_ship",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_solar_system_id",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_structure_id",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_station_id",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_access_token",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_refresh_token",
"type": "binary"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "4C6974D764592ED3279CFD3FF85CE89B146C7E16628F0A4278E319A4A5E5FD8C",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "character_v1_unique_eve_id_index",
"keys": [
{
"type": "atom",
"value": "eve_id"
}
],
"name": "unique_eve_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}