diff --git a/Procfile.dev b/Procfile.dev index 0ac4b20..9fa44e8 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,2 +1,3 @@ web: bin/rails server -b 0.0.0.0 -p 3041 +job: bin/jobs css: bin/rails tailwindcss:watch diff --git a/app/controllers/analytics_controller.rb b/app/controllers/analytics_controller.rb index e9b8135..4031a38 100644 --- a/app/controllers/analytics_controller.rb +++ b/app/controllers/analytics_controller.rb @@ -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| diff --git a/app/javascript/controllers/timeline_controller.js b/app/javascript/controllers/timeline_controller.js new file mode 100644 index 0000000..3a53404 --- /dev/null +++ b/app/javascript/controllers/timeline_controller.js @@ -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)) + } +} \ No newline at end of file diff --git a/app/models/rule.rb b/app/models/rule.rb index 42fdb47..231665c 100644 --- a/app/models/rule.rb +++ b/app/models/rule.rb @@ -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 diff --git a/app/views/analytics/index.html.erb b/app/views/analytics/index.html.erb index 2734268..20e1283 100644 --- a/app/views/analytics/index.html.erb +++ b/app/views/analytics/index.html.erb @@ -155,24 +155,26 @@