commit
9944f5d532
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,143 @@ |
||||
div.divider { |
||||
height: inherit; |
||||
width: 1px; |
||||
background: #828ba0; |
||||
margin-left: 10px; |
||||
margin-right: 10px; |
||||
} |
||||
|
||||
input.profile-item { |
||||
margin-left: 10px; |
||||
color: #828ba0; |
||||
outline: none; |
||||
} |
||||
|
||||
.header-account { |
||||
font-size: 18px; |
||||
} |
||||
|
||||
.label-account { |
||||
font-size: 1rem; |
||||
} |
||||
|
||||
.white { |
||||
color: #fff; |
||||
} |
||||
|
||||
.card-body-account { |
||||
max-width: none !important; |
||||
} |
||||
|
||||
.form-input { |
||||
display: flex; |
||||
margin-bottom: 1rem; |
||||
} |
||||
|
||||
.form-checkbox { |
||||
margin-right: 0.5rem; |
||||
align-self: center; |
||||
} |
||||
|
||||
.label-checkbox { |
||||
font-size: 14px; |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
.o-flow-x { |
||||
overflow-x: auto; |
||||
} |
||||
|
||||
.fs-14 { |
||||
font-size: 14px; |
||||
} |
||||
|
||||
.acc-link-active { |
||||
&:hover, &:focus { |
||||
background-color: $primary !important; |
||||
color: #fff !important; |
||||
} |
||||
background-color: $primary; |
||||
color: #fff; |
||||
} |
||||
|
||||
.table-watchlist { |
||||
margin: 30px; |
||||
|
||||
@include media-breakpoint-down(md) { |
||||
margin: 0; |
||||
} |
||||
} |
||||
|
||||
.form-error { |
||||
display: block; |
||||
font-size: 13px; |
||||
line-height: 1.2; |
||||
padding-top: 10px; |
||||
} |
||||
|
||||
.navbar-account { |
||||
@include media-breakpoint-down(sm) { |
||||
max-width: 100%; |
||||
flex-grow: 1; |
||||
} |
||||
} |
||||
|
||||
.nav-item.account { |
||||
@include media-breakpoint-down(sm) { |
||||
width: auto; |
||||
display: inline-grid; |
||||
} |
||||
} |
||||
|
||||
.nav.account { |
||||
@include media-breakpoint-down(sm) { |
||||
display: inline; |
||||
} |
||||
} |
||||
|
||||
li.public-tags-address { |
||||
list-style: none; |
||||
display: flex; |
||||
} |
||||
|
||||
input.public-tags-address { |
||||
flex-basis: 557px; |
||||
width: auto; |
||||
} |
||||
|
||||
.add-form-field { |
||||
background: none; |
||||
border: none; |
||||
margin-left: 0.5px; |
||||
} |
||||
|
||||
.remove-form-field { |
||||
background: none; |
||||
border: none; |
||||
|
||||
svg { |
||||
margin-top: 3px; |
||||
} |
||||
} |
||||
|
||||
.multiple-input-fields-container { |
||||
padding-inline-start: 0; |
||||
display: inline-block; |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
.line-input { |
||||
@include media-breakpoint-up(md) { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
|
||||
.form-group { |
||||
flex-grow: 1; |
||||
margin-right: 4rem; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.mr-4-rem { |
||||
margin-right: 4rem; |
||||
} |
@ -0,0 +1,43 @@ |
||||
import $ from 'jquery' |
||||
|
||||
const $removeButton = $('.remove-form-field')[0] |
||||
const $container = $('#' + $removeButton.dataset.container) |
||||
const index = parseInt($container[0].dataset.index) |
||||
|
||||
if (index <= 1) { |
||||
$('.remove-form-field').hide() |
||||
} |
||||
|
||||
$('.add-form-field').on('click', (event) => { |
||||
event.preventDefault() |
||||
console.log(event) |
||||
const $container = $('#' + event.currentTarget.dataset.container) |
||||
const index = parseInt($container[0].dataset.index) |
||||
if (index < 10) { |
||||
$container.append($.parseHTML(event.currentTarget.dataset.prototype)) |
||||
$container[0].dataset.index = index + 1 |
||||
} |
||||
if (index >= 9) { |
||||
$('.add-form-field').hide() |
||||
} |
||||
if (index <= 1) { |
||||
$('.remove-form-field').show() |
||||
} |
||||
}) |
||||
|
||||
$('[data-multiple-input-field-container]').on('click', '.remove-form-field', (event) => { |
||||
event.preventDefault() |
||||
console.log(event) |
||||
const $container = $('#' + event.currentTarget.dataset.container) |
||||
const index = parseInt($container[0].dataset.index) |
||||
if (index > 1) { |
||||
$container[0].dataset.index = index - 1 |
||||
event.currentTarget.parentElement.remove() |
||||
} |
||||
if (index >= 10) { |
||||
$('.add-form-field').show() |
||||
} |
||||
if (index <= 2) { |
||||
$('.remove-form-field').hide() |
||||
} |
||||
}) |
@ -0,0 +1,19 @@ |
||||
import $ from 'jquery' |
||||
|
||||
$('[data-delete-item]').on('click', (event) => { |
||||
event.preventDefault() |
||||
|
||||
if (confirm('Are you sure you want to delete item?')) { |
||||
$(event.currentTarget.parentElement).find('form').trigger('submit') |
||||
} |
||||
}) |
||||
|
||||
$('[data-delete-request]').on('click', (event) => { |
||||
event.preventDefault() |
||||
|
||||
const result = prompt('Public tags: "' + event.currentTarget.dataset.tags.replace(';', '" and "') + '" will be removed.\nWhy do you want to remove tags?') |
||||
if (result) { |
||||
$(event.currentTarget.parentElement).find('[name="remove_reason"]').val(result) |
||||
$(event.currentTarget.parentElement).find('form').trigger('submit') |
||||
} |
||||
}) |
@ -0,0 +1,83 @@ |
||||
defmodule BlockScoutWeb.Account.Api.V1.FallbackController do |
||||
use Phoenix.Controller |
||||
|
||||
alias BlockScoutWeb.Account.Api.V1.UserView |
||||
alias Ecto.Changeset |
||||
|
||||
def call(conn, {:identity, _}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "User not found"}) |
||||
end |
||||
|
||||
def call(conn, {:watchlist, _}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Watchlist not found"}) |
||||
end |
||||
|
||||
def call(conn, {:error, %{reason: :item_not_found}}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Item not found"}) |
||||
end |
||||
|
||||
def call(conn, {:error, %Changeset{} = changeset}) do |
||||
conn |
||||
|> put_status(:unprocessable_entity) |
||||
|> put_view(UserView) |
||||
|> render(:changeset_errors, changeset: changeset) |
||||
end |
||||
|
||||
def call(conn, {:create_tag, {:error, message}}) do |
||||
conn |
||||
|> put_status(:unprocessable_entity) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: message}) |
||||
end |
||||
|
||||
def call(conn, {:watchlist_delete, false}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Watchlist address not found"}) |
||||
end |
||||
|
||||
def call(conn, {:tag_delete, false}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Tag not found"}) |
||||
end |
||||
|
||||
def call(conn, {:api_key_delete, false}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Api key not found"}) |
||||
end |
||||
|
||||
def call(conn, {:custom_abi_delete, false}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Custom ABI not found"}) |
||||
end |
||||
|
||||
def call(conn, {:public_tag_delete, false}) do |
||||
conn |
||||
|> put_status(:not_found) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Error"}) |
||||
end |
||||
|
||||
def call(conn, {:auth, _}) do |
||||
conn |
||||
|> put_status(:unauthorized) |
||||
|> put_view(UserView) |
||||
|> render(:message, %{message: "Unauthorized"}) |
||||
end |
||||
end |
@ -0,0 +1,87 @@ |
||||
defmodule BlockScoutWeb.Account.Api.V1.TagsController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [current_user: 1] |
||||
|
||||
alias BlockScoutWeb.Models.{GetAddressTags, GetTransactionTags, UserFromAuth} |
||||
alias Explorer.Account.Identity |
||||
alias Explorer.{Chain, Repo} |
||||
alias Explorer.Chain.Hash.{Address, Full} |
||||
|
||||
action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController) |
||||
|
||||
def tags_address(conn, %{"address_hash" => address_hash}) do |
||||
personal_tags = |
||||
if is_nil(current_user(conn)) do |
||||
%{personal_tags: [], watchlist_names: []} |
||||
else |
||||
uid = current_user(conn).id |
||||
|
||||
with {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:watchlist, %{watchlists: [watchlist | _]}} <- |
||||
{:watchlist, Repo.account_repo().preload(identity, :watchlists)}, |
||||
{:address_hash, {:ok, address_hash}} <- {:address_hash, Address.cast(address_hash)} do |
||||
GetAddressTags.get_address_tags(address_hash, %{id: identity.id, watchlist_id: watchlist.id}) |
||||
else |
||||
_ -> |
||||
%{personal_tags: [], watchlist_names: []} |
||||
end |
||||
end |
||||
|
||||
public_tags = |
||||
case Address.cast(address_hash) do |
||||
{:ok, address_hash} -> |
||||
GetAddressTags.get_public_tags(address_hash) |
||||
|
||||
_ -> |
||||
%{common_tags: []} |
||||
end |
||||
|
||||
conn |
||||
|> put_status(200) |
||||
|> render(:address_tags, %{tags_map: Map.merge(personal_tags, public_tags)}) |
||||
end |
||||
|
||||
def tags_transaction(conn, %{"transaction_hash" => transaction_hash}) do |
||||
transaction = |
||||
with {:ok, transaction_hash} <- Full.cast(transaction_hash), |
||||
{:ok, transaction} <- Chain.hash_to_transaction(transaction_hash) do |
||||
transaction |
||||
else |
||||
_ -> |
||||
nil |
||||
end |
||||
|
||||
personal_tags = |
||||
if is_nil(current_user(conn)) do |
||||
%{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} |
||||
else |
||||
uid = current_user(conn).id |
||||
|
||||
with {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:watchlist, %{watchlists: [watchlist | _]}} <- |
||||
{:watchlist, Repo.account_repo().preload(identity, :watchlists)}, |
||||
false <- is_nil(transaction) do |
||||
GetTransactionTags.get_transaction_with_addresses_tags(transaction, %{ |
||||
id: identity.id, |
||||
watchlist_id: watchlist.id |
||||
}) |
||||
else |
||||
_ -> |
||||
%{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} |
||||
end |
||||
end |
||||
|
||||
public_tags_from = |
||||
if is_nil(transaction), do: [], else: GetAddressTags.get_public_tags(transaction.from_address_hash).common_tags |
||||
|
||||
public_tags_to = |
||||
if is_nil(transaction), do: [], else: GetAddressTags.get_public_tags(transaction.to_address_hash).common_tags |
||||
|
||||
public_tags = %{common_tags: public_tags_from ++ public_tags_to} |
||||
|
||||
conn |
||||
|> put_status(200) |
||||
|> render(:transaction_tags, %{tags_map: Map.merge(personal_tags, public_tags)}) |
||||
end |
||||
end |
@ -0,0 +1,476 @@ |
||||
defmodule BlockScoutWeb.Account.Api.V1.UserController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [current_user: 1] |
||||
import Ecto.Query, only: [from: 2] |
||||
|
||||
alias BlockScoutWeb.Models.UserFromAuth |
||||
alias Explorer.Account.Api.Key, as: ApiKey |
||||
alias Explorer.Account.CustomABI |
||||
alias Explorer.Account.{Identity, PublicTagsRequest, TagAddress, TagTransaction, WatchlistAddress} |
||||
alias Explorer.ExchangeRates.Token |
||||
alias Explorer.{Market, Repo} |
||||
alias Plug.CSRFProtection |
||||
|
||||
action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController) |
||||
|
||||
@ok_message "OK" |
||||
|
||||
def info(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:user_info, %{identity: identity}) |
||||
end |
||||
end |
||||
|
||||
def watchlist(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:watchlist, %{watchlists: [watchlist | _]}} <- |
||||
{:watchlist, Repo.account_repo().preload(identity, :watchlists)}, |
||||
watchlist_with_addresses <- preload_watchlist_address_fetched_coin_balance(watchlist) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:watchlist_addresses, %{ |
||||
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), |
||||
watchlist_addresses: watchlist_with_addresses.watchlist_addresses |
||||
}) |
||||
end |
||||
end |
||||
|
||||
def delete_watchlist(conn, %{"id" => watchlist_address_id}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:watchlist, %{watchlists: [watchlist | _]}} <- |
||||
{:watchlist, Repo.account_repo().preload(identity, :watchlists)}, |
||||
{count, _} <- WatchlistAddress.delete(watchlist_address_id, watchlist.id), |
||||
{:watchlist_delete, true} <- {:watchlist_delete, count > 0} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:message, %{message: @ok_message}) |
||||
end |
||||
end |
||||
|
||||
def create_watchlist(conn, %{ |
||||
"address_hash" => address_hash, |
||||
"name" => name, |
||||
"notification_settings" => %{ |
||||
"native" => %{ |
||||
"incoming" => watch_coin_input, |
||||
"outcoming" => watch_coin_output |
||||
}, |
||||
"ERC-20" => %{ |
||||
"incoming" => watch_erc_20_input, |
||||
"outcoming" => watch_erc_20_output |
||||
}, |
||||
"ERC-721" => %{ |
||||
"incoming" => watch_erc_721_input, |
||||
"outcoming" => watch_erc_721_output |
||||
} |
||||
# , |
||||
# "ERC-1155" => %{ |
||||
# "incoming" => watch_erc_1155_input, |
||||
# "outcoming" => watch_erc_1155_output |
||||
# } |
||||
}, |
||||
"notification_methods" => %{ |
||||
"email" => notify_email |
||||
} |
||||
}) do |
||||
watchlist_params = %{ |
||||
name: name, |
||||
watch_coin_input: watch_coin_input, |
||||
watch_coin_output: watch_coin_output, |
||||
watch_erc_20_input: watch_erc_20_input, |
||||
watch_erc_20_output: watch_erc_20_output, |
||||
watch_erc_721_input: watch_erc_721_input, |
||||
watch_erc_721_output: watch_erc_721_output, |
||||
watch_erc_1155_input: watch_erc_721_input, |
||||
watch_erc_1155_output: watch_erc_721_output, |
||||
notify_email: notify_email, |
||||
address_hash: address_hash |
||||
} |
||||
|
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:watchlist, %{watchlists: [watchlist | _]}} <- |
||||
{:watchlist, Repo.account_repo().preload(identity, :watchlists)}, |
||||
{:ok, watchlist_address} <- |
||||
WatchlistAddress.create(Map.put(watchlist_params, :watchlist_id, watchlist.id)), |
||||
watchlist_address_preloaded <- WatchlistAddress.preload_address_fetched_coin_balance(watchlist_address) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:watchlist_address, %{ |
||||
watchlist_address: watchlist_address_preloaded, |
||||
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() |
||||
}) |
||||
end |
||||
end |
||||
|
||||
def update_watchlist(conn, %{ |
||||
"id" => watchlist_address_id, |
||||
"address_hash" => address_hash, |
||||
"name" => name, |
||||
"notification_settings" => %{ |
||||
"native" => %{ |
||||
"incoming" => watch_coin_input, |
||||
"outcoming" => watch_coin_output |
||||
}, |
||||
"ERC-20" => %{ |
||||
"incoming" => watch_erc_20_input, |
||||
"outcoming" => watch_erc_20_output |
||||
}, |
||||
"ERC-721" => %{ |
||||
"incoming" => watch_erc_721_input, |
||||
"outcoming" => watch_erc_721_output |
||||
} |
||||
# , |
||||
# "ERC-1155" => %{ |
||||
# "incoming" => watch_erc_1155_input, |
||||
# "outcoming" => watch_erc_1155_output |
||||
# } |
||||
}, |
||||
"notification_methods" => %{ |
||||
"email" => notify_email |
||||
} |
||||
}) do |
||||
watchlist_params = %{ |
||||
id: watchlist_address_id, |
||||
name: name, |
||||
watch_coin_input: watch_coin_input, |
||||
watch_coin_output: watch_coin_output, |
||||
watch_erc_20_input: watch_erc_20_input, |
||||
watch_erc_20_output: watch_erc_20_output, |
||||
watch_erc_721_input: watch_erc_721_input, |
||||
watch_erc_721_output: watch_erc_721_output, |
||||
watch_erc_1155_input: watch_erc_721_input, |
||||
watch_erc_1155_output: watch_erc_721_output, |
||||
notify_email: notify_email, |
||||
address_hash: address_hash |
||||
} |
||||
|
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:watchlist, %{watchlists: [watchlist | _]}} <- |
||||
{:watchlist, Repo.account_repo().preload(identity, :watchlists)}, |
||||
{:ok, watchlist_address} <- |
||||
WatchlistAddress.update(Map.put(watchlist_params, :watchlist_id, watchlist.id)), |
||||
watchlist_address_preloaded <- WatchlistAddress.preload_address_fetched_coin_balance(watchlist_address) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:watchlist_address, %{ |
||||
watchlist_address: watchlist_address_preloaded, |
||||
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() |
||||
}) |
||||
end |
||||
end |
||||
|
||||
def tags_address(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
address_tags <- TagAddress.get_tags_address_by_identity_id(identity.id) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:address_tags, %{address_tags: address_tags}) |
||||
end |
||||
end |
||||
|
||||
def delete_tag_address(conn, %{"id" => tag_id}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{count, _} <- TagAddress.delete(tag_id, identity.id), |
||||
{:tag_delete, true} <- {:tag_delete, count > 0} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:message, %{message: @ok_message}) |
||||
end |
||||
end |
||||
|
||||
def create_tag_address(conn, %{"address_hash" => address_hash, "name" => name}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, address_tag} <- |
||||
TagAddress.create(%{ |
||||
name: name, |
||||
address_hash: address_hash, |
||||
identity_id: identity.id |
||||
}) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:address_tag, %{address_tag: address_tag}) |
||||
end |
||||
end |
||||
|
||||
def update_tag_address(conn, %{"id" => tag_id} = attrs) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, address_tag} <- |
||||
TagAddress.update( |
||||
reject_nil_map_values(%{ |
||||
id: tag_id, |
||||
name: attrs["name"], |
||||
address_hash: attrs["address_hash"], |
||||
identity_id: identity.id |
||||
}) |
||||
) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:address_tag, %{address_tag: address_tag}) |
||||
end |
||||
end |
||||
|
||||
def tags_transaction(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
transaction_tags <- TagTransaction.get_tags_transaction_by_identity_id(identity.id) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:transaction_tags, %{transaction_tags: transaction_tags}) |
||||
end |
||||
end |
||||
|
||||
def delete_tag_transaction(conn, %{"id" => tag_id}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{count, _} <- TagTransaction.delete(tag_id, identity.id), |
||||
{:tag_delete, true} <- {:tag_delete, count > 0} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:message, %{message: @ok_message}) |
||||
end |
||||
end |
||||
|
||||
def create_tag_transaction(conn, %{"transaction_hash" => tx_hash, "name" => name}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, transaction_tag} <- |
||||
TagTransaction.create(%{ |
||||
name: name, |
||||
tx_hash: tx_hash, |
||||
identity_id: identity.id |
||||
}) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:transaction_tag, %{transaction_tag: transaction_tag}) |
||||
end |
||||
end |
||||
|
||||
def update_tag_transaction(conn, %{"id" => tag_id} = attrs) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, transaction_tag} <- |
||||
TagTransaction.update( |
||||
reject_nil_map_values(%{ |
||||
id: tag_id, |
||||
name: attrs["name"], |
||||
tx_hash: attrs["transaction_hash"], |
||||
identity_id: identity.id |
||||
}) |
||||
) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:transaction_tag, %{transaction_tag: transaction_tag}) |
||||
end |
||||
end |
||||
|
||||
def api_keys(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
api_keys <- ApiKey.get_api_keys_by_identity_id(identity.id) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:api_keys, %{api_keys: api_keys}) |
||||
end |
||||
end |
||||
|
||||
def delete_api_key(conn, %{"api_key" => api_key_uuid}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{count, _} <- ApiKey.delete(api_key_uuid, identity.id), |
||||
{:api_key_delete, true} <- {:api_key_delete, count > 0} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:message, %{message: @ok_message}) |
||||
end |
||||
end |
||||
|
||||
def create_api_key(conn, %{"name" => api_key_name}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, api_key} <- |
||||
ApiKey.create(%{name: api_key_name, identity_id: identity.id}) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:api_key, %{api_key: api_key}) |
||||
end |
||||
end |
||||
|
||||
def update_api_key(conn, %{"name" => api_key_name, "api_key" => api_key_value}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, api_key} <- |
||||
ApiKey.update(%{value: api_key_value, name: api_key_name, identity_id: identity.id}) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:api_key, %{api_key: api_key}) |
||||
end |
||||
end |
||||
|
||||
def custom_abis(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
custom_abis <- CustomABI.get_custom_abis_by_identity_id(identity.id) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:custom_abis, %{custom_abis: custom_abis}) |
||||
end |
||||
end |
||||
|
||||
def delete_custom_abi(conn, %{"id" => id}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{count, _} <- CustomABI.delete(id, identity.id), |
||||
{:custom_abi_delete, true} <- {:custom_abi_delete, count > 0} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:message, %{message: @ok_message}) |
||||
end |
||||
end |
||||
|
||||
def create_custom_abi(conn, %{"contract_address_hash" => contract_address_hash, "name" => name, "abi" => abi}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, custom_abi} <- |
||||
CustomABI.create(%{ |
||||
name: name, |
||||
address_hash: contract_address_hash, |
||||
abi: abi, |
||||
identity_id: identity.id |
||||
}) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:custom_abi, %{custom_abi: custom_abi}) |
||||
end |
||||
end |
||||
|
||||
def update_custom_abi( |
||||
conn, |
||||
%{ |
||||
"id" => id |
||||
} = params |
||||
) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, custom_abi} <- |
||||
CustomABI.update( |
||||
reject_nil_map_values(%{ |
||||
id: id, |
||||
name: params["name"], |
||||
address_hash: params["contract_address_hash"], |
||||
abi: params["abi"], |
||||
identity_id: identity.id |
||||
}) |
||||
) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:custom_abi, %{custom_abi: custom_abi}) |
||||
end |
||||
end |
||||
|
||||
def public_tags_requests(conn, _params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
public_tags_requests <- PublicTagsRequest.get_public_tags_requests_by_identity_id(identity.id) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:public_tags_requests, %{public_tags_requests: public_tags_requests}) |
||||
end |
||||
end |
||||
|
||||
def delete_public_tags_request(conn, %{"id" => id, "remove_reason" => remove_reason}) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:public_tag_delete, true} <- |
||||
{:public_tag_delete, |
||||
PublicTagsRequest.mark_as_deleted_public_tags_request(%{ |
||||
id: id, |
||||
identity_id: identity.id, |
||||
remove_reason: remove_reason |
||||
})} do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:message, %{message: @ok_message}) |
||||
end |
||||
end |
||||
|
||||
def create_public_tags_request(conn, params) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, public_tags_request} <- |
||||
PublicTagsRequest.create(%{ |
||||
full_name: params["full_name"], |
||||
email: params["email"], |
||||
tags: params["tags"], |
||||
website: params["website"], |
||||
additional_comment: params["additional_comment"], |
||||
addresses: params["addresses"], |
||||
company: params["company"], |
||||
is_owner: params["is_owner"], |
||||
identity_id: identity.id |
||||
}) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:public_tags_request, %{public_tags_request: public_tags_request}) |
||||
end |
||||
end |
||||
|
||||
def update_public_tags_request( |
||||
conn, |
||||
%{ |
||||
"id" => id |
||||
} = params |
||||
) do |
||||
with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, |
||||
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, |
||||
{:ok, public_tags_request} <- |
||||
PublicTagsRequest.update( |
||||
reject_nil_map_values(%{ |
||||
id: id, |
||||
full_name: params["full_name"], |
||||
email: params["email"], |
||||
tags: params["tags"], |
||||
website: params["website"], |
||||
additional_comment: params["additional_comment"], |
||||
addresses: params["addresses"], |
||||
company: params["company"], |
||||
is_owner: params["is_owner"], |
||||
identity_id: identity.id |
||||
}) |
||||
) do |
||||
conn |
||||
|> put_status(200) |
||||
|> render(:public_tags_request, %{public_tags_request: public_tags_request}) |
||||
end |
||||
end |
||||
|
||||
def get_csrf(conn, _) do |
||||
with {:auth, %{id: _}} <- {:auth, current_user(conn)} do |
||||
conn |
||||
|> put_resp_header("x-bs-account-csrf", CSRFProtection.get_csrf_token()) |
||||
|> put_status(200) |
||||
|> render(:message, %{message: "ok"}) |
||||
end |
||||
end |
||||
|
||||
defp reject_nil_map_values(map) when is_map(map) do |
||||
Map.reject(map, fn {_k, v} -> is_nil(v) end) |
||||
end |
||||
|
||||
defp preload_watchlist_address_fetched_coin_balance(watchlist) do |
||||
watchlist |
||||
|> Repo.account_repo().preload(watchlist_addresses: from(wa in WatchlistAddress, order_by: [desc: wa.id])) |
||||
|> WatchlistAddress.preload_address_fetched_coin_balance() |
||||
end |
||||
end |
@ -0,0 +1,65 @@ |
||||
defmodule BlockScoutWeb.Account.ApiKeyController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Account.Api.Key, as: ApiKey |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
|
||||
def new(conn, _params) do |
||||
authenticate!(conn) |
||||
|
||||
render(conn, "form.html", method: :create, api_key: empty_api_key()) |
||||
end |
||||
|
||||
def create(conn, %{"key" => api_key}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case ApiKey.create(%{name: api_key["name"], identity_id: current_user.id}) do |
||||
{:ok, _} -> |
||||
redirect(conn, to: api_key_path(conn, :index)) |
||||
|
||||
{:error, invalid_api_key} -> |
||||
render(conn, "form.html", method: :create, api_key: invalid_api_key) |
||||
end |
||||
end |
||||
|
||||
def create(conn, _) do |
||||
redirect(conn, to: api_key_path(conn, :index)) |
||||
end |
||||
|
||||
def index(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render(conn, "index.html", api_keys: ApiKey.get_api_keys_by_identity_id(current_user.id)) |
||||
end |
||||
|
||||
def edit(conn, %{"id" => api_key}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case ApiKey.get_api_key_by_value_and_identity_id(api_key, current_user.id) do |
||||
nil -> |
||||
not_found(conn) |
||||
|
||||
%ApiKey{} = api_key -> |
||||
render(conn, "form.html", method: :update, api_key: ApiKey.changeset(api_key)) |
||||
end |
||||
end |
||||
|
||||
def update(conn, %{"id" => api_key, "key" => %{"value" => api_key, "name" => name}}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
ApiKey.update(%{value: api_key, identity_id: current_user.id, name: name}) |
||||
|
||||
redirect(conn, to: api_key_path(conn, :index)) |
||||
end |
||||
|
||||
def delete(conn, %{"id" => api_key}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
ApiKey.delete(api_key, current_user.id) |
||||
|
||||
redirect(conn, to: api_key_path(conn, :index)) |
||||
end |
||||
|
||||
defp empty_api_key, do: ApiKey.changeset() |
||||
end |
@ -0,0 +1,73 @@ |
||||
defmodule BlockScoutWeb.Account.AuthController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias BlockScoutWeb.Models.UserFromAuth |
||||
alias Explorer.Account |
||||
alias Plug.CSRFProtection |
||||
|
||||
plug(Ueberauth) |
||||
|
||||
def request(conn, _) do |
||||
not_found(conn) |
||||
end |
||||
|
||||
def logout(conn, _params) do |
||||
conn |
||||
|> configure_session(drop: true) |
||||
|> redirect(to: root()) |
||||
end |
||||
|
||||
def profile(conn, _params), |
||||
do: conn |> get_session(:current_user) |> do_profile(conn) |
||||
|
||||
defp do_profile(nil, conn), |
||||
do: redirect(conn, to: root()) |
||||
|
||||
defp do_profile(%{} = user, conn), |
||||
do: render(conn, :profile, user: user) |
||||
|
||||
def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do |
||||
conn |
||||
|> put_flash(:error, "Failed to authenticate.") |
||||
|> redirect(to: root()) |
||||
end |
||||
|
||||
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do |
||||
case UserFromAuth.find_or_create(auth) do |
||||
{:ok, user} -> |
||||
CSRFProtection.get_csrf_token() |
||||
|
||||
conn |
||||
|> put_session(:current_user, user) |
||||
|> redirect(to: root()) |
||||
|
||||
{:error, reason} -> |
||||
conn |
||||
|> put_flash(:error, reason) |
||||
|> redirect(to: root()) |
||||
end |
||||
end |
||||
|
||||
def callback(conn, _) do |
||||
not_found(conn) |
||||
end |
||||
|
||||
# for importing in other controllers |
||||
def authenticate!(conn) do |
||||
current_user(conn) || redirect(conn, to: root()) |
||||
end |
||||
|
||||
def current_user(%{private: %{plug_session: %{"current_user" => _}}} = conn) do |
||||
if Account.enabled?() do |
||||
get_session(conn, :current_user) |
||||
else |
||||
nil |
||||
end |
||||
end |
||||
|
||||
def current_user(_), do: nil |
||||
|
||||
defp root do |
||||
System.get_env("NETWORK_PATH") || "/" |
||||
end |
||||
end |
@ -0,0 +1,87 @@ |
||||
defmodule BlockScoutWeb.Account.CustomABIController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Ecto.Changeset |
||||
alias Explorer.Account.CustomABI |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
|
||||
def new(conn, _params) do |
||||
authenticate!(conn) |
||||
|
||||
render(conn, "form.html", method: :create, custom_abi: empty_custom_abi()) |
||||
end |
||||
|
||||
def create(conn, %{"custom_abi" => custom_abi}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case CustomABI.create(%{ |
||||
name: custom_abi["name"], |
||||
address_hash: custom_abi["address_hash"], |
||||
abi: custom_abi["abi"], |
||||
identity_id: current_user.id |
||||
}) do |
||||
{:ok, _} -> |
||||
redirect(conn, to: custom_abi_path(conn, :index)) |
||||
|
||||
{:error, invalid_custom_abi} -> |
||||
render(conn, "form.html", method: :create, custom_abi: invalid_custom_abi) |
||||
end |
||||
end |
||||
|
||||
def create(conn, _) do |
||||
redirect(conn, to: custom_abi_path(conn, :index)) |
||||
end |
||||
|
||||
def index(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render(conn, "index.html", custom_abis: CustomABI.get_custom_abis_by_identity_id(current_user.id)) |
||||
end |
||||
|
||||
def edit(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case CustomABI.get_custom_abi_by_id_and_identity_id(id, current_user.id) do |
||||
nil -> |
||||
not_found(conn) |
||||
|
||||
%CustomABI{} = custom_abi -> |
||||
render(conn, "form.html", method: :update, custom_abi: CustomABI.changeset_without_constraints(custom_abi)) |
||||
end |
||||
end |
||||
|
||||
def update(conn, %{"id" => id, "custom_abi" => %{"abi" => abi, "name" => name, "address_hash" => address_hash}}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case CustomABI.update(%{ |
||||
id: id, |
||||
name: name, |
||||
address_hash: address_hash, |
||||
abi: abi, |
||||
identity_id: current_user.id |
||||
}) do |
||||
{:error, %Changeset{} = custom_abi} -> |
||||
render(conn, "form.html", method: :update, custom_abi: custom_abi) |
||||
|
||||
_ -> |
||||
redirect(conn, to: custom_abi_path(conn, :index)) |
||||
end |
||||
end |
||||
|
||||
def update(conn, _) do |
||||
authenticate!(conn) |
||||
|
||||
redirect(conn, to: custom_abi_path(conn, :index)) |
||||
end |
||||
|
||||
def delete(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
CustomABI.delete(id, current_user.id) |
||||
|
||||
redirect(conn, to: custom_abi_path(conn, :index)) |
||||
end |
||||
|
||||
defp empty_custom_abi, do: CustomABI.changeset_without_constraints() |
||||
end |
@ -0,0 +1,114 @@ |
||||
defmodule BlockScoutWeb.Account.PublicTagsRequestController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Ecto.Changeset |
||||
alias Explorer.Account.PublicTagsRequest |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
|
||||
def index(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render(conn, "index.html", |
||||
public_tags_requests: PublicTagsRequest.get_public_tags_requests_by_identity_id(current_user.id) |
||||
) |
||||
end |
||||
|
||||
def new(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render(conn, "form.html", |
||||
method: :create, |
||||
public_tags_request: |
||||
PublicTagsRequest.changeset_without_constraints(%PublicTagsRequest{}, %{ |
||||
full_name: current_user.name, |
||||
email: current_user.email |
||||
}) |
||||
) |
||||
end |
||||
|
||||
def create(conn, %{"public_tags_request" => public_tags_request}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case PublicTagsRequest.create(%{ |
||||
full_name: public_tags_request["full_name"], |
||||
email: public_tags_request["email"], |
||||
tags: public_tags_request["tags"], |
||||
website: public_tags_request["website"], |
||||
additional_comment: public_tags_request["additional_comment"], |
||||
addresses: public_tags_request["addresses"], |
||||
company: public_tags_request["company"], |
||||
is_owner: public_tags_request["is_owner"], |
||||
identity_id: current_user.id |
||||
}) do |
||||
{:ok, _} -> |
||||
redirect(conn, to: public_tags_request_path(conn, :index)) |
||||
|
||||
{:error, invalid_public_tags_request} -> |
||||
render(conn, "form.html", method: :create, public_tags_request: invalid_public_tags_request) |
||||
end |
||||
end |
||||
|
||||
def create(conn, _) do |
||||
redirect(conn, to: public_tags_request_path(conn, :index)) |
||||
end |
||||
|
||||
def edit(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case PublicTagsRequest.get_public_tags_request_by_id_and_identity_id(id, current_user.id) do |
||||
nil -> |
||||
not_found(conn) |
||||
|
||||
%PublicTagsRequest{} = public_tags_request -> |
||||
render(conn, "form.html", |
||||
method: :update, |
||||
public_tags_request: PublicTagsRequest.changeset_without_constraints(public_tags_request) |
||||
) |
||||
end |
||||
end |
||||
|
||||
def update(conn, %{ |
||||
"id" => id, |
||||
"public_tags_request" => public_tags_request |
||||
}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case PublicTagsRequest.update(%{ |
||||
id: id, |
||||
full_name: public_tags_request["full_name"], |
||||
email: public_tags_request["email"], |
||||
tags: public_tags_request["tags"], |
||||
website: public_tags_request["website"], |
||||
additional_comment: public_tags_request["additional_comment"], |
||||
addresses: public_tags_request["addresses"], |
||||
company: public_tags_request["company"], |
||||
is_owner: public_tags_request["is_owner"], |
||||
identity_id: current_user.id |
||||
}) do |
||||
{:error, %Changeset{} = public_tags_request} -> |
||||
render(conn, "form.html", method: :update, public_tags_request: public_tags_request) |
||||
|
||||
_ -> |
||||
redirect(conn, to: public_tags_request_path(conn, :index)) |
||||
end |
||||
end |
||||
|
||||
def update(conn, _) do |
||||
authenticate!(conn) |
||||
|
||||
redirect(conn, to: public_tags_request_path(conn, :index)) |
||||
end |
||||
|
||||
def delete(conn, %{"id" => id, "remove_reason" => remove_reason}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
PublicTagsRequest.mark_as_deleted_public_tags_request(%{ |
||||
id: id, |
||||
identity_id: current_user.id, |
||||
remove_reason: remove_reason |
||||
}) |
||||
|
||||
redirect(conn, to: public_tags_request_path(conn, :index)) |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
defmodule BlockScoutWeb.Account.TagAddressController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Account.TagAddress |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
|
||||
def index(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render(conn, "index.html", address_tags: TagAddress.get_tags_address_by_identity_id(current_user.id)) |
||||
end |
||||
|
||||
def new(conn, _params) do |
||||
authenticate!(conn) |
||||
|
||||
render(conn, "form.html", tag_address: new_tag()) |
||||
end |
||||
|
||||
def create(conn, %{"tag_address" => tag_address}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case TagAddress.create(%{ |
||||
name: tag_address["name"], |
||||
address_hash: tag_address["address_hash"], |
||||
identity_id: current_user.id |
||||
}) do |
||||
{:ok, _} -> |
||||
redirect(conn, to: tag_address_path(conn, :index)) |
||||
|
||||
{:error, invalid_tag_address} -> |
||||
render(conn, "form.html", tag_address: invalid_tag_address) |
||||
end |
||||
end |
||||
|
||||
def create(conn, _) do |
||||
redirect(conn, to: tag_address_path(conn, :index)) |
||||
end |
||||
|
||||
def delete(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
TagAddress.delete(id, current_user.id) |
||||
|
||||
redirect(conn, to: tag_address_path(conn, :index)) |
||||
end |
||||
|
||||
defp new_tag, do: TagAddress.changeset() |
||||
end |
@ -0,0 +1,49 @@ |
||||
defmodule BlockScoutWeb.Account.TagTransactionController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Account.TagTransaction |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
|
||||
def index(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render(conn, "index.html", tx_tags: TagTransaction.get_tags_transaction_by_identity_id(current_user.id)) |
||||
end |
||||
|
||||
def new(conn, _params) do |
||||
authenticate!(conn) |
||||
|
||||
render(conn, "form.html", tag_transaction: new_tag()) |
||||
end |
||||
|
||||
def create(conn, %{"tag_transaction" => tag_address}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case TagTransaction.create(%{ |
||||
name: tag_address["name"], |
||||
tx_hash: tag_address["tx_hash"], |
||||
identity_id: current_user.id |
||||
}) do |
||||
{:ok, _} -> |
||||
redirect(conn, to: tag_transaction_path(conn, :index)) |
||||
|
||||
{:error, invalid_tag_transaction} -> |
||||
render(conn, "form.html", tag_transaction: invalid_tag_transaction) |
||||
end |
||||
end |
||||
|
||||
def create(conn, _) do |
||||
redirect(conn, to: tag_transaction_path(conn, :index)) |
||||
end |
||||
|
||||
def delete(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
TagTransaction.delete(id, current_user.id) |
||||
|
||||
redirect(conn, to: tag_transaction_path(conn, :index)) |
||||
end |
||||
|
||||
defp new_tag, do: TagTransaction.changeset() |
||||
end |
@ -0,0 +1,92 @@ |
||||
defmodule BlockScoutWeb.Account.WatchlistAddressController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Account.WatchlistAddress |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
|
||||
def new(conn, _params) do |
||||
authenticate!(conn) |
||||
|
||||
render(conn, "form.html", method: :create, watchlist_address: empty_watchlist_address()) |
||||
end |
||||
|
||||
def create(conn, %{"watchlist_address" => wa_params}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case WatchlistAddress.create(params_to_attributes(wa_params, current_user.watchlist_id)) do |
||||
{:ok, _watchlist_address} -> |
||||
redirect(conn, to: watchlist_path(conn, :show)) |
||||
|
||||
{:error, changeset} -> |
||||
render(conn, "form.html", method: :create, watchlist_address: changeset) |
||||
end |
||||
end |
||||
|
||||
def edit(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case WatchlistAddress.get_watchlist_address_by_id_and_watchlist_id(id, current_user.watchlist_id) do |
||||
nil -> |
||||
not_found(conn) |
||||
|
||||
%WatchlistAddress{} = watchlist_address -> |
||||
render(conn, "form.html", method: :update, watchlist_address: WatchlistAddress.changeset(watchlist_address)) |
||||
end |
||||
end |
||||
|
||||
def update(conn, %{"id" => id, "watchlist_address" => wa_params}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
case wa_params |
||||
|> params_to_attributes(current_user.watchlist_id) |
||||
|> Map.put(:id, id) |
||||
|> WatchlistAddress.update() do |
||||
{:ok, _watchlist_address} -> |
||||
redirect(conn, to: watchlist_path(conn, :show)) |
||||
|
||||
{:error, changeset} -> |
||||
render(conn, "form.html", method: :update, watchlist_address: changeset) |
||||
end |
||||
end |
||||
|
||||
def delete(conn, %{"id" => id}) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
WatchlistAddress.delete(id, current_user.watchlist_id) |
||||
|
||||
redirect(conn, to: watchlist_path(conn, :show)) |
||||
end |
||||
|
||||
defp empty_watchlist_address, do: WatchlistAddress.changeset() |
||||
|
||||
defp params_to_attributes( |
||||
%{ |
||||
"address_hash" => address_hash, |
||||
"name" => name, |
||||
"watch_coin_input" => watch_coin_input, |
||||
"watch_coin_output" => watch_coin_output, |
||||
"watch_erc_20_input" => watch_erc_20_input, |
||||
"watch_erc_20_output" => watch_erc_20_output, |
||||
"watch_erc_721_input" => watch_nft_input, |
||||
"watch_erc_721_output" => watch_nft_output, |
||||
"notify_email" => notify_email |
||||
}, |
||||
watchlist_id |
||||
) do |
||||
%{ |
||||
address_hash: address_hash, |
||||
name: name, |
||||
watch_coin_input: watch_coin_input, |
||||
watch_coin_output: watch_coin_output, |
||||
watch_erc_20_input: watch_erc_20_input, |
||||
watch_erc_20_output: watch_erc_20_output, |
||||
watch_erc_721_input: watch_nft_input, |
||||
watch_erc_721_output: watch_nft_output, |
||||
watch_erc_1155_input: watch_nft_input, |
||||
watch_erc_1155_output: watch_nft_output, |
||||
notify_email: notify_email, |
||||
watchlist_id: watchlist_id |
||||
} |
||||
end |
||||
end |
@ -0,0 +1,26 @@ |
||||
defmodule BlockScoutWeb.Account.WatchlistController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] |
||||
import Ecto.Query, only: [from: 2] |
||||
|
||||
alias Explorer.Account.{Watchlist, WatchlistAddress} |
||||
alias Explorer.Repo |
||||
|
||||
def show(conn, _params) do |
||||
current_user = authenticate!(conn) |
||||
|
||||
render( |
||||
conn, |
||||
"show.html", |
||||
watchlist: watchlist_with_addresses(current_user) |
||||
) |
||||
end |
||||
|
||||
defp watchlist_with_addresses(user) do |
||||
Watchlist |
||||
|> Repo.account_repo().get(user.watchlist_id) |
||||
|> Repo.account_repo().preload(watchlist_addresses: from(wa in WatchlistAddress, order_by: [desc: wa.id])) |
||||
|> WatchlistAddress.preload_address_fetched_coin_balance() |
||||
end |
||||
end |
@ -0,0 +1,75 @@ |
||||
defmodule BlockScoutWeb.Models.GetAddressTags do |
||||
@moduledoc """ |
||||
Get various types of tags associated with the address |
||||
""" |
||||
|
||||
import Ecto.Query, only: [from: 2] |
||||
|
||||
alias Explorer.Account.{TagAddress, WatchlistAddress} |
||||
alias Explorer.Chain.Hash |
||||
alias Explorer.Repo |
||||
alias Explorer.Tags.{AddressTag, AddressToTag} |
||||
|
||||
def get_address_tags(nil, nil), |
||||
do: %{personal_tags: [], watchlist_names: []} |
||||
|
||||
def get_address_tags(%Hash{} = address_hash, current_user) do |
||||
%{ |
||||
# common_tags: get_tags_on_address(address_hash), |
||||
personal_tags: get_personal_tags(address_hash, current_user), |
||||
watchlist_names: get_watchlist_names_on_address(address_hash, current_user) |
||||
} |
||||
end |
||||
|
||||
def get_address_tags(_, _), do: %{personal_tags: [], watchlist_names: []} |
||||
|
||||
def get_public_tags(%Hash{} = address_hash) do |
||||
%{ |
||||
common_tags: get_tags_on_address(address_hash) |
||||
} |
||||
end |
||||
|
||||
def get_tags_on_address(%Hash{} = address_hash) do |
||||
query = |
||||
from( |
||||
tt in AddressTag, |
||||
left_join: att in AddressToTag, |
||||
on: tt.id == att.tag_id, |
||||
where: att.address_hash == ^address_hash, |
||||
where: tt.label != ^"validator", |
||||
select: %{label: tt.label, display_name: tt.display_name, address_hash: att.address_hash} |
||||
) |
||||
|
||||
Repo.all(query) |
||||
end |
||||
|
||||
def get_tags_on_address(_), do: [] |
||||
|
||||
def get_personal_tags(%Hash{} = address_hash, %{id: id}) do |
||||
query = |
||||
from( |
||||
ta in TagAddress, |
||||
where: ta.address_hash_hash == ^address_hash, |
||||
where: ta.identity_id == ^id, |
||||
select: %{label: ta.name, display_name: ta.name, address_hash: ta.address_hash} |
||||
) |
||||
|
||||
Repo.account_repo().all(query) |
||||
end |
||||
|
||||
def get_personal_tags(_, _), do: [] |
||||
|
||||
def get_watchlist_names_on_address(%Hash{} = address_hash, %{watchlist_id: watchlist_id}) do |
||||
query = |
||||
from( |
||||
wa in WatchlistAddress, |
||||
where: wa.address_hash_hash == ^address_hash, |
||||
where: wa.watchlist_id == ^watchlist_id, |
||||
select: %{label: wa.name, display_name: wa.name} |
||||
) |
||||
|
||||
Repo.account_repo().all(query) |
||||
end |
||||
|
||||
def get_watchlist_names_on_address(_, _), do: [] |
||||
end |
@ -0,0 +1,41 @@ |
||||
defmodule BlockScoutWeb.Models.GetTransactionTags do |
||||
@moduledoc """ |
||||
Get various types of tags associated with the transaction |
||||
""" |
||||
|
||||
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] |
||||
|
||||
alias Explorer.Account.TagTransaction |
||||
alias Explorer.Chain.Transaction |
||||
alias Explorer.Repo |
||||
|
||||
def get_transaction_with_addresses_tags( |
||||
%Transaction{} = transaction, |
||||
%{id: identity_id, watchlist_id: watchlist_id} |
||||
) do |
||||
tx_tag = get_transaction_tags(transaction.hash, %{id: identity_id}) |
||||
addresses_tags = get_addresses_tags_for_transaction(transaction, %{id: identity_id, watchlist_id: watchlist_id}) |
||||
Map.put(addresses_tags, :personal_tx_tag, tx_tag) |
||||
end |
||||
|
||||
def get_transaction_with_addresses_tags(_, _), do: %{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} |
||||
|
||||
def get_transaction_tags(transaction_hash, %{id: identity_id}) do |
||||
Repo.account_repo().get_by(TagTransaction, tx_hash_hash: transaction_hash, identity_id: identity_id) |
||||
end |
||||
|
||||
def get_transaction_tags(_, _), do: nil |
||||
|
||||
def get_addresses_tags_for_transaction( |
||||
%Transaction{} = transaction, |
||||
%{id: identity_id, watchlist_id: watchlist_id} |
||||
) do |
||||
from_tags = get_address_tags(transaction.from_address_hash, %{id: identity_id, watchlist_id: watchlist_id}) |
||||
to_tags = get_address_tags(transaction.to_address_hash, %{id: identity_id, watchlist_id: watchlist_id}) |
||||
|
||||
%{ |
||||
personal_tags: Enum.dedup(from_tags.personal_tags ++ to_tags.personal_tags), |
||||
watchlist_names: Enum.dedup(from_tags.watchlist_names ++ to_tags.watchlist_names) |
||||
} |
||||
end |
||||
end |
@ -0,0 +1,136 @@ |
||||
defmodule BlockScoutWeb.Models.UserFromAuth do |
||||
@moduledoc """ |
||||
Retrieve the user information from an auth request |
||||
""" |
||||
require Logger |
||||
require Poison |
||||
|
||||
alias Explorer.Account.Identity |
||||
alias Explorer.Repo |
||||
alias Ueberauth.Auth |
||||
|
||||
import Ecto.Query, only: [from: 2] |
||||
|
||||
def find_or_create(%Auth{} = auth, api_call? \\ false) do |
||||
case find_identity(auth) do |
||||
[] -> |
||||
case create_identity(auth) do |
||||
%Identity{} = identity -> |
||||
{:ok, return_value(identity, auth, api_call?)} |
||||
|
||||
{:error, changeset} -> |
||||
{:error, changeset} |
||||
end |
||||
|
||||
[%{} = identity | _] -> |
||||
update_identity(identity, update_identity_map(auth)) |
||||
{:ok, return_value(identity, auth, api_call?)} |
||||
end |
||||
end |
||||
|
||||
defp return_value(identity, _auth, true) do |
||||
identity |
||||
end |
||||
|
||||
defp return_value(identity, auth, false) do |
||||
basic_info(auth, identity) |
||||
end |
||||
|
||||
defp create_identity(auth) do |
||||
with {:ok, %Identity{} = identity} <- Repo.account_repo().insert(new_identity(auth)), |
||||
{:ok, _watchlist} <- add_watchlist(identity) do |
||||
identity |
||||
end |
||||
end |
||||
|
||||
defp update_identity(identity, attrs) do |
||||
identity |
||||
|> Identity.changeset(attrs) |
||||
|> Repo.account_repo().update() |
||||
end |
||||
|
||||
defp new_identity(auth) do |
||||
%Identity{ |
||||
uid: auth.uid, |
||||
uid_hash: auth.uid, |
||||
email: email_from_auth(auth), |
||||
name: name_from_auth(auth), |
||||
nickname: nickname_from_auth(auth), |
||||
avatar: avatar_from_auth(auth) |
||||
} |
||||
end |
||||
|
||||
defp add_watchlist(identity) do |
||||
watchlist = Ecto.build_assoc(identity, :watchlists, %{}) |
||||
|
||||
with {:ok, _} <- Repo.account_repo().insert(watchlist), |
||||
do: {:ok, identity} |
||||
end |
||||
|
||||
def find_identity(auth_or_uid) do |
||||
Repo.account_repo().all(query_identity(auth_or_uid)) |
||||
end |
||||
|
||||
def query_identity(%Auth{} = auth) do |
||||
from(i in Identity, where: i.uid_hash == ^auth.uid) |
||||
end |
||||
|
||||
def query_identity(id) do |
||||
from(i in Identity, where: i.id == ^id) |
||||
end |
||||
|
||||
defp basic_info(auth, identity) do |
||||
%{watchlists: [watchlist | _]} = Repo.account_repo().preload(identity, :watchlists) |
||||
|
||||
%{ |
||||
id: identity.id, |
||||
uid: auth.uid, |
||||
email: email_from_auth(auth), |
||||
name: name_from_auth(auth), |
||||
nickname: nickname_from_auth(auth), |
||||
avatar: avatar_from_auth(auth), |
||||
watchlist_id: watchlist.id |
||||
} |
||||
end |
||||
|
||||
defp update_identity_map(auth) do |
||||
%{ |
||||
email: email_from_auth(auth), |
||||
name: name_from_auth(auth), |
||||
nickname: nickname_from_auth(auth), |
||||
avatar: avatar_from_auth(auth) |
||||
} |
||||
end |
||||
|
||||
# github does it this way |
||||
defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image |
||||
|
||||
# facebook does it this way |
||||
defp avatar_from_auth(%{info: %{image: image}}), do: image |
||||
|
||||
# default case if nothing matches |
||||
defp avatar_from_auth(auth) do |
||||
Logger.warn(auth.provider <> " needs to find an avatar URL!") |
||||
Logger.debug(Poison.encode!(auth)) |
||||
nil |
||||
end |
||||
|
||||
defp email_from_auth(%{info: %{email: email}}), do: email |
||||
|
||||
defp nickname_from_auth(%{info: %{nickname: nickname}}), do: nickname |
||||
|
||||
defp name_from_auth(%{info: %{name: name}}) |
||||
when name != "" and not is_nil(name), |
||||
do: name |
||||
|
||||
defp name_from_auth(%{info: info}) do |
||||
[info.first_name, info.last_name, info.nickname] |
||||
|> Enum.map(&(&1 |> to_string() |> String.trim())) |
||||
|> case do |
||||
["", "", nick] -> nick |
||||
["", lastname, _] -> lastname |
||||
[name, "", _] -> name |
||||
[name, lastname, _] -> name <> " " <> lastname |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,21 @@ |
||||
defmodule BlockScoutWeb.Plug.CheckAccountAPI do |
||||
@moduledoc """ |
||||
Checks if the Account functionality enabled for API level. |
||||
""" |
||||
import Plug.Conn |
||||
|
||||
alias Explorer.Account |
||||
|
||||
def init(opts), do: opts |
||||
|
||||
def call(conn, _opts) do |
||||
if Account.enabled?() do |
||||
conn |
||||
else |
||||
conn |
||||
|> put_resp_content_type("application/json") |
||||
|> send_resp(404, Jason.encode!(%{message: "Account functionality is disabled"})) |
||||
|> halt() |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,31 @@ |
||||
defmodule BlockScoutWeb.Plug.CheckAccountWeb do |
||||
@moduledoc """ |
||||
Checks if the Account functionality enabled for web interface. |
||||
""" |
||||
import Phoenix.Controller |
||||
alias Phoenix.View |
||||
import Plug.Conn |
||||
|
||||
alias Explorer.Account |
||||
|
||||
def init(opts), do: opts |
||||
|
||||
def call(conn, _opts) do |
||||
if Account.enabled?() do |
||||
conn |
||||
else |
||||
inner_view = |
||||
View.render( |
||||
BlockScoutWeb.PageNotFoundView, |
||||
"index.html", |
||||
token: nil |
||||
) |
||||
|
||||
conn |
||||
|> put_status(404) |
||||
|> put_view(BlockScoutWeb.LayoutView) |
||||
|> render(:app, inner_content: inner_view) |
||||
|> halt() |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,223 @@ |
||||
defmodule BlockScoutWeb.Plug.RedisCookie do |
||||
@moduledoc """ |
||||
Extended version of Plug.Session.COOKIE from https://github.com/elixir-plug/plug/blob/main/lib/plug/session/cookie.ex |
||||
Added Redis to have a possibility to invalidate session |
||||
""" |
||||
|
||||
require Logger |
||||
@behaviour Plug.Session.Store |
||||
|
||||
alias Plug.Crypto |
||||
alias Plug.Crypto.{KeyGenerator, MessageEncryptor, MessageVerifier} |
||||
|
||||
@impl true |
||||
def init(opts) do |
||||
opts |
||||
|> build_opts() |
||||
|> build_rotating_opts(opts[:rotating_options]) |
||||
|> Map.delete(:secret_key_base) |
||||
end |
||||
|
||||
@impl true |
||||
def get(conn, raw_cookie, opts) do |
||||
opts = Map.put(opts, :secret_key_base, conn.secret_key_base) |
||||
|
||||
[opts | opts.rotating_options] |
||||
|> Enum.find_value(:error, &read_raw_cookie(raw_cookie, &1)) |
||||
|> decode(opts.serializer, opts.log) |
||||
|> check_in_redis(raw_cookie) |
||||
end |
||||
|
||||
@impl true |
||||
def put(conn, _sid, term, opts) do |
||||
%{serializer: serializer, key_opts: key_opts, signing_salt: signing_salt} = opts |
||||
binary = encode(term, serializer) |
||||
|
||||
opts |
||||
|> case do |
||||
%{encryption_salt: nil} -> |
||||
MessageVerifier.sign(binary, derive(conn.secret_key_base, signing_salt, key_opts)) |
||||
|
||||
%{encryption_salt: encryption_salt} -> |
||||
MessageEncryptor.encrypt( |
||||
binary, |
||||
derive(conn.secret_key_base, encryption_salt, key_opts), |
||||
derive(conn.secret_key_base, signing_salt, key_opts) |
||||
) |
||||
end |
||||
|> store_to_redis() |
||||
end |
||||
|
||||
@impl true |
||||
def delete(_conn, sid, _opts) do |
||||
remove_from_redis(sid) |
||||
:ok |
||||
end |
||||
|
||||
defp encode(term, :external_term_format) do |
||||
:erlang.term_to_binary(term) |
||||
end |
||||
|
||||
defp encode(term, serializer) do |
||||
{:ok, binary} = serializer.encode(term) |
||||
binary |
||||
end |
||||
|
||||
defp decode({:ok, binary}, :external_term_format, log) do |
||||
{:term, |
||||
try do |
||||
Crypto.non_executable_binary_to_term(binary) |
||||
rescue |
||||
e -> |
||||
Logger.log( |
||||
log, |
||||
"Plug.Session could not decode incoming session cookie. Reason: " <> |
||||
Exception.message(e) |
||||
) |
||||
|
||||
%{} |
||||
end} |
||||
end |
||||
|
||||
defp decode({:ok, binary}, serializer, _log) do |
||||
case serializer.decode(binary) do |
||||
{:ok, term} -> {:custom, term} |
||||
_ -> {:custom, %{}} |
||||
end |
||||
end |
||||
|
||||
defp decode(:error, _serializer, false) do |
||||
{nil, %{}} |
||||
end |
||||
|
||||
defp decode(:error, _serializer, log) do |
||||
Logger.log( |
||||
log, |
||||
"Plug.Session could not verify incoming session cookie. " <> |
||||
"This may happen when the session settings change or a stale cookie is sent." |
||||
) |
||||
|
||||
{nil, %{}} |
||||
end |
||||
|
||||
defp prederive(secret_key_base, value, key_opts) |
||||
when is_binary(secret_key_base) and is_binary(value) do |
||||
{:prederived, derive(secret_key_base, value, Keyword.delete(key_opts, :cache))} |
||||
end |
||||
|
||||
defp prederive(_secret_key_base, value, _key_opts) do |
||||
value |
||||
end |
||||
|
||||
defp derive(_secret_key_base, {:prederived, value}, _key_opts) do |
||||
value |
||||
end |
||||
|
||||
defp derive(secret_key_base, {module, function, args}, key_opts) do |
||||
derive(secret_key_base, apply(module, function, args), key_opts) |
||||
end |
||||
|
||||
defp derive(secret_key_base, key, key_opts) do |
||||
secret_key_base |
||||
|> validate_secret_key_base() |
||||
|> KeyGenerator.generate(key, key_opts) |
||||
end |
||||
|
||||
defp validate_secret_key_base(nil), |
||||
do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be set") |
||||
|
||||
defp validate_secret_key_base(secret_key_base) when byte_size(secret_key_base) < 64, |
||||
do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be at least 64 bytes") |
||||
|
||||
defp validate_secret_key_base(secret_key_base), do: secret_key_base |
||||
|
||||
defp check_signing_salt(opts) do |
||||
case opts[:signing_salt] do |
||||
nil -> raise ArgumentError, "cookie store expects :signing_salt as option" |
||||
salt -> salt |
||||
end |
||||
end |
||||
|
||||
defp check_serializer(serializer) when is_atom(serializer), do: serializer |
||||
|
||||
defp check_serializer(_), |
||||
do: raise(ArgumentError, "cookie store expects :serializer option to be a module") |
||||
|
||||
defp read_raw_cookie(raw_cookie, opts) do |
||||
signing_salt = derive(opts.secret_key_base, opts.signing_salt, opts.key_opts) |
||||
|
||||
opts |
||||
|> case do |
||||
%{encryption_salt: nil} -> |
||||
MessageVerifier.verify(raw_cookie, signing_salt) |
||||
|
||||
%{encryption_salt: _} -> |
||||
encryption_salt = derive(opts.secret_key_base, opts.encryption_salt, opts.key_opts) |
||||
|
||||
MessageEncryptor.decrypt(raw_cookie, encryption_salt, signing_salt) |
||||
end |
||||
|> case do |
||||
:error -> nil |
||||
result -> result |
||||
end |
||||
end |
||||
|
||||
defp build_opts(opts) do |
||||
encryption_salt = opts[:encryption_salt] |
||||
signing_salt = check_signing_salt(opts) |
||||
|
||||
iterations = Keyword.get(opts, :key_iterations, 1000) |
||||
length = Keyword.get(opts, :key_length, 32) |
||||
digest = Keyword.get(opts, :key_digest, :sha256) |
||||
log = Keyword.get(opts, :log, :debug) |
||||
secret_key_base = Keyword.get(opts, :secret_key_base) |
||||
key_opts = [iterations: iterations, length: length, digest: digest, cache: Plug.Keys] |
||||
|
||||
serializer = check_serializer(opts[:serializer] || :external_term_format) |
||||
|
||||
%{ |
||||
secret_key_base: secret_key_base, |
||||
encryption_salt: prederive(secret_key_base, encryption_salt, key_opts), |
||||
signing_salt: prederive(secret_key_base, signing_salt, key_opts), |
||||
key_opts: key_opts, |
||||
serializer: serializer, |
||||
log: log |
||||
} |
||||
end |
||||
|
||||
defp build_rotating_opts(opts, rotating_opts) when is_list(rotating_opts) do |
||||
Map.put(opts, :rotating_options, Enum.map(rotating_opts, &build_opts/1)) |
||||
end |
||||
|
||||
defp build_rotating_opts(opts, _), do: Map.put(opts, :rotating_options, []) |
||||
|
||||
defp store_to_redis(cookie) do |
||||
Redix.command(:redix, ["SET", hash(cookie), 1]) |
||||
|
||||
cookie |
||||
end |
||||
|
||||
defp remove_from_redis(sid) do |
||||
Redix.command(:redix, ["DEL", sid]) |
||||
end |
||||
|
||||
defp check_in_redis({sid, map}, _cookie) when is_nil(sid) or map == %{}, do: {nil, %{}} |
||||
|
||||
defp check_in_redis({_sid, session}, cookie) do |
||||
hash = hash(cookie) |
||||
|
||||
case Redix.command(:redix, ["GET", hash]) do |
||||
{:ok, one} when one in [1, "1"] -> |
||||
{hash, session} |
||||
|
||||
_ -> |
||||
{nil, %{}} |
||||
end |
||||
end |
||||
|
||||
defp hash(cookie) do |
||||
:sha256 |
||||
|> :crypto.hash(cookie) |
||||
|> Base.encode16() |
||||
end |
||||
end |
@ -0,0 +1,34 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :api_keys %> |
||||
<div class="col-sm-10"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Update"), else: gettext("Add") %> <%= gettext "API key"%></h1> |
||||
<div class="col-sm-10 card-body-account"> |
||||
<% path = if @method == :update, do: api_key_path(@conn, @method, @api_key.data.value), else: api_key_path(@conn, @method) %> |
||||
<%= form_for @api_key, path, fn f -> %> |
||||
<%= if f.data.value do %> |
||||
<div class="form-group"> |
||||
<%= label f, :value, gettext("API key"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :value, class: "form-control", placeholder: gettext("API key"), readonly: true %> |
||||
<%= error_tag f, :value, class: "text-danger form-error" %> |
||||
</div> |
||||
<% end %> |
||||
<div class="form-group"> |
||||
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this API key"), maxlength: 255 %> |
||||
<%= error_tag f, :name, class: "text-danger form-error" %> |
||||
</div> |
||||
<br> |
||||
<div class="form-group float-right form-input"> |
||||
<a class="btn btn-line" href="<%= api_key_path(@conn, :index) %>"><%= gettext "Back to API keys (Cancel)"%></a> |
||||
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,51 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :api_keys %> |
||||
<div class="col-md-10"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "API keys" %> </h1> |
||||
<br> |
||||
<%= if Enum.count(@api_keys) < Key.get_max_api_keys_count() do %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "Create an API key to use with your RPC и EthRPC API requests." %> <a href="https://docs.blockscout.com/for-users/api"> <%= gettext "Learn more" %></a> |
||||
</div> |
||||
</div> |
||||
<% else %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "You can create 3 API keys per account." %> <a href="https://docs.blockscout.com/for-users/api"> <%= gettext "Learn more" %></a> |
||||
</div> |
||||
</div> |
||||
<% end %> |
||||
<div class="col-sm"> |
||||
<div class="mb-3 row o-flow-x"> |
||||
<%= if @api_keys != [] do %> |
||||
<table class="table mb-3 table-watchlist"> |
||||
<thead style="font-size: 14px; color: #6c757d" > |
||||
<tr> |
||||
<th scope="col"><%= gettext "Name" %></th> |
||||
<th scope="col"><%= gettext "API key" %></th> |
||||
<th scope="col"></th> |
||||
<th scope="col"></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody style="font-size: 15px; color: #6c757d" > |
||||
<%= Enum.map(@api_keys, fn key -> |
||||
render("row.html", api_key: key, conn: @conn) |
||||
end) %> |
||||
</tbody> |
||||
</table> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if Enum.count(@api_keys) < Key.get_max_api_keys_count() do %> |
||||
<a class="button button-primary button-sm" href="<%= api_key_path(@conn, :new) %>"><%= gettext "Add API key" %></a> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/delete-item-handler.js") %>"></script> |
||||
</section> |
@ -0,0 +1,18 @@ |
||||
<tr> |
||||
<td><%= @api_key.name %></td> |
||||
<td> |
||||
<span><%= @api_key.value %></span> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", |
||||
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @api_key.value, aria_label: gettext("Copy API key"), title: gettext("Copy API key"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> |
||||
</td> |
||||
<td> |
||||
<form method="post" action="<%= api_key_path(@conn, :delete, @api_key.value) %>"> |
||||
<input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>"> |
||||
<input type="hidden" name="_method" value="delete"> |
||||
</form> |
||||
<a href="" data-delete-item><%= gettext("Remove") %></a> |
||||
</td> |
||||
<td> |
||||
<%= link gettext("Edit"), to: api_key_path(@conn, :edit, @api_key.value) %> |
||||
</td> |
||||
</tr> |
@ -0,0 +1,36 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :profile %> |
||||
<div class="col-md"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account">Profile</h1> |
||||
<br> |
||||
<div class="col-sm"> |
||||
<img src="<%= @user.avatar %>" alt="<%= @user.nickname %>" size="42" height="42" width="42" > |
||||
</div> |
||||
<br> |
||||
<div class="col-sm"> |
||||
<div class="mb-4 row"> |
||||
<label for="static-name" class="col-sm-6 col-form-label label-account">Name</label> |
||||
<div class="col-sm"> |
||||
<input type="text" readonly class="form-control-plaintext profile-item label" id="static-name" value="<%= @user.name %>"> |
||||
</div> |
||||
</div> |
||||
<div class="mb-4 row"> |
||||
<label for="static-nickname" class="col-sm-6 col-form-label label-account">Nickname</label> |
||||
<div class="col-sm"> |
||||
<input type="text" readonly class="form-control-plaintext profile-item" id="static-nickname" value="<%= @user.nickname %>"> |
||||
</div> |
||||
</div> |
||||
<div class="mb-4 row"> |
||||
<label for="static-email" class="col-sm-6 col-form-label label-account">Email</label> |
||||
<div class="col-sm"> |
||||
<input type="text" readonly class="form-control-plaintext profile-item" id="static-email" value="<%= @user.email %>"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,25 @@ |
||||
<div class="col-2 mb-3 navbar-account"> |
||||
<ul class="nav account flex-column nav-pills"> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :profile) %>" aria-current="page" href="<%= auth_path(@conn, :profile) %>"><%= gettext "Profile" %></a> |
||||
</li> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :watchlist) %>" href="<%= watchlist_path(@conn, :show) %>"><%= gettext "Watch list" %></a> |
||||
</li> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :address_tags) %>" href="<%= tag_address_path(@conn, :index) %>"><%= gettext "Address Tags" %></a> |
||||
</li> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :transaction_tags) %>" href="<%= tag_transaction_path(@conn, :index) %>"><%= gettext "Transaction Tags" %></a> |
||||
</li> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :api_keys) %>" href="<%= api_key_path(@conn, :index) %>"><%= gettext "API keys" %></a> |
||||
</li> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :custom_abis) %>" href="<%= custom_abi_path(@conn, :index) %>"><%= gettext "Custom ABI" %></a> |
||||
</li> |
||||
<li class="nav-item account"> |
||||
<a class="<%= nav_class(@active_item, :public_tags) %>" href="<%= public_tags_request_path(@conn, :index) %>"><%= gettext "Public tags" %></a> |
||||
</li> |
||||
</ul> |
||||
</div> |
@ -0,0 +1,39 @@ |
||||
<% abi = format_abi(@custom_abi) %> |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :custom_abis %> |
||||
<div class="col-sm-10"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Update"), else: gettext("Add") %> <%= gettext "Custom ABI"%></h1> |
||||
<div class="col-sm-10 card-body-account"> |
||||
<% path = if @method == :update, do: custom_abi_path(@conn, @method, @custom_abi.data.id), else: custom_abi_path(@conn, @method) %> |
||||
<%= form_for @custom_abi, path, fn f -> %> |
||||
<div class="form-group"> |
||||
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this Custom ABI"), maxlength: 255 %> |
||||
<%= error_tag f, :name, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-group"> |
||||
<%= label f, :address_hash, gettext("Contract Address"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :address_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000", maxlength: 42 %> |
||||
<%= error_tag f, :address_hash, class: "text-danger form-error" %> |
||||
<%= error_tag f, :identity_id, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-group"> |
||||
<%= label f, :abi, gettext("ABI"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= textarea f, :abi, class: "form-control", placeholder: "[{...}]", value: abi %> |
||||
<%= error_tag f, :abi, class: "text-danger form-error" %> |
||||
</div> |
||||
<br> |
||||
<div class="form-group float-right form-input"> |
||||
<a class="btn btn-line" href="<%= custom_abi_path(@conn, :index) %>"><%= gettext "Back to Custom ABI (Cancel)"%></a> |
||||
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,51 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :custom_abis %> |
||||
<div class="col-md-10"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Custom ABI" %></h1> |
||||
<br> |
||||
<%= if Enum.count(@custom_abis) < CustomABI.get_max_custom_abis_count() do %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "Create a Custom ABI to interact with contracts." %> |
||||
</div> |
||||
</div> |
||||
<% else %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "You can create up to 15 Custom ABIs per account." %> |
||||
</div> |
||||
</div> |
||||
<% end %> |
||||
<div class="col-sm"> |
||||
<div class="mb-3 row o-flow-x"> |
||||
<%= if @custom_abis != [] do %> |
||||
<table class="table mb-3 table-watchlist"> |
||||
<thead style="font-size: 14px; color: #6c757d" > |
||||
<tr> |
||||
<th scope="col"><%= gettext "Name" %></th> |
||||
<th scope="col"><%= gettext "Contract Address" %></th> |
||||
<th scope="col"></th> |
||||
<th scope="col"></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody style="font-size: 15px; color: #6c757d" > |
||||
<%= Enum.map(@custom_abis, fn key -> |
||||
render("row.html", custom_abi: key, conn: @conn) |
||||
end) %> |
||||
</tbody> |
||||
</table> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if Enum.count(@custom_abis) < CustomABI.get_max_custom_abis_count() do %> |
||||
<a class="button button-primary button-sm" href="<%= custom_abi_path(@conn, :new) %>"><%= gettext "Add Custom ABI" %></a> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/delete-item-handler.js") %>"></script> |
||||
</section> |
@ -0,0 +1,18 @@ |
||||
<tr> |
||||
<td><%= @custom_abi.name %></td> |
||||
<td> |
||||
<%= link(@custom_abi.address_hash, to: address_contract_path(@conn, :index, @custom_abi.address_hash)) %> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", |
||||
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @custom_abi.address_hash, aria_label: gettext("Copy Contract Address"), title: gettext("Copy Contract Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> |
||||
</td> |
||||
<td> |
||||
<form method="post" action="<%= custom_abi_path(@conn, :delete, @custom_abi.id) %>"> |
||||
<input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>"> |
||||
<input type="hidden" name="_method" value="delete"> |
||||
</form> |
||||
<a href="" data-delete-item><%= gettext("Remove") %></a> |
||||
</td> |
||||
<td> |
||||
<%= link gettext("Edit"), to: custom_abi_path(@conn, :edit, @custom_abi.id) %> |
||||
</td> |
||||
</tr> |
@ -0,0 +1,8 @@ |
||||
<div class="form-group"> |
||||
<%= label @f, :addresses, gettext("Address*"), class: "control-label", style: "font-size: 14px" %> |
||||
<div> |
||||
<%= array_input @f, :addresses, maxlength: 42, size: 70, placeholder: gettext "Smart contract / Address (0x...)" %> |
||||
<%= array_add_button @f, :addresses, maxlength: 42, size: 70, placeholder: gettext "Smart contract / Address (0x...)" %> |
||||
</div> |
||||
<%= error_tag @f, :addresses, class: "text-danger form-error pt-0" %> |
||||
</div> |
@ -0,0 +1,72 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :public_tags %> |
||||
<div class="col-sm-10"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Request to edit a public tag/label"), else: gettext("Request a public tag/label") %></h1> |
||||
<div class="col-sm-10 card-body-account"> |
||||
<% path = if @method == :update, do: public_tags_request_path(@conn, @method, @public_tags_request.data.id), else: public_tags_request_path(@conn, @method) %> |
||||
<%= form_for @public_tags_request, path, fn f -> %> |
||||
<div> |
||||
<div class="line-input"> |
||||
<div class="form-group"> |
||||
<%= label f, :full_name, gettext("Your name*"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :full_name, class: "form-control", placeholder: gettext("Your name"), maxlength: 255 %> |
||||
<%= error_tag f, :full_name, class: "text-danger form-error" %> |
||||
<%= error_tag f, :identity_id, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-group"> |
||||
<%= label f, :company, gettext("Company name"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :company, class: "form-control", placeholder: "Company name", maxlength: 255 %> |
||||
<%= error_tag f, :company, class: "text-danger form-error" %> |
||||
</div> |
||||
</div> |
||||
<div class="line-input"> |
||||
<div class="form-group"> |
||||
<%= label f, :email, gettext("E-mail*"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= email_input f, :email, class: "form-control", placeholder: "E-mail", maxlength: 42 %> |
||||
<%= error_tag f, :email, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-group"> |
||||
<%= label f, :website, gettext("Company website"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :website, class: "form-control", placeholder: "Company website", maxlength: 255 %> |
||||
<%= error_tag f, :website, class: "text-danger form-error" %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="mb-3"> |
||||
<div> |
||||
<%= radio_button(f, :is_owner, true) %> |
||||
<%= label f, :is_owner_true, "I want to add tags for my project" %> |
||||
</div> |
||||
<div> |
||||
<%= radio_button(f, :is_owner, false) %> |
||||
<%= label f, :is_owner_false, "I want to report an incorrect public tag" %> |
||||
</div> |
||||
<%= error_tag f, :is_owner, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-group mr-4-rem"> |
||||
<%= label f, :tags, gettext("Public tags* (2 tags maximum, please use \";\" as a divider)"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :tags, class: "form-control", placeholder: "Public tags", maxlength: 71 %> |
||||
<%= error_tag f, :tags, class: "text-danger form-error" %> |
||||
</div> |
||||
<%= render "address_field.html", f: f %> |
||||
<div class="form-group mr-4-rem"> |
||||
<%= label f, :additional_comment, gettext("Description*"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= textarea f, :additional_comment, class: "form-control", placeholder: "Specify the reason for adding tags and color preference(s).", maxlength: 255 %> |
||||
<%= error_tag f, :additional_comment, class: "text-danger form-error" %> |
||||
</div> |
||||
<br> |
||||
<div class="form-group float-left form-input"> |
||||
<a class="btn btn-line" href="<%= public_tags_request_path(@conn, :index) %>"><%= gettext "Cancel"%></a> |
||||
<%= submit gettext("Send request"), class: "button button-primary button-sm ml-3" %> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/public-tags-request-form.js") %>"></script> |
||||
</section> |
@ -0,0 +1,44 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :public_tags %> |
||||
<div class="col-md-10"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Public tags" %> </h1> |
||||
<br> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." %> |
||||
</div> |
||||
</div> |
||||
<div class="col-sm"> |
||||
<div class="mb-3 row o-flow-x"> |
||||
<%= if @public_tags_requests != [] do %> |
||||
<table class="table mb-3 table-watchlist"> |
||||
<thead style="font-size: 14px; color: #6c757d" > |
||||
<tr> |
||||
<th scope="col"><%= gettext "Public tag" %></th> |
||||
<th scope="col"><%= gettext "Smart contract / Address" %></th> |
||||
<th scope="col"><%= gettext "Submission date" %></th> |
||||
<th scope="col"></th> |
||||
<th scope="col"></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody style="font-size: 15px; color: #6c757d" > |
||||
<%= Enum.map(@public_tags_requests, fn x -> |
||||
render("row.html", public_tags_request: x, conn: @conn) |
||||
end) %> |
||||
</tbody> |
||||
</table> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if Enum.count(@public_tags_requests) < PublicTagsRequest.get_max_public_tags_request_count() do %> |
||||
<a class="button button-primary button-sm" href="<%= public_tags_request_path(@conn, :new) %>"><%= gettext "Request to add public tag" %></a> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/delete-item-handler.js") %>"></script> |
||||
</section> |
@ -0,0 +1,20 @@ |
||||
<tr> |
||||
<td> |
||||
<%= for tag <- String.split(@public_tags_request.tags, ";") do %> |
||||
<div class="bs-label black-hole mb-1"><%= tag %></div> |
||||
<% end %> |
||||
</td> |
||||
<td><%= Enum.join(@public_tags_request.addresses, "\n") %></td> |
||||
<td><%= Calendar.strftime(@public_tags_request.inserted_at, "%b %d, %Y") %></td> |
||||
<td> |
||||
<%= link (render BlockScoutWeb.CommonComponentsView, "_svg_pen.html"), to: public_tags_request_path(@conn, :edit, @public_tags_request.id) %> |
||||
</td> |
||||
<td> |
||||
<form method="post" action="<%= public_tags_request_path(@conn, :delete, @public_tags_request.id) %>"> |
||||
<input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>"> |
||||
<input type="hidden" name="_method" value="delete"> |
||||
<input type="hidden" name="remove_reason" value=""> |
||||
</form> |
||||
<a href="" data-delete-request data-tags="<%= @public_tags_request.tags %>"><%= (render BlockScoutWeb.CommonComponentsView, "_svg_trash.html") %></a> |
||||
</td> |
||||
</tr> |
@ -0,0 +1,33 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :address_tags %> |
||||
<div class="col-sm-10"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Add address tag"%></h1> |
||||
<div class="col-sm-10 card-body-account"> |
||||
<%= form_for @tag_address, tag_address_path(@conn, :create), fn f -> %> |
||||
<div class="form-group"> |
||||
<%= label f, :address_hash, gettext("Address"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :address_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000", maxlength: 42 %> |
||||
<%= error_tag f, :address_hash, class: "text-danger form-error" %> |
||||
<%= error_tag f, :identity_id, class: "text-danger form-error" %> |
||||
</div> |
||||
|
||||
<div class="form-group"> |
||||
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this address"), maxlength: 35 %> |
||||
<%= error_tag f, :name, class: "text-danger form-error" %> |
||||
</div> |
||||
|
||||
<div class="form-group float-right form-input"> |
||||
<a class="btn btn-line" href="<%= tag_address_path(@conn, :index) %>"><%= gettext "Back to Address Tags (Cancel)"%></a> |
||||
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,41 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :address_tags %> |
||||
<div class="col-md"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Address Tags" %></h1> |
||||
<br> |
||||
<div class="col-sm"> |
||||
<div class="mb-3 row o-flow-x"> |
||||
<%= if @address_tags == [] do %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "You don't have address tags yet" %> |
||||
</div> |
||||
</div> |
||||
<h2></h2> |
||||
<% else %> |
||||
<table class="table mb-3 table-watchlist"> |
||||
<thead style="font-size: 14px; color: #6c757d" > |
||||
<tr> |
||||
<th scope="col"><%= gettext "Name" %></th> |
||||
<th scope="col"><%= gettext "Address" %></th> |
||||
<th scope="col"><%= gettext "Action" %></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody style="font-size: 15px; color: #6c757d" > |
||||
<%= Enum.map(@address_tags, fn at -> |
||||
render("row.html", address_tag: at, conn: @conn) |
||||
end) %> |
||||
</tbody> |
||||
</table> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if Enum.count(@address_tags) < TagAddress.get_max_tags_count() do %> |
||||
<a class="button button-primary button-sm" href="<%= tag_address_path(@conn, :new) %>"><%= gettext "Add address tag" %></a> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,15 @@ |
||||
<%= if @address_tag.address_hash do %> |
||||
<tr> |
||||
<td><%= @address_tag.name %></td> |
||||
<td> |
||||
<div> |
||||
<%= link(trimmed_hash(@address_tag.address_hash), to: address_path(@conn, :show, @address_tag.address_hash)) %> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", |
||||
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @address_tag.address_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> |
||||
</td> |
||||
<td> |
||||
<%= link "Remove Tag", to: tag_address_path(@conn, :delete, @address_tag.id), method: :delete %> |
||||
</div> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
@ -0,0 +1,33 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :transaction_tags %> |
||||
<div class="col-sm-10"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Add transaction tag"%></h1> |
||||
<div class="col-sm-10 card-body-account"> |
||||
<%= form_for @tag_transaction, tag_transaction_path(@conn, :create), fn f -> %> |
||||
<div class="form-group"> |
||||
<%= label f, :tx_hash, gettext("Transaction"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :tx_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000000000000000000000000000", maxlength: 66 %> |
||||
<%= error_tag f, :tx_hash, class: "text-danger form-error" %> |
||||
<%= error_tag f, :identity_id, class: "text-danger form-error" %> |
||||
</div> |
||||
|
||||
<div class="form-group"> |
||||
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this transaction"), maxlength: 35 %> |
||||
<%= error_tag f, :name, class: "text-danger form-error" %> |
||||
</div> |
||||
|
||||
<div class="form-group float-right form-input"> |
||||
<a class="btn btn-line" href="<%= tag_transaction_path(@conn, :index) %>"><%= gettext "Back to Transaction Tags (Cancel)"%></a> |
||||
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,41 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :transaction_tags %> |
||||
<div class="col-md"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Transaction Tags" %></h1> |
||||
<br> |
||||
<div class="col-sm"> |
||||
<div class="mb-3 row o-flow-x"> |
||||
<%= if @tx_tags == [] do %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "You don't have transaction tags yet" %> |
||||
</div> |
||||
</div> |
||||
<h2></h2> |
||||
<% else %> |
||||
<table class="table mb-3 table-watchlist"> |
||||
<thead style="font-size: 14px; color: #6c757d" > |
||||
<tr> |
||||
<th scope="col"><%= gettext "Name" %></th> |
||||
<th scope="col"><%= gettext "Transaction" %></th> |
||||
<th scope="col"><%= gettext "Action" %></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody style="font-size: 15px; color: #6c757d" > |
||||
<%= Enum.map(@tx_tags, fn at -> |
||||
render("row.html", tx_tag: at, conn: @conn) |
||||
end) %> |
||||
</tbody> |
||||
</table> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if Enum.count(@tx_tags) < TagTransaction.get_max_tags_count() do %> |
||||
<a class="button button-primary button-sm" href="<%= tag_transaction_path(@conn, :new) %>"><%= gettext "Add transaction tag" %></a> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,18 @@ |
||||
<%= if @tx_tag.tx_hash do %> |
||||
<tr> |
||||
<td><%= @tx_tag.name %></td> |
||||
<td> |
||||
<div> |
||||
<%= link(@tx_tag.tx_hash, |
||||
to: transaction_path(BlockScoutWeb.Endpoint, :show, @tx_tag.tx_hash), |
||||
"data-test": "transaction_hash_link", |
||||
class: "text-truncate") %> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", |
||||
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @tx_tag.tx_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> |
||||
</div> |
||||
</td> |
||||
<td> |
||||
<%= link "Remove Tag", to: tag_transaction_path(@conn, :delete, @tx_tag.id), method: :delete %> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
@ -0,0 +1,42 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :watchlist %> |
||||
<div class="col-md"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account"><%= gettext "Watch list" %></h1> |
||||
<br> |
||||
<div class="col-sm"> |
||||
<div class="mb-4 row o-flow-x"> |
||||
<%= if @watchlist.watchlist_addresses == [] do %> |
||||
<div style="min-width: 100%;"> |
||||
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list"> |
||||
<%= gettext "You don't have addresses on you watchlist yet" %> |
||||
</div> |
||||
</div> |
||||
<h2></h2> |
||||
<% else %> |
||||
<table class="table mb-4 table-watchlist"> |
||||
<thead style="font-size: 14px; color: #6c757d" > |
||||
<tr> |
||||
<th scope="col"><%= gettext "Name" %></th> |
||||
<th scope="col"><%= gettext "Address" %></th> |
||||
<th scope="col"><%= gettext "Balance" %></th> |
||||
<th scope="col"><%= gettext "Actions" %></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody style="font-size: 15px; color: #6c757d" > |
||||
<%= Enum.map(@watchlist.watchlist_addresses, fn wa -> |
||||
render(WatchlistAddressView, "row.html", watchlist_address: wa, exchange_rate: exchange_rate(), conn: @conn) |
||||
end) %> |
||||
</tbody> |
||||
</table> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if Enum.count(@watchlist.watchlist_addresses) < WatchlistAddress.get_max_watchlist_addresses_count() do %> |
||||
<a class="button button-primary button-sm" href="<%= watchlist_address_path(@conn, :new) %>"><%= gettext "Add address" %></a> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,91 @@ |
||||
<section class="container"> |
||||
<div class="row"> |
||||
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :watchlist %> |
||||
<div class="col-sm-10"> |
||||
<div class="card"> |
||||
<div class="card-body" > |
||||
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Edit Watch list address"), else: gettext "Add address to the Watch list" %></h1> |
||||
<div class="col-sm-10 card-body-account"> |
||||
<% path = if @method == :update, do: watchlist_address_path(@conn, @method, @watchlist_address.data.id), else: watchlist_address_path(@conn, @method) %> |
||||
<%= form_for @watchlist_address, path, fn f -> %> |
||||
<div class="form-group"> |
||||
<%= label f, :address_hash, gettext("Address"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :address_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000", maxlength: 42 %> |
||||
<%= error_tag f, :address_hash, class: "text-danger form-error" %> |
||||
<%= error_tag f, :watchlist_id, class: "text-danger form-error" %> |
||||
</div> |
||||
|
||||
<div class="form-group"> |
||||
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %> |
||||
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this address"), maxlength: 35 %> |
||||
<%= error_tag f, :name, class: "text-danger form-error" %> |
||||
</div> |
||||
<br> |
||||
<p style="font-size: 14px;"><%= gettext("Please select what types of notifications you will receive:") %></p> |
||||
<div class="row"> |
||||
<div class="col-sm"> |
||||
<h3><%= gettext("Ether") %></h3> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :watch_coin_input, class: "form-checkbox" %> |
||||
<%= label f, :watch_coin_input, gettext("Incoming"), class: "label-checkbox" %> |
||||
<%= error_tag f, :watch_coin_input, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :watch_coin_output, class: "form-checkbox" %> |
||||
<%= label f, :watch_coin_output, gettext("Outgoing"), class: "label-checkbox" %> |
||||
<%= error_tag f, :watch_coin_output, class: "text-danger form-error" %> |
||||
</div> |
||||
</div> |
||||
<div class="col-sm"> |
||||
<h3><%= gettext "ERC-20 tokens (beta)" %></h3> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :watch_erc_20_input, class: "form-checkbox" %> |
||||
<%= label f, :watch_erc_20_input, gettext("Incoming"), class: "label-checkbox" %> |
||||
<%= error_tag f, :watch_erc_20_input, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :watch_erc_20_output, class: "form-checkbox" %> |
||||
<%= label f, :watch_erc_20_output, gettext("Outgoing"), class: "label-checkbox" %> |
||||
<%= error_tag f, :watch_erc_20_output, class: "text-danger form-error" %> |
||||
</div> |
||||
</div> |
||||
<div class="col-md"> |
||||
<h3><%= gettext "ERC-721, ERC-1155 tokens (NFT) (beta)" %></h3> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :watch_erc_721_input, class: "form-checkbox" %> |
||||
<%= label f, :watch_erc_721_input, gettext("Incoming"), class: "label-checkbox" %> |
||||
<%= error_tag f, :watch_erc_721_input, class: "text-danger form-error" %> |
||||
</div> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :watch_erc_721_output, class: "form-checkbox" %> |
||||
<%= label f, :watch_erc_721_output, gettext("Outgoing"), class: "label-checkbox" %> |
||||
<%= error_tag f, :watch_erc_721_output, class: "text-danger form-error" %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<br> |
||||
<p style="font-size: 14px;"><%= gettext "Please select notification methods:"%></p> |
||||
<div class="form-input"> |
||||
<%= checkbox f, :notify_email, class: "form-checkbox" %> |
||||
<%= label f, :notify_email, gettext("Email notifications"), class: "label-checkbox" %> |
||||
<%= error_tag f, :notify_email, class: "text-danger form-error" %> |
||||
</div> |
||||
|
||||
<br> |
||||
<%= if @method != :create do %> |
||||
<%= link gettext("Remove from Watch list"), to: watchlist_address_path(@conn, :delete, @watchlist_address.data.id), method: :delete, class: "btn btn-danger white button-sm", style: "color: #fff !important;" %> |
||||
<% end %> |
||||
<div class="form-group float-right form-input"> |
||||
<a class="btn btn-line" href="<%= watchlist_path(@conn, :show) %>"> |
||||
<%= gettext "Back to Watch list (Cancel)" %> |
||||
</a> |
||||
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,29 @@ |
||||
<tr> |
||||
<td><%= @watchlist_address.name %></td> |
||||
<td> |
||||
<div> |
||||
<%= link(trimmed_hash(@watchlist_address.address_hash), to: address_path(@conn, :show, @watchlist_address.address_hash)) %> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", |
||||
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @watchlist_address.address_hash, aria_label: gettext("Copy From Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> |
||||
</div> |
||||
</td> |
||||
<td> |
||||
<%= balance_ether(@watchlist_address.fetched_coin_balance) %> |
||||
<br> |
||||
|
||||
<span class="address-current-balance"> |
||||
(<span |
||||
data-wei-value="<%= if @watchlist_address.fetched_coin_balance, do: @watchlist_address.fetched_coin_balance.value %>" |
||||
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>" |
||||
data-placement="top" |
||||
data-toggle="tooltip" |
||||
data-html="true" |
||||
title='<%= "@ " <> to_string(@exchange_rate.usd_value) <> "/" <> gettext("Ether") %>' |
||||
> |
||||
</span>) |
||||
</span> |
||||
</td> |
||||
<td> |
||||
<%= link(gettext("Edit"), to: watchlist_address_path(@conn, :edit, @watchlist_address.id)) %> |
||||
</td> |
||||
</tr> |
@ -0,0 +1,16 @@ |
||||
<%= for personal_tag <- @tags.personal_tags do %> |
||||
<%= if personal_tag.address_hash do %> |
||||
<%= if personal_tag.label =~ "dark forest" do %> |
||||
<%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tag.display_name, additional_classes: ["df", "ml-1"] %> |
||||
<% else %> |
||||
<%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tag.display_name, additional_classes: [tag_name_to_label(personal_tag.label), "ml-1"] %> |
||||
<% end %> |
||||
<% end %> |
||||
<% end %> |
||||
<%= for watchlist_name <- @tags.watchlist_names do %> |
||||
<%= if watchlist_name.label =~ "dark forest" do %> |
||||
<%= render BlockScoutWeb.FormView, "_tag.html", text: watchlist_name.display_name, additional_classes: ["df", "ml-1"] %> |
||||
<% else %> |
||||
<%= render BlockScoutWeb.FormView, "_tag.html", text: watchlist_name.display_name, additional_classes: [tag_name_to_label(watchlist_name.label), "ml-1"] %> |
||||
<% end %> |
||||
<% end %> |
After Width: | Height: | Size: 366 B |
After Width: | Height: | Size: 548 B |
After Width: | Height: | Size: 410 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue