Display local time in the browser
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
web: bin/rails server -b 0.0.0.0 -p 3041
|
web: bin/rails server -b 0.0.0.0 -p 3041
|
||||||
|
job: bin/jobs
|
||||||
css: bin/rails tailwindcss:watch
|
css: bin/rails tailwindcss:watch
|
||||||
|
|||||||
@@ -99,15 +99,17 @@ class AnalyticsController < ApplicationController
|
|||||||
.group("DATE_TRUNC('hour', timestamp)")
|
.group("DATE_TRUNC('hour', timestamp)")
|
||||||
.count
|
.count
|
||||||
|
|
||||||
# Convert to chart format
|
# Convert to chart format - keep everything in UTC for consistency
|
||||||
timeline_data = (0..23).map do |hour_ago|
|
timeline_data = (0..23).map do |hour_ago|
|
||||||
hour_time = hour_ago.hours.ago
|
hour_time = hour_ago.hours.ago
|
||||||
hour_key = hour_time.strftime("%Y-%m-%d %H:00:00")
|
hour_key = hour_time.utc.beginning_of_hour
|
||||||
|
|
||||||
{
|
{
|
||||||
time: hour_time.strftime("%H:00"),
|
# Store as ISO string for JavaScript to handle timezone conversion
|
||||||
|
time_iso: hour_time.iso8601,
|
||||||
total: events_by_hour[hour_key] || 0
|
total: events_by_hour[hour_key] || 0
|
||||||
}
|
}
|
||||||
end.reverse
|
end
|
||||||
|
|
||||||
# Action distribution for pie chart
|
# Action distribution for pie chart
|
||||||
action_distribution = @event_breakdown.map do |action, count|
|
action_distribution = @event_breakdown.map do |action, count|
|
||||||
|
|||||||
54
app/javascript/controllers/timeline_controller.js
Normal file
54
app/javascript/controllers/timeline_controller.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Timeline controller for handling timezone conversion and animations
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["row", "time", "bar"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.maxTotal = this.calculateMaxTotal()
|
||||||
|
this.updateTimeline()
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateMaxTotal() {
|
||||||
|
const totals = this.rowTargets.map(row => parseInt(row.dataset.total))
|
||||||
|
return Math.max(...totals, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeline() {
|
||||||
|
this.rowTargets.forEach((row, index) => {
|
||||||
|
this.updateRow(row, index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRow(row, index) {
|
||||||
|
const timeIso = row.dataset.timeIso
|
||||||
|
const total = parseInt(row.dataset.total)
|
||||||
|
const timeElement = this.timeTargets.find(target => target.closest('[data-timeline-target="row"]') === row)
|
||||||
|
const barElement = this.barTargets.find(target => target.closest('[data-timeline-target="row"]') === row)
|
||||||
|
|
||||||
|
// Convert ISO time to local time
|
||||||
|
const date = new Date(timeIso)
|
||||||
|
const localTime = date.toLocaleTimeString(undefined, {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
})
|
||||||
|
|
||||||
|
timeElement.textContent = localTime
|
||||||
|
timeElement.title = date.toLocaleString(undefined, {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'short'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Animate the bar width with a slight delay for each row
|
||||||
|
const barWidth = Math.max((total / this.maxTotal) * 100, 5)
|
||||||
|
setTimeout(() => {
|
||||||
|
barElement.style.width = `${barWidth}%`
|
||||||
|
}, 100 + (index * 50))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,10 @@ class Rule < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Network-specific methods
|
# Network-specific methods
|
||||||
|
def network_range?
|
||||||
|
network_range.present?
|
||||||
|
end
|
||||||
|
|
||||||
def cidr
|
def cidr
|
||||||
network_rule? ? network_range&.cidr : conditions&.dig("cidr")
|
network_rule? ? network_range&.cidr : conditions&.dig("cidr")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -155,24 +155,26 @@
|
|||||||
<!-- Events Timeline Chart -->
|
<!-- Events Timeline Chart -->
|
||||||
<div class="bg-white shadow rounded-lg">
|
<div class="bg-white shadow rounded-lg">
|
||||||
<div class="px-6 py-4 border-b border-gray-200">
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
<h3 class="text-lg font-medium text-gray-900">Events Timeline (Last 24 Hours)</h3>
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">Events Timeline (Last 24 Hours)</h3>
|
||||||
|
<span class="text-sm text-gray-500">Times shown in your local timezone</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4" data-controller="timeline">
|
||||||
<% @chart_data[:timeline].each do |data| %>
|
<% @chart_data[:timeline].each do |data| %>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center" data-timeline-target="row" data-time-iso="<%= data[:time_iso] %>" data-total="<%= data[:total] %>">
|
||||||
<div class="w-16 text-sm text-gray-500"><%= data[:time] %></div>
|
<div class="w-20 text-sm text-gray-500" data-timeline-target="time">--:--</div>
|
||||||
<div class="flex-1 mx-4">
|
<div class="flex-1 mx-4">
|
||||||
<div class="bg-gray-200 rounded-full h-4">
|
<div class="bg-gray-200 rounded-full h-4">
|
||||||
<div class="bg-blue-600 h-4 rounded-full"
|
<div class="bg-blue-600 h-4 rounded-full" data-timeline-target="bar" style="width: 0%"></div>
|
||||||
style="width: <%= [((data[:total].to_f / [@chart_data[:timeline].map { |d| d[:total] }.max, 1].max) * 100), 5].max %>%">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-12 text-sm text-gray-900 text-right"><%= data[:total] %></div>
|
<div class="w-12 text-sm text-gray-900 text-right"><%= data[:total] %></div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= form.label :waf_action, "Action", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :waf_action, "Action", class: "block text-sm font-medium text-gray-700" %>
|
||||||
<%= form.select :waf_action,
|
<%= form.select :waf_action,
|
||||||
options_for_select([['All', ''], ['Allow', 'allow'], ['Block', 'block'], ['Challenge', 'challenge']], params[:waf_action]),
|
options_for_select([['All', ''], ['Allow', 'allow'], ['Deny', 'deny'], ['Redirect', 'redirect'], ['Challenge', 'challenge']], params[:waf_action]),
|
||||||
{ }, { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
|
{ }, { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -94,7 +94,8 @@
|
|||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||||
<%= case event.waf_action
|
<%= case event.waf_action
|
||||||
when 'allow' then 'bg-green-100 text-green-800'
|
when 'allow' then 'bg-green-100 text-green-800'
|
||||||
when 'deny', 'block' then 'bg-red-100 text-red-800'
|
when 'deny' then 'bg-red-100 text-red-800'
|
||||||
|
when 'redirect' then 'bg-blue-100 text-blue-800'
|
||||||
when 'challenge' then 'bg-yellow-100 text-yellow-800'
|
when 'challenge' then 'bg-yellow-100 text-yellow-800'
|
||||||
else 'bg-gray-100 text-gray-800'
|
else 'bg-gray-100 text-gray-800'
|
||||||
end %>">
|
end %>">
|
||||||
|
|||||||
@@ -75,4 +75,6 @@ Rails.application.configure do
|
|||||||
|
|
||||||
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
||||||
# config.generators.apply_rubocop_autocorrect_after_generate!
|
# config.generators.apply_rubocop_autocorrect_after_generate!
|
||||||
|
config.active_job.queue_adapter = :solid_queue
|
||||||
|
config.solid_queue.connects_to = { database: { writing: :queue } }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ Rails.application.configure do
|
|||||||
# config.action_mailer.raise_delivery_errors = false
|
# config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
# Set host to be used by links generated in mailer templates.
|
# Set host to be used by links generated in mailer templates.
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
config.action_mailer.default_url_options = {
|
||||||
|
host: ENV.fetch("BAFFLE_HOST", "example.com")
|
||||||
|
}
|
||||||
|
|
||||||
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
|
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
|
||||||
# config.action_mailer.smtp_settings = {
|
# config.action_mailer.smtp_settings = {
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ Rails.application.configure do
|
|||||||
config.action_mailer.delivery_method = :test
|
config.action_mailer.delivery_method = :test
|
||||||
|
|
||||||
# Set host to be used by links generated in mailer templates.
|
# Set host to be used by links generated in mailer templates.
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
config.action_mailer.default_url_options = {
|
||||||
|
host: ENV.fetch("BAFFLE_HOST", "example.com")
|
||||||
|
}
|
||||||
|
|
||||||
# Print deprecation notices to the stderr.
|
# Print deprecation notices to the stderr.
|
||||||
config.active_support.deprecation = :stderr
|
config.active_support.deprecation = :stderr
|
||||||
|
|||||||
Reference in New Issue
Block a user