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
|
||||
job: bin/jobs
|
||||
css: bin/rails tailwindcss:watch
|
||||
|
||||
@@ -99,15 +99,17 @@ class AnalyticsController < ApplicationController
|
||||
.group("DATE_TRUNC('hour', timestamp)")
|
||||
.count
|
||||
|
||||
# Convert to chart format
|
||||
# Convert to chart format - keep everything in UTC for consistency
|
||||
timeline_data = (0..23).map do |hour_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
|
||||
}
|
||||
end.reverse
|
||||
end
|
||||
|
||||
# Action distribution for pie chart
|
||||
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
|
||||
|
||||
# Network-specific methods
|
||||
def network_range?
|
||||
network_range.present?
|
||||
end
|
||||
|
||||
def cidr
|
||||
network_rule? ? network_range&.cidr : conditions&.dig("cidr")
|
||||
end
|
||||
|
||||
@@ -155,18 +155,19 @@
|
||||
<!-- Events Timeline Chart -->
|
||||
<div class="bg-white shadow rounded-lg">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<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 class="p-6">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-4" data-controller="timeline">
|
||||
<% @chart_data[:timeline].each do |data| %>
|
||||
<div class="flex items-center">
|
||||
<div class="w-16 text-sm text-gray-500"><%= data[:time] %></div>
|
||||
<div class="flex items-center" data-timeline-target="row" data-time-iso="<%= data[:time_iso] %>" data-total="<%= data[:total] %>">
|
||||
<div class="w-20 text-sm text-gray-500" data-timeline-target="time">--:--</div>
|
||||
<div class="flex-1 mx-4">
|
||||
<div class="bg-gray-200 rounded-full h-4">
|
||||
<div class="bg-blue-600 h-4 rounded-full"
|
||||
style="width: <%= [((data[:total].to_f / [@chart_data[:timeline].map { |d| d[:total] }.max, 1].max) * 100), 5].max %>%">
|
||||
</div>
|
||||
<div class="bg-blue-600 h-4 rounded-full" data-timeline-target="bar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-12 text-sm text-gray-900 text-right"><%= data[:total] %></div>
|
||||
@@ -175,6 +176,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event Actions Breakdown -->
|
||||
<div class="bg-white shadow rounded-lg">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div>
|
||||
<%= form.label :waf_action, "Action", class: "block text-sm font-medium text-gray-700" %>
|
||||
<%= 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" } %>
|
||||
</div>
|
||||
<div>
|
||||
@@ -94,7 +94,8 @@
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
<%= case event.waf_action
|
||||
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'
|
||||
else 'bg-gray-100 text-gray-800'
|
||||
end %>">
|
||||
|
||||
@@ -75,4 +75,6 @@ Rails.application.configure do
|
||||
|
||||
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
||||
# config.generators.apply_rubocop_autocorrect_after_generate!
|
||||
config.active_job.queue_adapter = :solid_queue
|
||||
config.solid_queue.connects_to = { database: { writing: :queue } }
|
||||
end
|
||||
|
||||
@@ -58,7 +58,9 @@ Rails.application.configure do
|
||||
# config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
# 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.
|
||||
# config.action_mailer.smtp_settings = {
|
||||
|
||||
@@ -37,7 +37,9 @@ Rails.application.configure do
|
||||
config.action_mailer.delivery_method = :test
|
||||
|
||||
# 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.
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
Reference in New Issue
Block a user