Packages

Phoenix LiveView dashboard components for ExESDB cluster monitoring

Current section

Files

Jump to
ex_esdb_dashboard lib dashboard operational_live.ex
Raw

lib/dashboard/operational_live.ex

defmodule ExESDBDashboard.OperationalLive do
@moduledoc """
Advanced Operational Monitoring LiveView for comprehensive system oversight.
This component provides real-time monitoring of all operational aspects of the
ExESDB cluster, including:
- System configuration changes
- Performance metrics and trends
- Security events and authentication
- Audit trail and compliance tracking
- Alert management and notifications
- Diagnostic information and debugging
- Log aggregation and analysis
This leverages the enhanced operational messaging patterns from ExESDB core
package for consistent node tracking, precise timestamps, and comprehensive
operational visibility.
"""
use Phoenix.LiveView
require Logger
# Update intervals
@refresh_interval 3_000
@metrics_history_limit 100
@log_buffer_limit 200
@alert_history_limit 50
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
subscribe_to_operational_events()
schedule_refresh()
end
socket =
socket
|> assign(:page_title, "Operational Monitoring")
|> assign(:active_tab, "overview")
|> assign(:auto_refresh, true)
|> assign(:last_updated, DateTime.utc_now())
|> assign(:operational_overview, get_initial_operational_overview())
|> assign(:performance_metrics, [])
|> assign(:security_events, [])
|> assign(:audit_trail, [])
|> assign(:alerts, [])
|> assign(:diagnostics, %{})
|> assign(:logs, [])
|> assign(:config_changes, [])
|> assign(:filters, %{
log_level: "all",
alert_severity: "all",
security_type: "all",
time_range: "1h"
})
{:ok, socket}
end
@impl true
def handle_params(params, _uri, socket) do
tab = Map.get(params, "tab", "overview")
{:noreply, assign(socket, :active_tab, tab)}
end
@impl true
def handle_event("switch_tab", %{"tab" => tab}, socket) do
{:noreply, assign(socket, :active_tab, tab)}
end
@impl true
def handle_event("toggle_auto_refresh", _params, socket) do
auto_refresh = !socket.assigns.auto_refresh
if auto_refresh and connected?(socket) do
schedule_refresh()
end
{:noreply, assign(socket, :auto_refresh, auto_refresh)}
end
@impl true
def handle_event("manual_refresh", _params, socket) do
{:noreply, refresh_operational_data(socket)}
end
@impl true
def handle_event("update_filter", %{"filter" => filter, "value" => value}, socket) do
filters = Map.put(socket.assigns.filters, String.to_existing_atom(filter), value)
{:noreply, assign(socket, :filters, filters)}
end
@impl true
def handle_event("clear_alerts", _params, socket) do
{:noreply, assign(socket, :alerts, [])}
end
@impl true
def handle_event("export_audit", _params, socket) do
# This could trigger an audit export in a real implementation
{:noreply, put_flash(socket, :info, "Audit trail export requested")}
end
# Handle operational alert messages
@impl true
def handle_info({:operational_alert, alert_data}, socket) do
updated_alerts = add_to_buffer(socket.assigns.alerts, alert_data, @alert_history_limit)
socket =
socket
|> assign(:alerts, updated_alerts)
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
{:noreply, socket}
end
# Handle operational metrics
@impl true
def handle_info({:operational_metrics, metrics_data}, socket) do
updated_metrics = add_to_buffer(socket.assigns.performance_metrics, metrics_data, @metrics_history_limit)
socket =
socket
|> assign(:performance_metrics, updated_metrics)
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
{:noreply, socket}
end
# Handle security events
@impl true
def handle_info({:security_event, security_data}, socket) do
updated_security = add_to_buffer(socket.assigns.security_events, security_data, 50)
socket =
socket
|> assign(:security_events, updated_security)
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
{:noreply, socket}
end
# Handle audit events
@impl true
def handle_info({:audit_event, audit_data}, socket) do
updated_audit = add_to_buffer(socket.assigns.audit_trail, audit_data, 100)
socket =
socket
|> assign(:audit_trail, updated_audit)
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
{:noreply, socket}
end
# Handle diagnostic events
@impl true
def handle_info({:diagnostic_event, diagnostic_data}, socket) do
component = diagnostic_data.component
updated_diagnostics = Map.put(socket.assigns.diagnostics, component, diagnostic_data)
socket =
socket
|> assign(:diagnostics, updated_diagnostics)
|> assign(:last_updated, DateTime.utc_now())
{:noreply, socket}
end
# Handle log events
@impl true
def handle_info({:log_event, log_data}, socket) do
updated_logs = add_to_buffer(socket.assigns.logs, log_data, @log_buffer_limit)
socket =
socket
|> assign(:logs, updated_logs)
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
{:noreply, socket}
end
# Handle configuration changes
@impl true
def handle_info({:config_change, config_data}, socket) do
updated_config = add_to_buffer(socket.assigns.config_changes, config_data, 20)
socket =
socket
|> assign(:config_changes, updated_config)
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
{:noreply, socket}
end
# Handle periodic refresh
@impl true
def handle_info(:refresh, socket) do
socket =
if socket.assigns.auto_refresh do
schedule_refresh()
refresh_operational_data(socket)
else
socket
end
{:noreply, socket}
end
# Catch-all for unknown messages
@impl true
def handle_info(_msg, socket) do
{:noreply, socket}
end
@impl true
def render(assigns) do
~H"""
<div class="operational-dashboard">
<!-- Header -->
<div class="dashboard-header">
<div class="header-content">
<div class="title-section">
<h1>πŸ” Operational Monitoring</h1>
<p class="subtitle">Comprehensive system oversight and analytics</p>
</div>
<div class="header-controls">
<button
class={"toggle-btn #{if @auto_refresh, do: "active", else: ""}"}
phx-click="toggle_auto_refresh"
>
<%= if @auto_refresh do %>⏸️ Pause<% else %>▢️ Resume<% end %>
</button>
<button class="refresh-btn" phx-click="manual_refresh">
πŸ”„ Refresh
</button>
</div>
</div>
<!-- Navigation Tabs -->
<nav class="operational-nav">
<button
phx-click="switch_tab"
phx-value-tab="overview"
class={"nav-tab #{if @active_tab == "overview", do: "active", else: ""}"}
>
πŸ“Š Overview
</button>
<button
phx-click="switch_tab"
phx-value-tab="performance"
class={"nav-tab #{if @active_tab == "performance", do: "active", else: ""}"}
>
⚑ Performance
</button>
<button
phx-click="switch_tab"
phx-value-tab="security"
class={"nav-tab #{if @active_tab == "security", do: "active", else: ""}"}
>
πŸ›‘οΈ Security
</button>
<button
phx-click="switch_tab"
phx-value-tab="audit"
class={"nav-tab #{if @active_tab == "audit", do: "active", else: ""}"}
>
πŸ“‹ Audit
</button>
<button
phx-click="switch_tab"
phx-value-tab="alerts"
class={"nav-tab #{if @active_tab == "alerts", do: "active", else: ""}"}
>
🚨 Alerts
</button>
<button
phx-click="switch_tab"
phx-value-tab="diagnostics"
class={"nav-tab #{if @active_tab == "diagnostics", do: "active", else: ""}"}
>
πŸ”§ Diagnostics
</button>
<button
phx-click="switch_tab"
phx-value-tab="logs"
class={"nav-tab #{if @active_tab == "logs", do: "active", else: ""}"}
>
πŸ“ Logs
</button>
</nav>
</div>
<!-- Tab Content -->
<main class="dashboard-content">
<%= if @active_tab == "overview" do %>
<.operational_overview_tab operational_overview={@operational_overview} />
<% end %>
<%= if @active_tab == "performance" do %>
<.performance_tab metrics={@performance_metrics} />
<% end %>
<%= if @active_tab == "security" do %>
<.security_tab events={@security_events} filter={@filters.security_type} />
<% end %>
<%= if @active_tab == "audit" do %>
<.audit_tab audit_trail={@audit_trail} />
<% end %>
<%= if @active_tab == "alerts" do %>
<.alerts_tab alerts={@alerts} filter={@filters.alert_severity} />
<% end %>
<%= if @active_tab == "diagnostics" do %>
<.diagnostics_tab diagnostics={@diagnostics} />
<% end %>
<%= if @active_tab == "logs" do %>
<.logs_tab logs={@logs} filter={@filters.log_level} />
<% end %>
<!-- Status Bar -->
<div class="status-bar">
<span class="last-updated">πŸ• Last updated: <%= format_datetime(@last_updated) %></span>
<span class="connection-status">
<%= if @auto_refresh, do: "🟒 Live", else: "⏸️ Paused" %>
</span>
</div>
</main>
</div>
"""
end
# Tab component functions would be defined here
defp operational_overview_tab(assigns) do
~H"""
<div class="tab-content">
<div class="overview-grid">
<div class="overview-card">
<h3>πŸ₯ System Health</h3>
<div class="health-summary">
<div class="health-score"><%= @operational_overview.health_score %>%</div>
<div class="health-status"><%= @operational_overview.health_status %></div>
</div>
</div>
<div class="overview-card">
<h3>🚨 Active Alerts</h3>
<div class="alert-summary">
<div class="alert-count critical"><%= @operational_overview.critical_alerts %> Critical</div>
<div class="alert-count warning"><%= @operational_overview.warning_alerts %> Warning</div>
</div>
</div>
<div class="overview-card">
<h3>πŸ“ˆ Performance</h3>
<div class="performance-summary">
<div class="metric">Response: <%= @operational_overview.avg_response_time %>ms</div>
<div class="metric">Throughput: <%= @operational_overview.throughput %>/s</div>
</div>
</div>
<div class="overview-card">
<h3>πŸ›‘οΈ Security</h3>
<div class="security-summary">
<div class="metric">Events: <%= @operational_overview.security_events_count %></div>
<div class="metric">Last: <%= @operational_overview.last_security_event %></div>
</div>
</div>
</div>
<!-- Recent Activity Timeline -->
<div class="activity-timeline">
<h3>πŸ“… Recent Activity</h3>
<div class="timeline-items">
<%= for activity <- @operational_overview.recent_activity do %>
<div class="timeline-item">
<span class="activity-time"><%= activity.time %></span>
<span class="activity-type"><%= activity.type %></span>
<span class="activity-message"><%= activity.message %></span>
</div>
<% end %>
</div>
</div>
</div>
"""
end
# Additional tab components would be implemented here...
defp performance_tab(assigns), do: ~H"<div>Performance metrics tab content</div>"
defp security_tab(assigns), do: ~H"<div>Security events tab content</div>"
defp audit_tab(assigns), do: ~H"<div>Audit trail tab content</div>"
defp alerts_tab(assigns), do: ~H"<div>Alerts management tab content</div>"
defp diagnostics_tab(assigns), do: ~H"<div>Diagnostics tab content</div>"
defp logs_tab(assigns), do: ~H"<div>Logs tab content</div>"
# Private helper functions
defp subscribe_to_operational_events do
# Subscribe to all operational channels
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_alerts")
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_metrics")
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_security")
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_audit")
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_diagnostics")
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_logging")
Phoenix.PubSub.subscribe(ExESDBDashboard.PubSub, "operational_config")
end
defp schedule_refresh do
Process.send_after(self(), :refresh, @refresh_interval)
end
defp refresh_operational_data(socket) do
socket
|> assign(:last_updated, DateTime.utc_now())
|> update_operational_overview()
end
defp update_operational_overview(socket) do
overview = calculate_operational_overview(socket.assigns)
assign(socket, :operational_overview, overview)
end
defp calculate_operational_overview(assigns) do
%{
health_score: calculate_health_score(assigns),
health_status: determine_health_status(assigns),
critical_alerts: count_alerts_by_severity(assigns.alerts, "critical"),
warning_alerts: count_alerts_by_severity(assigns.alerts, "warning"),
avg_response_time: calculate_avg_response_time(assigns.performance_metrics),
throughput: calculate_throughput(assigns.performance_metrics),
security_events_count: length(assigns.security_events),
last_security_event: get_last_security_event(assigns.security_events),
recent_activity: generate_recent_activity(assigns)
}
end
defp get_initial_operational_overview do
%{
health_score: 95,
health_status: "Healthy",
critical_alerts: 0,
warning_alerts: 0,
avg_response_time: 45,
throughput: 1250,
security_events_count: 0,
last_security_event: "None",
recent_activity: []
}
end
defp add_to_buffer(buffer, new_item, limit) do
[new_item | buffer]
|> Enum.take(limit)
end
defp calculate_health_score(_assigns), do: 95
defp determine_health_status(_assigns), do: "Healthy"
defp count_alerts_by_severity(alerts, severity), do: Enum.count(alerts, &(&1.severity == severity))
defp calculate_avg_response_time(_metrics), do: 45
defp calculate_throughput(_metrics), do: 1250
defp get_last_security_event([]), do: "None"
defp get_last_security_event([latest | _]), do: format_datetime(latest.timestamp)
defp generate_recent_activity(_assigns), do: []
defp format_datetime(%DateTime{} = dt) do
Calendar.strftime(dt, "%H:%M:%S")
end
defp format_datetime(_), do: "unknown"
end