Add build timestamp to footer with shared time formatting helpers
- Add compile-time @build_timestamp to Application module - Create ToweropsWeb.TimeHelpers module for shared time formatting - Update footer to display 'Last deployed Xm ago · YYYY-MM-DD HH:MM:SS UTC' - Refactor AgentLive.Helpers to delegate to shared TimeHelpers - Consolidate duplicate time formatting logic into single module - Timestamp updates automatically on each build/deploy
This commit is contained in:
parent
654fcb541c
commit
ae16acf016
4 changed files with 144 additions and 26 deletions
|
|
@ -5,6 +5,9 @@ defmodule Towerops.Application do
|
|||
|
||||
use Application
|
||||
|
||||
# Capture the build/deploy timestamp at compile time
|
||||
@build_timestamp DateTime.utc_now()
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
# Run migrations on startup (Ecto handles locking for concurrent runs)
|
||||
|
|
@ -44,4 +47,13 @@ defmodule Towerops.Application do
|
|||
ToweropsWeb.Endpoint.config_change(changed, removed)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the build/deploy timestamp.
|
||||
|
||||
This timestamp is captured at compile time and represents when the
|
||||
application was last built/deployed.
|
||||
"""
|
||||
@spec build_timestamp() :: DateTime.t()
|
||||
def build_timestamp, do: @build_timestamp
|
||||
end
|
||||
|
|
|
|||
|
|
@ -411,15 +411,28 @@ defmodule ToweropsWeb.Layouts do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Renders the application footer with copyright notice.
|
||||
Renders the application footer with copyright notice and deploy timestamp.
|
||||
"""
|
||||
def footer(assigns) do
|
||||
alias ToweropsWeb.TimeHelpers
|
||||
|
||||
build_time = Towerops.Application.build_timestamp()
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:build_time, build_time)
|
||||
|> assign(:time_ago, TimeHelpers.format_time_ago(build_time))
|
||||
|> assign(:formatted_time, TimeHelpers.format_iso8601(build_time))
|
||||
|
||||
~H"""
|
||||
<footer class="border-t border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
|
||||
<p class="text-center text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Copyright © {Date.utc_today().year} Towerops
|
||||
</p>
|
||||
<div class="flex flex-col items-center gap-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<p>Copyright © {Date.utc_today().year} Towerops</p>
|
||||
<p class="text-xs">
|
||||
Last deployed {@time_ago} · {@formatted_time}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
"""
|
||||
|
|
|
|||
106
lib/towerops_web/helpers/time_helpers.ex
Normal file
106
lib/towerops_web/helpers/time_helpers.ex
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
defmodule ToweropsWeb.TimeHelpers do
|
||||
@moduledoc """
|
||||
Helper functions for formatting time and date values.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Formats a DateTime into a human-readable "time ago" string.
|
||||
|
||||
Supports short time periods (seconds, minutes, hours, days) and
|
||||
long time periods (months, years) for things like build timestamps.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> datetime = DateTime.add(DateTime.utc_now(), -65, :second)
|
||||
iex> ToweropsWeb.TimeHelpers.format_time_ago(datetime)
|
||||
"1m ago"
|
||||
|
||||
iex> datetime = DateTime.add(DateTime.utc_now(), -7200, :second)
|
||||
iex> ToweropsWeb.TimeHelpers.format_time_ago(datetime)
|
||||
"2h ago"
|
||||
|
||||
"""
|
||||
@spec format_time_ago(DateTime.t() | nil) :: String.t()
|
||||
def format_time_ago(nil), do: "Never"
|
||||
|
||||
def format_time_ago(datetime) do
|
||||
now = DateTime.utc_now()
|
||||
diff = DateTime.diff(now, datetime, :second)
|
||||
|
||||
cond do
|
||||
diff < 60 ->
|
||||
"#{diff}s ago"
|
||||
|
||||
diff < 3600 ->
|
||||
minutes = div(diff, 60)
|
||||
"#{minutes}m ago"
|
||||
|
||||
diff < 86_400 ->
|
||||
hours = div(diff, 3600)
|
||||
"#{hours}h ago"
|
||||
|
||||
diff < 2_592_000 ->
|
||||
days = div(diff, 86_400)
|
||||
"#{days}d ago"
|
||||
|
||||
diff < 31_536_000 ->
|
||||
months = div(diff, 2_592_000)
|
||||
"#{months}mo ago"
|
||||
|
||||
true ->
|
||||
years = div(diff, 31_536_000)
|
||||
"#{years}y ago"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a DateTime into a full date and time string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> datetime = ~U[2026-01-15 14:34:00Z]
|
||||
iex> ToweropsWeb.TimeHelpers.format_datetime(datetime)
|
||||
"Jan 15, 2026 at 02:34 PM UTC"
|
||||
|
||||
"""
|
||||
@spec format_datetime(DateTime.t() | nil) :: String.t()
|
||||
def format_datetime(nil), do: "Never"
|
||||
|
||||
def format_datetime(datetime) do
|
||||
Calendar.strftime(datetime, "%b %d, %Y at %I:%M %p %Z")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a DateTime into a short date string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> datetime = ~U[2026-01-15 14:34:00Z]
|
||||
iex> ToweropsWeb.TimeHelpers.format_date(datetime)
|
||||
"Jan 15, 2026"
|
||||
|
||||
"""
|
||||
@spec format_date(DateTime.t() | nil) :: String.t()
|
||||
def format_date(nil), do: "Never"
|
||||
|
||||
def format_date(datetime) do
|
||||
Calendar.strftime(datetime, "%b %d, %Y")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a DateTime into ISO 8601 format with UTC timezone.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> datetime = ~U[2026-01-15 14:34:00Z]
|
||||
iex> ToweropsWeb.TimeHelpers.format_iso8601(datetime)
|
||||
"2026-01-15 14:34:00 UTC"
|
||||
|
||||
"""
|
||||
@spec format_iso8601(DateTime.t() | nil) :: String.t()
|
||||
def format_iso8601(nil), do: "Never"
|
||||
|
||||
def format_iso8601(datetime) do
|
||||
Calendar.strftime(datetime, "%Y-%m-%d %H:%M:%S UTC")
|
||||
end
|
||||
end
|
||||
|
|
@ -57,19 +57,10 @@ defmodule ToweropsWeb.AgentLive.Helpers do
|
|||
Formats a DateTime into a human-readable "time ago" string.
|
||||
|
||||
Returns strings like "5s ago", "3m ago", "2h ago", or "5d ago".
|
||||
|
||||
Delegates to ToweropsWeb.TimeHelpers.format_time_ago/1.
|
||||
"""
|
||||
def format_last_seen(nil), do: "Never"
|
||||
|
||||
def format_last_seen(datetime) do
|
||||
seconds_ago = DateTime.diff(DateTime.utc_now(), datetime, :second)
|
||||
|
||||
cond do
|
||||
seconds_ago < 60 -> "#{seconds_ago}s ago"
|
||||
seconds_ago < 3600 -> "#{div(seconds_ago, 60)}m ago"
|
||||
seconds_ago < 86_400 -> "#{div(seconds_ago, 3600)}h ago"
|
||||
true -> "#{div(seconds_ago, 86_400)}d ago"
|
||||
end
|
||||
end
|
||||
defdelegate format_last_seen(datetime), to: ToweropsWeb.TimeHelpers, as: :format_time_ago
|
||||
|
||||
@doc """
|
||||
Formats uptime in seconds to a human-readable string.
|
||||
|
|
@ -94,23 +85,19 @@ defmodule ToweropsWeb.AgentLive.Helpers do
|
|||
Formats a DateTime into a full date and time string.
|
||||
|
||||
Returns strings like "Jan 15, 2026 at 2:34 PM UTC".
|
||||
"""
|
||||
def format_datetime(nil), do: "Never"
|
||||
|
||||
def format_datetime(datetime) do
|
||||
Calendar.strftime(datetime, "%b %d, %Y at %I:%M %p %Z")
|
||||
end
|
||||
Delegates to ToweropsWeb.TimeHelpers.format_datetime/1.
|
||||
"""
|
||||
defdelegate format_datetime(datetime), to: ToweropsWeb.TimeHelpers
|
||||
|
||||
@doc """
|
||||
Formats a DateTime into a short date string.
|
||||
|
||||
Returns strings like "Jan 15, 2026".
|
||||
"""
|
||||
def format_date(nil), do: "Never"
|
||||
|
||||
def format_date(datetime) do
|
||||
Calendar.strftime(datetime, "%b %d, %Y")
|
||||
end
|
||||
Delegates to ToweropsWeb.TimeHelpers.format_date/1.
|
||||
"""
|
||||
defdelegate format_date(datetime), to: ToweropsWeb.TimeHelpers
|
||||
|
||||
@doc """
|
||||
Formats a DateTime into a relative time with full datetime in parentheses.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue