towerops/lib/towerops_web/live/help_live/index.ex
2026-02-04 17:56:48 -06:00

1672 lines
85 KiB
Elixir

defmodule ToweropsWeb.HelpLive.Index do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Accounts
alias Towerops.Accounts.Scope
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
current_user
|> Scope.for_user()
|> Scope.put_organization(current_organization)
end
socket =
socket
|> assign(:page_title, "Help")
|> assign(:current_user, current_user)
|> assign(:is_authenticated, is_authenticated)
|> assign(:current_scope, current_scope)
|> assign(:timezone, if(current_user, do: current_user.timezone, else: "UTC"))
|> assign(:generated_password, nil)
|> assign(:password_generating, false)
# Generate initial password
if connected?(socket) do
send(self(), :fetch_random_password)
end
{:ok, socket}
end
@impl true
def handle_params(params, _url, socket) do
section = Map.get(params, "section", "about")
socket =
socket
|> assign(:active_section, section)
|> assign(:generated_password, nil)
|> assign(:password_generating, false)
{:noreply, socket}
end
@impl true
def handle_event("generate_password", _params, socket) do
socket = assign(socket, :password_generating, true)
send(self(), :fetch_random_password)
{:noreply, socket}
end
@impl true
def handle_info(:fetch_random_password, socket) do
case generate_random_password() do
{:ok, password} ->
{:noreply,
socket
|> assign(:generated_password, password)
|> assign(:password_generating, false)
|> put_flash(:info, "Password generated successfully from random.org")}
{:error, reason} ->
{:noreply,
socket
|> assign(:password_generating, false)
|> put_flash(:error, "Failed to generate password: #{reason}")}
end
end
defp generate_random_password do
# Generate a 24-character truly random password from random.org
# Format: uppercase, lowercase, and digits
url =
"https://www.random.org/strings/?num=1&len=24&digits=on&upperalpha=on&loweralpha=on&unique=on&format=plain&rnd=new"
case Req.get(url) do
{:ok, %{status: 200, body: body}} ->
password = String.trim(body)
{:ok, password}
{:ok, %{status: status}} ->
{:error, "random.org returned status #{status}"}
{:error, error} ->
{:error, Exception.message(error)}
end
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
attr :generated_password, :string, default: nil
attr :password_generating, :boolean, default: false
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=about"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "about" 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
]}
>
About Towerops
</.link>
</li>
<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=sites"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "sites" 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
]}
>
Sites
</.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>
<li>
<.link
patch={~p"/help?section=mikrotik"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "mikrotik" 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
]}
>
MikroTik
</.link>
</li>
<li>
<.link
patch={~p"/help?section=graphs"}
class={[
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
if @active_section == "graphs" 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
]}
>
Graphs & Live Polling
</.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 %>
<% "about" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
About 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 modern network monitoring and management platform designed to provide
comprehensive visibility into your network infrastructure. It came out of a need to simplify
network monitoring and alerting while running a wireless ISP.
</p>
<p class="text-gray-600 dark:text-gray-400">
Our promise to you:
<ul class="list-disc list-inside">
<li>No random marketing emails or sign up for our newsletter popups.</li>
<li>No trackers EVER, no external libraries used.</li>
<li>No ads or sponsored content.</li>
<li>No data collection or sharing.</li>
<li>
Our remote agent is
<a href="https://github.com/towerops-app/towerops-agent/">
fully open source
</a>
and ONLY collects monitoring jobs from the server and WILL NEVER allow us to access any part of your internal network.
</li>
</ul>
</p>
<p class="text-gray-600 dark:text-gray-400 mt-4">
Think of Towerops as combining the best of LibreNMS, Icinga2/Nagios, and PagerDuty into
a single, unified platform. You get deep network device monitoring with SNMP auto-discovery,
flexible health checks and alerting, and intelligent incident management—all with a modern
interface that makes complex monitoring workflows simple and accessible.
</p>
<p class="text-gray-600 dark:text-gray-400 mt-4">
Towerops is not and has no plans to be a replacement for a full WISP/network billing system.
</p>
<div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Network Monitoring
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Like LibreNMS, discover and monitor network devices via SNMP with support for
multi-vendor equipment, interface statistics, and topology mapping.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Health Checks & Alerting
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Like Icinga2/Nagios, configure flexible monitoring checks with customizable thresholds
and alert conditions for any metric or device state.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Incident Management
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Like PagerDuty, manage incidents with intelligent alerting, escalation policies,
and notification routing to keep your team informed.
</p>
</div>
</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>
<p class="text-sm text-blue-800 dark:text-blue-300">
Ready to get started? Check out the
<.link
patch={~p"/help?section=getting-started"}
class="underline hover:text-blue-900 dark:hover:text-blue-200"
>
Getting Started
</.link>
guide to begin monitoring your network infrastructure.
</p>
</div>
</div>
</div>
</div>
</div>
<% "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>
<% "sites" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
Sites
</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">
<p class="text-gray-600 dark:text-gray-400">
Sites are an optional organizational feature that lets you group devices by physical or logical
locations such as offices, datacenters, customer locations, or network zones. Sites are disabled
by default to keep things simple for single-location deployments.
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
When to Enable Sites
</h3>
<p class="text-gray-600 dark:text-gray-400">
Consider enabling sites if you:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside mt-3">
<li>
<strong class="text-gray-900 dark:text-white">
Manage multiple physical locations
</strong>
- Offices, datacenters, retail stores, etc.
</li>
<li>
<strong class="text-gray-900 dark:text-white">
Want to group credentials by location
</strong>
- Different SNMP communities or credentials per site
</li>
<li>
<strong class="text-gray-900 dark:text-white">
Need location-based monitoring
</strong>
- Assign different remote pollers to different sites
</li>
<li>
<strong class="text-gray-900 dark:text-white">
Organize by customer or department
</strong>
- Logical groupings for multi-tenant or large networks
</li>
</ul>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
When to Skip Sites
</h3>
<p class="text-gray-600 dark:text-gray-400">
You can skip enabling sites if you:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside mt-3">
<li>
<strong class="text-gray-900 dark:text-white">Have a single location</strong>
- All devices in one office or datacenter
</li>
<li>
<strong class="text-gray-900 dark:text-white">Want a simpler setup</strong>
- Devices belong directly to your organization without extra grouping
</li>
<li>
<strong class="text-gray-900 dark:text-white">
Don't need location-based credentials
</strong>
- Organization-level credentials work for all devices
</li>
</ul>
<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">
Note
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
Sites are optional and disabled by default. You can start without sites and enable them
later if your needs change. When sites are disabled, all devices belong directly to
your organization.
</p>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
How Sites Work
</h3>
<p class="text-gray-600 dark:text-gray-400">
When sites are enabled, devices can be organized into sites, and credentials (SNMP communities,
usernames, passwords) can be set at three levels:
</p>
<div class="mt-4 space-y-3">
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
1. Organization Level
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Default credentials that apply to all devices across all sites in your organization.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
2. Site Level (when sites enabled)
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Override organization defaults for all devices at a specific site. Useful when different
locations have different network configurations.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
3. Device Level
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Override organization or site credentials for a specific device. Useful for devices with
unique configurations.
</p>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Enabling Sites
</h3>
<div class="space-y-4">
<!-- 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">
Navigate to Organization Settings
</h4>
<p class="text-gray-600 dark:text-gray-400">
Go to
<.code>Settings</.code>
<.code>Organization</.code>
and scroll to the "Site Organization" section.
</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">
Enable the "Use Sites" Toggle
</h4>
<p class="text-gray-600 dark:text-gray-400">
Check the box for "Use sites to organize devices" and save your settings.
</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">
Create Your First Site
</h4>
<p class="text-gray-600 dark:text-gray-400">
Navigate to
<.code>Sites</.code>
<.code>Add Site</.code>
to create your first site. Give it a descriptive name like "Main Office" or "Dallas Datacenter".
</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 Sites
</h4>
<p class="text-gray-600 dark:text-gray-400">
When creating or editing devices, you can now assign them to specific sites. The site
selector will appear in the device form.
</p>
</div>
</div>
</div>
<div class="mt-8 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">
Disabling Sites
</h4>
<p class="text-sm text-yellow-800 dark:text-yellow-300">
If you disable sites after enabling them, all devices will be removed from their sites
and assigned directly to the organization. Site assignments will be lost, but devices
will remain in your organization and continue to be monitored.
</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>
<% "graphs" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
Graphs & Live Polling
</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">
<p class="text-gray-600 dark:text-gray-400">
Towerops provides powerful visualization capabilities for all monitored metrics. Every sensor,
interface, and metric can be viewed as a time-series graph with multiple time ranges and a
real-time live polling mode for instant feedback.
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Accessing Graphs
</h3>
<p class="text-gray-600 dark:text-gray-400">
Graphs are available throughout Towerops wherever metrics are displayed:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside mt-3">
<li>
<strong class="text-gray-900 dark:text-white">Device Overview:</strong>
Click any metric tile (CPU, Memory, Temperature, etc.) to view its graph
</li>
<li>
<strong class="text-gray-900 dark:text-white">Sensors Tab:</strong>
Click the graph icon next to any sensor reading
</li>
<li>
<strong class="text-gray-900 dark:text-white">Interfaces Tab:</strong>
Click the graph icon next to any interface to view traffic graphs
</li>
<li>
<strong class="text-gray-900 dark:text-white">Storage Tab:</strong>
Click any storage volume to view usage over time
</li>
</ul>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Time Ranges
</h3>
<p class="text-gray-600 dark:text-gray-400">
All graphs support multiple time ranges for analyzing trends at different scales:
</p>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">
1 Hour
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Recent activity with high detail
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">
6 Hours
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Half-day trends and patterns
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">
12 Hours
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Business day overview
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">
24 Hours (Default)
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Full day of activity
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">
7 Days
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Weekly trends and patterns
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">
30 Days
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Monthly overview and capacity planning
</p>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Live Polling Mode
</h3>
<p class="text-gray-600 dark:text-gray-400">
Live mode provides real-time sensor monitoring with data updating every second. This is perfect for:
</p>
<ul class="space-y-2 text-gray-600 dark:text-gray-400 list-disc list-inside mt-3">
<li>Testing configuration changes and seeing immediate effects</li>
<li>Monitoring system load during maintenance or upgrades</li>
<li>Watching temperature changes during thermal testing</li>
<li>Observing traffic patterns during load testing</li>
<li>Real-time troubleshooting of performance issues</li>
</ul>
<div class="mt-6 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<div class="flex gap-3">
<.icon
name="hero-signal"
class="h-5 w-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5"
/>
<div>
<h4 class="text-sm font-semibold text-green-900 dark:text-green-300 mb-1">
How Live Mode Works
</h4>
<ul class="text-sm text-green-800 dark:text-green-300 space-y-1 list-disc list-inside">
<li>Polls sensors directly via SNMP every 1 second</li>
<li>Displays a rolling 5-minute window (300 data points)</li>
<li>Updates chart in real-time as new data arrives</li>
<li>Automatically stops when you switch to another time range</li>
<li>Works with remote pollers - polling happens on the agent</li>
</ul>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Using Live Mode
</h3>
<div class="space-y-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">
1
</div>
</div>
<div class="flex-1">
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
Open Any Graph
</h4>
<p class="text-gray-600 dark:text-gray-400">
Navigate to any device and click on a metric graph (CPU, Memory, Temperature, Traffic, etc.)
</p>
</div>
</div>
<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">
Click the "Live" Button
</h4>
<p class="text-gray-600 dark:text-gray-400">
The Live button has a distinctive green gradient style and will pulse when active
</p>
</div>
</div>
<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">
Watch Real-Time Updates
</h4>
<p class="text-gray-600 dark:text-gray-400">
The graph will start updating every second with fresh data. A pulsing green indicator
shows that live polling is active.
</p>
</div>
</div>
<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">
Switch Back to Historical Data
</h4>
<p class="text-gray-600 dark:text-gray-400">
Click any other time range button to stop live polling and view historical data
</p>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Supported Metrics in Live Mode
</h3>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1 flex items-center gap-2">
<.icon name="hero-cpu-chip" class="h-4 w-4 text-blue-500" />
CPU / Processors
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Real-time CPU load and utilization
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1 flex items-center gap-2">
<.icon name="hero-circle-stack" class="h-4 w-4 text-blue-500" />
Memory Usage
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
RAM utilization percentage
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1 flex items-center gap-2">
<.icon name="hero-fire" class="h-4 w-4 text-orange-500" /> Temperature
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Device and component temperatures
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1 flex items-center gap-2">
<.icon name="hero-bolt" class="h-4 w-4 text-yellow-500" /> Voltage
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Power supply voltages
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1 flex items-center gap-2">
<.icon name="hero-server-stack" class="h-4 w-4 text-purple-500" /> Storage
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Disk usage and capacity
</p>
</div>
<div class="p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1 flex items-center gap-2">
<.icon name="hero-hashtag" class="h-4 w-4 text-green-500" /> Custom Metrics
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">
Sessions, connections, and counts
</p>
</div>
</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">
Note About Traffic Graphs
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
Live mode is currently only available for sensor metrics (CPU, memory, temperature, etc.).
Interface traffic graphs use historical data only, as traffic calculations require
comparing multiple SNMP polls over time.
</p>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Tips for Using Graphs
</h3>
<div class="space-y-3">
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Multiple Sensors on One Graph
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
When viewing aggregate metrics (like "Temperature" for all sensors), the graph
automatically displays all sensors of that type with different colors for easy comparison.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Max and Min Values
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Historical graphs (non-live) display the maximum and minimum values for the selected
time range at the bottom of the chart for quick reference.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Traffic Graph Direction
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Interface traffic graphs show outbound traffic as positive values (above zero) and
inbound traffic as negative values (below zero) for easy visualization of bidirectional flow.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Automatic Unit Scaling
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Traffic graphs automatically scale units (bps, Kbps, Mbps, Gbps) based on the data
range for optimal readability. Hover over data points to see exact values.
</p>
</div>
</div>
<div class="mt-8 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-light-bulb"
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">
Performance Tip
</h4>
<p class="text-sm text-yellow-800 dark:text-yellow-300">
Live polling makes direct SNMP requests every second. While this provides instant
feedback, keeping many live graphs open simultaneously may impact device performance.
Use live mode for troubleshooting and testing, then switch back to historical ranges
for routine monitoring.
</p>
</div>
</div>
</div>
</div>
</div>
<% "mikrotik" -> %>
<div class="p-6">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
MikroTik Configuration
</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">
<p class="text-gray-600 dark:text-gray-400">
Towerops supports read-only monitoring of MikroTik RouterOS devices via SSH.
In addition to SNMP polling, SSH access allows Towerops to retrieve detailed system information
and create configuration backups of your MikroTik devices for disaster recovery and audit purposes.
</p>
<div class="mt-4 space-y-4">
<div class="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">
Read-Only Access
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
Towerops currently operates in read-only mode for MikroTik devices. Configuration changes,
reboots, and other administrative actions are not supported. This ensures your device
configurations remain unchanged by monitoring operations.
</p>
</div>
</div>
</div>
<div class="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-server"
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">
Cloud or Agent-Based Connections
</h4>
<p class="text-sm text-blue-800 dark:text-blue-300">
Both SNMP polling and SSH connections to MikroTik devices can be performed from either
Towerops cloud infrastructure or your remote poller (agent). For publicly accessible devices,
cloud polling is the simplest option. For devices on private networks or when you prefer
to keep SSH credentials on your local network, deploy a remote poller. The device assignment
determines which poller handles both SNMP and SSH operations.
</p>
</div>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Security Best Practices
</h3>
<p class="text-gray-600 dark:text-gray-400">
For security purposes, it is strongly recommended to create a dedicated read-only user account
for Towerops rather than using an administrator account. This follows the principle of least
privilege and minimizes potential security risks. Even though Towerops only performs read-only
operations, limiting the account permissions provides defense in depth.
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
Creating a Read-Only User
</h3>
<p class="text-gray-600 dark:text-gray-400">
Connect to your MikroTik device via SSH or terminal and execute the following commands to
create a read-only user named <.code>towerops</.code>:
</p>
<div class="mt-4 space-y-3">
<div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2 font-medium">
Step 1: Create a new user group with read-only permissions
</p>
<div class="p-4 bg-black rounded-lg not-prose">
<div class="flex items-start gap-2">
<code class="text-sm text-white font-mono break-all flex-1">
/user group add name=readonly policy=ssh,read,test,api
</code>
<button
type="button"
phx-click={JS.dispatch("phx:copy", to: "#step1-command")}
class="flex-shrink-0 p-1 text-blue-400 hover:text-blue-300"
title="Copy to clipboard"
>
<.icon name="hero-clipboard-document" class="h-4 w-4" />
</button>
<input
type="hidden"
id="step1-command"
value="/user group add name=readonly policy=ssh,read,test,api"
/>
</div>
</div>
</div>
<div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2 font-medium">
Step 2: Create the monitoring user with a strong password
</p>
<div class="p-4 bg-black rounded-lg not-prose">
<div class="flex items-start gap-2">
<code class="text-sm text-white font-mono break-all flex-1">
/user add name=towerops password={if @generated_password,
do: @generated_password,
else: "YOUR_STRONG_PASSWORD"} group=readonly
</code>
<%= if @generated_password do %>
<button
type="button"
phx-click={JS.dispatch("phx:copy", to: "#generated-password-value")}
class="flex-shrink-0 p-1 text-blue-400 hover:text-blue-300"
title="Copy to clipboard"
>
<.icon name="hero-clipboard-document" class="h-4 w-4" />
</button>
<input
type="hidden"
id="generated-password-value"
value={@generated_password}
/>
<% end %>
</div>
</div>
<div class="mt-3 flex items-start gap-3">
<.button
type="button"
phx-click="generate_password"
disabled={@password_generating}
>
<%= if @password_generating do %>
<.icon name="hero-arrow-path" class="h-4 w-4 mr-1 animate-spin" />
Generating...
<% else %>
<.icon name="hero-sparkles" class="h-4 w-4 mr-1" />
Regenerate Random Password
<% end %>
</.button>
<%= if @generated_password do %>
<p class="text-xs text-yellow-600 dark:text-yellow-400 font-medium mt-1.5">
⚠️ This truly random password from random.org will only be shown once!
</p>
<% end %>
</div>
</div>
<div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2 font-medium">
Step 3: Verify the user was created successfully
</p>
<div class="p-4 bg-black rounded-lg not-prose">
<div class="flex items-start gap-2">
<code class="text-sm text-white font-mono break-all flex-1">
/user print detail where name=towerops
</code>
<button
type="button"
phx-click={JS.dispatch("phx:copy", to: "#step3-command")}
class="flex-shrink-0 p-1 text-blue-400 hover:text-blue-300"
title="Copy to clipboard"
>
<.icon name="hero-clipboard-document" class="h-4 w-4" />
</button>
<input
type="hidden"
id="step3-command"
value="/user print detail where name=towerops"
/>
</div>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Permissions Explained
</h3>
<p class="text-gray-600 dark:text-gray-400">
The
<.code>readonly</.code>
group includes the following permissions. Note that while these
permissions are granted to the user account, Towerops only performs read operations and does
not make any configuration changes:
</p>
<div class="mt-4 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">
Permission
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Description
</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-mono text-gray-900 dark:text-white">
ssh
</td>
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
Allow SSH access to the device
</td>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
read
</td>
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
Allow viewing configuration and status (read-only)
</td>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
test
</td>
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
Allow executing diagnostic commands (ping, traceroute, etc.)
</td>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
api
</td>
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
Allow API access for automated monitoring
</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">
Important Notes
</h4>
<ul class="text-sm text-blue-800 dark:text-blue-300 space-y-1 list-disc list-inside">
<li>The read-only user cannot modify device configuration</li>
<li>No write, reboot, or sensitive permissions are granted</li>
<li>Use a strong, unique password for the monitoring account</li>
<li>Consider restricting SSH access by source IP if possible</li>
</ul>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
Configuring in Towerops
</h3>
<p class="text-gray-600 dark:text-gray-400">
After creating the read-only user, configure SSH credentials in Towerops:
</p>
<div class="mt-4 space-y-4">
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Organization-Level Configuration
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Navigate to
<.code>Settings</.code>
<.code>Organization</.code>
to set default
SSH credentials for all MikroTik devices in your organization.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Site-Level Configuration
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
Navigate to
<.code>Sites</.code>
→ select a site →
<.code>Edit</.code>
to override
credentials for all devices at a specific location.
</p>
</div>
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Device-Level Configuration
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
When editing a MikroTik device, you can specify unique SSH credentials that override
organization and site defaults.
</p>
</div>
</div>
<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-shield-check"
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">
Security Recommendations
</h4>
<ul class="text-sm text-yellow-800 dark:text-yellow-300 space-y-1 list-disc list-inside">
<li>Always use SSL/TLS for SSH connections (API-SSL on port 8729)</li>
<li>Store credentials at the organization or site level when possible</li>
<li>Rotate passwords periodically following your security policies</li>
<li>Monitor access logs for unauthorized SSH connection attempts</li>
<li>
Consider using SSH keys instead of passwords (if supported by your setup)
</li>
</ul>
</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