Merge branch 'master' into optimized-indexer

pull/162/head
Luke Imhoff 7 years ago
commit 34de762677
  1. 15
      .circleci/config.yml
  2. 20
      .circleci/setup-heroku.sh
  3. 7
      Procfile
  4. 1
      README.md
  5. 1
      apps/explorer/README.md
  6. 141
      apps/explorer/lib/explorer/chain.ex
  7. 399
      apps/explorer/test/explorer/chain_test.exs
  8. 1
      apps/explorer_web/README.md
  9. 10
      apps/explorer_web/assets/brunch-config.js
  10. 26
      apps/explorer_web/assets/css/_typography.scss
  11. 18
      apps/explorer_web/assets/css/_utilities.scss
  12. 111
      apps/explorer_web/assets/css/_variables.scss
  13. 27
      apps/explorer_web/assets/css/app.scss
  14. 1
      apps/explorer_web/assets/css/components/_all.scss
  15. 2
      apps/explorer_web/assets/css/components/_blocks.scss
  16. 54
      apps/explorer_web/assets/css/components/_container.scss
  17. 16
      apps/explorer_web/assets/css/components/_footer.scss
  18. 96
      apps/explorer_web/assets/css/components/_header.scss
  19. 170
      apps/explorer_web/assets/css/components/_menu.scss
  20. 15
      apps/explorer_web/assets/css/components/_transactions.scss
  21. 74
      apps/explorer_web/assets/css/explorer/_button.scss
  22. 4
      apps/explorer_web/assets/css/explorer/_filter.scss
  23. 430
      apps/explorer_web/assets/css/explorer/_sb-admin-2.scss
  24. 71
      apps/explorer_web/assets/css/explorer/_wrapper.scss
  25. 3
      apps/explorer_web/assets/css/theme/_fonts.scss
  26. 928
      apps/explorer_web/assets/css/theme/_neutral-variables.scss
  27. 1
      apps/explorer_web/assets/css/theme/_variables.scss
  28. 1
      apps/explorer_web/assets/js/app.js
  29. 4
      apps/explorer_web/assets/js/lib/sidebar.js
  30. 45
      apps/explorer_web/assets/package-lock.json
  31. 2
      apps/explorer_web/assets/package.json
  32. 3
      apps/explorer_web/config/prod.exs
  33. 12
      apps/explorer_web/lib/explorer_web/controllers/address_controller.ex
  34. 48
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_controller.ex
  35. 34
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex
  36. 34
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex
  37. 11
      apps/explorer_web/lib/explorer_web/controllers/block_controller.ex
  38. 5
      apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
  39. 13
      apps/explorer_web/lib/explorer_web/router.ex
  40. 38
      apps/explorer_web/lib/explorer_web/templates/address/show.html.eex
  41. 96
      apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
  42. 108
      apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex
  43. 99
      apps/explorer_web/lib/explorer_web/templates/block/show.html.eex
  44. 110
      apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
  45. 11
      apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
  46. 32
      apps/explorer_web/lib/explorer_web/templates/layout/_header.html.eex
  47. 88
      apps/explorer_web/lib/explorer_web/templates/layout/app.html.eex
  48. 12
      apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex
  49. 15
      apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
  50. 17
      apps/explorer_web/lib/explorer_web/views/address_view.ex
  51. 4
      apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex
  52. 6
      apps/explorer_web/lib/explorer_web/views/layout_view.ex
  53. 12
      apps/explorer_web/lib/explorer_web/views/transaction_view.ex
  54. 14
      apps/explorer_web/lib/phoenix/html/safe.ex
  55. 8
      apps/explorer_web/lib/phoenix/param.ex
  56. 143
      apps/explorer_web/priv/gettext/default.pot
  57. 143
      apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
  58. 12
      apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs
  59. 16
      apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
  60. 79
      apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs
  61. 14
      apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs
  62. 344
      apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs
  63. 67
      apps/explorer_web/test/explorer_web/views/address_transaction_view_test.exs
  64. 49
      bin/deploy
  65. 12
      bin/start-pgbouncer-stunnel
  66. 2
      coveralls.json
  67. 3
      elixir_buildpack.config
  68. 7
      phoenix_static_buildpack.config

@ -158,13 +158,8 @@ jobs:
fingerprints:
- "c4:fd:a8:f8:48:a8:09:e5:3e:be:30:62:4d:6f:6f:36"
- run:
name: Setup Heroku
command: bash .circleci/setup-heroku.sh
# TODO update for deployment to AWS
- run:
name: Deploy Production to Heroku
command: bin/deploy poa-explorer-production
deploy_staging:
docker:
# Ensure .tool-versions matches
@ -182,13 +177,7 @@ jobs:
fingerprints:
- "c4:fd:a8:f8:48:a8:09:e5:3e:be:30:62:4d:6f:6f:36"
- run:
name: Setup Heroku
command: bash .circleci/setup-heroku.sh
- run:
name: Deploy Staging to Heroku
command: bin/deploy poa-explorer-staging
# TODO update for deployment to AWS
dialyzer:
docker:
# Ensure .tool-versions matches

@ -1,20 +0,0 @@
#!/bin/bash
wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz
sudo mkdir -p /usr/local/lib /usr/local/bin
sudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib
sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku
cat > ~/.netrc << EOF
machine api.heroku.com
login $HEROKU_LOGIN
password $HEROKU_API_KEY
EOF
cat >> ~/.ssh/config << EOF
VerifyHostKeyDNS yes
StrictHostKeyChecking no
EOF
cat >> ~/.ssh/known_hosts << EOF
heroku.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAu8erSx6jh+8ztsfHwkNeFr/SZaSOcvoa8AyMpaerGIPZDB2TKNgNkMSYTLYGDK2ivsqXopo2W7dpQRBIVF80q9mNXy5tbt1WE04gbOBB26Wn2hF4bk3Tu+BNMFbvMjPbkVlC2hcFuQJdH4T2i/dtauyTpJbD/6ExHR9XYVhdhdMs0JsjP/Q5FNoWh2ff9YbZVpDQSTPvusUp4liLjPfa/i0t+2LpNCeWy8Y+V9gUlDWiyYwrfMVI0UwNCZZKHs1Unpc11/4HLitQRtvuk0Ot5qwwBxbmtvCDKZvj1aFBid71/mYdGRPYZMIxq1zgP1acePC1zfTG/lvuQ7d0Pe0kaw==
EOF

@ -1,7 +0,0 @@
web: bin/start-pgbouncer-stunnel mix phx.server
worker: bin/start-pgbouncer-stunnel mix exq.start
scheduler: bin/start-pgbouncer-stunnel mix exq.start scheduler
blocks: bin/start-pgbouncer-stunnel mix scrape.blocks 1000000
receipts: bin/start-pgbouncer-stunnel mix scrape.receipts 10000
internal_transactions: bin/start-pgbouncer-stunnel mix scrape.internal_transactions 10000
balances: bin/start-pgbouncer-stunnel mix scrape.balances 1000000

@ -13,7 +13,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain.
## Required Accounts
* Heroku for deployment
* GitHub for code storage

@ -12,7 +12,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain.
## Required Accounts
* Heroku for deployment
* Github for code storage

