towerops/lib/towerops_web/live/agent_live/index.html.heex
Graham McIntire fa591fde05
Revert manual data directory setup instructions from agent modal
The Docker agent now handles permissions automatically via the
entrypoint script, so users don't need to manually create the
data directory or run chown commands.

Reverted the UI back to the simple docker-compose example.
2026-01-14 09:36:59 -06:00

256 lines
9.4 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
current_organization={@current_organization}
active_page="agents"
>
<.header>
{@page_title}
<:subtitle>Manage remote agents for local SNMP polling</:subtitle>
<:actions>
<.button phx-click={JS.show(to: "#new-agent-form")} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> New Agent
</.button>
</:actions>
</.header>
<!-- New Agent Form -->
<div id="new-agent-form" class="hidden mt-6">
<div class="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg p-6">
<h3 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100 mb-4">
Create New Agent
</h3>
<.form for={@agent_form} phx-submit="create_agent">
<div class="space-y-4">
<.input
field={@agent_form[:name]}
type="text"
label="Agent Name"
placeholder="e.g., Datacenter A"
required
/>
<div class="flex gap-3">
<.button type="submit" variant="primary">
Create Agent
</.button>
<button
type="button"
phx-click={JS.hide(to: "#new-agent-form")}
class="px-3 py-2 text-sm font-semibold text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100"
>
Cancel
</button>
</div>
</div>
</.form>
</div>
</div>
<%= if @agent_tokens == [] do %>
<div class="text-center py-16">
<.icon name="hero-server" class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-600" />
<h3 class="mt-4 text-lg font-semibold text-zinc-900 dark:text-zinc-100">No agents</h3>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
Get started by creating your first remote agent.
</p>
<div class="mt-6">
<.button phx-click={JS.show(to: "#new-agent-form")} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> New Agent
</.button>
</div>
</div>
<% else %>
<div class="mt-6">
<.table id="agents-table" rows={@agent_tokens}>
<:col :let={agent} label="Name">
<div class="font-medium text-zinc-900 dark:text-zinc-100">{agent.name}</div>
<%= if agent.metadata["version"] do %>
<div class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
v{agent.metadata["version"]}
</div>
<% end %>
</:col>
<:col :let={agent} label="Status">
<% {status, label} = agent_status(agent) %>
<span class={"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium #{status_badge_class(status)}"}>
{label}
</span>
</:col>
<:col :let={agent} label="Equipment">
<div class="text-sm text-zinc-900 dark:text-zinc-100">
{Map.get(@equipment_counts, agent.id, 0)}
</div>
<div class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
directly assigned
</div>
</:col>
<:col :let={agent} label="Last Seen">
<div class="text-sm text-zinc-600 dark:text-zinc-400">
{format_last_seen(agent.last_seen_at)}
</div>
<%= if agent.last_ip do %>
<div class="text-xs text-zinc-500 dark:text-zinc-500 mt-0.5">
{agent.last_ip}
</div>
<% end %>
</:col>
<:col :let={agent} label="Metadata">
<%= if agent.metadata["hostname"] do %>
<div class="text-sm text-zinc-600 dark:text-zinc-400">
<.icon name="hero-computer-desktop" class="h-4 w-4 inline" />
{agent.metadata["hostname"]}
</div>
<% end %>
</:col>
<:col :let={agent} label="Created">
<div class="text-sm text-zinc-600 dark:text-zinc-400">
{Calendar.strftime(agent.inserted_at, "%b %d, %Y")}
</div>
</:col>
<:action :let={agent}>
<%= if agent.enabled do %>
<div class="flex items-center gap-3">
<button
type="button"
phx-click="show_setup"
phx-value-id={agent.id}
class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
View Setup
</button>
<button
type="button"
phx-click="revoke_agent"
phx-value-id={agent.id}
data-confirm="Are you sure you want to revoke this agent? It will no longer be able to authenticate."
class="text-sm font-medium text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
>
Revoke
</button>
</div>
<% else %>
<span class="text-xs text-zinc-500 dark:text-zinc-400">Revoked</span>
<% end %>
</:action>
</.table>
</div>
<% end %>
<!-- Token Display Modal -->
<%= if @show_token_modal && @new_token do %>
<div
id="token-modal"
class="fixed inset-0 z-50 overflow-y-auto"
phx-mounted={JS.show()}
>
<div class="flex min-h-screen items-center justify-center p-4">
<div
class="fixed inset-0 bg-zinc-500/75 dark:bg-zinc-950/75 transition-opacity"
phx-click="close_token_modal"
>
</div>
<div class="relative bg-white dark:bg-zinc-900 rounded-lg shadow-xl max-w-2xl w-full">
<div class="p-6">
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
Agent Setup Instructions
</h3>
<p class="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
Use the token and Docker Compose configuration below to deploy this agent.
</p>
</div>
<button
type="button"
phx-click="close_token_modal"
class="text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
>
<.icon name="hero-x-mark" class="h-6 w-6" />
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
Agent Name
</label>
<div class="text-base font-mono text-zinc-900 dark:text-zinc-100">
{@new_token.agent_token.name}
</div>
</div>
<div>
<label class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
Authentication Token
</label>
<div class="relative">
<input
type="text"
readonly
value={@new_token.token}
class="w-full font-mono text-sm bg-zinc-50 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded px-3 py-2 pr-24 text-zinc-900 dark:text-zinc-100"
id="agent-token-input"
/>
<button
type="button"
phx-click={
JS.dispatch("phx:copy", to: "#agent-token-input")
|> JS.push("token_copied")
}
class="absolute right-2 top-1/2 -translate-y-1/2 px-3 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300 bg-zinc-100 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded hover:bg-zinc-200 dark:hover:bg-zinc-700"
>
<.icon name="hero-clipboard" class="h-4 w-4" />
</button>
</div>
</div>
<div class="bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4">
<h4 class="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-2">
Docker Compose Example
</h4>
<pre class="text-xs font-mono text-zinc-600 dark:text-zinc-400 overflow-x-auto"><code>services:
towerops-agent:
image: {@agent_image}
restart: unless-stopped
environment:
- TOWEROPS_API_URL={url(@socket, ~p"/")}
- TOWEROPS_AGENT_TOKEN={@new_token.token}
volumes:
- ./data:/data</code></pre>
<button
type="button"
phx-click={JS.dispatch("phx:copy", to: "#docker-compose-example")}
class="mt-2 text-xs text-blue-600 dark:text-blue-400 hover:underline"
>
Copy to clipboard
</button>
<textarea id="docker-compose-example" class="sr-only">services:
towerops-agent:
image: {@agent_image}
restart: unless-stopped
environment:
- TOWEROPS_API_URL={url(@socket, ~p"/")}
- TOWEROPS_AGENT_TOKEN={@new_token.token}
volumes:
- ./data:/data</textarea>
</div>
</div>
<div class="mt-6 flex justify-end">
<.button phx-click="close_token_modal" variant="primary">
I've Saved the Token
</.button>
</div>
</div>
</div>
</div>
</div>
<% end %>
</Layouts.authenticated>