documentation updates

This commit is contained in:
Graham McIntire 2026-01-26 16:36:52 -06:00
parent 6782309682
commit 19a818647b
No known key found for this signature in database
6 changed files with 564 additions and 1 deletions

View file

@ -211,6 +211,12 @@ defmodule ToweropsWeb.Layouts do
>
Agents
</.nav_link>
<.nav_link
navigate={~p"/help"}
active={@active_page == "help"}
>
Help
</.nav_link>
</div>
</div>

View file

@ -495,7 +495,7 @@
<div class="mt-6 flex justify-end">
<.button phx-click="close_token_modal" variant="primary">
I've Saved the Token
Close
</.button>
</div>
</div>

View file

@ -0,0 +1,538 @@
defmodule ToweropsWeb.HelpLive.Index do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Accounts
alias Towerops.Organizations
@impl true
def mount(_params, session, socket) do
# Check if user is logged in via session
current_user = get_current_user(socket, session)
is_authenticated = !is_nil(current_user)
# Load user's default organization if authenticated
current_organization =
if current_user do
case Organizations.list_user_organizations(current_user.id) do
[first_org | _] -> first_org
[] -> nil
end
end
# Build current_scope if authenticated
current_scope =
if current_user do
%{
user: current_user,
impersonating?: false
}
end
socket =
socket
|> assign(:page_title, "Help")
|> assign(:current_user, current_user)
|> assign(:is_authenticated, is_authenticated)
|> assign(:current_scope, current_scope)
|> assign(:current_organization, current_organization)
|> assign(:timezone, if(current_user, do: current_user.timezone, else: "UTC"))
{:ok, socket}
end
@impl true
def handle_params(params, _url, socket) do
section = Map.get(params, "section", "getting-started")
{:noreply, assign(socket, active_section: section)}
end
defp get_current_user(socket, session) do
# Try to get current_user from socket assigns (if already set by on_mount)
with nil <- Map.get(socket.assigns, :current_user),
token when not is_nil(token) <- session["user_token"],
{user, _authenticated_at} <- Accounts.get_user_by_session_token(token) do
user
else
%Towerops.Accounts.User{} = user -> user
_ -> nil
end
end
slot :inner_block, required: true
defp code(assigns) do
~H"""
<span class="font-mono bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded">
{render_slot(@inner_block)}
</span>
"""
end
attr :active_section, :string, required: true
defp help_content(assigns) do
~H"""
<div class="w-full">
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Help & Documentation</h1>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Learn how to use Towerops to monitor your network infrastructure
</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<!-- Sidebar Navigation -->
<div class="lg:col-span-1">
<nav class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4 sticky top-4">
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
Sections
</h3>
<ul class="space-y-1">
<li>
<.link
patch={~p"/help?section=getting-started"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "getting-started" do
"bg-blue-50 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
else
"text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
end
]}
>
Getting Started
</.link>
</li>
<li>
<.link
patch={~p"/help?section=cloud-pollers"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "cloud-pollers" do
"bg-blue-50 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
else
"text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
end
]}
>
Cloud Pollers
</.link>
</li>
<li>
<.link
patch={~p"/help?section=agents"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "agents" do
"bg-blue-50 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
else
"text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
end
]}
>
Remote Pollers
</.link>
</li>
</ul>
</nav>
</div>
<!-- Content Area -->
<div class="lg:col-span-3">
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
<%= case @active_section do %>
<% "getting-started" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
Getting Started with Towerops
</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">
<p class="text-gray-600 dark:text-gray-400">
Towerops is a network monitoring platform that helps you keep track of your network devices,
monitor their health, and get alerts when issues occur.
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Quick Start Guide
</h3>
<div class="space-y-6">
<!-- Step 1 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
1
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Create a Site
</h4>
<p class="text-gray-600 dark:text-gray-400">
Sites represent physical or logical locations where your network devices are located.
Start by creating a site for your office, data center, or any location where you have equipment.
</p>
<p class="text-sm text-gray-500 dark:text-gray-500 mt-2">
Navigate to
<.code>Sites</.code>
<.code>Add Site</.code>
</p>
</div>
</div>
<!-- Step 2 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
2
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Add Your First Device
</h4>
<p class="text-gray-600 dark:text-gray-400">
Add network devices like routers, switches, access points, or servers. You'll need the device's
IP address or hostname, and SNMP community string (for SNMP-enabled devices).
</p>
<p class="text-sm text-gray-500 dark:text-gray-500 mt-2">
Navigate to
<.code>Devices</.code>
<.code>Add Device</.code>
</p>
</div>
</div>
<!-- Step 3 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
3
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
View Device Metrics
</h4>
<p class="text-gray-600 dark:text-gray-400">
Click on any device to view real-time metrics including CPU usage, memory, temperature,
interface traffic, and more. Explore the different tabs to see network topology, port status,
and historical performance data.
</p>
</div>
</div>
</div>
<div class="mt-8 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<div class="flex gap-3">
<.icon
name="hero-light-bulb"
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
/>
<div>
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
Pro Tip
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
For devices behind firewalls or in remote locations, you can deploy a remote poller
to monitor devices locally without exposing them to the internet. See the
<.link
patch={~p"/help?section=agents"}
class="underline hover:text-blue-900 dark:hover:text-blue-200"
>
Remote Pollers
</.link>
section for more information.
</p>
</div>
</div>
</div>
</div>
</div>
<% "cloud-pollers" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
Cloud Pollers
</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">
<p class="text-gray-600 dark:text-gray-400">
If you have publicly reachable devices, they will be automatically polled from one of our cloud pollers unless overridden by your settings.
Our cloud infrastructure monitors your devices without requiring any additional setup or agent installation.
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Active Cloud Pollers
</h3>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
<thead class="bg-gray-50 dark:bg-gray-800/75">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Location
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
IPv4 Address
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
IPv6 Address
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
DFW (Dallas-Fort Worth)
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-600 dark:text-gray-400">
144.202.64.79
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-600 dark:text-gray-400">
2001:19f0:6401:0af2:5400:05ff:fee7:1bfb
</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<div class="flex gap-3">
<.icon
name="hero-information-circle"
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
/>
<div>
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
Firewall Configuration
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
If your devices are behind a firewall, make sure to allow SNMP traffic (UDP port 161) and ICMP (for ping monitoring) from these IP addresses.
For devices not accessible from the public internet, consider using a
<.link
patch={~p"/help?section=agents"}
class="underline hover:text-blue-900 dark:hover:text-blue-200"
>
Remote Poller
</.link>
instead.
</p>
</div>
</div>
</div>
</div>
</div>
<% "agents" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
Remote Pollers
</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">
<p class="text-gray-600 dark:text-gray-400">
Remote pollers allow you to monitor devices that are not accessible from the public internet,
such as devices behind firewalls, in private networks, or at remote locations. A remote poller
runs on your network and securely communicates with Towerops to poll your devices locally.
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Requirements
</h3>
<p class="text-gray-600 dark:text-gray-400">
Remote pollers can run on any system that supports Docker Compose, including:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside">
<li>Linux servers (Ubuntu, Debian, CentOS, RHEL, etc.)</li>
<li>Windows Server with Docker Desktop</li>
<li>macOS with Docker Desktop</li>
<li>Raspberry Pi or other ARM-based systems</li>
<li>Virtual machines (VMware, Hyper-V, Proxmox, etc.)</li>
</ul>
<div class="mt-4 p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<p class="text-sm text-gray-600 dark:text-gray-400">
<strong class="text-gray-900 dark:text-white">Minimum requirements:</strong>
1 CPU core, 512 MB RAM, and network access to your devices
</p>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Getting Started
</h3>
<div class="space-y-6">
<!-- Step 1 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
1
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Create an Agent Token
</h4>
<p class="text-gray-600 dark:text-gray-400">
Navigate to the
<.code>Agents</.code>
page in Towerops and create a new agent token. This token authenticates your remote poller with the Towerops platform.
</p>
</div>
</div>
<!-- Step 2 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
2
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Get Docker Compose Configuration
</h4>
<p class="text-gray-600 dark:text-gray-400">
On the Agents page, add a new remote agent. Copy the
<.code>docker-compose.yml</.code>
file contents from the agent details, which includes your authentication token pre-configured.
</p>
</div>
</div>
<!-- Step 3 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
3
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Deploy the Remote Poller
</h4>
<p class="text-gray-600 dark:text-gray-400">
Transfer the
<.code>docker-compose.yml</.code>
file to your server and run:
</p>
<div class="mt-3 p-4 bg-black rounded-lg not-prose">
<span class="text-sm text-white font-mono">docker compose up -d</span>
</div>
<p class="text-gray-600 dark:text-gray-400 mt-3">
The remote poller will start automatically and connect to Towerops.
</p>
</div>
</div>
<!-- Step 4 -->
<div class="flex gap-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold">
4
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Assign Devices to Remote Poller
</h4>
<p class="text-gray-600 dark:text-gray-400">
You can assign devices to your remote poller in several ways:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside mt-2">
<li>
<strong class="text-gray-900 dark:text-white">Per Organization:</strong>
Set a default agent for all devices in your organization
</li>
<li>
<strong class="text-gray-900 dark:text-white">Per Site:</strong>
Set a default agent for all devices at a specific site
</li>
<li>
<strong class="text-gray-900 dark:text-white">Per Device:</strong>
Select the remote poller when creating or editing individual devices
</li>
</ul>
<p class="text-gray-600 dark:text-gray-400 mt-3">
Devices assigned to a remote poller will be monitored from your local network instead
of from the cloud.
</p>
</div>
</div>
</div>
<div class="mt-8 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<div class="flex gap-3">
<.icon
name="hero-shield-check"
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
/>
<div>
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
Security Note
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
Remote pollers use secure WebSocket connections to communicate with Towerops.
Your agent token is encrypted and should be kept confidential. The remote poller
only needs outbound HTTPS access (port 443) to connect to Towerops - no inbound
ports need to be opened on your firewall.
</p>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Managing Remote Pollers
</h3>
<p class="text-gray-600 dark:text-gray-400">
You can view the status of your remote pollers on the Agents page. The page shows:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside mt-3">
<li>Last connection time</li>
<li>Number of devices assigned to each poller</li>
<li>Connection status (online/offline)</li>
</ul>
<div class="mt-6 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<div class="flex gap-3">
<.icon
name="hero-exclamation-triangle"
class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5"
/>
<div>
<h4 class="text-sm font-semibold text-yellow-900 dark:text-yellow-300 mb-1">
Important
</h4>
<p class="text-sm text-yellow-800 dark:text-yellow-300">
If a remote poller goes offline, devices assigned to it will not be monitored until
the poller comes back online. Consider setting up monitoring alerts for your remote
pollers to be notified if they disconnect.
</p>
</div>
</div>
</div>
</div>
</div>
<% _ -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
Section Not Found
</h2>
<p class="text-gray-600 dark:text-gray-400">
The requested help section could not be found.
</p>
</div>
<% end %>
</div>
</div>
</div>
</div>
"""
end
end

View file

@ -0,0 +1,17 @@
<%= if @is_authenticated do %>
<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
current_organization={@current_organization}
active_page="help"
timezone={@timezone}
>
<.help_content active_section={@active_section} />
</Layouts.authenticated>
<% else %>
<Layouts.app flash={@flash}>
<div class="py-8">
<.help_content active_section={@active_section} />
</div>
</Layouts.app>
<% end %>

View file

@ -45,6 +45,7 @@ defmodule ToweropsWeb.Router do
get "/", PageController, :home
get "/privacy", PageController, :privacy
get "/docs/api", ApiDocsController, :index
live "/help", HelpLive.Index, :index
end
# Agent communication via WebSocket channel at /socket/agent

View file

@ -1,4 +1,5 @@
2025-01-26
* Cloud poller improvements
* Accessability improved
* Added initial help documentation