@ -3,7 +3,7 @@ defmodule Explorer.Chain do
The chain context.
"""
import Ecto.Query, only: [from: 2, order_by: 2, preload: 2, where: 2, where: 3]
import Ecto.Query, only: [from: 2, or_where: 3, order_by: 2, preload: 2, where: 2]
alias Ecto.{Changeset, Multi}
alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction, Wei}
@ -16,6 +16,8 @@ defmodule Explorer.Chain do
"""
@type association :: atom()
@type direction :: :from | :to
@typedoc """
* `:optional` - the association is optional and only needs to be loaded if available
* `:required` - the association is required and MUST be loaded. If it is not available, then the parent struct
@ -34,6 +36,7 @@ defmodule Explorer.Chain do
@type pagination :: map()
@typep after_hash_option :: {:after_hash, Hash.t()}
@typep direction_option :: {:direction, direction}
@typep inserted_after_option :: {:inserted_after, DateTime.t()}
@typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association}
@typep pagination_option :: {:pagination, pagination}
@ -41,6 +44,40 @@ defmodule Explorer.Chain do
@typep timestamps_option :: {:timestamps, timestamps}
# Functions
@doc """
`t:Explorer.Chain.Transaction/0`s from `address`.
## Options
* `:direction` - if specified, will filter transactions by address type. If `:to` is specified, only transactions
where the "to" address matches will be returned. Likewise, if `:from` is specified, only transactions where the
"from" address matches will be returned. If :direction is omitted, transactions either to or from the address
will be returned.
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
"""
@spec address_to_transactions(Address.t(), [
direction_option | necessity_by_association_option | pagination_option
]) :: %Scrivener.Page{entries: [Transaction.t()]}
def address_to_transactions(%Address{hash: hash}, options \\ []) when is_list(options) do
address_hash_to_transactions(hash, options)
end
@doc """
The `t:Explorer.Chain.Address.t/0` `balance` in `unit`.
"""
@spec balance(Address.t(), :wei) :: Wei.t() | nil
@spec balance(Address.t(), :gwei) :: Wei.gwei() | nil
@spec balance(Address.t(), :ether) :: Wei.ether() | nil
def balance(%Address{balance: balance}, unit) do
case balance do
nil -> nil
_ -> Wei.to(balance, unit)
end
end
@doc """
The number of `t:Explorer.Chain.Block.t/0`.
@ -146,26 +183,50 @@ defmodule Explorer.Chain do
end
@doc """
`t:Explorer.Chain.Transaction/0`s from `address`.
The fee a `transaction` paid for the `t:Explorer.Transaction.t/0` `gas`
## Options
If the transaction is pending, then the fee will be a range of `unit`
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
iex> Explorer.Chain.fee(
...> %Explorer.Chain.Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil},
...> :wei
...> )
{:maximum, Decimal.new(6)}
If the transaction has been confirmed in block, then the fee will be the actual fee paid in `unit` for the `gas_used`
in the `receipt`.
iex> Explorer.Chain.fee(
...> %Explorer.Chain.Transaction{
...> gas: Decimal.new(3),
...> gas_price: Decimal.new(2),
...> receipt: %Explorer.Chain.Receipt{gas_used: Decimal.new(2)}
...> },
...> :wei
...> )
{:actual, Decimal.new(4)}
"""
@spec from_address_to_transactions(Address.t(), [
necessity_by_association_option | pagination_option
]) :: %Scrivener.Page{entries: [Transaction.t()]}
def from_address_to_transactions(address = %Address{}, options \\ []) when is_list(options) do
address_to_transactions(address, Keyword.put(options, :direction, :from))
@spec fee(%Transaction{receipt: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()}
def fee(%Transaction{gas: gas, gas_price: gas_price, receipt: nil}, unit) do
fee =
gas
|> Decimal.mult(gas_price)
|> Wei.to(unit)
{:maximum, fee}
end
@spec fee(%Transaction{receipt: Receipt.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()}
def fee(%Transaction{gas_price: gas_price, receipt: %Receipt{gas_used: gas_used}}, unit) do
fee =
gas_used
|> Decimal.mult(gas_price)
|> Wei.to(unit)
{:actual, fee}
end
@doc """
TODO
"""
def get_latest_block do
Repo.one(from(b in Block, limit: 1, order_by: [desc: b.number]))
end
@ -407,7 +468,7 @@ defmodule Explorer.Chain do
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
`t:Explorer.Chain.Block.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
"""
@ -476,11 +537,22 @@ defmodule Explorer.Chain do
@doc """
Finds `t:Explorer.Chain.Block.t/0` with `number`
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Block.t/0` will not be included in the page `entries`.
"""
@spec number_to_block(Block.block_number()) :: {:ok, Block.t()} | {:error, :not_found}
def number_to_block(number) do
@spec number_to_block(Block.block_number(), [necessity_by_association_option]) ::
{:ok, Block.t()} | {:error, :not_found}
def number_to_block(number, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
Block
|> where(number: ^number)
|> join_associations(necessity_by_association)
|> Repo.one()
|> case do
nil -> {:error, :not_found}
@ -867,15 +939,16 @@ defmodule Explorer.Chain do
## Private Functions
defp address_hash_to_transaction(
defp address_hash_to_transactions(
%Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash,
named_arguments
)
when is_list(named_arguments) do
field =
case Keyword.fetch!(named_arguments, :direction) do
:to -> :to_address_hash
:from -> :from_address_hash
address_fields =
case Keyword.get(named_arguments, :direction) do
:to -> [:to_address_hash]
:from -> [:from_address_hash]
nil -> [:from_address_hash, :to_address_hash]
end
necessity_by_association = Keyword.get(named_arguments, :necessity_by_association, %{})
@ -883,15 +956,11 @@ defmodule Explorer.Chain do
Transaction
|> join_associations(necessity_by_association)
|> chronologically()
|> where([t], field(t, ^field) == ^address_hash)
|> reverse_chronologically()
|> where_address_fields_match(address_fields, address_hash)
|> Repo.paginate(pagination)
end
defp address_to_transactions(%Address{hash: address_hash}, options) when is_list(options) do
address_hash_to_transaction(address_hash, options)
end
defp after_hash(query, options) do
case Keyword.fetch(options, :after_hash) do
{:ok, hash} ->
@ -929,10 +998,6 @@ defmodule Explorer.Chain do
{status, Enum.reverse(acc)}
end
defp chronologically(query) do
from(q in query, order_by: [desc: q.inserted_at, desc: q.hash])
end
defp ecto_schema_module_changes_list_to_address_hash_set({ecto_schema_module, changes_list}) do
Enum.reduce(changes_list, MapSet.new(), fn changes, acc ->
changes
@ -1116,6 +1181,10 @@ defmodule Explorer.Chain do
end)
end
defp reverse_chronologically(query) do
from(q in query, order_by: [desc: q.inserted_at, desc: q.hash])
end
defp timestamp_params(changes, timestamps) when is_map(changes) do
Map.merge(changes, timestamps)
end
@ -1147,6 +1216,12 @@ defmodule Explorer.Chain do
|> Repo.paginate(pagination)
end
defp where_address_fields_match(query, address_fields, address_hash) do
Enum.reduce(address_fields, query, fn field, query ->
or_where(query, [t], field(t, ^field) == ^address_hash)
end)
end
defp where_pending(query, options) when is_list(options) do
pending = Keyword.get(options, :pending)

@ -10,9 +10,9 @@ defmodule Explorer.ChainTest do
# Tests
describe "block_to_transactions/1" do
describe "address_to_transactions/2" do
test "without transactions" do
block = insert(:block)
address = insert(:address)
assert Repo.aggregate(Transaction, :count, :hash) == 0
@ -20,25 +20,80 @@ defmodule Explorer.ChainTest do
entries: [],
page_number: 1,
total_entries: 0
} = Chain.block_to_transactions(block)
} = Chain.address_to_transactions(address)
end
test "with transactions" do
block = insert(:block)
%Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0)
test "with from transactions" do
%Transaction{from_address_hash: from_address_hash, hash: transaction_hash} = insert(:transaction)
address = Repo.get!(Address, from_address_hash)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.block_to_transactions(block)
} = Chain.address_to_transactions(address, direction: :from)
end
test "with transaction with receipt required without receipt does not return transaction" do
block = %Block{hash: block_hash} = insert(:block)
test "with to transactions" do
%Transaction{to_address_hash: to_address_hash, hash: transaction_hash} = insert(:transaction)
address = Repo.get!(Address, to_address_hash)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :to)
end
test "with to and from transactions and direction: :from" do
%Transaction{from_address_hash: address_hash, hash: from_transaction_hash} = insert(:transaction)
%Transaction{} = insert(:transaction, to_address_hash: address_hash)
address = Repo.get!(Address, address_hash)
# only contains "from" transaction
assert %Scrivener.Page{
entries: [%Transaction{hash: ^from_transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :from)
end
test "with to and from transactions and direction: :to" do
%Transaction{from_address_hash: address_hash} = insert(:transaction)
%Transaction{hash: to_transaction_hash} = insert(:transaction, to_address_hash: address_hash)
address = Repo.get!(Address, address_hash)
# only contains "to" transaction
assert %Scrivener.Page{
entries: [%Transaction{hash: ^to_transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :to)
end
test "with to and from transactions and no :direction option" do
%Transaction{from_address_hash: address_hash, hash: from_transaction_hash} = insert(:transaction)
%Transaction{hash: to_transaction_hash} = insert(:transaction, to_address_hash: address_hash)
address = Repo.get!(Address, address_hash)
# only contains "to" transaction
assert %Scrivener.Page{
entries: [
%Transaction{hash: ^to_transaction_hash},
%Transaction{hash: ^from_transaction_hash}
],
page_number: 1,
total_entries: 2
} = Chain.address_to_transactions(address)
end
test "with transactions with receipt required without receipt does not return transaction" do
address = %Address{hash: to_address_hash} = insert(:address)
block = insert(:block)
%Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} =
insert(:transaction, block_hash: block_hash, index: 0)
insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: to_address_hash)
insert(
:receipt,
@ -46,15 +101,15 @@ defmodule Explorer.ChainTest do
transaction_index: transaction_index_with_receipt
)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash, index: 1)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}],
page_number: 1,
total_entries: 1
} =
Chain.block_to_transactions(
block,
Chain.address_to_transactions(
address,
necessity_by_association: %{receipt: :required}
)
@ -63,8 +118,8 @@ defmodule Explorer.ChainTest do
page_number: 1,
total_entries: 2
} =
Chain.block_to_transactions(
block,
Chain.address_to_transactions(
address,
necessity_by_association: %{receipt: :optional}
)
@ -80,85 +135,51 @@ defmodule Explorer.ChainTest do
end
test "with transactions can be paginated" do
block = insert(:block)
transactions = Enum.map(0..1, &insert(:transaction, block_hash: block.hash, index: &1))
adddress = %Address{hash: to_address_hash} = insert(:address)
transactions = insert_list(2, :transaction, to_address_hash: to_address_hash)
[%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions
[%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions
assert %Scrivener.Page{
entries: [%Transaction{hash: ^second_transaction_hash}],
entries: [%Transaction{hash: ^newest_transaction_hash}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.block_to_transactions(block, pagination: %{page_size: 1})
} = Chain.address_to_transactions(adddress, pagination: %{page_size: 1})
assert %Scrivener.Page{
entries: [%Transaction{hash: ^first_transaction_hash}],
entries: [%Transaction{hash: ^oldest_transaction_hash}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.block_to_transactions(block, pagination: %{page: 2, page_size: 1})
end
end
describe "block_to_transaction_bound/1" do
test "without transactions" do
block = insert(:block)
assert Chain.block_to_transaction_count(block) == 0
end
test "with transactions" do
block = insert(:block)
insert(:transaction, block_hash: block.hash, index: 0)
assert Chain.block_to_transaction_count(block) == 1
end
end
describe "confirmations/1" do
test "with block.number == max_block_number " do
block = insert(:block)
max_block_number = Chain.max_block_number()
assert block.number == max_block_number
assert Chain.confirmations(block, max_block_number: max_block_number) == 0
end
test "with block.number < max_block_number" do
block = insert(:block)
max_block_number = block.number + 2
assert block.number < max_block_number
assert Chain.confirmations(block, max_block_number: max_block_number) == max_block_number - block.number
} = Chain.address_to_transactions(adddress, pagination: %{page: 2, page_size: 1})
end
end
describe "gas_price/2" do
test ":wei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1)
describe "balance/2" do
test "with Address.t with :wei" do
assert Chain.balance(%Address{balance: Decimal.new(1)}, :wei) == Decimal.new(1)
assert Chain.balance(%Address{balance: nil}, :wei) == nil
end
test ":gwei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :gwei) == Decimal.new("1e-9")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e9")}, :gwei) == Decimal.new(1)
test "with Address.t with :gwei" do
assert Chain.balance(%Address{balance: Decimal.new(1)}, :gwei) == Decimal.new("1e-9")
assert Chain.balance(%Address{balance: Decimal.new("1e9")}, :gwei) == Decimal.new(1)
assert Chain.balance(%Address{balance: nil}, :gwei) == nil
end
test ":ether unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :ether) == Decimal.new("1e-18")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e18")}, :ether) == Decimal.new(1)
test "with Address.t with :ether" do
assert Chain.balance(%Address{balance: Decimal.new(1)}, :ether) == Decimal.new("1e-18")
assert Chain.balance(%Address{balance: Decimal.new("1e18")}, :ether) == Decimal.new(1)
assert Chain.balance(%Address{balance: nil}, :ether) == nil
end
end
describe "from_address_to_transactions/2" do
describe "block_to_transactions/1" do
test "without transactions" do
address = insert(:address)
block = insert(:block)
assert Repo.aggregate(Transaction, :count, :hash) == 0
@ -166,27 +187,25 @@ defmodule Explorer.ChainTest do
entries: [],
page_number: 1,
total_entries: 0
} = Chain.from_address_to_transactions(address)
} = Chain.block_to_transactions(block)
end
test "with transactions" do
%Transaction{from_address_hash: from_address_hash, hash: transaction_hash} = insert(:transaction)
address = Repo.get!(Address, from_address_hash)
block = insert(:block)
%Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.from_address_to_transactions(address)
} = Chain.block_to_transactions(block)
end
test "with transactions with receipt required without receipt does not return transaction" do
address = %Address{hash: from_address_hash} = insert(:address)
block = insert(:block)
test "with transaction with receipt required without receipt does not return transaction" do
block = %Block{hash: block_hash} = insert(:block)
%Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} =
insert(:transaction, block_hash: block.hash, index: 0, from_address_hash: from_address_hash)
insert(:transaction, block_hash: block_hash, index: 0)
insert(
:receipt,
@ -194,15 +213,15 @@ defmodule Explorer.ChainTest do
transaction_index: transaction_index_with_receipt
)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, from_address_hash: from_address_hash)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash, index: 1)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}],
page_number: 1,
total_entries: 1
} =
Chain.from_address_to_transactions(
address,
Chain.block_to_transactions(
block,
necessity_by_association: %{receipt: :required}
)
@ -211,8 +230,8 @@ defmodule Explorer.ChainTest do
page_number: 1,
total_entries: 2
} =
Chain.from_address_to_transactions(
address,
Chain.block_to_transactions(
block,
necessity_by_association: %{receipt: :optional}
)
@ -228,26 +247,129 @@ defmodule Explorer.ChainTest do
end
test "with transactions can be paginated" do
adddress = %Address{hash: from_address_hash} = insert(:address)
transactions = insert_list(2, :transaction, from_address_hash: from_address_hash)
block = insert(:block)
[%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions
transactions = Enum.map(0..1, &insert(:transaction, block_hash: block.hash, index: &1))
[%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions
assert %Scrivener.Page{
entries: [%Transaction{hash: ^newest_transaction_hash}],
entries: [%Transaction{hash: ^second_transaction_hash}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.from_address_to_transactions(adddress, pagination: %{page_size: 1})
} = Chain.block_to_transactions(block, pagination: %{page_size: 1})
assert %Scrivener.Page{
entries: [%Transaction{hash: ^oldest_transaction_hash}],
entries: [%Transaction{hash: ^first_transaction_hash}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.from_address_to_transactions(adddress, pagination: %{page: 2, page_size: 1})
} = Chain.block_to_transactions(block, pagination: %{page: 2, page_size: 1})
end
end
describe "block_to_transaction_bound/1" do
test "without transactions" do
block = insert(:block)
assert Chain.block_to_transaction_count(block) == 0
end
test "with transactions" do
block = insert(:block)
insert(:transaction, block_hash: block.hash, index: 0)
assert Chain.block_to_transaction_count(block) == 1
end
end
describe "confirmations/1" do
test "with block.number == max_block_number " do
block = insert(:block)
max_block_number = Chain.max_block_number()
assert block.number == max_block_number
assert Chain.confirmations(block, max_block_number: max_block_number) == 0
end
test "with block.number < max_block_number" do
block = insert(:block)
max_block_number = block.number + 2
assert block.number < max_block_number
assert Chain.confirmations(block, max_block_number: max_block_number) == max_block_number - block.number
end
end
describe "fee/2" do
test "without receipt with :wei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :wei) ==
{:maximum, Decimal.new(6)}
end
test "without receipt with :gwei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :gwei) ==
{:maximum, Decimal.new("6e-9")}
end
test "without receipt with :ether unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :ether) ==
{:maximum, Decimal.new("6e-18")}
end
test "with receipt with :wei unit" do
assert Chain.fee(
%Transaction{
gas: Decimal.new(3),
gas_price: Decimal.new(2),
receipt: %Receipt{gas_used: Decimal.new(2)}
},
:wei
) == {:actual, Decimal.new(4)}
end
test "with receipt with :gwei unit" do
assert Chain.fee(
%Transaction{
gas: Decimal.new(3),
gas_price: Decimal.new(2),
receipt: %Receipt{gas_used: Decimal.new(2)}
},
:gwei
) == {:actual, Decimal.new("4e-9")}
end
test "with receipt with :ether unit" do
assert Chain.fee(
%Transaction{
gas: Decimal.new(3),
gas_price: Decimal.new(2),
receipt: %Receipt{gas_used: Decimal.new(2)}
},
:ether
) == {:actual, Decimal.new("4e-18")}
end
end
describe "gas_price/2" do
test ":wei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1)
end
test ":gwei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :gwei) == Decimal.new("1e-9")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e9")}, :gwei) == Decimal.new(1)
end
test ":ether unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :ether) == Decimal.new("1e-18")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e18")}, :ether) == Decimal.new(1)
end
end
@ -352,101 +474,6 @@ defmodule Explorer.ChainTest do
end
end
describe "to_address_to_transactions/2" do
test "without transactions" do
address = insert(:address)
assert Repo.aggregate(Transaction, :count, :hash) == 0
assert %Scrivener.Page{
entries: [],
page_number: 1,
total_entries: 0
} = Chain.to_address_to_transactions(address)
end
test "with transactions" do
%Transaction{to_address_hash: to_address_hash, hash: transaction_hash} = insert(:transaction)
address = Repo.get!(Address, to_address_hash)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.to_address_to_transactions(address)
end
test "with transactions with receipt required without receipt does not return transaction" do
address = %Address{hash: to_address_hash} = insert(:address)
block = insert(:block)
%Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} =
insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: to_address_hash)
insert(
:receipt,
transaction_hash: transaction_hash_with_receipt,
transaction_index: transaction_index_with_receipt
)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}],
page_number: 1,
total_entries: 1
} =
Chain.to_address_to_transactions(
address,
necessity_by_association: %{receipt: :required}
)
assert %Scrivener.Page{
entries: transactions,
page_number: 1,
total_entries: 2
} =
Chain.to_address_to_transactions(
address,
necessity_by_association: %{receipt: :optional}
)
assert length(transactions) == 2
transaction_by_hash =
Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} ->
{hash, transaction}
end)
assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt]
assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt]
end
test "with transactions can be paginated" do
adddress = %Address{hash: to_address_hash} = insert(:address)
transactions = insert_list(2, :transaction, to_address_hash: to_address_hash)
[%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions
assert %Scrivener.Page{
entries: [%Transaction{hash: ^newest_transaction_hash}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.to_address_to_transactions(adddress, pagination: %{page_size: 1})
assert %Scrivener.Page{
entries: [%Transaction{hash: ^oldest_transaction_hash}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.to_address_to_transactions(adddress, pagination: %{page: 2, page_size: 1})
end
end
describe "transaction_hash_to_internal_transactions/1" do
test "without transaction" do
{:ok, hash} =

@ -11,7 +11,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we
## Required Accounts
* Heroku for deployment
* Github for code storage

@ -60,12 +60,20 @@ exports.config = {
ignore: [/vendor/]
},
copycat: {
'fonts': ['node_modules/font-awesome/fonts']
},
sass: {
mode: 'native',
precision: 8,
allowCache: true,
options: {
includePaths: ['node_modules/bootstrap/scss', 'node_modules/jasmine-core/lib']
includePaths: [
'node_modules/bootstrap/scss',
'node_modules/font-awesome/scss',
'node_modules/jasmine-core/lib'
]
}
},

@ -1,17 +1,13 @@
body {
color: $ma-grey;
font-family: $ma-body-sans-serif;
color: $gray-500;
font-family: $font-family-sans-serif;
}
h1 {
font-size: 28px;
font-family: $ma-sans-serif;
@media screen and (max-width: 768px) {
font-size: 22px;
}
font-family: $font-family-sans-serif;
}
h2 {
@ -28,7 +24,7 @@ h4 {
font-size: 16px !important;
line-height: 24px !important;
font-weight: 300 !important;
color: $ma-md-grey !important;
color: $gray-600 !important;
margin-top: 4px !important;
&.underline {
@ -38,22 +34,16 @@ h4 {
p {
font-size: 12px;
color: $ma-md-grey;
color: $gray-600;
@media screen and (max-width: 768px) {
font-size: 14px !important;
}
}
.special {
font-size: 11px;
font-style: italic;
}
a {
text-decoration: none;
color: $ma-teal;
color: $gray-800;
font-size: 12px;
&:hover,
@ -61,7 +51,3 @@ a {
text-decoration: none;
}
}
.support {
font-size: 15px !important;
}

@ -461,25 +461,25 @@ $padding-xs: 5px;
// Border --------------------------------------------------
.u-border {
border: 1px solid $ma-darkblue;
border: 1px solid $primary;
&-left {
border-left: 1px solid lighten($gray-light, 42%);
border-left: 1px solid lighten($gray-300, 42%);
}
&-right {
border-right: 1px solid lighten($gray-light, 42%);
border-right: 1px solid lighten($gray-300, 42%);
}
&-top {
border-top: 1px solid lighten($gray-light, 42%);
border-top: 1px solid lighten($gray-300, 42%);
}
&-bottom {
border-bottom: 1px solid lighten($gray-light, 42%);
border-bottom: 1px solid lighten($gray-300, 42%);
&-bold {
border-bottom: 2px solid lighten($gray-light, 32%);
border-bottom: 2px solid lighten($gray-300, 32%);
}
}
@ -531,10 +531,6 @@ $padding-xs: 5px;
.u-underline {
text-decoration: underline;
&-active {
text-decoration-color:$ma-teal;
}
}
.u-list-style {
@ -575,7 +571,7 @@ $padding-xs: 5px;
right: 0;
height: 50%;
width: 100%;
background: linear-gradient(to bottom, rgba($ma-white, 0), rgba($ma-white, 1) 100%);
background: linear-gradient(to bottom, rgba($white, 0), rgba($white, 1) 100%);
}
}

@ -1,111 +0,0 @@
// Variables
// --------------------------------------------------
//== Colors
//
$ma-green: #80bb41;
$ma-grey: #414144;
$ma-md-grey: #797a7c;
$ma-lightgrey: #dddede;
$ma-teal: #009579;
$ma-blue: #2474bb;
$ma-darkblue: darken(#2474bb, 20%);
$ma-red: #c03b44;
$ma-white: #fff;
$ma-black: #2d2926;
$ma-evergreen: #16433c;
// Fonts
//
$ma-sans-serif: 'Boxed Bold', Helvetica, Arial, sans-serif;
$ma-body-sans-serif: 'Roboto', Helvetica, sans-serif;
// Bootstrap Variables:
$bootstrap-sass-asset-helper: false;
//## Gray and brand colors for use across Bootstrap.
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: lighten($gray-base, 20%) !default;
// #333
$gray: lighten($gray-base, 33.5%) !default; // #555
$gray-light: lighten($gray-base, 46.7%) !default; // #777
$gray-lighter: lighten($gray-base, 93.5%) !default; // #eee
$brand-primary: $ma-blue;
$brand-success: $ma-green;
$brand-info: #5bc0de !default;
$brand-warning: #f0ad4e !default;
$brand-danger: $ma-red;
//== Scaffolding
//
//## Settings for some of the most global styles.
//** Background color for `<body>`.
$body-bg: #fff !default; //** Global text color on `<body>`.
$text-color: $gray-dark !default;
$text-inactive: $ma-lightgrey;
//** Global textual link color.
$link-color: $ma-darkblue!default; //** Link hover color set via `darken()` function.
$link-hover-color: darken($link-color, 15%) !default; //** Link hover decoration.
$link-hover-decoration: underline !default;
//== Typography
//
//## Font, line-height, and color for body text, headings, and more.
$font-family-sans-serif: "Boxed Bold", Helvetica, Arial, sans-serif !default;
$font-family-body-san-serif: "Roboto", Helvetica, Arial, sans-serif !default; //** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
$font-family-base: $font-family-sans-serif !default;
//** Unit-less `line-height` for use in components like buttons.
$line-height-base: 1.428571429 !default; // 20/14
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
//** By default, this inherits from the `<body>`.
$headings-font-family: inherit !default;
$headings-font-weight: 500 !default;
$headings-line-height: 1.1 !default;
$headings-color: inherit !default;
//== Media queries breakpoints
//
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
// Extra small screen / phone
//** Deprecated `$screen-xs` as of v3.0.1
$screen-xs: 480px !default; //** Deprecated `$screen-xs-min` as of v3.2.0
$screen-xs-min: $screen-xs !default; //** Deprecated `$screen-phone` as of v3.0.1
$screen-phone: $screen-xs-min !default;
// Small screen / tablet
//** Deprecated `$screen-sm` as of v3.0.1
$screen-sm: 768px !default;
$screen-sm-min: $screen-sm !default; //** Deprecated `$screen-tablet` as of v3.0.1
$screen-tablet: $screen-sm-min !default;
// Medium screen / desktop
//** Deprecated `$screen-md` as of v3.0.1
$screen-md: 992px !default;
$screen-md-min: $screen-md !default; //** Deprecated `$screen-desktop` as of v3.0.1
$screen-desktop: $screen-md-min !default;
// Large screen / wide desktop
//** Deprecated `$screen-lg` as of v3.0.1
$screen-lg: 1200px !default;
$screen-lg-min: $screen-lg !default; //** Deprecated `$screen-lg-desktop` as of v3.0.1
$screen-lg-desktop: $screen-lg-min !default;
// So media queries don't overlap when required, provide a maximum
$screen-xs-max: ($screen-sm-min - 1) !default;
$screen-sm-max: ($screen-md-min - 1) !default;
$screen-md-max: ($screen-lg-min - 1) !default;
//== Grid system
//
//## Define your custom responsive grid.
//** Number of columns in the grid.
$grid-columns: 12 !default; //** Padding between columns. Gets divided in half for the left and right.
$grid-gutter-width: 30px !default; // Navbar collapse
//** Point at which the navbar becomes uncollapsed.
$grid-float-breakpoint: $screen-sm-min !default; //** Point at which the navbar begins collapsing.
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
$container-tablet: (720px + $grid-gutter-width) !default; //** For `$screen-sm-min` and up.
$container-sm: $container-tablet !default;
// Medium screen / desktop
$container-desktop: (940px + $grid-gutter-width) !default; //** For `$screen-md-min` and up.
$container-md: $container-desktop !default;
// Large screen / wide desktop
$container-large-desktop: (1140px + $grid-gutter-width) !default; //** For `$screen-lg-min` and up.
$container-lg: $container-large-desktop !default;

@ -5,11 +5,26 @@
.alert:empty { display: none; }
/* This file is for your main application css. */
$icon-font-path: "/fonts/"; /* use fonts from priv/static/fonts/ */
@import "bootstrap";
@import "explorer/sb-admin-2";
@import "variables";
@import "fonts";
@import "typography";
// Font Awesome
@import "font-awesome";
// Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions";
@import "theme/variables";
@import "node_modules/bootstrap/scss/mixins";
@import "node_modules/bootstrap/scss/root";
@import "node_modules/bootstrap/scss/reboot";
// Bootstrap Components
@import "node_modules/bootstrap/scss/dropdown";
@import "node_modules/bootstrap/scss/transitions";
// Custom SCSS
@import "explorer/wrapper";
@import "theme/fonts";
@import "utilities";
@import "typography";
@import "components/menu";
@import "explorer/button";
@import "explorer/filter";

@ -5,7 +5,6 @@
@import "block";
@import "blocks";
@import "chain";
@import "container";
@import "footer";
@import "header";
@import "internal_transaction";

@ -3,6 +3,7 @@
&__container {
padding: explorer-size(-1) explorer-size(0);
display: flex;
& + & { padding-top: 0; }
&--title { padding-top: explorer-size(0); }
}
@ -12,6 +13,7 @@
@include explorer-typography("title");
color: explorer-color("slate", "900");
margin: 0;
width: 90%;
}
&__headline-title { line-height: 18px; }

@ -1,54 +0,0 @@
%explorer-container-height {
margin-top: $explorer-header-small;
min-height: calc(100vh - #{$explorer-header-small} - #{explorer-size(-1) * 2});
}
@media (orientation: landscape) and (max-height: $explorer-breakpoint-landscape) {
%explorer-container-height {
margin-top: $explorer-header-medium;
min-height: calc(100vh - #{$explorer-header-medium} - #{explorer-size(-1) * 2});
}
}
@media (min-width: $explorer-breakpoint-landscape) {
%explorer-container-height {
margin-top: $explorer-header-large;
min-height: calc(100vh - #{$explorer-header-large} - #{explorer-size(-1) * 2});
}
}
.container {
@extend %explorer-container-height;
background: #f5f7fa;
padding-top: explorer-size(-1);
padding-bottom: explorer-size(-1);
&__section {
display: block;
padding: 0 explorer-size(-2);
margin: 0 auto;
}
}
@media (min-width: $explorer-breakpoint-md) {
.container {
&__section {
&--partitioned {
display: flex;
justify-content: space-between;
align-items: stretch;
}
}
&__subsection {
flex: 1;
margin-right: explorer-size(-2);
& + & { margin-left: explorer-size(-2); }
}
}
}

@ -1,8 +1,16 @@
.footer {
@include explorer-typography("body1");
font-size: 12px;
position: fixed;
width: calc(100% - 200px);
display: block;
background: explorer-color("slate", "900");
color: explorer-color("white");
bottom: 0;
background: explorer-color("gray", "50");
color:explorer-color("gray", "700");
text-align: center;
padding: explorer-size(-1) 0;
padding: 14px;
height: 50px;
@media (max-width: 768px) {
width: 100%;
}
}

@ -3,24 +3,32 @@
background: explorer-color("gray", "50");
box-shadow: 0 2px 2px 0 rgba(explorer-color("slate", "900"), 0.16),
0 0px 2px 0 rgba(explorer-color("slate", "900"), 0.12);
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
&__container {
margin-top: 7px;
}
&__row {
vertical-align: middle;
display: flex;
}
&__container { width: 100%; }
&__row { vertical-align: middle; }
&__cell {
@extend %explorer-header-height;
&--logo { @extend %explorer-header-square-width; }
&--links {
display: flex;
flex-direction: row-reverse;
padding-right: explorer-size(0);
}
&--links {
display: flex;
flex-direction: row-reverse;
padding-right: explorer-size(0);
}
&--search {
@include explorer-typography("body1");
padding-left: explorer-size(0);
padding-left: 8px;
width: 90%;
}
&--search-form {
@ -31,7 +39,7 @@
height: 16px;
width: 16px;
position: absolute;
top: 14px;
top: 18px;
left: 16px;
opacity: 0.5;
}
@ -40,65 +48,17 @@
width: 100%;
border: none;
background-color: transparent;
height: explorer-size(1);
padding-left: explorer-size(1);
padding-right: explorer-size(-1);
height: 50px;
padding-left: 45px;
@media (max-width: 768px) {
width: 90%;
padding-top: 5px;
}
}
&--search-input[placeholder] {
text-overflow: ellipsis;
}
}
}
&__logo-link {
@extend %explorer-header-height;
@extend %explorer-header-square-width;
display: flex;
background: explorer-color("blue", "500");
align-items: center;
justify-content: center;
}
&__logo {
width: 32px;
height: 36px;
display: block;
}
&__link {
@extend %explorer-header-height;
display: block;
text-decoration: none;
border-top: explorer-size(-4) solid transparent;
border-left: explorer-size(-4) solid transparent;
border-right: explorer-size(-4) solid transparent;
padding: explorer-size(-3);
color: explorer-color("slate", "900");
&:hover {
border-top-color: explorer-color("blue", "500");
color: explorer-color("blue", "500");
}
& + & { margin-right: explorer-size(-2); }
&--optional { display: none; }
}
&__link-name {
@include explorer-typography("body1");
margin-left: explorer-size(-3);
}
&__link-image, &__link-name {
display: inline-block;
vertical-align: middle;
}
}
@media (min-width: $explorer-breakpoint-sm) {
.header {
&__link {
&--optional { display: block; }
}
}
}

@ -0,0 +1,170 @@
/* ---------------------------------------------------
SIDEBAR STYLE
----------------------------------------------------- */
#sidebar {
min-width: 200px;
background: $gray-200;
position: sticky;
top: 0px;
@media (max-width: 768px) {
transition: all 0.6s cubic-bezier(0.945, 0.020, 0.270, 0.665);
transform-origin: bottom left;
}
}
#sidebar .active {
@media (max-width: 768px) {
margin-left: -200px;
transform: rotateY(100deg);
}
}
#sidebar .sidebar-header {
background-color: $primary;
color: $gray-800;
height: 66px;
padding: 12px 12px 17px;
text-align: center;
box-shadow:0 2px 2px 0 rgba(38,50,56,.16), 0 0 2px 0 rgba(38,50,56,.12);
}
#sidebar .menu-items {
list-style: none;
padding: 0.5em 0;
margin: 0;
div {
color: $gray-800;
padding: 24px 2px 24px 18px;
font-size: 13px;
font-weight: normal;
background-repeat: no-repeat;
background-position: left 20px center;
transition: all 0.15s linear;
cursor: pointer;
img {
width: 35px;
height: 100%;
margin-right: 5px;
}
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
&:focus {
outline: none;
}
}
}
#sidebar ul li.active > a, a[aria-expanded="true"] {
}
#sidebar li {
list-style-type: none;
padding-left: 5px;
line-height: 25px;
}
a[data-toggle="collapse"] {
position: relative;
}
#pageSubmenu {
margin-bottom: 5px;
}
/* ---------------------------------------------------
CONTENT STYLE
----------------------------------------------------- */
#sidebarCollapse {
display: none;
}
/* ---------------------------------------------------
MEDIAQUERIES
----------------------------------------------------- */
@media (max-width: 768px) {
#sidebar {
margin-left: -200px;
transform: rotateY(90deg);
height: 100vh;
position: fixed;
}
#sidebar.active {
margin-left: 0;
transform: none;
outline: none;
~ #content {
position: absolute;
right: -200px;
}
}
#sidebar .menu-items div {
padding-top: 11px;
}
#sidebarCollapse {
display: block;
width: 45px;
height: 65px;
background-image: none;
border-color: transparent !important;
box-shadow: 0 2px 2px 0 rgba(38,50,56,.16), 0 0 2px 0 rgba(38,50,56,.12);
outline: none;
}
#sidebarCollapse span {
width: 80%;
height: 2px;
margin: 0 auto;
display: block;
background: $black;
transition: all 0.8s;
}
#sidebarCollapse span:first-of-type,
#sidebarCollapse span:nth-of-type(2),
#sidebarCollapse span:last-of-type {
transform: none;
opacity: 1;
margin: 7px auto;
}
#sidebarCollapse.active span {
transform: none;
opacity: 1;
margin: 0 auto;
}
#sidebarCollapse.active span:first-of-type {
transform: rotate(45deg) translate(2px, 2px);
}
#sidebarCollapse.active span:nth-of-type(2) {
opacity: 0;
}
#sidebarCollapse.active span:last-of-type {
transform: rotate(-45deg) translate(1px, -1px);
}
}
.social-media {
font-size: 20px;
padding: 5px;
margin-left: 10px;
margin-top: 4px;
color: $gray-500;
}

@ -3,14 +3,25 @@
&__container {
padding: explorer-size(-1) explorer-size(0);
& + & { padding-top: 0; }
&--title { padding-top: explorer-size(0); }
&__flex {
display: flex;
}
& + & {
padding-top: 0;
}
&--title {
padding-top: explorer-size(0);
}
}
&__headline-title,
&__title {
@include explorer-typography("title");
color: explorer-color("slate", "900");
width: 90%;
}
&__title { margin: 0; }

@ -8,95 +8,61 @@
border-radius: .14589803rem;
&--primary {
background-color: $ma-white !important;
border: 1px solid $ma-green;
color: $ma-black !important;
background-color: $primary !important;
color: $black !important;
-webkit-transition: background-color .25s, color .25s !important;
transition: background-color .25s, color .25s;
&:hover,
&:focus {
background-color: $ma-grey !important;
color: lighten($ma-grey, 60%) !important;
background-color: $gray-700 !important;
color: $gray-200 !important;
text-decoration: none;
}
&:disabled {
background-color: $ma-lightgrey;
color: lighten($ma-grey,40%);
background-color: $gray-300;
color: $gray-800;
text-decoration: none;
}
}
&--secondary {
background-color: explorer-color("gray", "400");
color: $ma-white;
font-size: 13px;
border: 1px solid $gray-500;
color: $gray-600;
-webkit-transition: background-color .25s;
transition: background-color .25s;
font-weight: 300;
font-weight: 400;
&:hover,
&:focus {
background-color: explorer-color("slate", "900");
color: $ma-white;
background-color: explorer-color("gray", "400");
color: explorer-color("gray", "900");;
text-decoration: none;
outline: none !important;
}
}
&--tertiary {
background-color: lighten($ma-grey, 60%);
color: $ma-grey;
background-color: $gray-500;
color: $gray-800;
-webkit-transition: background-color .25s, color .25s;
transition: background-color .25s, color .25s;
&:hover,
&:focus {
background-color: $ma-grey;
color: lighten($ma-grey, 60%);
}
}
&--hidden {
color: darken($ma-green, 20%);
width: 112px;
font-weight: 400;
border: 1px solid lighten($ma-grey, 50%);
padding: 22px !important;
&:hover,
&:focus {
background-color: $ma-grey;
color: $ma-white;
text-decoration: none;
}
@media screen and (max-width: 768px) {
width: 100%;
}
}
&--activate {
border: 1px solid $ma-teal;
width: 100px;
color: $ma-teal;
-webkit-transition: background-color .25s, color .25s;
transition: background-color .25s, color .25s;
&:hover,
&:focus {
background-color: $gray-800;
color: $gray-400;
}
}
&--xsmall {
max-width: 40px;
font-size: 1rem;
font-size: 11px;
padding: 6px 9px 6px !important;
}
&--small {
font-size: 1.25rem;
font-size: 12px;
padding: 10px 20px 10px;
}
@ -119,8 +85,8 @@
}
&--disabled {
background-color: $ma-lightgrey;
color: lighten($ma-grey,40%);
background-color: $gray-300;
color: $gray-500;
text-decoration: none;
}

@ -0,0 +1,4 @@
.filter {
min-width: 100%;
transform: translate3d(1px, 26px, 0px) !important;
}

@ -1,430 +0,0 @@
body {
background-color: #f8f8f8;
}
#wrapper {
width: 100%;
}
#page-wrapper {
padding: 0 15px;
min-height: 568px;
background-color: white;
}
@media (min-width: 768px) {
#page-wrapper {
position: inherit;
margin: 0 0 0 250px;
padding: 0 30px;
border-left: 1px solid #e7e7e7;
}
}
.navbar-top-links {
margin-right: 0;
}
.navbar-top-links li {
display: inline-block;
}
.navbar-top-links li:last-child {
margin-right: 15px;
}
.navbar-top-links li a {
padding: 15px;
min-height: 50px;
}
.navbar-top-links .dropdown-menu li {
display: block;
}
.navbar-top-links .dropdown-menu li:last-child {
margin-right: 0;
}
.navbar-top-links .dropdown-menu li a {
padding: 3px 20px;
min-height: 0;
}
.navbar-top-links .dropdown-menu li a div {
white-space: normal;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
width: 310px;
min-width: 0;
}
.navbar-top-links .dropdown-messages {
margin-left: 5px;
}
.navbar-top-links .dropdown-tasks {
margin-left: -59px;
}
.navbar-top-links .dropdown-alerts {
margin-left: -123px;
}
.navbar-top-links .dropdown-user {
right: 0;
left: auto;
}
.sidebar .sidebar-nav.navbar-collapse {
padding-left: 0;
padding-right: 0;
}
.sidebar .sidebar-search {
padding: 15px;
}
.sidebar ul li {
border-bottom: 1px solid #e7e7e7;
}
.sidebar ul li a.active {
background-color: #eeeeee;
}
.sidebar .arrow {
float: right;
}
.sidebar .fa.arrow:before {
content: "\f104";
}
.sidebar .active > a > .fa.arrow:before {
content: "\f107";
}
.sidebar .nav-second-level li,
.sidebar .nav-third-level li {
border-bottom: none !important;
}
.sidebar .nav-second-level li a {
padding-left: 37px;
}
.sidebar .nav-third-level li a {
padding-left: 52px;
}
@media (min-width: 768px) {
.sidebar {
z-index: 1;
position: absolute;
width: 250px;
margin-top: 51px;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
margin-left: auto;
}
}
.btn-outline {
color: inherit;
background-color: transparent;
transition: all .5s;
}
.btn-primary.btn-outline {
color: #428bca;
}
.btn-success.btn-outline {
color: #5cb85c;
}
.btn-info.btn-outline {
color: #5bc0de;
}
.btn-warning.btn-outline {
color: #f0ad4e;
}
.btn-danger.btn-outline {
color: #d9534f;
}
.btn-primary.btn-outline:hover,
.btn-success.btn-outline:hover,
.btn-info.btn-outline:hover,
.btn-warning.btn-outline:hover,
.btn-danger.btn-outline:hover {
color: white;
}
.chat {
margin: 0;
padding: 0;
list-style: none;
}
.chat li {
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px dotted #999999;
}
.chat li.left .chat-body {
margin-left: 60px;
}
.chat li.right .chat-body {
margin-right: 60px;
}
.chat li .chat-body p {
margin: 0;
}
.panel .slidedown .glyphicon,
.chat .glyphicon {
margin-right: 5px;
}
.chat-panel .panel-body {
height: 350px;
overflow-y: scroll;
}
.login-panel {
margin-top: 25%;
}
.flot-chart {
display: block;
height: 400px;
}
.flot-chart-content {
width: 100%;
height: 100%;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
background: transparent;
}
table.dataTable thead .sorting_asc:after {
content: "\f0de";
float: right;
font-family: fontawesome;
}
table.dataTable thead .sorting_desc:after {
content: "\f0dd";
float: right;
font-family: fontawesome;
}
table.dataTable thead .sorting:after {
content: "\f0dc";
float: right;
font-family: fontawesome;
color: rgba(50, 50, 50, 0.5);
}
.btn-circle {
width: 30px;
height: 30px;
padding: 6px 0;
border-radius: 15px;
text-align: center;
font-size: 12px;
line-height: 1.428571429;
}
.btn-circle.btn-lg {
width: 50px;
height: 50px;
padding: 10px 16px;
border-radius: 25px;
font-size: 18px;
line-height: 1.33;
}
.btn-circle.btn-xl {
width: 70px;
height: 70px;
padding: 10px 16px;
border-radius: 35px;
font-size: 24px;
line-height: 1.33;
}
.show-grid [class^="col-"] {
padding-top: 10px;
padding-bottom: 10px;
border: 1px solid #ddd;
background-color: #eee !important;
}
.show-grid {
margin: 15px 0;
}
.huge {
font-size: 40px;
}
.panel-green {
border-color: #5cb85c;
}
.panel-green > .panel-heading {
border-color: #5cb85c;
color: white;
background-color: #5cb85c;
}
.panel-green > a {
color: #5cb85c;
}
.panel-green > a:hover {
color: #3d8b3d;
}
.panel-red {
border-color: #d9534f;
}
.panel-red > .panel-heading {
border-color: #d9534f;
color: white;
background-color: #d9534f;
}
.panel-red > a {
color: #d9534f;
}
.panel-red > a:hover {
color: #b52b27;
}
.panel-yellow {
border-color: #f0ad4e;
}
.panel-yellow > .panel-heading {
border-color: #f0ad4e;
color: white;
background-color: #f0ad4e;
}
.panel-yellow > a {
color: #f0ad4e;
}
.panel-yellow > a:hover {
color: #df8a13;
}
.timeline {
position: relative;
padding: 20px 0 20px;
list-style: none;
}
.timeline:before {
content: " ";
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 3px;
margin-left: -1.5px;
background-color: #eeeeee;
}
.timeline > li {
position: relative;
margin-bottom: 20px;
}
.timeline > li:before,
.timeline > li:after {
content: " ";
display: table;
}
.timeline > li:after {
clear: both;
}
.timeline > li:before,
.timeline > li:after {
content: " ";
display: table;
}
.timeline > li:after {
clear: both;
}
.timeline > li > .timeline-panel {
float: left;
position: relative;
width: 46%;
padding: 20px;
border: 1px solid #d4d4d4;
border-radius: 2px;
-webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
}
.timeline > li > .timeline-panel:before {
content: " ";
display: inline-block;
position: absolute;
top: 26px;
right: -15px;
border-top: 15px solid transparent;
border-right: 0 solid #ccc;
border-bottom: 15px solid transparent;
border-left: 15px solid #ccc;
}
.timeline > li > .timeline-panel:after {
content: " ";
display: inline-block;
position: absolute;
top: 27px;
right: -14px;
border-top: 14px solid transparent;
border-right: 0 solid #fff;
border-bottom: 14px solid transparent;
border-left: 14px solid #fff;
}
.timeline > li > .timeline-badge {
z-index: 100;
position: absolute;
top: 16px;
left: 50%;
width: 50px;
height: 50px;
margin-left: -25px;
border-radius: 50% 50% 50% 50%;
text-align: center;
font-size: 1.4em;
line-height: 50px;
color: #fff;
background-color: #999999;
}
.timeline > li.timeline-inverted > .timeline-panel {
float: right;
}
.timeline > li.timeline-inverted > .timeline-panel:before {
right: auto;
left: -15px;
border-right-width: 15px;
border-left-width: 0;
}
.timeline > li.timeline-inverted > .timeline-panel:after {
right: auto;
left: -14px;
border-right-width: 14px;
border-left-width: 0;
}
.timeline-badge.primary {
background-color: #2e6da4 !important;
}
.timeline-badge.success {
background-color: #3f903f !important;
}
.timeline-badge.warning {
background-color: #f0ad4e !important;
}
.timeline-badge.danger {
background-color: #d9534f !important;
}
.timeline-badge.info {
background-color: #5bc0de !important;
}
.timeline-title {
margin-top: 0;
color: inherit;
}
.timeline-body > p,
.timeline-body > ul {
margin-bottom: 0;
}
.timeline-body > p + p {
margin-top: 5px;
}
@media (max-width: 767px) {
ul.timeline:before {
left: 40px;
}
ul.timeline > li > .timeline-panel {
width: calc(10%);
width: -moz-calc(10%);
width: -webkit-calc(10%);
}
ul.timeline > li > .timeline-badge {
top: 16px;
left: 15px;
margin-left: 0;
}
ul.timeline > li > .timeline-panel {
float: right;
}
ul.timeline > li > .timeline-panel:before {
right: auto;
left: -15px;
border-right-width: 15px;
border-left-width: 0;
}
ul.timeline > li > .timeline-panel:after {
right: auto;
left: -14px;
border-right-width: 14px;
border-left-width: 0;
}
}

@ -0,0 +1,71 @@
.wrapper {
display: flex;
align-items: stretch;
height: 100vh !important;
}
#content {
width: 100%;
display: flex;
flex-direction: column;
min-height: 100vh;
transition: all 0.3s;
transition: all 0.6s cubic-bezier(0.945, 0.020, 0.270, 0.665);
position: absolute;
right: 0px;
@media (min-width: 768px) {
position: static;
}
}
.content-container {
padding-top: 90px;
@media (max-width: 768px){
padding-top: 40px;
}
}
.content-header {
width: calc(100% - 200px);
display: flex;
z-index: 1000;
@media (min-width: 768px){
position: absolute;
}
}
.container {
&__section {
display: block;
padding: 0 15px;
margin: 0 10px;
}
}
@media (min-width: 768px) {
.container {
&__section {
&--partitioned {
display: flex;
justify-content: space-between;
align-items: stretch;
}
}
&__subsection {
flex: 1;
margin-right: 10px;
& + & { margin-left: 5px; }
}
}
}

@ -31,9 +31,6 @@
font-weight: 700;
}
@font-face {
font-family: 'Boxed Bold';
src: url("/fonts/boxed-bold.otf");

@ -0,0 +1,928 @@
// Variables
//
// Variables should follow the `$component-state-property-size` formula for
// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
//
// Color system
//
// stylelint-disable
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #6c757d !default;
$gray-700: #495057 !default;
$gray-800: #343a40 !default;
$gray-900: #212529 !default;
$black: #000 !default;
$grays: () !default;
$grays: map-merge((
"100": $gray-100,
"200": $gray-200,
"300": $gray-300,
"400": $gray-400,
"500": $gray-500,
"600": $gray-600,
"700": $gray-700,
"800": $gray-800,
"900": $gray-900
), $grays);
$blue: #4786ff !default;
$indigo: #6610f2 !default;
$purple: #8940d5 !default;
$pink: #e83e8c !default;
$red: #dc3545 !default;
$orange: #fd7e14 !default;
$yellow: #ffc107 !default;
$green: #1bb85a !default;
$teal: #20c997 !default;
$cyan: #17a2b8 !default;
$colors: () !default;
$colors: map-merge((
"blue": $blue,
"indigo": $indigo,
"purple": $purple,
"pink": $pink,
"red": $red,
"orange": $orange,
"yellow": $yellow,
"green": $green,
"teal": $teal,
"cyan": $cyan,
"white": $white,
"gray": $gray-600,
"gray-dark": $gray-800
), $colors);
$primary: $blue !default;
$secondary: $gray-600 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-800 !default;
$theme-colors: () !default;
$theme-colors: map-merge((
"primary": $primary,
"secondary": $secondary,
"success": $success,
"info": $info,
"warning": $warning,
"danger": $danger,
"light": $light,
"dark": $dark
), $theme-colors);
// stylelint-enable
// Set a specific jump point for requesting color jumps
$theme-color-interval: 8% !default;
// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255.
$yiq-contrasted-threshold: 150 !default;
// Customize the light and dark text colors for use in our YIQ color contrast function.
$yiq-text-dark: $gray-900 !default;
$yiq-text-light: $white !default;
// Options
//
// Quickly modify global styling by enabling or disabling optional features.
$enable-caret: true !default;
$enable-rounded: true !default;
$enable-shadows: false !default;
$enable-gradients: false !default;
$enable-transitions: true !default;
$enable-hover-media-query: false !default; // Deprecated, no longer affects any compiled CSS
$enable-grid-classes: true !default;
$enable-print-styles: true !default;
// Spacing
//
// Control the default styling of most Bootstrap elements by modifying these
// variables. Mostly focused on spacing.
// You can add more entries to the $spacers map, should you need more variation.
// stylelint-disable
$spacer: 1rem !default;
$spacers: () !default;
$spacers: map-merge((
0: 0,
1: ($spacer * .25),
2: ($spacer * .5),
3: $spacer,
4: ($spacer * 1.5),
5: ($spacer * 3)
), $spacers);
// This variable affects the `.h-*` and `.w-*` classes.
$sizes: () !default;
$sizes: map-merge((
25: 25%,
50: 50%,
75: 75%,
100: 100%,
auto: auto
), $sizes);
// stylelint-enable
// Body
//
// Settings for the `<body>` element.
$body-bg: $white !default;
$body-color: $gray-900 !default;
// Links
//
// Style anchor elements.
$link-color: theme-color("primary") !default;
$link-decoration: none !default;
$link-hover-color: darken($link-color, 15%) !default;
$link-hover-decoration: underline !default;
// Paragraphs
//
// Style p element.
$paragraph-margin-bottom: 1rem !default;
// Grid breakpoints
//
// Define the minimum dimensions at which your layout will change,
// adapting to different screen sizes, for use in media queries.
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px
) !default;
@include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
@include _assert-starts-at-zero($grid-breakpoints);
// Grid containers
//
// Define the maximum width of `.container` for different screen sizes.
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px
) !default;
@include _assert-ascending($container-max-widths, "$container-max-widths");
// Grid columns
//
// Set the number of columns and specify the width of the gutters.
$grid-columns: 12 !default;
$grid-gutter-width: 30px !default;
// Components
//
// Define common padding and border radius sizes and more.
$line-height-lg: 1.5 !default;
$line-height-sm: 1.5 !default;
$border-width: 1px !default;
$border-color: $gray-300 !default;
$border-radius: .25rem !default;
$border-radius-lg: .3rem !default;
$border-radius-sm: .2rem !default;
$box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default;
$box-shadow: 0 .5rem 1rem rgba($black, .15) !default;
$box-shadow-lg: 0 1rem 3rem rgba($black, .175) !default;
$component-active-color: $white !default;
$component-active-bg: theme-color("primary") !default;
$caret-width: .3em !default;
$transition-base: all .2s ease-in-out !default;
$transition-fade: opacity .15s linear !default;
$transition-collapse: height .35s ease !default;
// Fonts
//
// Font, line-height, and color for body text, headings, and more.
// stylelint-disable value-keyword-case
$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
$font-family-base: $font-family-sans-serif !default;
// stylelint-enable value-keyword-case
$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
$font-size-lg: ($font-size-base * 1.25) !default;
$font-size-sm: ($font-size-base * .875) !default;
$font-weight-light: 300 !default;
$font-weight-normal: 400 !default;
$font-weight-bold: 700 !default;
$font-weight-base: $font-weight-normal !default;
$line-height-base: 1.5 !default;
$h1-font-size: $font-size-base * 2.5 !default;
$h2-font-size: $font-size-base * 2 !default;
$h3-font-size: $font-size-base * 1.75 !default;
$h4-font-size: $font-size-base * 1.5 !default;
$h5-font-size: $font-size-base * 1.25 !default;
$h6-font-size: $font-size-base !default;
$headings-margin-bottom: ($spacer / 2) !default;
$headings-font-family: inherit !default;
$headings-font-weight: 500 !default;
$headings-line-height: 1.2 !default;
$headings-color: inherit !default;
$display1-size: 6rem !default;
$display2-size: 5.5rem !default;
$display3-size: 4.5rem !default;
$display4-size: 3.5rem !default;
$display1-weight: 300 !default;
$display2-weight: 300 !default;
$display3-weight: 300 !default;
$display4-weight: 300 !default;
$display-line-height: $headings-line-height !default;
$lead-font-size: ($font-size-base * 1.25) !default;
$lead-font-weight: 300 !default;
$small-font-size: 80% !default;
$text-muted: $gray-600 !default;
$blockquote-small-color: $gray-600 !default;
$blockquote-font-size: ($font-size-base * 1.25) !default;
$hr-border-color: rgba($black, .1) !default;
$hr-border-width: $border-width !default;
$mark-padding: .2em !default;
$dt-font-weight: $font-weight-bold !default;
$kbd-box-shadow: inset 0 -.1rem 0 rgba($black, .25) !default;
$nested-kbd-font-weight: $font-weight-bold !default;
$list-inline-padding: .5rem !default;
$mark-bg: #fcf8e3 !default;
$hr-margin-y: $spacer !default;
// Tables
//
// Customizes the `.table` component with basic values, each used across all table variations.
$table-cell-padding: .75rem !default;
$table-cell-padding-sm: .3rem !default;
$table-bg: transparent !default;
$table-accent-bg: rgba($black, .05) !default;
$table-hover-bg: rgba($black, .075) !default;
$table-active-bg: $table-hover-bg !default;
$table-border-width: $border-width !default;
$table-border-color: $gray-300 !default;
$table-head-bg: $gray-200 !default;
$table-head-color: $gray-700 !default;
$table-dark-bg: $gray-900 !default;
$table-dark-accent-bg: rgba($white, .05) !default;
$table-dark-hover-bg: rgba($white, .075) !default;
$table-dark-border-color: lighten($gray-900, 7.5%) !default;
$table-dark-color: $body-bg !default;
$table-striped-order: odd !default;
$table-caption-color: $text-muted !default;
// Buttons + Forms
//
// Shared variables that are reassigned to `$input-` and `$btn-` specific variables.
$input-btn-padding-y: .375rem !default;
$input-btn-padding-x: .75rem !default;
$input-btn-line-height: $line-height-base !default;
$input-btn-focus-width: .2rem !default;
$input-btn-focus-color: rgba($component-active-bg, .25) !default;
$input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color !default;
$input-btn-padding-y-sm: .25rem !default;
$input-btn-padding-x-sm: .5rem !default;
$input-btn-line-height-sm: $line-height-sm !default;
$input-btn-padding-y-lg: .5rem !default;
$input-btn-padding-x-lg: 1rem !default;
$input-btn-line-height-lg: $line-height-lg !default;
$input-btn-border-width: $border-width !default;
// Buttons
//
// For each of Bootstrap's buttons, define text, background, and border color.
$btn-padding-y: $input-btn-padding-y !default;
$btn-padding-x: $input-btn-padding-x !default;
$btn-line-height: $input-btn-line-height !default;
$btn-padding-y-sm: $input-btn-padding-y-sm !default;
$btn-padding-x-sm: $input-btn-padding-x-sm !default;
$btn-line-height-sm: $input-btn-line-height-sm !default;
$btn-padding-y-lg: $input-btn-padding-y-lg !default;
$btn-padding-x-lg: $input-btn-padding-x-lg !default;
$btn-line-height-lg: $input-btn-line-height-lg !default;
$btn-border-width: $input-btn-border-width !default;
$btn-font-weight: $font-weight-normal !default;
$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
$btn-focus-width: $input-btn-focus-width !default;
$btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
$btn-disabled-opacity: .65 !default;
$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default;
$btn-link-disabled-color: $gray-600 !default;
$btn-block-spacing-y: .5rem !default;
// Allows for customizing button radius independently from global border radius
$btn-border-radius: $border-radius !default;
$btn-border-radius-lg: $border-radius-lg !default;
$btn-border-radius-sm: $border-radius-sm !default;
$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
// Forms
$label-margin-bottom: .5rem !default;
$input-padding-y: $input-btn-padding-y !default;
$input-padding-x: $input-btn-padding-x !default;
$input-line-height: $input-btn-line-height !default;
$input-padding-y-sm: $input-btn-padding-y-sm !default;
$input-padding-x-sm: $input-btn-padding-x-sm !default;
$input-line-height-sm: $input-btn-line-height-sm !default;
$input-padding-y-lg: $input-btn-padding-y-lg !default;
$input-padding-x-lg: $input-btn-padding-x-lg !default;
$input-line-height-lg: $input-btn-line-height-lg !default;
$input-bg: $white !default;
$input-disabled-bg: $gray-200 !default;
$input-color: $gray-700 !default;
$input-border-color: $gray-400 !default;
$input-border-width: $input-btn-border-width !default;
$input-box-shadow: inset 0 1px 1px rgba($black, .075) !default;
$input-border-radius: $border-radius !default;
$input-border-radius-lg: $border-radius-lg !default;
$input-border-radius-sm: $border-radius-sm !default;
$input-focus-bg: $input-bg !default;
$input-focus-border-color: lighten($component-active-bg, 25%) !default;
$input-focus-color: $input-color !default;
$input-focus-width: $input-btn-focus-width !default;
$input-focus-box-shadow: $input-btn-focus-box-shadow !default;
$input-placeholder-color: $gray-600 !default;
$input-plaintext-color: $body-color !default;
$input-height-border: $input-border-width * 2 !default;
$input-height-inner: ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2) !default;
$input-height: calc(#{$input-height-inner} + #{$input-height-border}) !default;
$input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2) !default;
$input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;
$input-height-inner-lg: ($font-size-lg * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2) !default;
$input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;
$input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
$form-text-margin-top: .25rem !default;
$form-check-input-gutter: 1.25rem !default;
$form-check-input-margin-y: .3rem !default;
$form-check-input-margin-x: .25rem !default;
$form-check-inline-margin-x: .75rem !default;
$form-check-inline-input-margin-x: .3125rem !default;
$form-group-margin-bottom: 1rem !default;
$input-group-addon-color: $input-color !default;
$input-group-addon-bg: $gray-200 !default;
$input-group-addon-border-color: $input-border-color !default;
$custom-control-gutter: 1.5rem !default;
$custom-control-spacer-x: 1rem !default;
$custom-control-indicator-size: 1rem !default;
$custom-control-indicator-bg: $gray-300 !default;
$custom-control-indicator-bg-size: 50% 50% !default;
$custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba($black, .1) !default;
$custom-control-indicator-disabled-bg: $gray-200 !default;
$custom-control-label-disabled-color: $gray-600 !default;
$custom-control-indicator-checked-color: $component-active-color !default;
$custom-control-indicator-checked-bg: $component-active-bg !default;
$custom-control-indicator-checked-disabled-bg: rgba(theme-color("primary"), .5) !default;
$custom-control-indicator-checked-box-shadow: none !default;
$custom-control-indicator-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
$custom-control-indicator-active-color: $component-active-color !default;
$custom-control-indicator-active-bg: lighten($component-active-bg, 35%) !default;
$custom-control-indicator-active-box-shadow: none !default;
$custom-checkbox-indicator-border-radius: $border-radius !default;
$custom-checkbox-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"), "#", "%23") !default;
$custom-checkbox-indicator-indeterminate-bg: $component-active-bg !default;
$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;
$custom-checkbox-indicator-icon-indeterminate: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E"), "#", "%23") !default;
$custom-checkbox-indicator-indeterminate-box-shadow: none !default;
$custom-radio-indicator-border-radius: 50% !default;
$custom-radio-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E"), "#", "%23") !default;
$custom-select-padding-y: .375rem !default;
$custom-select-padding-x: .75rem !default;
$custom-select-height: $input-height !default;
$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator
$custom-select-line-height: $input-btn-line-height !default;
$custom-select-color: $input-color !default;
$custom-select-disabled-color: $gray-600 !default;
$custom-select-bg: $input-bg !default;
$custom-select-disabled-bg: $gray-200 !default;
$custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions
$custom-select-indicator-color: $gray-800 !default;
$custom-select-indicator: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"), "#", "%23") !default;
$custom-select-border-width: $input-btn-border-width !default;
$custom-select-border-color: $input-border-color !default;
$custom-select-border-radius: $border-radius !default;
$custom-select-focus-border-color: $input-focus-border-color !default;
$custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5) !default;
$custom-select-font-size-sm: 75% !default;
$custom-select-height-sm: $input-height-sm !default;
$custom-select-font-size-lg: 125% !default;
$custom-select-height-lg: $input-height-lg !default;
$custom-range-track-width: 100% !default;
$custom-range-track-height: .5rem !default;
$custom-range-track-cursor: pointer !default;
$custom-range-track-bg: $gray-300 !default;
$custom-range-track-border-radius: 1rem !default;
$custom-range-track-box-shadow: inset 0 .25rem .25rem rgba($black, .1) !default;
$custom-range-thumb-width: 1rem !default;
$custom-range-thumb-height: $custom-range-thumb-width !default;
$custom-range-thumb-bg: $component-active-bg !default;
$custom-range-thumb-border: 0 !default;
$custom-range-thumb-border-radius: 1rem !default;
$custom-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default;
$custom-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
$custom-range-thumb-active-bg: lighten($component-active-bg, 35%) !default;
$custom-file-height: $input-height !default;
$custom-file-focus-border-color: $input-focus-border-color !default;
$custom-file-focus-box-shadow: $input-btn-focus-box-shadow !default;
$custom-file-padding-y: $input-btn-padding-y !default;
$custom-file-padding-x: $input-btn-padding-x !default;
$custom-file-line-height: $input-btn-line-height !default;
$custom-file-color: $input-color !default;
$custom-file-bg: $input-bg !default;
$custom-file-border-width: $input-btn-border-width !default;
$custom-file-border-color: $input-border-color !default;
$custom-file-border-radius: $input-border-radius !default;
$custom-file-box-shadow: $input-box-shadow !default;
$custom-file-button-color: $custom-file-color !default;
$custom-file-button-bg: $input-group-addon-bg !default;
$custom-file-text: (
en: "Browse"
) !default;
// Form validation
$form-feedback-margin-top: $form-text-margin-top !default;
$form-feedback-font-size: $small-font-size !default;
$form-feedback-valid-color: theme-color("success") !default;
$form-feedback-invalid-color: theme-color("danger") !default;
// Dropdowns
//
// Dropdown menu container and contents.
$dropdown-min-width: 10rem !default;
$dropdown-padding-y: .5rem !default;
$dropdown-spacer: .125rem !default;
$dropdown-bg: $white !default;
$dropdown-border-color: rgba($black, .15) !default;
$dropdown-border-radius: $border-radius !default;
$dropdown-border-width: $border-width !default;
$dropdown-divider-bg: $gray-200 !default;
$dropdown-box-shadow: 0 .5rem 1rem rgba($black, .175) !default;
$dropdown-link-color: $gray-900 !default;
$dropdown-link-hover-color: darken($gray-900, 5%) !default;
$dropdown-link-hover-bg: $gray-100 !default;
$dropdown-link-active-color: $component-active-color !default;
$dropdown-link-active-bg: $component-active-bg !default;
$dropdown-link-disabled-color: $gray-600 !default;
$dropdown-item-padding-y: .25rem !default;
$dropdown-item-padding-x: 1.5rem !default;
$dropdown-header-color: $gray-600 !default;
// Z-index master list
//
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
$zindex-dropdown: 1000 !default;
$zindex-sticky: 1020 !default;
$zindex-fixed: 1030 !default;
$zindex-modal-backdrop: 1040 !default;
$zindex-modal: 1050 !default;
$zindex-popover: 1060 !default;
$zindex-tooltip: 1070 !default;
// Navs
$nav-link-padding-y: .5rem !default;
$nav-link-padding-x: 1rem !default;
$nav-link-disabled-color: $gray-600 !default;
$nav-tabs-border-color: $gray-300 !default;
$nav-tabs-border-width: $border-width !default;
$nav-tabs-border-radius: $border-radius !default;
$nav-tabs-link-hover-border-color: $gray-200 $gray-200 $nav-tabs-border-color !default;
$nav-tabs-link-active-color: $gray-700 !default;
$nav-tabs-link-active-bg: $body-bg !default;
$nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default;
$nav-pills-border-radius: $border-radius !default;
$nav-pills-link-active-color: $component-active-color !default;
$nav-pills-link-active-bg: $component-active-bg !default;
$nav-divider-color: $gray-200 !default;
$nav-divider-margin-y: ($spacer / 2) !default;
// Navbar
$navbar-padding-y: ($spacer / 2) !default;
$navbar-padding-x: $spacer !default;
$navbar-nav-link-padding-x: .5rem !default;
$navbar-brand-font-size: $font-size-lg !default;
// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link
$nav-link-height: ($font-size-base * $line-height-base + $nav-link-padding-y * 2) !default;
$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default;
$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2 !default;
$navbar-toggler-padding-y: .25rem !default;
$navbar-toggler-padding-x: .75rem !default;
$navbar-toggler-font-size: $font-size-lg !default;
$navbar-toggler-border-radius: $btn-border-radius !default;
$navbar-dark-color: rgba($white, .5) !default;
$navbar-dark-hover-color: rgba($white, .75) !default;
$navbar-dark-active-color: $white !default;
$navbar-dark-disabled-color: rgba($white, .25) !default;
$navbar-dark-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
$navbar-dark-toggler-border-color: rgba($white, .1) !default;
$navbar-light-color: rgba($black, .5) !default;
$navbar-light-hover-color: rgba($black, .7) !default;
$navbar-light-active-color: rgba($black, .9) !default;
$navbar-light-disabled-color: rgba($black, .3) !default;
$navbar-light-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
$navbar-light-toggler-border-color: rgba($black, .1) !default;
// Pagination
$pagination-padding-y: .5rem !default;
$pagination-padding-x: .75rem !default;
$pagination-padding-y-sm: .25rem !default;
$pagination-padding-x-sm: .5rem !default;
$pagination-padding-y-lg: .75rem !default;
$pagination-padding-x-lg: 1.5rem !default;
$pagination-line-height: 1.25 !default;
$pagination-color: $link-color !default;
$pagination-bg: $white !default;
$pagination-border-width: $border-width !default;
$pagination-border-color: $gray-300 !default;
$pagination-focus-box-shadow: $input-btn-focus-box-shadow !default;
$pagination-focus-outline: 0 !default;
$pagination-hover-color: $link-hover-color !default;
$pagination-hover-bg: $gray-200 !default;
$pagination-hover-border-color: $gray-300 !default;
$pagination-active-color: $component-active-color !default;
$pagination-active-bg: $component-active-bg !default;
$pagination-active-border-color: $pagination-active-bg !default;
$pagination-disabled-color: $gray-600 !default;
$pagination-disabled-bg: $white !default;
$pagination-disabled-border-color: $gray-300 !default;
// Jumbotron
$jumbotron-padding: 2rem !default;
$jumbotron-bg: $gray-200 !default;
// Cards
$card-spacer-y: .75rem !default;
$card-spacer-x: 1.25rem !default;
$card-border-width: $border-width !default;
$card-border-radius: $border-radius !default;
$card-border-color: rgba($black, .125) !default;
$card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}) !default;
$card-cap-bg: rgba($black, .03) !default;
$card-bg: $white !default;
$card-img-overlay-padding: 1.25rem !default;
$card-group-margin: ($grid-gutter-width / 2) !default;
$card-deck-margin: $card-group-margin !default;
$card-columns-count: 3 !default;
$card-columns-gap: 1.25rem !default;
$card-columns-margin: $card-spacer-y !default;
// Tooltips
$tooltip-font-size: $font-size-sm !default;
$tooltip-max-width: 200px !default;
$tooltip-color: $white !default;
$tooltip-bg: $black !default;
$tooltip-border-radius: $border-radius !default;
$tooltip-opacity: .9 !default;
$tooltip-padding-y: .25rem !default;
$tooltip-padding-x: .5rem !default;
$tooltip-margin: 0 !default;
$tooltip-arrow-width: .8rem !default;
$tooltip-arrow-height: .4rem !default;
$tooltip-arrow-color: $tooltip-bg !default;
// Popovers
$popover-font-size: $font-size-sm !default;
$popover-bg: $white !default;
$popover-max-width: 276px !default;
$popover-border-width: $border-width !default;
$popover-border-color: rgba($black, .2) !default;
$popover-border-radius: $border-radius-lg !default;
$popover-box-shadow: 0 .25rem .5rem rgba($black, .2) !default;
$popover-header-bg: darken($popover-bg, 3%) !default;
$popover-header-color: $headings-color !default;
$popover-header-padding-y: .5rem !default;
$popover-header-padding-x: .75rem !default;
$popover-body-color: $body-color !default;
$popover-body-padding-y: $popover-header-padding-y !default;
$popover-body-padding-x: $popover-header-padding-x !default;
$popover-arrow-width: 1rem !default;
$popover-arrow-height: .5rem !default;
$popover-arrow-color: $popover-bg !default;
$popover-arrow-outer-color: fade-in($popover-border-color, .05) !default;
// Badges
$badge-font-size: 75% !default;
$badge-font-weight: $font-weight-bold !default;
$badge-padding-y: .25em !default;
$badge-padding-x: .4em !default;
$badge-border-radius: $border-radius !default;
$badge-pill-padding-x: .6em !default;
// Use a higher than normal value to ensure completely rounded edges when
// customizing padding or font-size on labels.
$badge-pill-border-radius: 10rem !default;
// Modals
// Padding applied to the modal body
$modal-inner-padding: 1rem !default;
$modal-dialog-margin: .5rem !default;
$modal-dialog-margin-y-sm-up: 1.75rem !default;
$modal-title-line-height: $line-height-base !default;
$modal-content-bg: $white !default;
$modal-content-border-color: rgba($black, .2) !default;
$modal-content-border-width: $border-width !default;
$modal-content-border-radius: $border-radius-lg !default;
$modal-content-box-shadow-xs: 0 .25rem .5rem rgba($black, .5) !default;
$modal-content-box-shadow-sm-up: 0 .5rem 1rem rgba($black, .5) !default;
$modal-backdrop-bg: $black !default;
$modal-backdrop-opacity: .5 !default;
$modal-header-border-color: $gray-200 !default;
$modal-footer-border-color: $modal-header-border-color !default;
$modal-header-border-width: $modal-content-border-width !default;
$modal-footer-border-width: $modal-header-border-width !default;
$modal-header-padding: 1rem !default;
$modal-lg: 800px !default;
$modal-md: 500px !default;
$modal-sm: 300px !default;
$modal-transition: transform .3s ease-out !default;
// Alerts
//
// Define alert colors, border radius, and padding.
$alert-padding-y: .75rem !default;
$alert-padding-x: 1.25rem !default;
$alert-margin-bottom: 1rem !default;
$alert-border-radius: $border-radius !default;
$alert-link-font-weight: $font-weight-bold !default;
$alert-border-width: $border-width !default;
$alert-bg-level: -10 !default;
$alert-border-level: -9 !default;
$alert-color-level: 6 !default;
// Progress bars
$progress-height: 1rem !default;
$progress-font-size: ($font-size-base * .75) !default;
$progress-bg: $gray-200 !default;
$progress-border-radius: $border-radius !default;
$progress-box-shadow: inset 0 .1rem .1rem rgba($black, .1) !default;
$progress-bar-color: $white !default;
$progress-bar-bg: theme-color("primary") !default;
$progress-bar-animation-timing: 1s linear infinite !default;
$progress-bar-transition: width .6s ease !default;
// List group
$list-group-bg: $white !default;
$list-group-border-color: rgba($black, .125) !default;
$list-group-border-width: $border-width !default;
$list-group-border-radius: $border-radius !default;
$list-group-item-padding-y: .75rem !default;
$list-group-item-padding-x: 1.25rem !default;
$list-group-hover-bg: $gray-100 !default;
$list-group-active-color: $component-active-color !default;
$list-group-active-bg: $component-active-bg !default;
$list-group-active-border-color: $list-group-active-bg !default;
$list-group-disabled-color: $gray-600 !default;
$list-group-disabled-bg: $list-group-bg !default;
$list-group-action-color: $gray-700 !default;
$list-group-action-hover-color: $list-group-action-color !default;
$list-group-action-active-color: $body-color !default;
$list-group-action-active-bg: $gray-200 !default;
// Image thumbnails
$thumbnail-padding: .25rem !default;
$thumbnail-bg: $body-bg !default;
$thumbnail-border-width: $border-width !default;
$thumbnail-border-color: $gray-300 !default;
$thumbnail-border-radius: $border-radius !default;
$thumbnail-box-shadow: 0 1px 2px rgba($black, .075) !default;
// Figures
$figure-caption-font-size: 90% !default;
$figure-caption-color: $gray-600 !default;
// Breadcrumbs
$breadcrumb-padding-y: .75rem !default;
$breadcrumb-padding-x: 1rem !default;
$breadcrumb-item-padding: .5rem !default;
$breadcrumb-margin-bottom: 1rem !default;
$breadcrumb-bg: $gray-200 !default;
$breadcrumb-divider-color: $gray-600 !default;
$breadcrumb-active-color: $gray-600 !default;
$breadcrumb-divider: quote("/") !default;
$breadcrumb-border-radius: $border-radius !default;
// Carousel
$carousel-control-color: $white !default;
$carousel-control-width: 15% !default;
$carousel-control-opacity: .5 !default;
$carousel-indicator-width: 30px !default;
$carousel-indicator-height: 3px !default;
$carousel-indicator-spacer: 3px !default;
$carousel-indicator-active-bg: $white !default;
$carousel-caption-width: 70% !default;
$carousel-caption-color: $white !default;
$carousel-control-icon-width: 20px !default;
$carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23") !default;
$carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23") !default;
$carousel-transition: transform .6s ease !default; // Define transform transition first if using multiple transitons (e.g., `transform 2s ease, opacity .5s ease-out`)
// Close
$close-font-size: $font-size-base * 1.5 !default;
$close-font-weight: $font-weight-bold !default;
$close-color: $black !default;
$close-text-shadow: 0 1px 0 $white !default;
// Code
$code-font-size: 87.5% !default;
$code-color: $pink !default;
$kbd-padding-y: .2rem !default;
$kbd-padding-x: .4rem !default;
$kbd-font-size: $code-font-size !default;
$kbd-color: $white !default;
$kbd-bg: $gray-900 !default;
$pre-color: $gray-900 !default;
$pre-scrollable-max-height: 340px !default;
// Printing
$print-page-size: a3 !default;
$print-body-min-width: map-get($grid-breakpoints, "lg") !default;

@ -22,3 +22,4 @@ import 'bootstrap'
// paths "./socket" or full ones "web/static/js/socket".
// import socket from "./socket"
import './lib/sidebar'

@ -0,0 +1,4 @@
$('#sidebarCollapse').on('click', function () {
$('#sidebar').toggleClass('active')
$(this).toggleClass('active')
})

@ -80,6 +80,11 @@
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
},
"ansi-color": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz",
"integrity": "sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o="
},
"ansi-escapes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
@ -1596,6 +1601,28 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"copycat-brunch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/copycat-brunch/-/copycat-brunch-1.1.0.tgz",
"integrity": "sha1-EOF7hZCeDhKZgd+5AwuYZkg9Zr0=",
"requires": {
"loggy": "0.2.2",
"mkdirp": "0.5.1",
"quickly-copy-file": "0.1.0"
},
"dependencies": {
"loggy": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/loggy/-/loggy-0.2.2.tgz",
"integrity": "sha1-ftyFcGqC12HOnO+Gjxr7rYQWVCc=",
"requires": {
"ansi-color": "0.2.1",
"date-utils": "1.2.21",
"growl": "1.8.1"
}
}
}
},
"core-js": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
@ -1829,6 +1856,11 @@
"assert-plus": "1.0.0"
}
},
"date-utils": {
"version": "1.2.21",
"resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz",
"integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -2638,6 +2670,11 @@
"resolved": "https://registry.npmjs.org/fn-args/-/fn-args-1.0.0.tgz",
"integrity": "sha1-l02voa6sSsfCH6Ccw7gPZQEG7TI="
},
"font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -5555,6 +5592,14 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
"quickly-copy-file": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/quickly-copy-file/-/quickly-copy-file-0.1.0.tgz",
"integrity": "sha1-0UBj/2WfaGl+fL/ugXoHu2NSN8I=",
"requires": {
"mkdirp": "0.5.1"
}
},
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",

@ -25,7 +25,9 @@
"babel-preset-react": "^6.24.1",
"bootstrap": "^4.1.0",
"brunch": "2.10.9",
"copycat-brunch": "^1.1.0",
"csswring": "^6.0.3",
"font-awesome": "^4.7.0",
"jquery": "^3.3.1",
"phoenix": "file:../../../deps/phoenix",
"phoenix_html": "file:../../../deps/phoenix_html",

@ -26,6 +26,7 @@ config :explorer_web, ExplorerWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE"),
url: [
scheme: "https",
host: Map.fetch!(System.get_env(), "HEROKU_APP_NAME") <> ".herokuapp.com",
# TODO update before prod push
host: "",
port: 443
]

@ -1,15 +1,7 @@
defmodule ExplorerWeb.AddressController do
use ExplorerWeb, :controller
alias Explorer.Chain
def show(conn, %{"id" => string}) do
with {:ok, hash} <- Chain.string_to_address_hash(string),
{:ok, address} <- Chain.hash_to_address(hash) do
render(conn, "show.html", address: address)
else
:error -> not_found(conn)
{:error, :not_found} -> not_found(conn)
end
def show(conn, %{"id" => id, "locale" => locale}) do
redirect(conn, to: address_transaction_path(conn, :index, locale, id))
end
end

@ -0,0 +1,48 @@
defmodule ExplorerWeb.AddressTransactionController do
@moduledoc """
Display all the Transactions that terminate at this Address.
"""
use ExplorerWeb, :controller
alias Explorer.Chain
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
options = [
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional,
receipt: :required
},
pagination: params
]
page =
Chain.address_to_transactions(
address,
Keyword.merge(options, current_filter(params))
)
render(conn, "index.html", address: address, filter: params["filter"], page: page)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
defp current_filter(params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to]
"from" -> [direction: :from]
_ -> []
end
end
end

@ -1,34 +0,0 @@
defmodule ExplorerWeb.AddressTransactionFromController do
@moduledoc """
Display all the Transactions that originate at this Address.
"""
use ExplorerWeb, :controller
alias Explorer.Chain
def index(conn, %{"address_id" => from_address_hash_string} = params) do
with {:ok, from_address_hash} <- Chain.string_to_address_hash(from_address_hash_string),
{:ok, from_address} <- Chain.hash_to_address(from_address_hash) do
page =
Chain.from_address_to_transactions(
from_address,
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional,
receipt: :required
},
pagination: params
)
render(conn, "index.html", page: page)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -1,34 +0,0 @@
defmodule ExplorerWeb.AddressTransactionToController do
@moduledoc """
Display all the Transactions that terminate at this Address.
"""
use ExplorerWeb, :controller
alias Explorer.Chain
def index(conn, %{"address_id" => to_address_hash_string} = params) do
with {:ok, to_address_hash} <- Chain.string_to_address_hash(to_address_hash_string),
{:ok, to_address} <- Chain.hash_to_address(to_address_hash) do
page =
Chain.to_address_to_transactions(
to_address,
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional,
receipt: :required
},
pagination: params
)
render(conn, "index.html", page: page)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -9,14 +9,7 @@ defmodule ExplorerWeb.BlockController do
render(conn, "index.html", blocks: blocks)
end
def show(conn, %{"id" => number}) do
case Chain.number_to_block(number) do
{:ok, block} ->
block_transaction_count = Chain.block_to_transaction_count(block)
render(conn, "show.html", block: block, block_transaction_count: block_transaction_count)
{:error, :not_found} ->
not_found(conn)
end
def show(conn, %{"id" => number, "locale" => locale}) do
redirect(conn, to: block_transaction_path(conn, :index, locale, number))
end
end

@ -7,7 +7,8 @@ defmodule ExplorerWeb.BlockTransactionController do
def index(conn, %{"block_id" => formatted_block_number} = params) do
with {:ok, block_number} <- param_to_block_number(formatted_block_number),
{:ok, block} <- Chain.number_to_block(block_number) do
{:ok, block} <- Chain.number_to_block(block_number, necessity_by_association: %{miner: :required}),
block_transaction_count <- Chain.block_to_transaction_count(block) do
page =
Chain.block_to_transactions(
block,
@ -20,7 +21,7 @@ defmodule ExplorerWeb.BlockTransactionController do
pagination: params
)
render(conn, "index.html", page: page)
render(conn, "index.html", block: block, block_transaction_count: block_transaction_count, page: page)
else
{:error, :invalid} ->
not_found(conn)

@ -55,17 +55,10 @@ defmodule ExplorerWeb.Router do
resources "/addresses", AddressController, only: [:show] do
resources(
"/transactions_to",
AddressTransactionToController,
"/transactions",
AddressTransactionController,
only: [:index],
as: :transaction_to
)
resources(
"/transactions_from",
AddressTransactionFromController,
only: [:index],
as: :transaction_from
as: :transaction
)
end

@ -1,38 +0,0 @@
<section class="container__section address">
<div class="address__header">
<h1 class="address__heading"><%= gettext "Address" %></h1>
<h3 class="address__subheading"><%= @address.hash %></h3>
</div>
<div class="address__container">
<div class="address__tabs">
<h2 class="address__tab address__tab--active">
<%= link(
gettext("Overview"),
to: address_path(@conn, :show, @conn.assigns.locale, @address.hash),
class: "address__link address__link--active"
) %>
</h2>
<h2 class="address__tab">
<%= link(gettext("Transactions To"), to: address_transaction_to_path(@conn, :index, @conn.assigns.locale, @address.hash), class: "address__link") %>
</h2>
<h2 class="address__tab">
<%= link(
gettext("Transactions From"),
to: address_transaction_from_path(@conn, :index, @conn.assigns.locale, @address.hash),
class: "address__link"
) %>
</h2>
</div>
<div class="address__attributes">
<dl>
<div class="address__item">
<dt class="address__item-key"><%= gettext "Balance" %></dt>
<dd class="address__item-value address__balance" title="<%= @address.hash %>">
<%= format_balance(@address.balance) %> <%= gettext "Ether" %>
</dd>
</div>
</dl>
</div>
</div>
</section>

@ -1,43 +1,65 @@
<section class="container__section block">
<div class="address__headline">
<h1 class="address__headline-title"><%= gettext("Address %{number}", number: @conn.params["address_id"]) %></h1>
<div class="address__pagination">
<%= pagination_links(
@conn,
@page,
["en", @conn.params["address_id"]],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &address_transaction_to_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
<div class="address__header">
<h1 class="address__heading"><%= gettext "Address" %></h1>
<h3 class="address__subheading"><%= @address.hash %></h3>
</div>
<div class="address__container">
<div class="address__attributes">
<dl>
<div class="address__item">
<dt class="address__item-key"><%= gettext "Balance" %></dt>
<dd class="address__item-value address__balance" title="<%= @address.hash %>">
<%= balance(@address) %> <%= gettext "Ether" %>
</dd>
</div>
</dl>
</div>
</div>
<div class="address__container">
<div class="address__tabs">
<h2 class="address__tab">
<%= link(
gettext("Overview"),
class: "address__link",
to: address_path(@conn, :show, @conn.assigns.locale, @conn.params["address_id"])
) %>
</h2>
<h2 class="address__tab address__tab--active">
<%= link(
gettext("Transactions To"),
gettext("Transactions"),
class: "address__link address__link--active",
to: address_transaction_to_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
to: address_transaction_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
</h2>
<h2 class="address__tab">
</div>
<div class="dropdown u-float-right u-push-sm-right u-push-sm-bottom">
<button data-test="filter_dropdown" class="button button--secondary dropdown-toggle" type="button"
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filter: <%= format_current_filter(@filter) %>
</button>
<div class="dropdown-menu dropdown-menu-right filter" aria-labelledby="dropdownMenu2">
<%= link(
gettext("Transactions From"),
class: "address__link",
to: address_transaction_from_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
gettext("All"),
class: "address__link address__link--active dropdown-item",
to: address_transaction_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
</h2>
<%= link(
gettext("To"),
class: "address__link address__link--active dropdown-item",
to: address_transaction_path(
@conn,
:index,
@conn.assigns.locale,
@conn.params["address_id"],
filter: "to"
)
) %>
<%= link(
gettext("From"),
class: "address__link address__link--active dropdown-item",
to: address_transaction_path(
@conn,
:index,
@conn.assigns.locale,
@conn.params["address_id"],
filter: "from"
)
) %>
</div>
</div>
<div class="transactions__container">
<table class="transactions__table">
@ -52,6 +74,7 @@
<th class="transactions__column-header transactions__column-header--optional"><%= gettext "From" %></th>
<th class="transactions__column-header transactions__column-header--optional"><%= gettext "To" %></th>
<th class="transactions__column-header"><%= gettext "Value" %></th>
<th class="transactions__column-header"><%= gettext "Fee" %></th>
</tr>
</thead>
<tbody>
@ -100,10 +123,27 @@
<td class="transactions__column transactions__column--value">
<%= value(transaction) %> <%= gettext "Ether" %>
</td>
<td class="transactions__column transactions__column--value">
<%= fee(transaction) %> <%= gettext "Ether" %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<div class="address__pagination">
<%= pagination_links(
@conn,
@page,
["en", @conn.params["address_id"]],
distance: 1,
filter: @conn.params["filter"],
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &address_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
</section>

@ -1,108 +0,0 @@
<section class="container__section block">
<div class="address__headline">
<h1 class="address__headline-title"><%= gettext("Address %{number}", number: @conn.params["address_id"]) %></h1>
<div class="address__pagination"><%= pagination_links @conn, @page, ["en", @conn.params["address_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &address_transaction_to_path/5 %></div>
<div class="address__pagination">
<%= pagination_links(
@conn,
@page,
["en", @conn.params["address_id"]],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &address_transaction_to_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
</div>
<div class="address__container">
<div class="address__tabs">
<h2 class="address__tab">
<%= link(
gettext("Overview"),
class: "address__link",
to: address_path(@conn, :show, @conn.assigns.locale, @conn.params["address_id"])
) %>
</h2>
<h2 class="address__tab">
<%= link(
gettext("Transactions To"),
class: "address__link",
to: address_transaction_to_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
</h2>
<h2 class="address__tab address__tab--active">
<%= link(
gettext("Transactions From"),
class: "address__link address__link--active",
to: address_transaction_from_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
</h2>
</div>
<div class="transactions__container">
<table class="transactions__table">
<thead class="transactions__header">
<tr>
<th class="transactions__column-header transactions__column-header--status">
<span class="transactions__column-title transactions__column-title--status"><%= gettext "Status" %></span>
</th>
<th class="transactions__column-header"><%= gettext "Hash" %></th>
<th class="transactions__column-header transactions__column-header--optional"><%= gettext "Block" %></th>
<th class="transactions__column-header"><%= gettext "Age" %></th>
<th class="transactions__column-header transactions__column-header--optional"><%= gettext "From" %></th>
<th class="transactions__column-header transactions__column-header--optional"><%= gettext "To" %></th>
<th class="transactions__column-header"><%= gettext "Value" %></th>
</tr>
</thead>
<tbody>
<%= for transaction <- @page.entries do %>
<tr class="transactions__row">
<td class="transactions__column transactions__column--status">
<div class="transactions__dot transactions__dot--<%= status(transaction) %>"></div>
</td>
<td class="transactions__column transactions__column--hash">
<div class="transactions__hash">
<%= link(
hash(transaction),
class: "transactions__link transactions__link--truncated transactions__link--long-hash",
to: transaction_path(@conn, :show, @conn.assigns.locale, transaction)
) %>
</div>
</td>
<td class="transactions__column transactions__column--block transactions__column--optional">
<%= link(
block(transaction),
class: "transactions__link",
to: block_path(@conn, :show, @conn.assigns.locale, transaction.block)
) %>
</td>
<td class="transactions__column transactions__column--age">
<%= transaction.block.timestamp |> Timex.from_now %>
</td>
<td class="transactions__column transactions__column--from transactions__column--optional">
<div class="transactions__hash">
<%= link(
from_address(transaction),
class: "transactions__link transactions__link--truncated transactions__link--hash",
to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address)
) %>
</div>
</td>
<td class="transactions__column transactions__column--to transactions__column--optional">
<div class="transactions__hash">
<%= link(
to_address(transaction),
class: "transactions__link transactions__link--truncated transactions__link--hash",
to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address)
) %>
</div>
</td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</section>

@ -1,99 +0,0 @@
<section class="container__section block">
<div class="block__header">
<h1 class="block__heading"><%= gettext "Block #%{number} Details", number: @block.number %></h1>
</div>
<div class="block__container">
<div class="block__tabs">
<h2 class="block__tab block__tab--active">
<%= link(
gettext("Overview"),
class: "block__link block__link--active",
to: block_path(@conn, :show, @conn.assigns.locale, @block.number)
) %>
</h2>
<h2 class="block__tab">
<%= link(
gettext("Transactions"),
class: "block__link",
to: block_transaction_path(@conn, :index, @conn.assigns.locale, @block.number)
) %>
</h2>
</div>
<div class="block__attributes">
<div class="block__column">
<dl>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Number" %></dt>
<dd class="block__item-value"><%= @block.number %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Timestamp" %></dt>
<dd class="block__item-value"><%= age(@block) %> (<%= formatted_timestamp(@block) %>)</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Transactions" %></dt>
<dd class="block__item-value">
<%= gettext "%{count} transactions in this block", count: @block_transaction_count %>
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Hash" %></dt>
<% hash = hash(@block) %>
<dd class="block__item-value" title="<%= hash %>"><%= hash %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Parent Hash" %></dt>
<% parent_hash = parent_hash(@block) %>
<dd class="block__item-value" title="<%= parent_hash %>">
<%= link(
parent_hash,
class: "block__link",
to: block_path(@conn, :show, @conn.assigns.locale, @block.number - 1)
) %>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Miner" %></dt>
<% miner_hash = miner_hash(@block) %>
<dd class="block__item-value" title="<%= miner_hash %>"><%= miner_hash %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Difficulty" %></dt>
<dd class="block__item-value" title="<%= @block.difficulty %>">
<%= @block.difficulty |> Cldr.Number.to_string! %>
</dd>
</div>
</dl>
</div>
<div class="block__column">
<dl>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Total Difficulty" %></dt>
<dd class="block__item-value" title="<%= @block.total_difficulty %>">
<%= @block.total_difficulty |> Cldr.Number.to_string! %>
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Size" %></dt>
<dd class="block__item-value"><%= Cldr.Unit.new(:byte, @block.size) |> Cldr.Unit.to_string! %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Gas Used" %></dt>
<dd class="block__item-value">
<%= @block.gas_used
|> Cldr.Number.to_string! %> (<%= (@block.gas_used / @block.gas_limit)
|> Cldr.Number.to_string!(format: "#.#%") %>)
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Gas Limit" %></dt>
<dd class="block__item-value"><%= @block.gas_limit |> Cldr.Number.to_string! %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Nonce" %></dt>
<dd class="block__item-value"><%= @block.nonce %></dd>
</div>
</dl>
</div>
</div>
</div>
</section>

@ -1,28 +1,84 @@
<section class="container__section block">
<div class="blocks__headline">
<h1 class="blocks__headline-title"><%= gettext("Showing #%{number}", number: @conn.params["block_id"]) %></h1>
<div class="blocks__pagination">
<%= pagination_links(
@conn,
@page,
["en", @conn.params["block_id"]],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &block_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %></div>
<div class="block__header">
<h1 class="block__heading"><%= gettext("Block Details") %></h1>
<h2 class="block__subheading"><%= @block.number %></h2>
</div>
<div class="block__container">
<div class="block__attributes">
<div class="block__column">
<dl>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Timestamp" %></dt>
<dd class="block__item-value"><%= age(@block) %> (<%= formatted_timestamp(@block) %>)</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Transactions" %></dt>
<dd class="block__item-value">
<%= gettext "%{count} transactions in this block", count: @block_transaction_count %>
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Hash" %></dt>
<dd class="block__item-value" title="<%= @block.hash %>"><%= @block.hash %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Parent Hash" %></dt>
<dd class="block__item-value" title="<%= @block.parent_hash %>">
<%= link(
@block.parent_hash,
class: "block__link",
to: block_path(@conn, :show, @conn.assigns.locale, @block.number - 1)
) %>
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Miner" %></dt>
<dd class="block__item-value" title="<%= @block.miner %>"><%= @block.miner %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Difficulty" %></dt>
<dd class="block__item-value" title="<%= @block.difficulty %>">
<%= @block.difficulty |> Cldr.Number.to_string! %>
</dd>
</div>
</dl>
</div>
<div class="block__column">
<dl>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Total Difficulty" %></dt>
<dd class="block__item-value" title="<%= @block.total_difficulty %>">
<%= @block.total_difficulty |> Cldr.Number.to_string! %>
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Size" %></dt>
<dd class="block__item-value"><%= Cldr.Unit.new(:byte, @block.size) |> Cldr.Unit.to_string! %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Gas Used" %></dt>
<dd class="block__item-value">
<%= @block.gas_used
|> Cldr.Number.to_string! %> (<%= (@block.gas_used / @block.gas_limit)
|> Cldr.Number.to_string!(format: "#.#%") %>)
</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Gas Limit" %></dt>
<dd class="block__item-value"><%= @block.gas_limit |> Cldr.Number.to_string! %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Nonce" %></dt>
<dd class="block__item-value"><%= @block.nonce %></dd>
</div>
</dl>
</div>
</div>
</div>
<div class="block__container">
<div class="block__tabs">
<h2 class="block__tab">
<%= link(
gettext("Overview"),
class: "block__link",
to: block_path(@conn, :show, @conn.assigns.locale, @conn.params["block_id"])
) %>
</h2>
<h2 class="block__tab block__tab--active">
<%= link(
gettext("Transactions"),
@ -95,4 +151,16 @@
</table>
</div>
</div>
<div class="blocks__pagination">
<%= pagination_links(
@conn,
@page,
["en", @conn.params["block_id"]],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &block_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %></div>
</section>

@ -56,6 +56,12 @@
<div class="blocks container__subsection">
<div class="blocks__container blocks__container--title">
<h2 class="blocks__title"><%= gettext "Blocks" %></h2>
<%= link to: block_path(@conn, :index, Gettext.get_locale), class: "header__link" do %>
<div class="button button--secondary button--xsmall u-float-right">View All</div>
<% end %>
</div>
<div>
</div>
<div class="blocks__container">
<table class="blocks__table">
@ -89,8 +95,11 @@
</div>
</div>
<div class="transactions container__subsection">
<div class="transactions__container transactions__container--title">
<div class="transactions__container transactions__container__flex transactions__container--title">
<h2 class="transactions__title"><%= gettext "Transactions" %></h2>
<%= link to: transaction_path(@conn, :index, Gettext.get_locale), class: "header__link" do %>
<div class="button button--secondary button--xsmall u-float-right">View All</div>
<% end %>
</div>
<div class="transactions__container">
<table class="transactions__table">

@ -1,32 +0,0 @@
<header class="header">
<table class="header__container" cellspacing="0" cellpadding="0" border="0">
<tr class="header__row">
<td class="header__cell header__cell--logo">
<a href="<%= chain_path(@conn, :show) %>" class="header__logo-link">
<%= logo_image(@conn, alt: gettext("POA Network Explorer"), class: "header__logo") %>
</a>
</td>
<td class="header__cell header__cell--search">
<%= form_for(
@conn,
chain_path(@conn, :search, Gettext.get_locale),
[class: "header__cell--search-form", method: :get, enforce_utf8: false],
fn f -> %>
<%= img_tag :svg, src: static_path(@conn, "/images/mgi.svg"), class: "header__cell--search-glass" %>
<%= search_input(
f,
:q,
class: 'header__cell--search-input',
placeholder: gettext("Search by address, transaction hash, or block number")
) %>
<% end) %>
</td>
<td class="header__cell header__cell--links" align="right">
<a href="<%= block_path(@conn, :index, Gettext.get_locale) %>" class="header__link">
<img class="header__link-image" src="<%= static_path(@conn, "/images/block.svg") %>" />
<div class="header__link-name header__link-name--blocks"><%= gettext("Blocks") %></div>
</a>
</td>
</tr>
</table>
</header>

@ -7,14 +7,86 @@
<title><%= gettext "POA Network Explorer" %></title>
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
</head>
<body>
<%= render ExplorerWeb.LayoutView, "_header.html", assigns %>
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<main role="main" class="container">
<%= render @view_module, @view_template, assigns %>
</main>
<%= render ExplorerWeb.LayoutView, "_footer.html", assigns %>
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
<div class="wrapper wrapper-overflow">
<!-- Sidebar Holder -->
<nav id="sidebar" class="sticky fixed-top">
<div class="sidebar-header">
<%= link to: chain_path(@conn, :show), "data-test": "header_logo" do %>
<img class="" src="<%= static_path(@conn, "/images/logo.svg") %>" />
<% end %>
</div>
<div class="menu-items">
<%= link to: block_path(@conn, :index, Gettext.get_locale), class: "header__link" do %>
<div tabindex="0" class="icon-locate">
<img class="" src="<%= static_path(@conn, "/images/block.svg") %>" />
<%= gettext("Blocks") %>
</div>
<% end %>
<div tabindex="0" class="icon-locate">
<a href="#pageSubmenu" data-toggle="collapse" aria-expanded="false">
<img class="" src="<%= static_path(@conn, "/images/transaction.svg") %>" />
<%= gettext("Transactions") %>
<i class="fa fa-caret-down"></i>
</a>
<ul class="collapse list-unstyled" id="pageSubmenu">
<li><%= link(gettext("Validated"), to: transaction_path(@conn, :index, Gettext.get_locale)) %></li>
<li><%= link(gettext("Pending"), to: pending_transaction_path(@conn, :index, Gettext.get_locale)) %></li>
</ul>
</div>
</div>
</nav>
<!-- Page Content Holder -->
<div id="content" class="size-content">
<div class="content-header sticky fixed-top">
<nav>
<button type="button" id="sidebarCollapse" class="navbar-btn" data-test="hamburger_menu_button">
<span></span>
<span></span>
<span></span>
</button>
</nav>
<header class="header">
<div class="header__container" cellspacing="0" cellpadding="0" border="0">
<div class="header__row">
<div class="header__cell header__cell--search">
<%= form_for @conn, chain_path(@conn, :search, Gettext.get_locale), [class: "header__cell--search-form", method: :get, enforce_utf8: false], fn f -> %>
<%= img_tag :svg, src: static_path(@conn, "/images/mgi.svg"), class: "header__cell--search-glass" %>
<%= search_input f, :q, class: 'header__cell--search-input', placeholder: gettext "Search by address, transaction hash, or block number" %>
<% end %>
</div>
<div class="header__cell header__cell--links" align="right">
<a href="https://www.facebook.com/PoaNetwork/" target="_blank" class="header__link">
<div class="social-media">
<i class="fa fa-facebook-f"></i>
</div>
</a>
<a href="https://www.instagram.com/PoaNetwork/" target="_blank" class="header__link">
<div class="social-media">
<i class="fa fa-instagram"></i>
</div>
</a>
<a href="https://www.twitter.com/PoaNetwork/" target="_blank" class="header__link">
<div class="social-media">
<i class="fa fa-twitter"></i>
</div>
</a>
</div>
</div>
</div>
</header>
</div>
<div class="content-container">
<main>
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= render @view_module, @view_template, assigns %>
<%= render ExplorerWeb.LayoutView, "_footer.html", assigns %>
</main>
</div>
<!-- <%= render ExplorerWeb.LayoutView, "_footer.html", assigns %> -->
</div>
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>

@ -1,12 +0,0 @@
defmodule ExplorerWeb.AddressTransactionFromView do
use ExplorerWeb, :view
alias ExplorerWeb.TransactionView
defdelegate block(transaction), to: TransactionView
defdelegate from_address(transaction), to: TransactionView
defdelegate hash(transaction), to: TransactionView
defdelegate status(transacton), to: TransactionView
defdelegate to_address(transaction), to: TransactionView
defdelegate value(transaction), to: TransactionView
end

@ -1,11 +1,22 @@
defmodule ExplorerWeb.AddressTransactionToView do
defmodule ExplorerWeb.AddressTransactionView do
use ExplorerWeb, :view
alias ExplorerWeb.TransactionView
alias ExplorerWeb.{AddressView, TransactionView}
defdelegate balance(address), to: AddressView
defdelegate block(transaction), to: TransactionView
defdelegate fee(transaction), to: TransactionView
defdelegate from_address(transaction), to: TransactionView
defdelegate hash(transaction), to: TransactionView
def format_current_filter(filter) do
case filter do
"to" -> gettext("To")
"from" -> gettext("From")
_ -> gettext("All")
end
end
defdelegate status(transacton), to: TransactionView
defdelegate to_address(transaction), to: TransactionView
defdelegate value(transaction), to: TransactionView

@ -1,13 +1,16 @@
defmodule ExplorerWeb.AddressView do
use ExplorerWeb, :view
@dialyzer :no_match
def format_balance(nil), do: "0"
alias Explorer.Chain
@dialyzer :no_match
def format_balance(balance) do
balance
|> Decimal.new()
|> Decimal.div(Decimal.new(1_000_000_000_000_000_000))
|> Decimal.to_string(:normal)
def balance(address) do
address
|> Chain.balance(:ether)
|> case do
nil -> ""
ether -> Cldr.Number.to_string!(ether, fractional_digits: 18)
end
end
end

@ -1,7 +1,7 @@
defmodule ExplorerWeb.BlockTransactionView do
use ExplorerWeb, :view
alias ExplorerWeb.TransactionView
alias ExplorerWeb.{BlockView, TransactionView}
# Functions
@ -11,4 +11,6 @@ defmodule ExplorerWeb.BlockTransactionView do
defdelegate status(transacton), to: TransactionView
defdelegate to_address(transaction), to: TransactionView
defdelegate value(transaction), to: TransactionView
defdelegate age(block), to: BlockView
defdelegate formatted_timestamp(block), to: BlockView
end

@ -1,9 +1,3 @@
defmodule ExplorerWeb.LayoutView do
use ExplorerWeb, :view
def logo_image(conn, alt: alt, class: class) do
conn
|> static_path("/images/logo.svg")
|> img_tag(class: class, alt: alt)
end
end

@ -29,6 +29,18 @@ defmodule ExplorerWeb.TransactionView do
end
end
def fee(transaction) do
transaction
|> Chain.fee(:ether)
|> case do
{:actual, actual} ->
Cldr.Number.to_string!(actual, fractional_digits: 18)
{:maximum, maximum} ->
"<= " <> Cldr.Number.to_string!(maximum, fractional_digits: 18)
end
end
def first_seen(%Transaction{inserted_at: inserted_at}) do
Timex.from_now(inserted_at)
end

@ -1,5 +1,17 @@
alias Explorer.Chain
alias Explorer.Chain.Hash
alias Explorer.Chain.{Address, Block, Hash, Transaction}
defimpl Phoenix.HTML.Safe, for: [Address, Transaction] do
def to_iodata(%@for{hash: hash}) do
@protocol.to_iodata(hash)
end
end
defimpl Phoenix.HTML.Safe, for: Block do
def to_iodata(%@for{number: number}) do
@protocol.to_iodata(number)
end
end
defimpl Phoenix.HTML.Safe, for: Hash do
def to_iodata(hash) do

@ -1,6 +1,6 @@
alias Explorer.Chain.{Address, Block, Hash, Transaction}
defimpl Phoenix.Param, for: Address do
defimpl Phoenix.Param, for: [Address, Transaction] do
def to_param(%@for{hash: hash}) do
@protocol.to_param(hash)
end
@ -17,9 +17,3 @@ defimpl Phoenix.Param, for: Hash do
to_string(hash)
end
end
defimpl Phoenix.Param, for: Transaction do
def to_param(%@for{hash: hash}) do
@protocol.to_param(hash)
end
end

@ -1,25 +1,23 @@
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:52
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:51
#: lib/explorer_web/templates/address_transaction/index.html.eex:73
#: lib/explorer_web/templates/block/index.html.eex:30
#: lib/explorer_web/templates/block_transaction/index.html.eex:43
#: lib/explorer_web/templates/chain/show.html.eex:65
#: lib/explorer_web/templates/chain/show.html.eex:101
#: lib/explorer_web/templates/block_transaction/index.html.eex:99
#: lib/explorer_web/templates/chain/show.html.eex:71
#: lib/explorer_web/templates/chain/show.html.eex:110
#: lib/explorer_web/templates/transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/overview.html.eex:37
msgid "Age"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:51
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:50
#: lib/explorer_web/templates/block_transaction/index.html.eex:42
#: lib/explorer_web/templates/address_transaction/index.html.eex:72
#: lib/explorer_web/templates/block_transaction/index.html.eex:98
#: lib/explorer_web/templates/chain/show.html.eex:28
#: lib/explorer_web/templates/chain/show.html.eex:100
#: lib/explorer_web/templates/chain/show.html.eex:109
#: lib/explorer_web/templates/transaction/index.html.eex:35
msgid "Block"
msgstr ""
#: lib/explorer_web/templates/chain/show.html.eex:58
#: lib/explorer_web/templates/layout/_header.html.eex:27
#: lib/explorer_web/templates/layout/app.html.eex:24
msgid "Blocks"
msgstr ""
@ -28,47 +26,45 @@ msgid "Copyright %{year} POA"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:32
#: lib/explorer_web/templates/block/show.html.eex:80
#: lib/explorer_web/templates/chain/show.html.eex:67
#: lib/explorer_web/templates/block_transaction/index.html.eex:60
#: lib/explorer_web/templates/chain/show.html.eex:73
msgid "Gas Used"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:50
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:49
#: lib/explorer_web/templates/block/show.html.eex:40
#: lib/explorer_web/templates/block_transaction/index.html.eex:41
#: lib/explorer_web/templates/chain/show.html.eex:99
#: lib/explorer_web/templates/address_transaction/index.html.eex:71
#: lib/explorer_web/templates/block_transaction/index.html.eex:22
#: lib/explorer_web/templates/block_transaction/index.html.eex:97
#: lib/explorer_web/templates/chain/show.html.eex:108
#: lib/explorer_web/templates/pending_transaction/index.html.eex:34
#: lib/explorer_web/templates/transaction/index.html.eex:34
msgid "Hash"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:29
#: lib/explorer_web/templates/chain/show.html.eex:64
#: lib/explorer_web/templates/chain/show.html.eex:70
msgid "Height"
msgstr ""
#: lib/explorer_web/templates/layout/_header.html.eex:6
#: lib/explorer_web/templates/layout/app.html.eex:7
msgid "POA Network Explorer"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:23
#: lib/explorer_web/templates/block/index.html.eex:31
#: lib/explorer_web/templates/block/show.html.eex:16
#: lib/explorer_web/templates/block/show.html.eex:34
#: lib/explorer_web/templates/block_transaction/index.html.eex:28
#: lib/explorer_web/templates/block_transaction/index.html.eex:16
#: lib/explorer_web/templates/block_transaction/index.html.eex:84
#: lib/explorer_web/templates/chain/show.html.eex:45
#: lib/explorer_web/templates/chain/show.html.eex:66
#: lib/explorer_web/templates/chain/show.html.eex:93
#: lib/explorer_web/templates/chain/show.html.eex:72
#: lib/explorer_web/templates/chain/show.html.eex:99
#: lib/explorer_web/templates/layout/app.html.eex:30
#: lib/explorer_web/templates/pending_transaction/index.html.eex:12
#: lib/explorer_web/templates/transaction/index.html.eex:12
msgid "Transactions"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:55
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:54
#: lib/explorer_web/templates/block_transaction/index.html.eex:46
#: lib/explorer_web/templates/chain/show.html.eex:102
#: lib/explorer_web/templates/address_transaction/index.html.eex:76
#: lib/explorer_web/templates/block_transaction/index.html.eex:102
#: lib/explorer_web/templates/chain/show.html.eex:111
#: lib/explorer_web/templates/pending_transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/index.html.eex:39
#: lib/explorer_web/templates/transaction/overview.html.eex:43
@ -80,22 +76,22 @@ msgstr ""
msgid "Block #%{number} Details"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:60
#: lib/explorer_web/templates/block_transaction/index.html.eex:40
msgid "Difficulty"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:33
#: lib/explorer_web/templates/block/show.html.eex:88
#: lib/explorer_web/templates/block_transaction/index.html.eex:68
#: lib/explorer_web/templates/transaction/overview.html.eex:93
#: lib/explorer_web/templates/transaction/show.html.eex:29
msgid "Gas Limit"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:55
#: lib/explorer_web/templates/block_transaction/index.html.eex:36
msgid "Miner"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:92
#: lib/explorer_web/templates/block_transaction/index.html.eex:72
#: lib/explorer_web/templates/transaction/overview.html.eex:75
msgid "Nonce"
msgstr ""
@ -104,19 +100,19 @@ msgstr ""
msgid "Number"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:45
#: lib/explorer_web/templates/block_transaction/index.html.eex:26
msgid "Parent Hash"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:76
#: lib/explorer_web/templates/block_transaction/index.html.eex:56
msgid "Size"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:30
#: lib/explorer_web/templates/block_transaction/index.html.eex:12
msgid "Timestamp"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:70
#: lib/explorer_web/templates/block_transaction/index.html.eex:50
msgid "Total Difficulty"
msgstr ""
@ -152,44 +148,43 @@ msgstr ""
msgid "%{confirmations} block confirmations"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:36
#: lib/explorer_web/templates/block_transaction/index.html.eex:18
msgid "%{count} transactions in this block"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:3
#: lib/explorer_web/templates/address_transaction/index.html.eex:3
#: lib/explorer_web/templates/transaction_log/index.html.eex:26
msgid "Address"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:53
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:52
#: lib/explorer_web/templates/block_transaction/index.html.eex:44
#: lib/explorer_web/templates/address_transaction/index.html.eex:52
#: lib/explorer_web/templates/address_transaction/index.html.eex:74
#: lib/explorer_web/templates/block_transaction/index.html.eex:100
#: lib/explorer_web/templates/pending_transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/overview.html.eex:47
#: lib/explorer_web/templates/transaction/show.html.eex:26
#: lib/explorer_web/views/address_transaction_view.ex:15
msgid "From"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:10
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:23
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:22
#: lib/explorer_web/templates/block/show.html.eex:9
#: lib/explorer_web/templates/block_transaction/index.html.eex:21
msgid "Overview"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:87
#: lib/explorer_web/views/transaction_view.ex:99
msgid "Success"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:54
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:53
#: lib/explorer_web/templates/block_transaction/index.html.eex:45
#: lib/explorer_web/templates/address_transaction/index.html.eex:41
#: lib/explorer_web/templates/address_transaction/index.html.eex:75
#: lib/explorer_web/templates/block_transaction/index.html.eex:101
#: lib/explorer_web/templates/pending_transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/overview.html.eex:61
#: lib/explorer_web/templates/transaction/show.html.eex:27
#: lib/explorer_web/views/address_transaction_view.ex:14
msgid "To"
msgstr ""
@ -202,7 +197,7 @@ msgstr ""
msgid "Transaction Status"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:29
#: lib/explorer_web/templates/address_transaction/index.html.eex:10
msgid "Balance"
msgstr ""
@ -228,15 +223,16 @@ msgstr ""
msgid "Showing %{count} Transactions"
msgstr ""
#: lib/explorer_web/templates/layout/app.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/templates/transaction/overview.html.eex:70
#: lib/explorer_web/views/transaction_view.ex:13
#: lib/explorer_web/views/transaction_view.ex:27
#: lib/explorer_web/views/transaction_view.ex:42
#: lib/explorer_web/views/transaction_view.ex:49
#: lib/explorer_web/views/transaction_view.ex:86
#: lib/explorer_web/views/transaction_view.ex:54
#: lib/explorer_web/views/transaction_view.ex:61
#: lib/explorer_web/views/transaction_view.ex:98
msgid "Pending"
msgstr ""
@ -299,24 +295,22 @@ msgstr ""
msgid "Next Page"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:84
#: lib/explorer_web/views/transaction_view.ex:96
msgid "Failed"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:85
#: lib/explorer_web/views/transaction_view.ex:97
msgid "Out of Gas"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:48
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:47
#: lib/explorer_web/templates/block_transaction/index.html.eex:39
#: lib/explorer_web/templates/address_transaction/index.html.eex:69
#: lib/explorer_web/templates/block_transaction/index.html.eex:95
#: lib/explorer_web/templates/pending_transaction/index.html.eex:31
#: lib/explorer_web/templates/transaction/index.html.eex:31
msgid "Status"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:3
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:3
#: lib/explorer_web/templates/address_transaction/index.html.eex:3
msgid "Address %{number}"
msgstr ""
@ -324,11 +318,11 @@ msgstr ""
msgid "Showing #%{number}"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:31
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:101
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:101
#: lib/explorer_web/templates/block_transaction/index.html.eex:90
#: lib/explorer_web/templates/chain/show.html.eex:128
#: lib/explorer_web/templates/address_transaction/index.html.eex:12
#: lib/explorer_web/templates/address_transaction/index.html.eex:124
#: lib/explorer_web/templates/address_transaction/index.html.eex:127
#: lib/explorer_web/templates/block_transaction/index.html.eex:146
#: lib/explorer_web/templates/chain/show.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:70
#: lib/explorer_web/templates/transaction/index.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:44
@ -345,11 +339,11 @@ msgstr ""
msgid "Internal Transactions"
msgstr ""
#: lib/explorer_web/templates/layout/_header.html.eex:20
#: lib/explorer_web/templates/layout/app.html.eex:56
msgid "Search by address, transaction hash, or block number"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:52
#: lib/explorer_web/templates/transaction/show.html.eex:54
msgid "There are no Internal Transactions"
msgstr ""
@ -377,3 +371,20 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:105
msgid "Wei"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:36
#: lib/explorer_web/views/address_transaction_view.ex:16
msgid "All"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:77
msgid "Fee"
msgstr ""
#: lib/explorer_web/templates/layout/app.html.eex:34
msgid "Validated"
msgstr ""
#: lib/explorer_web/templates/block_transaction/index.html.eex:3
msgid "Block Details"
msgstr ""

@ -10,28 +10,26 @@ msgid ""
msgstr ""
"Language: en\n"
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:52
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:51
#: lib/explorer_web/templates/address_transaction/index.html.eex:73
#: lib/explorer_web/templates/block/index.html.eex:30
#: lib/explorer_web/templates/block_transaction/index.html.eex:43
#: lib/explorer_web/templates/chain/show.html.eex:65
#: lib/explorer_web/templates/chain/show.html.eex:101
#: lib/explorer_web/templates/block_transaction/index.html.eex:99
#: lib/explorer_web/templates/chain/show.html.eex:71
#: lib/explorer_web/templates/chain/show.html.eex:110
#: lib/explorer_web/templates/transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/overview.html.eex:37
msgid "Age"
msgstr "Age"
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:51
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:50
#: lib/explorer_web/templates/block_transaction/index.html.eex:42
#: lib/explorer_web/templates/address_transaction/index.html.eex:72
#: lib/explorer_web/templates/block_transaction/index.html.eex:98
#: lib/explorer_web/templates/chain/show.html.eex:28
#: lib/explorer_web/templates/chain/show.html.eex:100
#: lib/explorer_web/templates/chain/show.html.eex:109
#: lib/explorer_web/templates/transaction/index.html.eex:35
msgid "Block"
msgstr "Block"
#: lib/explorer_web/templates/chain/show.html.eex:58
#: lib/explorer_web/templates/layout/_header.html.eex:27
#: lib/explorer_web/templates/layout/app.html.eex:24
msgid "Blocks"
msgstr "Blocks"
@ -40,47 +38,45 @@ msgid "Copyright %{year} POA"
msgstr "%{year} POA Network Ltd. All rights reserved"
#: lib/explorer_web/templates/block/index.html.eex:32
#: lib/explorer_web/templates/block/show.html.eex:80
#: lib/explorer_web/templates/chain/show.html.eex:67
#: lib/explorer_web/templates/block_transaction/index.html.eex:60
#: lib/explorer_web/templates/chain/show.html.eex:73
msgid "Gas Used"
msgstr "Gas Used"
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:50
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:49
#: lib/explorer_web/templates/block/show.html.eex:40
#: lib/explorer_web/templates/block_transaction/index.html.eex:41
#: lib/explorer_web/templates/chain/show.html.eex:99
#: lib/explorer_web/templates/address_transaction/index.html.eex:71
#: lib/explorer_web/templates/block_transaction/index.html.eex:22
#: lib/explorer_web/templates/block_transaction/index.html.eex:97
#: lib/explorer_web/templates/chain/show.html.eex:108
#: lib/explorer_web/templates/pending_transaction/index.html.eex:34
#: lib/explorer_web/templates/transaction/index.html.eex:34
msgid "Hash"
msgstr "Hash"
#: lib/explorer_web/templates/block/index.html.eex:29
#: lib/explorer_web/templates/chain/show.html.eex:64
#: lib/explorer_web/templates/chain/show.html.eex:70
msgid "Height"
msgstr "Height"
#: lib/explorer_web/templates/layout/_header.html.eex:6
#: lib/explorer_web/templates/layout/app.html.eex:7
msgid "POA Network Explorer"
msgstr "POA Network Explorer"
#: lib/explorer_web/templates/address_transaction/index.html.eex:23
#: lib/explorer_web/templates/block/index.html.eex:31
#: lib/explorer_web/templates/block/show.html.eex:16
#: lib/explorer_web/templates/block/show.html.eex:34
#: lib/explorer_web/templates/block_transaction/index.html.eex:28
#: lib/explorer_web/templates/block_transaction/index.html.eex:16
#: lib/explorer_web/templates/block_transaction/index.html.eex:84
#: lib/explorer_web/templates/chain/show.html.eex:45
#: lib/explorer_web/templates/chain/show.html.eex:66
#: lib/explorer_web/templates/chain/show.html.eex:93
#: lib/explorer_web/templates/chain/show.html.eex:72
#: lib/explorer_web/templates/chain/show.html.eex:99
#: lib/explorer_web/templates/layout/app.html.eex:30
#: lib/explorer_web/templates/pending_transaction/index.html.eex:12
#: lib/explorer_web/templates/transaction/index.html.eex:12
msgid "Transactions"
msgstr "Transactions"
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:55
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:54
#: lib/explorer_web/templates/block_transaction/index.html.eex:46
#: lib/explorer_web/templates/chain/show.html.eex:102
#: lib/explorer_web/templates/address_transaction/index.html.eex:76
#: lib/explorer_web/templates/block_transaction/index.html.eex:102
#: lib/explorer_web/templates/chain/show.html.eex:111
#: lib/explorer_web/templates/pending_transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/index.html.eex:39
#: lib/explorer_web/templates/transaction/overview.html.eex:43
@ -92,22 +88,22 @@ msgstr "Value"
msgid "Block #%{number} Details"
msgstr "Block #%{number} Details"
#: lib/explorer_web/templates/block/show.html.eex:60
#: lib/explorer_web/templates/block_transaction/index.html.eex:40
msgid "Difficulty"
msgstr "Difficulty"
#: lib/explorer_web/templates/block/index.html.eex:33
#: lib/explorer_web/templates/block/show.html.eex:88
#: lib/explorer_web/templates/block_transaction/index.html.eex:68
#: lib/explorer_web/templates/transaction/overview.html.eex:93
#: lib/explorer_web/templates/transaction/show.html.eex:29
msgid "Gas Limit"
msgstr "Gas Limit"
#: lib/explorer_web/templates/block/show.html.eex:55
#: lib/explorer_web/templates/block_transaction/index.html.eex:36
msgid "Miner"
msgstr "Validator"
#: lib/explorer_web/templates/block/show.html.eex:92
#: lib/explorer_web/templates/block_transaction/index.html.eex:72
#: lib/explorer_web/templates/transaction/overview.html.eex:75
msgid "Nonce"
msgstr "Nonce"
@ -116,19 +112,19 @@ msgstr "Nonce"
msgid "Number"
msgstr "Height"
#: lib/explorer_web/templates/block/show.html.eex:45
#: lib/explorer_web/templates/block_transaction/index.html.eex:26
msgid "Parent Hash"
msgstr "Parent Hash"
#: lib/explorer_web/templates/block/show.html.eex:76
#: lib/explorer_web/templates/block_transaction/index.html.eex:56
msgid "Size"
msgstr "Size"
#: lib/explorer_web/templates/block/show.html.eex:30
#: lib/explorer_web/templates/block_transaction/index.html.eex:12
msgid "Timestamp"
msgstr "Timestamp"
#: lib/explorer_web/templates/block/show.html.eex:70
#: lib/explorer_web/templates/block_transaction/index.html.eex:50
msgid "Total Difficulty"
msgstr "Total Difficulty"
@ -164,44 +160,43 @@ msgstr "Input"
msgid "%{confirmations} block confirmations"
msgstr "%{confirmations} block confirmations"
#: lib/explorer_web/templates/block/show.html.eex:36
#: lib/explorer_web/templates/block_transaction/index.html.eex:18
msgid "%{count} transactions in this block"
msgstr "%{count} transactions in this block"
#: lib/explorer_web/templates/address/show.html.eex:3
#: lib/explorer_web/templates/address_transaction/index.html.eex:3
#: lib/explorer_web/templates/transaction_log/index.html.eex:26
msgid "Address"
msgstr "Address"
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:53
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:52
#: lib/explorer_web/templates/block_transaction/index.html.eex:44
#: lib/explorer_web/templates/address_transaction/index.html.eex:52
#: lib/explorer_web/templates/address_transaction/index.html.eex:74
#: lib/explorer_web/templates/block_transaction/index.html.eex:100
#: lib/explorer_web/templates/pending_transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/overview.html.eex:47
#: lib/explorer_web/templates/transaction/show.html.eex:26
#: lib/explorer_web/views/address_transaction_view.ex:15
msgid "From"
msgstr "From"
#: lib/explorer_web/templates/address/show.html.eex:10
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:23
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:22
#: lib/explorer_web/templates/block/show.html.eex:9
#: lib/explorer_web/templates/block_transaction/index.html.eex:21
msgid "Overview"
msgstr "Overview"
#: lib/explorer_web/views/transaction_view.ex:87
#: lib/explorer_web/views/transaction_view.ex:99
msgid "Success"
msgstr "Success"
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:54
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:53
#: lib/explorer_web/templates/block_transaction/index.html.eex:45
#: lib/explorer_web/templates/address_transaction/index.html.eex:41
#: lib/explorer_web/templates/address_transaction/index.html.eex:75
#: lib/explorer_web/templates/block_transaction/index.html.eex:101
#: lib/explorer_web/templates/pending_transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/overview.html.eex:61
#: lib/explorer_web/templates/transaction/show.html.eex:27
#: lib/explorer_web/views/address_transaction_view.ex:14
msgid "To"
msgstr "To"
@ -214,7 +209,7 @@ msgstr "Transaction Hash"
msgid "Transaction Status"
msgstr "Transaction Status"
#: lib/explorer_web/templates/address/show.html.eex:29
#: lib/explorer_web/templates/address_transaction/index.html.eex:10
msgid "Balance"
msgstr "Balance"
@ -240,15 +235,16 @@ msgstr "Showing #%{start_block} to #%{end_block}"
msgid "Showing %{count} Transactions"
msgstr "Showing %{count} Transactions"
#: lib/explorer_web/templates/layout/app.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/templates/transaction/overview.html.eex:70
#: lib/explorer_web/views/transaction_view.ex:13
#: lib/explorer_web/views/transaction_view.ex:27
#: lib/explorer_web/views/transaction_view.ex:42
#: lib/explorer_web/views/transaction_view.ex:49
#: lib/explorer_web/views/transaction_view.ex:86
#: lib/explorer_web/views/transaction_view.ex:54
#: lib/explorer_web/views/transaction_view.ex:61
#: lib/explorer_web/views/transaction_view.ex:98
msgid "Pending"
msgstr "Pending"
@ -311,24 +307,22 @@ msgstr ""
msgid "Next Page"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:84
#: lib/explorer_web/views/transaction_view.ex:96
msgid "Failed"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:85
#: lib/explorer_web/views/transaction_view.ex:97
msgid "Out of Gas"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:48
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:47
#: lib/explorer_web/templates/block_transaction/index.html.eex:39
#: lib/explorer_web/templates/address_transaction/index.html.eex:69
#: lib/explorer_web/templates/block_transaction/index.html.eex:95
#: lib/explorer_web/templates/pending_transaction/index.html.eex:31
#: lib/explorer_web/templates/transaction/index.html.eex:31
msgid "Status"
msgstr ""
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:3
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:3
#: lib/explorer_web/templates/address_transaction/index.html.eex:3
msgid "Address %{number}"
msgstr ""
@ -336,11 +330,11 @@ msgstr ""
msgid "Showing #%{number}"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:31
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:101
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:101
#: lib/explorer_web/templates/block_transaction/index.html.eex:90
#: lib/explorer_web/templates/chain/show.html.eex:128
#: lib/explorer_web/templates/address_transaction/index.html.eex:12
#: lib/explorer_web/templates/address_transaction/index.html.eex:124
#: lib/explorer_web/templates/address_transaction/index.html.eex:127
#: lib/explorer_web/templates/block_transaction/index.html.eex:146
#: lib/explorer_web/templates/chain/show.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:70
#: lib/explorer_web/templates/transaction/index.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:44
@ -357,11 +351,11 @@ msgstr ""
msgid "Internal Transactions"
msgstr ""
#: lib/explorer_web/templates/layout/_header.html.eex:20
#: lib/explorer_web/templates/layout/app.html.eex:56
msgid "Search by address, transaction hash, or block number"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:52
#: lib/explorer_web/templates/transaction/show.html.eex:54
msgid "There are no Internal Transactions"
msgstr ""
@ -389,3 +383,20 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:105
msgid "Wei"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:36
#: lib/explorer_web/views/address_transaction_view.ex:16
msgid "All"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:77
msgid "Fee"
msgstr ""
#: lib/explorer_web/templates/layout/app.html.eex:34
msgid "Validated"
msgstr ""
#: lib/explorer_web/templates/block_transaction/index.html.eex:3
msgid "Block Details"
msgstr ""

@ -4,20 +4,14 @@ defmodule ExplorerWeb.AddressControllerTest do
alias Explorer.Chain.{Credit, Debit}
describe "GET show/3" do
test "without address returns not found", %{conn: conn} do
conn = get(conn, "/en/addresses/unknown")
assert html_response(conn, 404)
end
test "with address returns an address", %{conn: conn} do
address = insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
test "redirects to addresses/:address_id/transactions", %{conn: conn} do
insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
Credit.refresh()
Debit.refresh()
conn = get(conn, "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
assert conn.assigns.address.hash == address.hash
assert redirected_to(conn) =~ "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed/transactions"
end
end
end

@ -1,11 +1,11 @@
defmodule ExplorerWeb.AddressTransactionToControllerTest do
defmodule ExplorerWeb.AddressTransactionControllerTest do
use ExplorerWeb.ConnCase
import ExplorerWeb.Router.Helpers, only: [address_transaction_to_path: 4]
import ExplorerWeb.Router.Helpers, only: [address_transaction_path: 4]
describe "GET index/2" do
test "without address", %{conn: conn} do
conn = get(conn, address_transaction_to_path(conn, :index, :en, "unknown"))
conn = get(conn, address_transaction_path(conn, :index, :en, "unknown"))
assert html_response(conn, 404)
end
@ -16,7 +16,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: address.hash)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 1
@ -36,7 +36,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
@ -54,7 +54,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
to_address_hash: address.hash
)
conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
@ -66,7 +66,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address), filter: "from")
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
@ -78,7 +78,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
transaction = insert(:transaction, block_hash: block.hash, index: 0, from_address_hash: address.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address), filter: "to")
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0

@ -1,79 +0,0 @@
defmodule ExplorerWeb.AddressTransactionFromControllerTest do
use ExplorerWeb.ConnCase
import ExplorerWeb.Router.Helpers, only: [address_transaction_from_path: 4]
describe "GET index/2" do
test "without address", %{conn: conn} do
conn = get(conn, address_transaction_from_path(conn, :index, :en, "unknown"))
assert html_response(conn, 404)
end
test "returns transactions from this address", %{conn: conn} do
address = insert(:address)
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, from_address_hash: address.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert html = html_response(conn, 200)
transaction_hash_divs = Floki.find(html, "td.transactions__column--hash div.transactions__hash a")
assert length(transaction_hash_divs) == 1
assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [
"/en/transactions/#{Phoenix.Param.to_param(transaction)}"
]
end
test "does not return transactions to this address", %{conn: conn} do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a receipt", %{conn: conn} do
block = insert(:block)
insert(:transaction, block_hash: block.hash, index: 0)
address = insert(:address)
conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a from address", %{conn: conn} do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a to address", %{conn: conn} do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
end
end

@ -6,18 +6,10 @@ defmodule ExplorerWeb.BlockControllerTest do
@locale "en"
describe "GET show/2" do
test "without block", %{conn: conn} do
test "with block redirects to block transactions route", %{conn: conn} do
insert(:block, number: 3)
conn = get(conn, "/en/blocks/3")
assert html_response(conn, 404)
end
test "with block returns a block", %{conn: conn} do
block = insert(:block)
conn = get(conn, block_path(conn, :show, @locale, block))
assert conn.assigns.block.hash == block.hash
assert redirected_to(conn) =~ "/en/blocks/3/transactions"
end
end

@ -1,71 +1,72 @@
defmodule ExplorerWeb.UserListTest do
defmodule ExplorerWeb.ContributorBrowsingTest do
use ExplorerWeb.FeatureCase, async: true
import Wallaby.Query, only: [css: 1, css: 2, link: 1]
# alias Explorer.Chain
alias Explorer.Chain.{Address, Block, Credit, Debit, Transaction}
alias Explorer.Chain.{Credit, Debit}
@logo css("img.header__logo")
@logo css("[data-test='header_logo']")
test "browses the home page", %{session: session} do
session |> visit("/")
assert current_path(session) == "/en"
session
|> assert_has(css(".header__logo"))
|> click(css("[data-test='hamburger_menu_button']"))
|> click(@logo)
|> assert_has(css("main", text: "Blocks"))
end
test "search for blocks", %{session: session} do
%Block{miner_hash: miner_hash} = insert(:block, number: 42)
block = insert(:block, number: 42)
refute block.miner_hash == nil
session
|> visit("/")
|> fill_in(css(".header__cell--search-input"), with: "42")
|> fill_in(css(".header__cell--search-input"), with: to_string(block.number))
|> send_keys([:enter])
|> assert_has(css(~s|.block__item dd[title="#{miner_hash}"]|))
|> assert_has(css(".block__item", text: to_string(block.miner_hash)))
end
test "search for transactions", %{session: session} do
input = "INPUT"
%Transaction{hash: hash} = insert(:transaction, input: input)
transaction = insert(:transaction, input: "socks")
session
|> visit("/")
|> fill_in(css(".header__cell--search-input"), with: to_string(hash))
|> fill_in(
css(".header__cell--search-input"),
with: to_string(transaction.hash)
)
|> send_keys([:enter])
|> assert_has(css(".transaction__item", text: input))
|> assert_has(css(".transaction__item", text: transaction.input))
end
test "search for address", %{session: session} do
%Address{hash: hash} = insert(:address)
string = to_string(hash)
address = insert(:address)
session
|> visit("/")
|> fill_in(css(".header__cell--search-input"), with: string)
|> fill_in(
css(".header__cell--search-input"),
with: to_string(address.hash)
)
|> send_keys([:enter])
|> assert_has(css(".address__subheading", text: string))
|> assert_has(css(".address__subheading", text: to_string(address.hash)))
end
test "views blocks", %{session: session} do
insert_list(4, :block, %{
timestamp: Timex.now() |> Timex.shift(hours: -1),
gas_used: 10
})
number = 311
number_string = to_string(number)
timestamp = Timex.now() |> Timex.shift(hours: -1)
Enum.map(307..310, &insert(:block, number: &1, timestamp: timestamp, gas_used: 10))
fifth_block =
insert(:block, %{
number: number,
timestamp: Timex.now() |> Timex.shift(hours: -1),
size: 9_999_999,
gas_limit: 5_030_101,
gas_used: 1_010_101,
gas_limit: 5_030_101
nonce: 123_456_789,
number: 311,
size: 9_999_999,
timestamp: timestamp
})
transaction = insert(:transaction, block_hash: fifth_block.hash, index: 0)
@ -80,124 +81,207 @@ defmodule ExplorerWeb.UserListTest do
session
|> visit("/en")
|> assert_has(css(".blocks__title", text: "Blocks"))
|> assert_has(css(".blocks__column--height", count: 2, text: "1"))
|> assert_has(css(".blocks__column--transactions-count", count: 5))
|> assert_has(css(".blocks__column--transactions-count", count: 1, text: "3"))
|> assert_has(css(".blocks__column--age", count: 5, text: "1 hour ago"))
|> assert_has(css(".blocks__column--gas-used", count: 5, text: "10"))
session
|> click(css("[data-test='hamburger_menu_button']"))
|> click(link("Blocks"))
|> assert_has(css(".blocks__column--height", text: number_string))
|> click(link(number_string))
|> assert_has(css(~s|.block__item dd[title="#{fifth_block.hash}"]|))
|> assert_has(css(~s|.block__item dd[title="#{fifth_block.miner_hash}"]|))
|> assert_has(css(".blocks__column--height", text: "311"))
|> click(link("311"))
|> assert_has(css(".block__item", text: to_string(fifth_block.hash)))
|> assert_has(css(".block__item", text: to_string(fifth_block.miner_hash)))
|> assert_has(css(".block__item", text: "9,999,999"))
|> assert_has(css(".block__item", text: "1 hour ago"))
|> assert_has(css(".block__item", text: "5,030,101"))
# |> assert_has(css(".block__item", text: to_string(fifth_block.nonce)))
|> assert_has(css(".block__item", text: to_string(fifth_block.nonce)))
|> assert_has(css(".block__item", text: "1,010,101"))
|> click(css(".block__link", text: "Transactions"))
|> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
end
test "views transactions", %{session: session} do
block =
insert(:block, %{
number: 555,
timestamp: Timex.now() |> Timex.shift(hours: -2),
gas_used: 123_987
})
Enum.each(0..3, &insert(:transaction, block_hash: block.hash, index: &1))
# pending_transaction = insert(:transaction, gas: 5891)
lincoln = insert(:address)
taft = insert(:address)
transaction =
insert(
:transaction,
block_hash: block.hash,
from_address_hash: taft.hash,
gas: Decimal.new(1_230_000_000_000_123_123),
gas_price: Decimal.new(7_890_000_000_898_912_300_045),
index: 4,
input: "0x00012",
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
nonce: 99045,
to_address_hash: lincoln.hash,
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether)
)
insert(:receipt, status: :error, transaction_hash: transaction.hash, transaction_index: transaction.index)
insert(:log, address_hash: lincoln.hash, transaction_hash: transaction.hash)
# From Lincoln to Taft.
transaction_from_lincoln =
insert(
:transaction,
block_hash: block.hash,
from_address_hash: lincoln.hash,
index: 5,
to_address_hash: taft.hash
describe "transactions and address pages" do
setup do
block =
insert(:block, %{
number: 555,
timestamp: Timex.now() |> Timex.shift(hours: -2),
gas_used: 123_987
})
for index <- 0..3, do: insert(:transaction, block_hash: block.hash, index: index)
pending = insert(:transaction, block_hash: nil, gas: 5891, index: nil)
lincoln = insert(:address)
taft = insert(:address)
transaction =
insert(
:transaction,
block_hash: block.hash,
value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether),
gas: Decimal.new(1_230_000_000_000_123_123),
gas_price: Decimal.new(7_890_000_000_898_912_300_045),
index: 4,
input: "0x00012",
nonce: 99045,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
from_address_hash: taft.hash,
to_address_hash: lincoln.hash
)
receipt = insert(:receipt, status: :ok, transaction_hash: transaction.hash, transaction_index: transaction.index)
insert(:log, address_hash: lincoln.hash, index: 0, transaction_hash: receipt.transaction_hash)
# From Lincoln to Taft.
txn_from_lincoln =
insert(
:transaction,
block_hash: block.hash,
index: 5,
from_address_hash: lincoln.hash,
to_address_hash: taft.hash
)
internal_receipt =
insert(:receipt, transaction_hash: txn_from_lincoln.hash, transaction_index: txn_from_lincoln.index)
internal = insert(:internal_transaction, transaction_hash: internal_receipt.transaction_hash)
Credit.refresh()
Debit.refresh()
{:ok,
%{
pending: pending,
internal: internal,
lincoln: lincoln,
taft: taft,
transaction: transaction,
txn_from_lincoln: txn_from_lincoln
}}
end
test "views transactions", %{session: session} do
session
|> visit("/en")
|> assert_has(css(".transactions__title", text: "Transactions"))
|> assert_has(css(".transactions__column--hash", count: 5))
|> assert_has(css(".transactions__column--value", count: 5))
|> assert_has(css(".transactions__column--age", count: 5, visible: false))
end
test "can see pending transactions", %{pending: pending, session: session} do
session
|> visit("/transactions")
|> click(css(".transactions__tab-link", text: "Pending"))
|> click(css(".transactions__link", text: to_string(pending.hash)))
|> assert_has(css(".transaction__item-value--status", text: "Pending"))
end
test "don't see pending transactions by default", %{session: session} do
session
|> visit("/transactions")
|> refute_has(css(".transactions__column--block", text: "Pending"))
end
test "can see a transaction's details", %{lincoln: lincoln, session: session, taft: taft, transaction: transaction} do
session
|> visit("/transactions")
|> click(link(to_string(transaction.hash)))
|> assert_has(css(".transaction__subheading", text: to_string(transaction.hash)))
|> assert_has(css(".transaction__item", text: "123,987"))
|> assert_has(css(".transaction__item", text: "5,656 POA"))
|> assert_has(css(".transaction__item", text: "Success"))
|> assert_has(
css(
".transaction__item",
text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)"
)
)
insert(:receipt, transaction_hash: transaction_from_lincoln.hash, transaction_index: transaction_from_lincoln.index)
# internal = insert(:internal_transaction, transaction_hash: transaction.hash)
Credit.refresh()
Debit.refresh()
# transaction_hash_string = to_string(transaction.hash)
session
|> visit("/en")
|> assert_has(css(".transactions__title", text: "Transactions"))
|> assert_has(css(".transactions__column--hash", count: 5))
|> assert_has(css(".transactions__column--value", count: 5))
|> assert_has(css(".transactions__column--age", count: 5, visible: false))
# |> visit("/transactions")
# |> click(css(".transactions__tab-link", text: "Pending"))
# |> click(css(".transactions__link", text: Chain.transaction_hash_to_string(pending_transaction.hash)))
# |> assert_has(css(".transaction__item-value--status", text: "Pending"))
# |> visit("/transactions")
# |> refute_has(css(".transactions__column--block", text: "Pending"))
# |> click(link(transaction_hash_string))
# |> assert_has(css(".transaction__subheading", text: transaction_hash_string))
# |> assert_has(css(".transaction__item", text: "123,987"))
# |> assert_has(css(".transaction__item", text: "5,656 POA"))
# |> assert_has(css(".transaction__item", text: "Success"))
# |> assert_has(
# css(
# ".transaction__item",
# text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)"
# )
# )
# |> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas"))
# |> assert_has(css(".transaction__item", text: "0x00012"))
# |> assert_has(css(".transaction__item", text: "99045"))
# |> assert_has(css(".transaction__item", text: "123,987"))
# |> assert_has(css(".transaction__item", text: "0xlincoln"))
# |> assert_has(css(".transaction__item", text: "0xhowardtaft"))
# |> assert_has(css(".transaction__item", text: "block confirmations"))
# |> assert_has(css(".transaction__item", text: "49 years ago"))
# |> assert_has(css(".transaction__item", text: "38 years ago"))
# |> click(link("Internal Transactions"))
# |> assert_has(css(".internal-transaction__table", text: internal.call_type))
# |> visit("/en/transactions/0xSk8")
# |> click(link("Logs"))
# |> assert_has(css(".transaction-log__link", text: "0xlincoln"))
# |> click(css(".transaction-log__link", text: "0xlincoln"))
# |> assert_has(css(".address__subheading", text: "0xlincoln"))
# |> click(css(".address__link", text: "Transactions To"))
# |> assert_has(css(".transactions__link--long-hash", text: "0xSk8"))
# |> click(css(".address__link", text: "Transactions From"))
# |> assert_has(
# css(".transactions__link--long-hash", text: Chain.transaction_hash_to_string(transaction_from_lincoln.hash))
# )
|> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas"))
|> assert_has(css(".transaction__item", text: "0x00012"))
|> assert_has(css(".transaction__item", text: "99045"))
|> assert_has(css(".transaction__item", text: "123,987"))
|> assert_has(css(".transaction__item", text: to_string(lincoln.hash)))
|> assert_has(css(".transaction__item", text: to_string(taft.hash)))
|> assert_has(css(".transaction__item", text: "block confirmations"))
|> assert_has(css(".transaction__item", text: "49 years ago"))
|> assert_has(css(".transaction__item", text: "38 years ago"))
end
test "can see internal transactions for a transaction", %{
internal: internal,
session: session,
txn_from_lincoln: txn_from_lincoln
} do
session
|> visit("/en/transactions/#{Phoenix.Param.to_param(txn_from_lincoln)}")
|> click(link("Internal Transactions"))
|> assert_has(css(".internal-transaction__table", text: internal.call_type))
end
test "can view a transaction's logs", %{lincoln: lincoln, session: session, transaction: transaction} do
session
|> visit("/en/transactions/#{Phoenix.Param.to_param(transaction)}")
|> click(link("Logs"))
|> assert_has(css(".transaction-log__link", text: to_string(lincoln.hash)))
end
test "can visit an address from the transaction logs page", %{
lincoln: lincoln,
session: session,
transaction: transaction
} do
session
|> visit("/en/transactions/#{Phoenix.Param.to_param(transaction)}/logs")
|> click(css(".transaction-log__link", text: to_string(lincoln.hash)))
|> assert_has(css(".address__subheading", text: to_string(lincoln.hash)))
end
test "see's all addresses transactions by default", %{
lincoln: lincoln,
session: session,
transaction: transaction,
txn_from_lincoln: txn_from_lincoln
} do
session
|> visit("/en/addresses/#{Phoenix.Param.to_param(lincoln)}")
|> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
|> assert_has(css(".transactions__link--long-hash", text: to_string(txn_from_lincoln.hash)))
end
test "can filter to only see transactions to an address", %{
lincoln: lincoln,
session: session,
transaction: transaction,
txn_from_lincoln: txn_from_lincoln
} do
session
|> visit("/en/addresses/#{Phoenix.Param.to_param(lincoln)}")
|> click(css("[data-test='filter_dropdown']", text: "Filter: All"))
|> click(css(".address__link", text: "To"))
|> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
|> refute_has(css(".transactions__link--long-hash", text: to_string(txn_from_lincoln.hash)))
end
test "can filter to only see transactions from an address", %{
lincoln: lincoln,
session: session,
transaction: transaction,
txn_from_lincoln: txn_from_lincoln
} do
session
|> visit("/en/addresses/#{Phoenix.Param.to_param(lincoln)}")
|> click(css("[data-test='filter_dropdown']", text: "Filter: All"))
|> click(css(".address__link", text: "From"))
|> assert_has(css(".transactions__link--long-hash", text: to_string(txn_from_lincoln.hash)))
|> refute_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
end
end
test "views addresses", %{session: session} do
@ -205,6 +289,6 @@ defmodule ExplorerWeb.UserListTest do
session
|> visit("/en/addresses/#{Phoenix.Param.to_param(address)}")
|> assert_has(css(".address__balance", text: "0.0000000000000005"))
|> assert_has(css(".address__balance", text: "0.000,000,000,000,000,500 POA"))
end
end

@ -0,0 +1,67 @@
defmodule ExplorerWeb.AddressTransactionViewTest do
use Explorer.DataCase
alias ExplorerWeb.AddressTransactionView
describe "fee/0" do
test "formats the fee for a successful transaction" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
to_address = insert(:address)
from_address = insert(:address)
transaction =
insert(
:transaction,
block_hash: block.hash,
from_address_hash: from_address.hash,
gas_price: Decimal.new(1_000_000_000.0),
index: 0,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_hash: to_address.hash,
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}")
)
insert(
:receipt,
gas_used: Decimal.new(435_334),
status: :ok,
transaction_hash: transaction.hash,
transaction_index: transaction.index
)
transaction =
transaction
|> Repo.preload([:receipt])
assert AddressTransactionView.fee(transaction) == "0.000,435,334,000,000,000"
end
test "fee returns max_gas for pending transaction" do
to_address = insert(:address)
from_address = insert(:address)
transaction =
insert(
:transaction,
from_address_hash: from_address.hash,
gas: Decimal.new(21000.0),
gas_price: Decimal.new(1_000_000_000.0),
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_hash: to_address.hash,
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}")
)
|> Repo.preload([:to_address, :from_address, :receipt])
assert AddressTransactionView.fee(transaction) == "<= 0.000,021,000,000,000,000"
end
end
end

@ -1,49 +0,0 @@
#!/bin/bash
set -ex
[[ -z ${HEROKU_APPLICATION} ]] && HEROKU_APPLICATION=$1
[[ ! -s "$(git rev-parse --git-dir)/shallow" ]] || git fetch --unshallow origin
if ! git remote | grep heroku
then
git remote add heroku git@heroku.com:${HEROKU_APPLICATION}.git
git fetch heroku
fi
WORKER_COUNT=$(heroku ps | grep 'worker\.' | wc -l)
SCHEDULER_COUNT=$(heroku ps | grep 'scheduler\.' | wc -l)
RECEIPTS_COUNT=$(heroku ps | grep 'receipts\.' | wc -l)
BLOCKS_COUNT=$(heroku ps | grep 'blocks\.' | wc -l)
if ! git diff HEAD heroku/master --exit-code -- priv/repo
then
if heroku features --app $HEROKU_APPLICATION | grep '\[+\] preboot'
then
heroku features:disable preboot --app $HEROKU_APPLICATION
heroku maintenance:on --app $HEROKU_APPLICATION
heroku scale worker=0 scheduler=0 receipts=0 blocks=0 --app $HEROKU_APPLICATION
heroku pg:killall --app $HEROKU_APPLICATION
git push heroku $CIRCLE_SHA1:refs/heads/master
heroku pg:backups capture --app $HEROKU_APPLICATION
heroku run "POOL_SIZE=2 mix ecto.migrate" --app $HEROKU_APPLICATION
heroku scale worker=${WORKER_COUNT} scheduler=${SCHEDULER_COUNT} receipts=${RECEIPTS_COUNT} blocks=${BLOCKS_COUNT} --app $HEROKU_APPLICATION
heroku restart --app $HEROKU_APPLICATION
heroku maintenance:off --app $HEROKU_APPLICATION
heroku features:enable preboot --app $HEROKU_APPLICATION
else
heroku maintenance:on --app $HEROKU_APPLICATION
heroku scale worker=0 scheduler=0 receipts=0 blocks=0 --app $HEROKU_APPLICATION
heroku pg:killall --app $HEROKU_APPLICATION
git push heroku $CIRCLE_SHA1:refs/heads/master
heroku pg:backups capture --app $HEROKU_APPLICATION
heroku run "POOL_SIZE=2 mix ecto.migrate" --app $HEROKU_APPLICATION
heroku scale worker=${WORKER_COUNT} scheduler=${SCHEDULER_COUNT} receipts=${RECEIPTS_COUNT} blocks=${BLOCKS_COUNT} --app $HEROKU_APPLICATION
heroku restart --app $HEROKU_APPLICATION
heroku maintenance:off --app $HEROKU_APPLICATION
fi
else
git push heroku $CIRCLE_SHA1:refs/heads/master
heroku pg:backups capture --app $HEROKU_APPLICATION
fi

@ -1,12 +0,0 @@
#!/usr/bin/env bash
# Placeholder for pgbouncer buildpack
function clean_up() {
KILL $CHILD_PID
exit
}
trap clean_up SIGHUP SIGINT SIGTERM
$@ &
CHILD_PID=$!
wait $CHILD_PID

@ -1,7 +1,7 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 84.3
"minimum_coverage": 84
},
"terminal_options": {
"file_column_width": 120

@ -1,3 +0,0 @@
erlang_version=20.3.2
elixir_version=1.6.4
always_rebuild=true

@ -1,7 +0,0 @@
assets_path=assets
clean_cache=true
node_version=9.4.0
npm_version=5.6.0
phoenix_relative_path=apps/explorer_web
# phx for phoenix 1.3 support.
phoenix_ex=phx
Loading…
Cancel
Save