Add page descriptions + rename Schedules to On-Call

- Alerts: add description subtitle
- On-Call (was Schedules): renamed in sidebar + page title, add description
- Maintenance: add description subtitle
- Trace: add title + description (was missing both)
- Sidebar nav updated: Schedules → On-Call
- All pages now have contextual help text explaining their purpose
This commit is contained in:
Graham McIntire 2026-03-13 15:30:39 -05:00
parent 37314bd154
commit 9bae7a886a
6 changed files with 119 additions and 66 deletions

View file

@ -148,7 +148,11 @@ defmodule ToweropsWeb.Layouts do
|> Map.put(:always_show_banner, always_show_banner)
~H"""
<div id="sidebar-wrapper" phx-hook="SidebarCollapse" class="min-h-screen bg-gray-50 dark:bg-gray-950">
<div
id="sidebar-wrapper"
phx-hook="SidebarCollapse"
class="min-h-screen bg-gray-50 dark:bg-gray-950"
>
<!-- Impersonation Banner -->
<%= if @current_scope && @current_scope.impersonating? do %>
<div class="bg-yellow-400 border-b-2 border-yellow-600 relative z-50">
@ -171,8 +175,8 @@ defmodule ToweropsWeb.Layouts do
</div>
</div>
<% end %>
<!-- Slim Top Bar -->
<!-- Slim Top Bar -->
<header class="sticky top-0 z-40 border-b border-gray-200 bg-white dark:border-white/10 dark:bg-gray-900">
<div class="flex h-14 items-center justify-between px-4">
<div class="flex items-center gap-2">
@ -219,8 +223,8 @@ defmodule ToweropsWeb.Layouts do
K
</kbd>
</button>
<!-- Notification bell -->
<!-- Notification bell -->
<.link
:if={@current_organization}
navigate={~p"/alerts"}
@ -238,8 +242,8 @@ defmodule ToweropsWeb.Layouts do
</span>
<% end %>
</.link>
<!-- Help icon -->
<!-- Help icon -->
<.link
navigate={~p"/help"}
class="p-2 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-800 transition-colors"
@ -247,11 +251,11 @@ defmodule ToweropsWeb.Layouts do
>
<.icon name="hero-question-mark-circle" class="h-5 w-5" />
</.link>
<!-- Theme toggle -->
<!-- Theme toggle -->
<.theme_toggle />
<!-- User menu -->
<!-- User menu -->
<button
type="button"
id="org-menu-button"
@ -326,8 +330,8 @@ defmodule ToweropsWeb.Layouts do
</div>
</div>
</header>
<!-- Desktop Sidebar -->
<!-- Desktop Sidebar -->
<aside
:if={@current_organization || (@current_scope && @current_scope.user)}
id="app-sidebar"
@ -336,7 +340,10 @@ defmodule ToweropsWeb.Layouts do
<!-- Nav sections -->
<nav class="flex-1 px-2 py-4 space-y-1">
<!-- MONITOR section -->
<p data-sidebar-section class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
<p
data-sidebar-section
class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500"
>
{t("Monitor")}
</p>
<.sidebar_link
@ -364,9 +371,12 @@ defmodule ToweropsWeb.Layouts do
icon="hero-map"
label={t("Network Map")}
/>
<!-- RESPOND section -->
<p data-sidebar-section class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
<!-- RESPOND section -->
<p
data-sidebar-section
class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500"
>
{t("Respond")}
</p>
<.sidebar_link
@ -386,11 +396,14 @@ defmodule ToweropsWeb.Layouts do
navigate={~p"/schedules"}
active={@active_page == "schedules"}
icon="hero-calendar-days"
label={t("Schedules")}
label={t("On-Call")}
/>
<!-- ANALYZE section -->
<p data-sidebar-section class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
<!-- ANALYZE section -->
<p
data-sidebar-section
class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500"
>
{t("Analyze")}
</p>
<.sidebar_link
@ -412,8 +425,8 @@ defmodule ToweropsWeb.Layouts do
label={t("Activity")}
/>
</nav>
<!-- Bottom pinned: org switcher + settings + collapse toggle -->
<!-- Bottom pinned: org switcher + settings + collapse toggle -->
<div class="border-t border-gray-200 dark:border-white/10 px-2 py-3 space-y-1">
<!-- Org switcher -->
<div :if={@current_organization} class="relative">
@ -432,8 +445,12 @@ defmodule ToweropsWeb.Layouts do
data-sidebar-tooltip={@current_organization.name}
>
<.icon name="hero-building-office-2" class="h-5 w-5 shrink-0" />
<span data-sidebar-text class="truncate flex-1 text-left">{@current_organization.name}</span>
<span data-sidebar-text><.icon name="hero-chevron-up-down" class="h-3 w-3 shrink-0" /></span>
<span data-sidebar-text class="truncate flex-1 text-left">
{@current_organization.name}
</span>
<span data-sidebar-text>
<.icon name="hero-chevron-up-down" class="h-3 w-3 shrink-0" />
</span>
</button>
<div
id="sidebar-org-switcher-menu"
@ -464,8 +481,8 @@ defmodule ToweropsWeb.Layouts do
</div>
</div>
</div>
<!-- Settings link -->
<!-- Settings link -->
<.sidebar_link
:if={@current_organization}
navigate={~p"/orgs/#{@current_organization.slug}/settings"}
@ -473,8 +490,8 @@ defmodule ToweropsWeb.Layouts do
icon="hero-cog-6-tooth"
label={t("Settings")}
/>
<!-- Collapse toggle -->
<!-- Collapse toggle -->
<button
type="button"
data-sidebar-toggle
@ -485,8 +502,8 @@ defmodule ToweropsWeb.Layouts do
</button>
</div>
</aside>
<!-- Mobile slide-out panel -->
<!-- Mobile slide-out panel -->
<div
:if={@current_organization || (@current_scope && @current_scope.user)}
id="mobile-menu"
@ -510,8 +527,8 @@ defmodule ToweropsWeb.Layouts do
<.icon name="hero-x-mark" class="size-5" />
</button>
</div>
<!-- Org name -->
<!-- Org name -->
<div
:if={@current_organization}
class="px-4 py-3 border-b border-gray-100 dark:border-white/5"
@ -523,8 +540,8 @@ defmodule ToweropsWeb.Layouts do
{@current_organization.name}
</p>
</div>
<!-- MONITOR -->
<!-- MONITOR -->
<div class="px-2 py-3">
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
{t("Monitor")}
@ -546,8 +563,8 @@ defmodule ToweropsWeb.Layouts do
<.icon name="hero-map" class="size-5" /> {t("Network Map")}
</.mobile_nav_link>
</div>
<!-- RESPOND -->
<!-- RESPOND -->
<div class="px-2 py-3 border-t border-gray-100 dark:border-white/5">
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
{t("Respond")}
@ -559,11 +576,11 @@ defmodule ToweropsWeb.Layouts do
<.icon name="hero-wrench-screwdriver" class="size-5" /> {t("Maintenance")}
</.mobile_nav_link>
<.mobile_nav_link navigate={~p"/schedules"} active={@active_page == "schedules"}>
<.icon name="hero-calendar-days" class="size-5" /> {t("Schedules")}
<.icon name="hero-calendar-days" class="size-5" /> {t("On-Call")}
</.mobile_nav_link>
</div>
<!-- ANALYZE -->
<!-- ANALYZE -->
<div class="px-2 py-3 border-t border-gray-100 dark:border-white/5">
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
{t("Analyze")}
@ -578,8 +595,8 @@ defmodule ToweropsWeb.Layouts do
<.icon name="hero-clock" class="size-5" /> {t("Activity")}
</.mobile_nav_link>
</div>
<!-- Account -->
<!-- Account -->
<div class="px-2 py-3 border-t border-gray-100 dark:border-white/5">
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
{t("Account")}
@ -603,8 +620,8 @@ defmodule ToweropsWeb.Layouts do
</div>
</div>
</div>
<!-- Main content -->
<!-- Main content -->
<div id="main-wrapper" class="flex flex-col min-h-[calc(100vh-3.5rem)]">
<main id="main-content" tabindex="-1" class="flex-1 py-6 sm:py-10 page-fade-in">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">

View file

@ -4,7 +4,7 @@
active_page="alerts"
>
<%!-- Compact header --%>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center justify-between mb-1">
<div class="flex items-center gap-3">
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Alerts")}</h1>
<span class="rounded px-2 py-0.5 text-xs font-medium bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400">
@ -12,6 +12,9 @@
</span>
</div>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
{t("Monitor and triage network alerts, grouped by site with subscriber impact.")}
</p>
<%!-- Filter pills --%>
<div class="flex items-center gap-2 mb-3 flex-wrap">

View file

@ -3,7 +3,7 @@
current_scope={@current_scope}
active_page="maintenance"
>
<div class="flex items-center justify-between mb-6">
<div class="flex items-center justify-between mb-1">
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Maintenance Windows")}</h1>
<.link
navigate={~p"/maintenance/new"}
@ -13,6 +13,9 @@
{t("New Window")}
</.link>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
{t("Schedule planned downtime to suppress alerts during maintenance work.")}
</p>
<%!-- Filter tabs --%>
<div class="flex items-center gap-2 mb-4 flex-wrap">

View file

@ -227,8 +227,8 @@
<% end %>
</div>
</div>
<!-- Alert Routing Section -->
<!-- Alert Routing Section -->
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8 border-t border-gray-200 dark:border-white/5">
<div>
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
@ -248,8 +248,10 @@
<label class={[
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
if(routing_value == "builtin",
do: "border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
else: "border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
do:
"border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
else:
"border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
)
]}>
<input
@ -265,17 +267,21 @@
{t("TowerOps Built-in")}
</span>
<span class="block text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{t("Use TowerOps schedules and escalation policies to notify on-call staff.")}
{t(
"Use TowerOps schedules and escalation policies to notify on-call staff."
)}
</span>
</div>
</label>
<!-- PagerDuty option -->
<!-- PagerDuty option -->
<label class={[
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
if(routing_value == "pagerduty",
do: "border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
else: "border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
do:
"border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
else:
"border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
)
]}>
<input
@ -287,20 +293,30 @@
/>
<div class="ml-3">
<span class="block text-sm font-semibold text-gray-900 dark:text-white">
<.icon name="hero-arrow-top-right-on-square" class="h-4 w-4 inline -mt-0.5" />
<.icon
name="hero-arrow-top-right-on-square"
class="h-4 w-4 inline -mt-0.5"
/>
{t("PagerDuty")}
</span>
<span class="block text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{t("Route alerts to PagerDuty for incident management and on-call notifications.")}
{t(
"Route alerts to PagerDuty for incident management and on-call notifications."
)}
</span>
<%= if routing_value == "pagerduty" && !@integrations["pagerduty"] do %>
<div class="mt-2 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-2.5">
<div class="flex items-center gap-2">
<.icon name="hero-exclamation-triangle" class="h-4 w-4 text-amber-500 shrink-0" />
<.icon
name="hero-exclamation-triangle"
class="h-4 w-4 text-amber-500 shrink-0"
/>
<span class="text-xs text-amber-800 dark:text-amber-200">
{t("PagerDuty integration not configured yet.")}
<.link
navigate={~p"/orgs/#{@organization.slug}/settings?tab=integrations"}
navigate={
~p"/orgs/#{@organization.slug}/settings?tab=integrations"
}
class="font-semibold underline"
>
{t("Set it up →")}
@ -311,13 +327,15 @@
<% end %>
</div>
</label>
<!-- Both option -->
<!-- Both option -->
<label class={[
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
if(routing_value == "both",
do: "border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
else: "border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
do:
"border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
else:
"border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
)
]}>
<input
@ -333,7 +351,9 @@
{t("Both")}
</span>
<span class="block text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{t("Send alerts to both PagerDuty and built-in escalation. Useful during migration.")}
{t(
"Send alerts to both PagerDuty and built-in escalation. Useful during migration."
)}
</span>
</div>
</label>

View file

@ -3,8 +3,8 @@
current_scope={@current_scope}
active_page="schedules"
>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Schedules")}</h1>
<div class="flex items-center justify-between mb-1">
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("On-Call & Schedules")}</h1>
<div class="flex items-center gap-2">
<%= if @tab == "escalation-policies" do %>
<.link
@ -25,6 +25,9 @@
<% end %>
</div>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
{t("Define on-call rotations and escalation policies for alert notifications.")}
</p>
<div class="flex items-center gap-2 mb-6">
<.link

View file

@ -11,6 +11,13 @@
true -> []
end
} />
<div class="mb-1">
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Trace")}</h1>
<p class="text-sm text-gray-500 dark:text-gray-400">
{t("Investigate subscriber impact, site health, and network path issues.")}
</p>
</div>
<div class="space-y-6">
<%!-- Search Bar --%>
<div class="card bg-base-100 shadow-sm border border-base-200 dark:border-white/10 dark:bg-gray-900">