First commit!
This commit is contained in:
112
app/views/events/index.html.erb
Normal file
112
app/views/events/index.html.erb
Normal file
@@ -0,0 +1,112 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><%= @project.name %> - Events</h1>
|
||||
<div>
|
||||
<%= link_to "← Back to Project", @project, class: "btn btn-secondary" %>
|
||||
<%= link_to "Analytics", analytics_project_path(@project), class: "btn btn-info" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5>Filters</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<%= form_with url: project_events_path(@project), method: :get, local: true, class: "row g-3" do |form| %>
|
||||
<div class="col-md-3">
|
||||
<%= form.label :ip, "IP Address", class: "form-label" %>
|
||||
<%= form.text_field :ip, value: params[:ip], class: "form-control", placeholder: "Filter by IP" %>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<%= form.label :waf_action, "Action", class: "form-label" %>
|
||||
<%= form.select :waf_action,
|
||||
options_for_select([['All', ''], ['Allow', 'allow'], ['Block', 'block'], ['Challenge', 'challenge']], params[:waf_action]),
|
||||
{}, { class: "form-select" } %>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<%= form.label :country, "Country", class: "form-label" %>
|
||||
<%= form.text_field :country, value: params[:country], class: "form-control", placeholder: "Country code (e.g. US)" %>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<%= form.submit "Apply Filters", class: "btn btn-primary me-2" %>
|
||||
<%= link_to "Clear", project_events_path(@project), class: "btn btn-outline-secondary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Events Table -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5>Events (<%= @events.count %>)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% if @events.any? %>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>IP Address</th>
|
||||
<th>Action</th>
|
||||
<th>Path</th>
|
||||
<th>Method</th>
|
||||
<th>Status</th>
|
||||
<th>Country</th>
|
||||
<th>User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @events.each do |event| %>
|
||||
<tr>
|
||||
<td><%= event.timestamp.strftime("%Y-%m-%d %H:%M:%S") %></td>
|
||||
<td><code><%= event.ip_address %></code></td>
|
||||
<td>
|
||||
<span class="badge bg-<%= event.blocked? ? 'danger' : event.allowed? ? 'success' : 'warning' %>">
|
||||
<%= event.waf_action %>
|
||||
</span>
|
||||
</td>
|
||||
<td><code><%= event.request_path %></code></td>
|
||||
<td><%= event.request_method %></td>
|
||||
<td><%= event.response_status %></td>
|
||||
<td>
|
||||
<% if event.country_code.present? %>
|
||||
<span class="badge bg-light text-dark"><%= event.country_code %></span>
|
||||
<% else %>
|
||||
<span class="text-muted">-</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="text-truncate" style="max-width: 200px;" title="<%= event.user_agent %>">
|
||||
<%= event.user_agent&.truncate(30) || '-' %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<% if @pagy.pages > 1 %>
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
<%== pagy_nav(@pagy) %>
|
||||
</div>
|
||||
<div class="text-center text-muted mt-2">
|
||||
Showing <%= @pagy.from %> to <%= @pagy.to %> of <%= @pagy.count %> events
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="text-center py-5">
|
||||
<p class="text-muted mb-3">
|
||||
<% if params[:ip].present? || params[:waf_action].present? || params[:country].present? %>
|
||||
No events found matching your filters.
|
||||
<% else %>
|
||||
No events have been received yet.
|
||||
<% end %>
|
||||
</p>
|
||||
<% if params[:ip].present? || params[:waf_action].present? || params[:country].present? %>
|
||||
<%= link_to "Clear Filters", project_events_path(@project), class: "btn btn-outline-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
85
app/views/layouts/application.html.erb
Normal file
85
app/views/layouts/application.html.erb
Normal file
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= content_for(:title) || "Baffle Hub - WAF Analytics" %></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Baffle Hub">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<%= yield :head %>
|
||||
|
||||
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
|
||||
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
|
||||
|
||||
<link rel="icon" href="/icon.png" type="image/png">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/icon.png">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
||||
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
|
||||
<style>
|
||||
.badge { font-size: 0.8em; }
|
||||
code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<%= link_to "Baffle Hub", root_path, class: "navbar-brand" %>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<%= link_to "Projects", projects_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "Rule Sets", rule_sets_path, class: "nav-link" %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<% if notice %>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<%= notice %>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if alert %>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<%= alert %>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
app/views/layouts/mailer.html.erb
Normal file
13
app/views/layouts/mailer.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
/* Email styles need to be inline */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
||||
1
app/views/layouts/mailer.text.erb
Normal file
1
app/views/layouts/mailer.text.erb
Normal file
@@ -0,0 +1 @@
|
||||
<%= yield %>
|
||||
200
app/views/projects/analytics.html.erb
Normal file
200
app/views/projects/analytics.html.erb
Normal file
@@ -0,0 +1,200 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><%= @project.name %> - Analytics</h1>
|
||||
<div>
|
||||
<%= link_to "← Back to Project", project_path(@project), class: "btn btn-secondary" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time Range Selector -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5>Time Range</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<%= form_with url: analytics_project_path(@project), method: :get, local: true do |form| %>
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-6">
|
||||
<%= form.label :time_range, "Time Range", class: "form-label" %>
|
||||
<%= form.select :time_range,
|
||||
options_for_select([
|
||||
["Last Hour", 1],
|
||||
["Last 6 Hours", 6],
|
||||
["Last 24 Hours", 24],
|
||||
["Last 7 Days", 168],
|
||||
["Last 30 Days", 720]
|
||||
], @time_range),
|
||||
{}, class: "form-select" %>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<%= form.submit "Update", class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3 class="text-primary"><%= number_with_delimiter(@total_events) %></h3>
|
||||
<p class="card-text">Total Events</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3 class="text-success"><%= number_with_delimiter(@allowed_events) %></h3>
|
||||
<p class="card-text">Allowed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3 class="text-danger"><%= number_with_delimiter(@blocked_events) %></h3>
|
||||
<p class="card-text">Blocked</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Top Blocked IPs -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Top Blocked IPs</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% if @top_blocked_ips.any? %>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Blocked Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @top_blocked_ips.each do |stat| %>
|
||||
<tr>
|
||||
<td><code><%= stat.ip_address %></code></td>
|
||||
<td><%= number_with_delimiter(stat.count) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-muted">No blocked events in this time range.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Country Distribution -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Top Countries</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% if @country_stats.any? %>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Country</th>
|
||||
<th>Events</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @country_stats.each do |stat| %>
|
||||
<tr>
|
||||
<td><%= stat.country_code || 'Unknown' %></td>
|
||||
<td><%= number_with_delimiter(stat.count) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-muted">No country data available.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Distribution -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Action Distribution</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% if @action_stats.any? %>
|
||||
<div class="row">
|
||||
<% @action_stats.each do |stat| %>
|
||||
<div class="col-md-3 text-center mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4><%= stat.action.upcase %></h4>
|
||||
<p class="card-text">
|
||||
<span class="badge bg-<%=
|
||||
case stat.action
|
||||
when 'allow', 'pass' then 'success'
|
||||
when 'block', 'deny' then 'danger'
|
||||
when 'challenge' then 'warning'
|
||||
when 'rate_limit' then 'info'
|
||||
else 'secondary'
|
||||
end %>">
|
||||
<%= number_with_delimiter(stat.count) %>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-muted">No action data available.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @total_events > 0 %>
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Block Rate</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="progress" style="height: 30px;">
|
||||
<% blocked_percentage = (@blocked_events.to_f / @total_events * 100).round(1) %>
|
||||
<% allowed_percentage = (@allowed_events.to_f / @total_events * 100).round(1) %>
|
||||
|
||||
<div class="progress-bar bg-success" style="width: <%= allowed_percentage %>%">
|
||||
<%= allowed_percentage %>% Allowed
|
||||
</div>
|
||||
<div class="progress-bar bg-danger" style="width: <%= blocked_percentage %>%">
|
||||
<%= blocked_percentage %>% Blocked
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-4">
|
||||
<%= link_to "View Events", events_project_path(@project), class: "btn btn-primary" %>
|
||||
<%= link_to "Export Data", "#", class: "btn btn-secondary", onclick: "alert('Export feature coming soon!')" %>
|
||||
</div>
|
||||
49
app/views/projects/index.html.erb
Normal file
49
app/views/projects/index.html.erb
Normal file
@@ -0,0 +1,49 @@
|
||||
<h1>Projects</h1>
|
||||
|
||||
<%= link_to "New Project", new_project_path, class: "btn btn-primary mb-3" %>
|
||||
|
||||
<div class="row">
|
||||
<% @projects.each do |project| %>
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><%= project.name %></h5>
|
||||
<span class="badge <%= project.enabled? ? 'bg-success' : 'bg-secondary' %>">
|
||||
<%= project.enabled? ? 'Active' : 'Disabled' %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<strong>Status:</strong>
|
||||
<span class="badge bg-<%= project.waf_status == 'active' ? 'success' : project.waf_status == 'idle' ? 'warning' : 'danger' %>">
|
||||
<%= project.waf_status %>
|
||||
</span>
|
||||
</p>
|
||||
<p class="card-text">
|
||||
<strong>Events (24h):</strong> <%= project.event_count(24.hours.ago) %>
|
||||
</p>
|
||||
<p class="card-text">
|
||||
<strong>Blocked (24h):</strong> <%= project.blocked_count(24.hours.ago) %>
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
<strong>DSN:</strong><br>
|
||||
<code><%= project.dsn %></code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<%= link_to "View", project_path(project), class: "btn btn-primary btn-sm" %>
|
||||
<%= link_to "Events", events_project_path(project), class: "btn btn-secondary btn-sm" %>
|
||||
<%= link_to "Analytics", analytics_project_path(project), class: "btn btn-info btn-sm" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @projects.empty? %>
|
||||
<div class="text-center my-5">
|
||||
<h3>No projects yet</h3>
|
||||
<p>Create your first project to start monitoring WAF events.</p>
|
||||
<%= link_to "Create Project", new_project_path, class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
32
app/views/projects/new.html.erb
Normal file
32
app/views/projects/new.html.erb
Normal file
@@ -0,0 +1,32 @@
|
||||
<h1>New Project</h1>
|
||||
|
||||
<%= form_with(model: @project, local: true) do |form| %>
|
||||
<% if @project.errors.any? %>
|
||||
<div class="alert alert-danger">
|
||||
<h4><%= pluralize(@project.errors.count, "error") %> prohibited this project from being saved:</h4>
|
||||
<ul>
|
||||
<% @project.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mb-3">
|
||||
<%= form.label :name, class: "form-label" %>
|
||||
<%= form.text_field :name, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<%= form.label :enabled, class: "form-label" %>
|
||||
<div class="form-check">
|
||||
<%= form.check_box :enabled, class: "form-check-input" %>
|
||||
<%= form.label :enabled, "Enable this project", class: "form-check-label" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<%= form.submit "Create Project", class: "btn btn-primary" %>
|
||||
<%= link_to "Cancel", projects_path, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
118
app/views/projects/show.html.erb
Normal file
118
app/views/projects/show.html.erb
Normal file
@@ -0,0 +1,118 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><%= @project.name %></h1>
|
||||
<div>
|
||||
<%= link_to "Edit", edit_project_path(@project), class: "btn btn-secondary" %>
|
||||
<%= link_to "Events", events_project_path(@project), class: "btn btn-primary" %>
|
||||
<%= link_to "Analytics", analytics_project_path(@project), class: "btn btn-info" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Project Status</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Status:</strong>
|
||||
<span class="badge bg-<%= @waf_status == 'active' ? 'success' : @waf_status == 'idle' ? 'warning' : 'danger' %>">
|
||||
<%= @waf_status %>
|
||||
</span>
|
||||
</p>
|
||||
<p><strong>Enabled:</strong>
|
||||
<span class="badge bg-<%= @project.enabled? ? 'success' : 'secondary' %>">
|
||||
<%= @project.enabled? ? 'Yes' : 'No' %>
|
||||
</span>
|
||||
</p>
|
||||
<p><strong>Events (24h):</strong> <%= @event_count %></p>
|
||||
<p><strong>Blocked (24h):</strong> <%= @blocked_count %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>DSN Configuration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>DSN:</strong></p>
|
||||
<code><%= @project.dsn %></code>
|
||||
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyDSN()">Copy</button>
|
||||
|
||||
<% if @project.internal_dsn.present? %>
|
||||
<hr>
|
||||
<p><strong>Internal DSN:</strong></p>
|
||||
<code><%= @project.internal_dsn %></code>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Recent Events</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% if @recent_events.any? %>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>IP</th>
|
||||
<th>Action</th>
|
||||
<th>Path</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @recent_events.limit(5).each do |event| %>
|
||||
<tr>
|
||||
<td><%= event.timestamp.strftime("%H:%M:%S") %></td>
|
||||
<td><%= event.ip_address %></td>
|
||||
<td>
|
||||
<span class="badge bg-<%= event.blocked? ? 'danger' : event.allowed? ? 'success' : 'warning' %>">
|
||||
<%= event.action %>
|
||||
</span>
|
||||
</td>
|
||||
<td><code><%= event.request_path %></code></td>
|
||||
<td><%= event.response_status %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<%= link_to "View All Events", events_project_path(@project), class: "btn btn-primary btn-sm" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-muted">No events received yet.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyDSN() {
|
||||
const dsnElement = document.querySelector('code');
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = dsnElement.textContent;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
// Show feedback
|
||||
const button = event.target;
|
||||
const originalText = button.textContent;
|
||||
button.textContent = 'Copied!';
|
||||
button.classList.add('btn-success');
|
||||
setTimeout(() => {
|
||||
button.textContent = originalText;
|
||||
button.classList.remove('btn-success');
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
22
app/views/pwa/manifest.json.erb
Normal file
22
app/views/pwa/manifest.json.erb
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "BaffleHub",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"description": "BaffleHub.",
|
||||
"theme_color": "red",
|
||||
"background_color": "red"
|
||||
}
|
||||
26
app/views/pwa/service-worker.js
Normal file
26
app/views/pwa/service-worker.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Add a service worker for processing Web Push notifications:
|
||||
//
|
||||
// self.addEventListener("push", async (event) => {
|
||||
// const { title, options } = await event.data.json()
|
||||
// event.waitUntil(self.registration.showNotification(title, options))
|
||||
// })
|
||||
//
|
||||
// self.addEventListener("notificationclick", function(event) {
|
||||
// event.notification.close()
|
||||
// event.waitUntil(
|
||||
// clients.matchAll({ type: "window" }).then((clientList) => {
|
||||
// for (let i = 0; i < clientList.length; i++) {
|
||||
// let client = clientList[i]
|
||||
// let clientPath = (new URL(client.url)).pathname
|
||||
//
|
||||
// if (clientPath == event.notification.data.path && "focus" in client) {
|
||||
// return client.focus()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (clients.openWindow) {
|
||||
// return clients.openWindow(event.notification.data.path)
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// })
|
||||
Reference in New Issue
Block a user