pg_sql_triggers 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.erb_lint.yml +0 -0
- data/.rspec +0 -0
- data/.rubocop.yml +6 -16
- data/AGENTS.md +8 -0
- data/CHANGELOG.md +104 -2
- data/COVERAGE.md +39 -41
- data/LICENSE +0 -0
- data/README.md +24 -3
- data/RELEASE.md +0 -0
- data/Rakefile +5 -0
- data/app/assets/javascripts/pg_sql_triggers/application.js +0 -0
- data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +0 -0
- data/app/assets/stylesheets/pg_sql_triggers/application.css +0 -0
- data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +0 -0
- data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +0 -0
- data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +6 -5
- data/app/controllers/pg_sql_triggers/application_controller.rb +0 -0
- data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +81 -64
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +111 -34
- data/app/controllers/pg_sql_triggers/migrations_controller.rb +13 -14
- data/app/controllers/pg_sql_triggers/tables_controller.rb +8 -0
- data/app/controllers/pg_sql_triggers/triggers_controller.rb +1 -0
- data/app/helpers/pg_sql_triggers/dashboard_helper.rb +19 -0
- data/app/helpers/pg_sql_triggers/permissions_helper.rb +3 -2
- data/app/models/pg_sql_triggers/application_record.rb +0 -0
- data/app/models/pg_sql_triggers/audit_log.rb +29 -47
- data/app/models/pg_sql_triggers/trigger_registry.rb +105 -78
- data/app/views/layouts/pg_sql_triggers/application.html.erb +0 -0
- data/app/views/pg_sql_triggers/audit_logs/index.html.erb +9 -5
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +107 -24
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +0 -0
- data/app/views/pg_sql_triggers/tables/index.html.erb +26 -14
- data/app/views/pg_sql_triggers/tables/show.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/show.html.erb +33 -0
- data/config/initializers/pg_sql_triggers.rb +0 -0
- data/config/routes.rb +0 -0
- data/db/migrate/{20251222000001_create_pg_sql_triggers_tables.rb → 20251222104327_create_pg_sql_triggers_tables.rb} +0 -0
- data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +0 -0
- data/db/migrate/{20260103000001_create_pg_sql_triggers_audit_log.rb → 20260103114508_create_pg_sql_triggers_audit_log.rb} +0 -0
- data/db/migrate/{20260228000001_add_for_each_to_pg_sql_triggers_registry.rb → 20260228162233_add_for_each_to_pg_sql_triggers_registry.rb} +0 -0
- data/db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb +9 -0
- data/docs/README.md +3 -0
- data/docs/api-reference.md +133 -0
- data/docs/audit-trail.md +1 -1
- data/docs/configuration.md +172 -0
- data/docs/getting-started.md +14 -0
- data/docs/kill-switch.md +0 -0
- data/docs/permissions.md +6 -9
- data/docs/troubleshooting.md +0 -0
- data/docs/ui-guide.md +0 -0
- data/docs/usage-guide.md +74 -0
- data/docs/web-ui.md +0 -0
- data/lib/generators/pg_sql_triggers/install_generator.rb +0 -0
- data/lib/generators/pg_sql_triggers/templates/README +0 -0
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +0 -0
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +14 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +0 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration.rb.erb +0 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +0 -0
- data/lib/generators/pg_sql_triggers/trigger_generator.rb +0 -0
- data/lib/generators/pg_sql_triggers/trigger_migration_generator.rb +0 -0
- data/lib/pg_sql_triggers/alerting.rb +77 -0
- data/lib/pg_sql_triggers/database_introspection.rb +0 -0
- data/lib/pg_sql_triggers/deferral_checksum.rb +54 -0
- data/lib/pg_sql_triggers/drift/db_queries.rb +14 -5
- data/lib/pg_sql_triggers/drift/detector.rb +9 -1
- data/lib/pg_sql_triggers/drift/reporter.rb +0 -0
- data/lib/pg_sql_triggers/drift.rb +5 -0
- data/lib/pg_sql_triggers/dsl/trigger_definition.rb +56 -2
- data/lib/pg_sql_triggers/dsl.rb +0 -0
- data/lib/pg_sql_triggers/engine.rb +35 -0
- data/lib/pg_sql_triggers/errors.rb +0 -0
- data/lib/pg_sql_triggers/events_checksum.rb +114 -0
- data/lib/pg_sql_triggers/migration.rb +5 -6
- data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +77 -73
- data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +0 -0
- data/lib/pg_sql_triggers/migrator/safety_validator.rb +3 -1
- data/lib/pg_sql_triggers/migrator.rb +90 -94
- data/lib/pg_sql_triggers/permissions/checker.rb +12 -15
- data/lib/pg_sql_triggers/permissions.rb +1 -0
- data/lib/pg_sql_triggers/rake_development_boot.rb +65 -0
- data/lib/pg_sql_triggers/registry/manager.rb +27 -13
- data/lib/pg_sql_triggers/registry/validator.rb +226 -2
- data/lib/pg_sql_triggers/registry.rb +0 -0
- data/lib/pg_sql_triggers/schema_dumper_extension.rb +32 -0
- data/lib/pg_sql_triggers/sql/kill_switch.rb +2 -1
- data/lib/pg_sql_triggers/sql.rb +0 -0
- data/lib/pg_sql_triggers/testing/dry_run.rb +0 -0
- data/lib/pg_sql_triggers/testing/function_tester.rb +97 -107
- data/lib/pg_sql_triggers/testing/safe_executor.rb +0 -0
- data/lib/pg_sql_triggers/testing/syntax_validator.rb +0 -0
- data/lib/pg_sql_triggers/testing.rb +0 -0
- data/lib/pg_sql_triggers/trigger_structure_dumper.rb +111 -0
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +17 -0
- data/lib/tasks/trigger_migrations.rake +235 -152
- data/rakelib/pg_sql_triggers_environment.rake +9 -0
- data/scripts/generate_coverage_report.rb +4 -1
- data/sig/pg_sql_triggers.rbs +0 -0
- metadata +65 -13
- data/GEM_ANALYSIS.md +0 -368
- data/Goal.md +0 -742
- data/pg_sql_triggers.gemspec +0 -53
|
@@ -21,7 +21,9 @@ module PgSqlTriggers
|
|
|
21
21
|
# @example Drop and re-execute triggers
|
|
22
22
|
# trigger.drop!(reason: "No longer needed", actor: current_user, confirmation: "EXECUTE TRIGGER_DROP")
|
|
23
23
|
# trigger.re_execute!(reason: "Fix drift", actor: current_user, confirmation: "EXECUTE TRIGGER_RE_EXECUTE")
|
|
24
|
-
# rubocop:disable Metrics/ClassLength
|
|
24
|
+
# rubocop:disable Metrics/ClassLength -- core AR model: groups lifecycle operations
|
|
25
|
+
# (enable!/disable!/drop!/re_execute!), drift helpers, audit hooks, and SQL builders.
|
|
26
|
+
# Splitting further would fragment tightly-coupled state and audit concerns.
|
|
25
27
|
class TriggerRegistry < PgSqlTriggers::ApplicationRecord
|
|
26
28
|
self.table_name = "pg_sql_triggers_registry"
|
|
27
29
|
|
|
@@ -39,6 +41,16 @@ module PgSqlTriggers
|
|
|
39
41
|
scope :for_environment, ->(env) { where(environment: [env, nil]) }
|
|
40
42
|
scope :by_source, ->(source) { where(source: source) }
|
|
41
43
|
|
|
44
|
+
# Case-insensitive search on trigger name and table name (used by web dashboard).
|
|
45
|
+
scope :matching_search, lambda { |raw|
|
|
46
|
+
query = raw.to_s.strip
|
|
47
|
+
next all if query.blank?
|
|
48
|
+
|
|
49
|
+
sanitized = ActiveRecord::Base.sanitize_sql_like(query)
|
|
50
|
+
term = "%#{sanitized}%"
|
|
51
|
+
where("trigger_name ILIKE :term OR table_name ILIKE :term", term: term)
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
# Returns the current drift state of this trigger.
|
|
43
55
|
#
|
|
44
56
|
# @return [String] One of: "in_sync", "drifted", "manual_override", "disabled", "dropped", "unknown"
|
|
@@ -119,32 +131,12 @@ module PgSqlTriggers
|
|
|
119
131
|
end
|
|
120
132
|
end
|
|
121
133
|
|
|
122
|
-
# Update the registry record (always update, even if trigger doesn't exist)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# If update! fails, try update_column which bypasses validations and callbacks
|
|
129
|
-
# and might not use execute in the same way
|
|
130
|
-
Rails.logger.warn("Could not update registry via update!: #{e.message}") if defined?(Rails.logger)
|
|
131
|
-
begin
|
|
132
|
-
# rubocop:disable Rails/SkipsModelValidations
|
|
133
|
-
update_column(:enabled, true)
|
|
134
|
-
# rubocop:enable Rails/SkipsModelValidations
|
|
135
|
-
after_state = capture_state
|
|
136
|
-
log_audit_success(:trigger_enable, actor, before_state: before_state, after_state: after_state)
|
|
137
|
-
rescue StandardError => update_error
|
|
138
|
-
# If update_column also fails, just set the in-memory attribute
|
|
139
|
-
# The test might reload, but we've done our best
|
|
140
|
-
# rubocop:disable Layout/LineLength
|
|
141
|
-
Rails.logger.warn("Could not update registry via update_column: #{update_error.message}") if defined?(Rails.logger)
|
|
142
|
-
# rubocop:enable Layout/LineLength
|
|
143
|
-
self.enabled = true
|
|
144
|
-
after_state = capture_state
|
|
145
|
-
log_audit_success(:trigger_enable, actor, before_state: before_state, after_state: after_state)
|
|
146
|
-
end
|
|
147
|
-
end
|
|
134
|
+
# Update the registry record (always update, even if trigger doesn't exist).
|
|
135
|
+
# If persistence fails for any reason, fall back to the in-memory attribute so
|
|
136
|
+
# callers/observers still see a consistent state for this request.
|
|
137
|
+
persist_enabled_state(true)
|
|
138
|
+
after_state = capture_state
|
|
139
|
+
log_audit_success(:trigger_enable, actor, before_state: before_state, after_state: after_state)
|
|
148
140
|
end
|
|
149
141
|
|
|
150
142
|
# Disables this trigger in the database and updates the registry.
|
|
@@ -191,32 +183,12 @@ module PgSqlTriggers
|
|
|
191
183
|
end
|
|
192
184
|
end
|
|
193
185
|
|
|
194
|
-
# Update the registry record (always update, even if trigger doesn't exist)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
# If update! fails, try update_column which bypasses validations and callbacks
|
|
201
|
-
# and might not use execute in the same way
|
|
202
|
-
Rails.logger.warn("Could not update registry via update!: #{e.message}") if defined?(Rails.logger)
|
|
203
|
-
begin
|
|
204
|
-
# rubocop:disable Rails/SkipsModelValidations
|
|
205
|
-
update_column(:enabled, false)
|
|
206
|
-
# rubocop:enable Rails/SkipsModelValidations
|
|
207
|
-
after_state = capture_state
|
|
208
|
-
log_audit_success(:trigger_disable, actor, before_state: before_state, after_state: after_state)
|
|
209
|
-
rescue StandardError => update_error
|
|
210
|
-
# If update_column also fails, just set the in-memory attribute
|
|
211
|
-
# The test might reload, but we've done our best
|
|
212
|
-
# rubocop:disable Layout/LineLength
|
|
213
|
-
Rails.logger.warn("Could not update registry via update_column: #{update_error.message}") if defined?(Rails.logger)
|
|
214
|
-
# rubocop:enable Layout/LineLength
|
|
215
|
-
self.enabled = false
|
|
216
|
-
after_state = capture_state
|
|
217
|
-
log_audit_success(:trigger_disable, actor, before_state: before_state, after_state: after_state)
|
|
218
|
-
end
|
|
219
|
-
end
|
|
186
|
+
# Update the registry record (always update, even if trigger doesn't exist).
|
|
187
|
+
# If persistence fails for any reason, fall back to the in-memory attribute so
|
|
188
|
+
# callers/observers still see a consistent state for this request.
|
|
189
|
+
persist_enabled_state(false)
|
|
190
|
+
after_state = capture_state
|
|
191
|
+
log_audit_success(:trigger_disable, actor, before_state: before_state, after_state: after_state)
|
|
220
192
|
end
|
|
221
193
|
|
|
222
194
|
# Drops this trigger from the database and removes it from the registry.
|
|
@@ -263,8 +235,9 @@ module PgSqlTriggers
|
|
|
263
235
|
# @param reason [String] Required reason for re-executing the trigger
|
|
264
236
|
# @param confirmation [String, nil] Optional confirmation text for kill switch protection
|
|
265
237
|
# @param actor [Hash, nil] Information about who is performing the action (must have :type and :id keys)
|
|
266
|
-
# @raise [ArgumentError] If reason is missing or empty
|
|
238
|
+
# @raise [ArgumentError] If reason is missing or empty
|
|
267
239
|
# @raise [PgSqlTriggers::KillSwitchError] If kill switch blocks the operation
|
|
240
|
+
# @raise [StandardError] If SQL cannot be generated to recreate the trigger
|
|
268
241
|
# @return [PgSqlTriggers::TriggerRegistry] self
|
|
269
242
|
def re_execute!(reason:, confirmation: nil, actor: nil)
|
|
270
243
|
actor ||= { type: "Console", id: "TriggerRegistry#re_execute!" }
|
|
@@ -314,6 +287,12 @@ module PgSqlTriggers
|
|
|
314
287
|
end
|
|
315
288
|
|
|
316
289
|
def calculate_checksum
|
|
290
|
+
deferral = PgSqlTriggers::DeferralChecksum.parts(
|
|
291
|
+
constraint_trigger: constraint_trigger,
|
|
292
|
+
deferrable: deferrable,
|
|
293
|
+
initially: initially
|
|
294
|
+
)
|
|
295
|
+
events_segment = PgSqlTriggers::EventsChecksum.segment_from_definition_json(definition)
|
|
317
296
|
Digest::SHA256.hexdigest([
|
|
318
297
|
trigger_name,
|
|
319
298
|
table_name,
|
|
@@ -321,7 +300,9 @@ module PgSqlTriggers
|
|
|
321
300
|
function_body || "",
|
|
322
301
|
condition || "",
|
|
323
302
|
timing || "before",
|
|
324
|
-
for_each || "row"
|
|
303
|
+
for_each || "row",
|
|
304
|
+
events_segment,
|
|
305
|
+
*deferral
|
|
325
306
|
].join)
|
|
326
307
|
end
|
|
327
308
|
|
|
@@ -393,8 +374,15 @@ module PgSqlTriggers
|
|
|
393
374
|
end
|
|
394
375
|
|
|
395
376
|
def recreate_trigger
|
|
396
|
-
|
|
397
|
-
|
|
377
|
+
# DSL triggers are recreated from the stored JSON definition (+build_trigger_sql_from_definition+).
|
|
378
|
+
# Other sources may persist a full trigger SQL payload in +function_body+.
|
|
379
|
+
sql = if source == "dsl"
|
|
380
|
+
build_trigger_sql_from_definition
|
|
381
|
+
else
|
|
382
|
+
function_body.presence || build_trigger_sql_from_definition
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
raise StandardError, "Cannot re-execute: no SQL could be generated" if sql.blank?
|
|
398
386
|
|
|
399
387
|
ActiveRecord::Base.connection.execute(sql)
|
|
400
388
|
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
@@ -416,15 +404,25 @@ module PgSqlTriggers
|
|
|
416
404
|
fn_name = defn["function_name"]
|
|
417
405
|
return nil if fn_name.blank?
|
|
418
406
|
|
|
419
|
-
t_name
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
407
|
+
t_name = table_name
|
|
408
|
+
constraint = ActiveModel::Type::Boolean.new.cast(defn["constraint_trigger"])
|
|
409
|
+
timing_kw = if constraint
|
|
410
|
+
"AFTER"
|
|
411
|
+
else
|
|
412
|
+
(defn["timing"] || timing || "before").to_s.upcase
|
|
413
|
+
end
|
|
414
|
+
events_sql = PgSqlTriggers::EventsChecksum.events_sql_fragment(
|
|
415
|
+
defn,
|
|
416
|
+
quote_column: ->(col) { quote_identifier(col) }
|
|
417
|
+
)
|
|
418
|
+
cond = defn["condition"] || condition
|
|
424
419
|
for_each_kw = (defn["for_each"] || for_each || "row").upcase
|
|
425
420
|
|
|
426
|
-
|
|
427
|
-
sql
|
|
421
|
+
create_kw = constraint ? "CREATE CONSTRAINT TRIGGER" : "CREATE TRIGGER"
|
|
422
|
+
sql = "#{create_kw} #{quote_identifier(trigger_name)} "
|
|
423
|
+
sql += "#{timing_kw} #{events_sql} ON #{quote_identifier(t_name)} "
|
|
424
|
+
deferral = deferral_sql_fragment(defn)
|
|
425
|
+
sql += "#{deferral} " if deferral.present?
|
|
428
426
|
sql += "FOR EACH #{for_each_kw} "
|
|
429
427
|
sql += "WHEN (#{cond}) " if cond.present?
|
|
430
428
|
sql += "EXECUTE FUNCTION #{fn_name}();"
|
|
@@ -433,6 +431,26 @@ module PgSqlTriggers
|
|
|
433
431
|
nil
|
|
434
432
|
end
|
|
435
433
|
|
|
434
|
+
def deferral_sql_fragment(defn)
|
|
435
|
+
return "" unless ActiveModel::Type::Boolean.new.cast(defn["constraint_trigger"])
|
|
436
|
+
|
|
437
|
+
case defn["deferrable"].to_s
|
|
438
|
+
when "not_deferrable"
|
|
439
|
+
"NOT DEFERRABLE"
|
|
440
|
+
when "deferrable"
|
|
441
|
+
case defn["initially"].to_s
|
|
442
|
+
when "deferred"
|
|
443
|
+
"DEFERRABLE INITIALLY DEFERRED"
|
|
444
|
+
when "immediate"
|
|
445
|
+
"DEFERRABLE INITIALLY IMMEDIATE"
|
|
446
|
+
else
|
|
447
|
+
"DEFERRABLE"
|
|
448
|
+
end
|
|
449
|
+
else
|
|
450
|
+
""
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
436
454
|
def update_registry_after_re_execute
|
|
437
455
|
update!(last_executed_at: Time.current)
|
|
438
456
|
if !enabled && ActiveRecord::Base.connection.table_exists?(table_name)
|
|
@@ -459,9 +477,7 @@ module PgSqlTriggers
|
|
|
459
477
|
}
|
|
460
478
|
end
|
|
461
479
|
|
|
462
|
-
|
|
463
|
-
def log_audit_success(operation, actor, reason: nil, confirmation_text: nil,
|
|
464
|
-
before_state: nil, after_state: nil, diff: nil)
|
|
480
|
+
def log_audit_success(operation, actor, **options)
|
|
465
481
|
return unless defined?(PgSqlTriggers::AuditLog)
|
|
466
482
|
|
|
467
483
|
PgSqlTriggers::AuditLog.log_success(
|
|
@@ -469,18 +485,13 @@ module PgSqlTriggers
|
|
|
469
485
|
trigger_name: trigger_name,
|
|
470
486
|
actor: actor,
|
|
471
487
|
environment: Rails.env,
|
|
472
|
-
|
|
473
|
-
confirmation_text: confirmation_text,
|
|
474
|
-
before_state: before_state,
|
|
475
|
-
after_state: after_state,
|
|
476
|
-
diff: diff
|
|
488
|
+
**options
|
|
477
489
|
)
|
|
478
490
|
rescue StandardError => e
|
|
479
491
|
Rails.logger.error("Failed to log audit entry: #{e.message}") if defined?(Rails.logger)
|
|
480
492
|
end
|
|
481
493
|
|
|
482
|
-
def log_audit_failure(operation, actor, error_message,
|
|
483
|
-
confirmation_text: nil, before_state: nil)
|
|
494
|
+
def log_audit_failure(operation, actor, error_message, **options)
|
|
484
495
|
return unless defined?(PgSqlTriggers::AuditLog)
|
|
485
496
|
|
|
486
497
|
PgSqlTriggers::AuditLog.log_failure(
|
|
@@ -489,14 +500,30 @@ module PgSqlTriggers
|
|
|
489
500
|
actor: actor,
|
|
490
501
|
environment: Rails.env,
|
|
491
502
|
error_message: error_message,
|
|
492
|
-
|
|
493
|
-
confirmation_text: confirmation_text,
|
|
494
|
-
before_state: before_state
|
|
503
|
+
**options
|
|
495
504
|
)
|
|
496
505
|
rescue StandardError => e
|
|
497
506
|
Rails.logger.error("Failed to log audit entry: #{e.message}") if defined?(Rails.logger)
|
|
498
507
|
end
|
|
499
|
-
|
|
508
|
+
|
|
509
|
+
# Persist the +enabled+ flag: +update!+, then +update_column+ if needed, then in-memory.
|
|
510
|
+
# Returns true when a DB write succeeded, false when only the in-memory value changed.
|
|
511
|
+
def persist_enabled_state(value)
|
|
512
|
+
update!(enabled: value)
|
|
513
|
+
true
|
|
514
|
+
rescue ActiveRecord::StatementInvalid, StandardError => e
|
|
515
|
+
Rails.logger.warn("Could not update registry via update!: #{e.message}") if defined?(Rails.logger)
|
|
516
|
+
begin
|
|
517
|
+
# rubocop:disable Rails/SkipsModelValidations -- fallback when update! fails (callbacks/validations/locks)
|
|
518
|
+
update_column(:enabled, value)
|
|
519
|
+
# rubocop:enable Rails/SkipsModelValidations
|
|
520
|
+
true
|
|
521
|
+
rescue ActiveRecord::StatementInvalid, StandardError => e2
|
|
522
|
+
Rails.logger.warn("Could not update registry via update_column: #{e2.message}") if defined?(Rails.logger)
|
|
523
|
+
self.enabled = value
|
|
524
|
+
false
|
|
525
|
+
end
|
|
526
|
+
end
|
|
500
527
|
end
|
|
501
528
|
# rubocop:enable Metrics/ClassLength
|
|
502
529
|
end
|
|
File without changes
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<h1 style="margin: 0;">Audit Log</h1>
|
|
5
5
|
<div style="display: flex; gap: 1rem;">
|
|
6
6
|
<%= link_to "Dashboard", dashboard_path, style: "padding: 0.5rem 1rem; background: #6c757d; color: white; text-decoration: none; border-radius: 4px; display: inline-block;" %>
|
|
7
|
-
<% csv_params = params.permit(:trigger_name, :operation, :status, :environment, :actor_id).to_h %>
|
|
7
|
+
<% csv_params = params.permit(:trigger_name, :operation, :status, :environment, :actor_id, :q).to_h %>
|
|
8
8
|
<%= link_to "Export CSV", audit_logs_path(csv_params.merge(format: :csv)), style: "padding: 0.5rem 1rem; background: #28a745; color: white; text-decoration: none; border-radius: 4px; display: inline-block;" %>
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
<div style="background: white; padding: 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
14
14
|
<h3 style="margin-top: 0; margin-bottom: 1rem;">Filters</h3>
|
|
15
15
|
<%= form_with url: audit_logs_path, method: :get, local: true, style: "display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;" do |f| %>
|
|
16
|
+
<div style="grid-column: 1 / -1;">
|
|
17
|
+
<%= label_tag :q, "Search", style: "display: block; margin-bottom: 0.5rem; font-weight: 600;" %>
|
|
18
|
+
<%= text_field_tag :q, params[:q], placeholder: "Trigger, operation, reason, error…", style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
19
|
+
</div>
|
|
16
20
|
<div>
|
|
17
21
|
<%= f.label :trigger_name, "Trigger Name", style: "display: block; margin-bottom: 0.5rem; font-weight: 600;" %>
|
|
18
22
|
<%= f.select :trigger_name, options_for_select([["All", ""]] + @available_trigger_names.map { |n| [n, n] }, params[:trigger_name]), {}, { style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" } %>
|
|
@@ -48,7 +52,7 @@
|
|
|
48
52
|
<!-- Results Summary -->
|
|
49
53
|
<div style="background: #f8f9fa; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;">
|
|
50
54
|
<strong>Total Results:</strong> <%= @total_count %> entries
|
|
51
|
-
<% if params[:trigger_name].present? || params[:operation].present? || params[:status].present? || params[:environment].present? %>
|
|
55
|
+
<% if params[:trigger_name].present? || params[:operation].present? || params[:status].present? || params[:environment].present? || params[:q].present? %>
|
|
52
56
|
<span style="color: #6c757d;">(filtered)</span>
|
|
53
57
|
<% end %>
|
|
54
58
|
</div>
|
|
@@ -144,7 +148,7 @@
|
|
|
144
148
|
<% if @total_pages > 1 %>
|
|
145
149
|
<div style="display: flex; justify-content: center; align-items: center; gap: 1rem; margin-top: 2rem;">
|
|
146
150
|
<% if @page > 1 %>
|
|
147
|
-
<% prev_params = params.except(:page).permit(:trigger_name, :operation, :status, :environment, :sort, :per_page).to_h %>
|
|
151
|
+
<% prev_params = params.except(:page).permit(:trigger_name, :operation, :status, :environment, :sort, :per_page, :q).to_h %>
|
|
148
152
|
<%= link_to "« Previous", audit_logs_path(prev_params.merge(page: @page - 1)), style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
149
153
|
<% else %>
|
|
150
154
|
<span style="padding: 0.5rem 1rem; background: #e9ecef; color: #6c757d; border-radius: 4px; cursor: not-allowed;">« Previous</span>
|
|
@@ -155,7 +159,7 @@
|
|
|
155
159
|
</span>
|
|
156
160
|
|
|
157
161
|
<% if @page < @total_pages %>
|
|
158
|
-
<% next_params = params.except(:page).permit(:trigger_name, :operation, :status, :environment, :sort, :per_page).to_h %>
|
|
162
|
+
<% next_params = params.except(:page).permit(:trigger_name, :operation, :status, :environment, :sort, :per_page, :q).to_h %>
|
|
159
163
|
<%= link_to "Next »", audit_logs_path(next_params.merge(page: @page + 1)), style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
160
164
|
<% else %>
|
|
161
165
|
<span style="padding: 0.5rem 1rem; background: #e9ecef; color: #6c757d; border-radius: 4px; cursor: not-allowed;">Next »</span>
|
|
@@ -166,7 +170,7 @@
|
|
|
166
170
|
<div style="background: white; padding: 3rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); text-align: center;">
|
|
167
171
|
<h3 style="margin-top: 0; color: #6c757d;">No audit log entries found</h3>
|
|
168
172
|
<p style="color: #6c757d;">
|
|
169
|
-
<% if params[:trigger_name].present? || params[:operation].present? || params[:status].present? || params[:environment].present? %>
|
|
173
|
+
<% if params[:trigger_name].present? || params[:operation].present? || params[:status].present? || params[:environment].present? || params[:q].present? %>
|
|
170
174
|
Try adjusting your filters or <%= link_to "clear filters", audit_logs_path, style: "color: #007bff;" %>.
|
|
171
175
|
<% else %>
|
|
172
176
|
Audit log entries will appear here as operations are performed.
|
|
@@ -27,8 +27,67 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
|
-
<% if @
|
|
31
|
-
<
|
|
30
|
+
<% if @stats[:total].positive? %>
|
|
31
|
+
<div style="background: white; padding: 1rem 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
32
|
+
<h3 style="margin-top: 0; margin-bottom: 1rem;">Search & filters</h3>
|
|
33
|
+
<%= form_with url: dashboard_path, method: :get, local: true, style: "display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; align-items: end;" do %>
|
|
34
|
+
<%= hidden_field_tag :page, params[:page] if params[:page].present? %>
|
|
35
|
+
<%= hidden_field_tag :per_page, params[:per_page] if params[:per_page].present? %>
|
|
36
|
+
<%= hidden_field_tag :trigger_page, 1 %>
|
|
37
|
+
<%= hidden_field_tag :trigger_per_page, @trigger_per_page %>
|
|
38
|
+
<div>
|
|
39
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Search</label>
|
|
40
|
+
<%= text_field_tag :q, @filter_query, placeholder: "Trigger or table name…", style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
41
|
+
</div>
|
|
42
|
+
<div>
|
|
43
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Table</label>
|
|
44
|
+
<%= select_tag :table, options_for_select([["All tables", ""]] + @filter_table_names.map { |t| [t, t] }, @filter_table), style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Drift state</label>
|
|
48
|
+
<%= select_tag :state, options_for_select([
|
|
49
|
+
["Any state", ""],
|
|
50
|
+
["In sync", "in_sync"],
|
|
51
|
+
["Drifted", "drifted"],
|
|
52
|
+
["Disabled", "disabled"],
|
|
53
|
+
["Dropped", "dropped"],
|
|
54
|
+
["Unknown (external)", "unknown"],
|
|
55
|
+
["Manual override", "manual_override"]
|
|
56
|
+
], @filter_state), style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Source</label>
|
|
60
|
+
<%= select_tag :source, options_for_select([
|
|
61
|
+
["Any source", ""],
|
|
62
|
+
["DSL", "dsl"],
|
|
63
|
+
["Generated", "generated"],
|
|
64
|
+
["Manual SQL", "manual_sql"]
|
|
65
|
+
], @filter_source), style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
66
|
+
</div>
|
|
67
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
68
|
+
<%= submit_tag "Apply", style: "padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 600;" %>
|
|
69
|
+
<%= link_to "Clear", dashboard_path(page: @page, per_page: @per_page), style: "padding: 0.5rem 1rem; background: #6c757d; color: white; text-decoration: none; border-radius: 4px; display: inline-block; line-height: 1.25;" %>
|
|
70
|
+
</div>
|
|
71
|
+
<% end %>
|
|
72
|
+
</div>
|
|
73
|
+
<% end %>
|
|
74
|
+
|
|
75
|
+
<% if @stats[:total].zero? %>
|
|
76
|
+
<div style="background: #e7f3ff; border-left: 4px solid #007bff; padding: 1.5rem; border-radius: 4px; margin-bottom: 2rem;">
|
|
77
|
+
<h3 style="margin-top: 0;">No triggers yet</h3>
|
|
78
|
+
<p style="margin-bottom: 1rem;">Get started by generating your first trigger using the form-based wizard.</p>
|
|
79
|
+
<%= link_to "View Tables", tables_path, class: "btn btn-primary" %>
|
|
80
|
+
</div>
|
|
81
|
+
<% elsif @trigger_list_total.zero? %>
|
|
82
|
+
<div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 1.5rem; border-radius: 4px; margin-bottom: 2rem;">
|
|
83
|
+
<h3 style="margin-top: 0;">No triggers match your filters</h3>
|
|
84
|
+
<p style="margin-bottom: 0;">Try adjusting search or filters, or <%= link_to "clear filters", dashboard_path(page: @page, per_page: @per_page), style: "color: #007bff;" %>.</p>
|
|
85
|
+
</div>
|
|
86
|
+
<% else %>
|
|
87
|
+
<h3>Triggers</h3>
|
|
88
|
+
<div style="margin-bottom: 1rem; color: #6c757d; font-size: 0.875rem;">
|
|
89
|
+
Showing <%= (@trigger_page - 1) * @trigger_per_page + 1 %>–<%= [@trigger_page * @trigger_per_page, @trigger_list_total].min %> of <%= @trigger_list_total %> trigger<%= @trigger_list_total == 1 ? "" : "s" %>
|
|
90
|
+
</div>
|
|
32
91
|
<table>
|
|
33
92
|
<thead>
|
|
34
93
|
<tr>
|
|
@@ -42,7 +101,7 @@
|
|
|
42
101
|
</tr>
|
|
43
102
|
</thead>
|
|
44
103
|
<tbody>
|
|
45
|
-
<% @triggers.
|
|
104
|
+
<% @triggers.each do |trigger| %>
|
|
46
105
|
<tr>
|
|
47
106
|
<td>
|
|
48
107
|
<%= link_to trigger.trigger_name, trigger_path(trigger), style: "color: #007bff; text-decoration: none; font-weight: 600;" %>
|
|
@@ -72,7 +131,7 @@
|
|
|
72
131
|
<% if trigger.enabled %>
|
|
73
132
|
<% form_id = "trigger-disable-#{trigger.id}-form" %>
|
|
74
133
|
<%= form_with url: disable_trigger_path(trigger), method: :post, local: false, id: form_id, style: "margin: 0;" do |f| %>
|
|
75
|
-
<%= f.hidden_field :redirect_to, value:
|
|
134
|
+
<%= f.hidden_field :redirect_to, value: request.fullpath %>
|
|
76
135
|
<button type="button" onclick="showKillSwitchModal('<%= form_id %>')"
|
|
77
136
|
style="padding: 0.25rem 0.75rem; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.75rem;">
|
|
78
137
|
Disable
|
|
@@ -86,7 +145,7 @@
|
|
|
86
145
|
<% else %>
|
|
87
146
|
<% form_id = "trigger-enable-#{trigger.id}-form" %>
|
|
88
147
|
<%= form_with url: enable_trigger_path(trigger), method: :post, local: false, id: form_id, style: "margin: 0;" do |f| %>
|
|
89
|
-
<%= f.hidden_field :redirect_to, value:
|
|
148
|
+
<%= f.hidden_field :redirect_to, value: request.fullpath %>
|
|
90
149
|
<button type="button" onclick="showKillSwitchModal('<%= form_id %>')"
|
|
91
150
|
style="padding: 0.25rem 0.75rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.75rem;">
|
|
92
151
|
Enable
|
|
@@ -104,13 +163,13 @@
|
|
|
104
163
|
<% begin %>
|
|
105
164
|
<% drift_info = trigger.drift_result %>
|
|
106
165
|
<% if drift_info && drift_info[:state] == 'drifted' %>
|
|
107
|
-
<%= render 'pg_sql_triggers/triggers/re_execute_modal', trigger: trigger, drift_info: drift_info, redirect_to:
|
|
166
|
+
<%= render 'pg_sql_triggers/triggers/re_execute_modal', trigger: trigger, drift_info: drift_info, redirect_to: request.fullpath, button_size: :small %>
|
|
108
167
|
<% end %>
|
|
109
168
|
<% rescue StandardError %>
|
|
110
169
|
<%# Skip if drift detection fails %>
|
|
111
170
|
<% end %>
|
|
112
171
|
|
|
113
|
-
<%= render 'pg_sql_triggers/triggers/drop_modal', trigger: trigger, redirect_to:
|
|
172
|
+
<%= render 'pg_sql_triggers/triggers/drop_modal', trigger: trigger, redirect_to: request.fullpath, button_size: :small %>
|
|
114
173
|
<% end %>
|
|
115
174
|
|
|
116
175
|
<% unless PgSqlTriggers::Permissions.can?(current_actor, :enable_trigger) || PgSqlTriggers::Permissions.can?(current_actor, :drop_trigger) %>
|
|
@@ -122,11 +181,35 @@
|
|
|
122
181
|
<% end %>
|
|
123
182
|
</tbody>
|
|
124
183
|
</table>
|
|
125
|
-
|
|
126
|
-
<div style="
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
184
|
+
|
|
185
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6; flex-wrap: wrap; gap: 0.75rem;">
|
|
186
|
+
<div>
|
|
187
|
+
<% if @trigger_total_pages > 1 %>
|
|
188
|
+
<% if @trigger_page > 1 %>
|
|
189
|
+
<%= link_to "← Previous", dashboard_path(dashboard_list_params(trigger_page: @trigger_page - 1)),
|
|
190
|
+
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
191
|
+
<% end %>
|
|
192
|
+
<% if @trigger_page < @trigger_total_pages %>
|
|
193
|
+
<%= link_to "Next →", dashboard_path(dashboard_list_params(trigger_page: @trigger_page + 1)),
|
|
194
|
+
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
195
|
+
<% end %>
|
|
196
|
+
<% end %>
|
|
197
|
+
</div>
|
|
198
|
+
<% if @trigger_total_pages > 1 %>
|
|
199
|
+
<div style="color: #6c757d; font-size: 0.875rem;">
|
|
200
|
+
Page <%= @trigger_page %> of <%= @trigger_total_pages %>
|
|
201
|
+
</div>
|
|
202
|
+
<% end %>
|
|
203
|
+
<%= form_with url: dashboard_path, method: :get, local: true, style: "display: flex; align-items: center; gap: 0.5rem; margin: 0;" do %>
|
|
204
|
+
<% dashboard_list_params.except(:trigger_page, :trigger_per_page).each do |key, value| %>
|
|
205
|
+
<%= hidden_field_tag key, value %>
|
|
206
|
+
<% end %>
|
|
207
|
+
<%= hidden_field_tag :trigger_page, 1 %>
|
|
208
|
+
<label style="color: #6c757d; font-size: 0.875rem;">Per page:</label>
|
|
209
|
+
<%= select_tag :trigger_per_page, options_for_select([10, 20, 50, 100], @trigger_per_page),
|
|
210
|
+
onchange: "this.form.submit()",
|
|
211
|
+
style: "padding: 0.25rem 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
212
|
+
<% end %>
|
|
130
213
|
</div>
|
|
131
214
|
<% end %>
|
|
132
215
|
|
|
@@ -290,30 +373,30 @@
|
|
|
290
373
|
|
|
291
374
|
<!-- Pagination Controls -->
|
|
292
375
|
<% if @total_pages > 1 %>
|
|
293
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6;">
|
|
376
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6; flex-wrap: wrap; gap: 0.75rem;">
|
|
294
377
|
<div>
|
|
295
378
|
<% if @page > 1 %>
|
|
296
|
-
<%= link_to "← Previous", dashboard_path(page: @page - 1, per_page: @per_page),
|
|
379
|
+
<%= link_to "← Previous", dashboard_path(dashboard_list_params(page: @page - 1, per_page: @per_page)),
|
|
297
380
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
298
381
|
<% end %>
|
|
299
382
|
<% if @page < @total_pages %>
|
|
300
|
-
<%= link_to "Next →", dashboard_path(page: @page + 1, per_page: @per_page),
|
|
383
|
+
<%= link_to "Next →", dashboard_path(dashboard_list_params(page: @page + 1, per_page: @per_page)),
|
|
301
384
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
302
385
|
<% end %>
|
|
303
386
|
</div>
|
|
304
387
|
<div style="color: #6c757d; font-size: 0.875rem;">
|
|
305
388
|
Page <%= @page %> of <%= @total_pages %>
|
|
306
389
|
</div>
|
|
307
|
-
|
|
390
|
+
<%= form_with url: dashboard_path, method: :get, local: true, style: "display: flex; align-items: center; gap: 0.5rem; margin: 0;" do %>
|
|
391
|
+
<% dashboard_list_params.except(:page, :per_page).each do |key, value| %>
|
|
392
|
+
<%= hidden_field_tag key, value %>
|
|
393
|
+
<% end %>
|
|
394
|
+
<%= hidden_field_tag :page, 1 %>
|
|
308
395
|
<label style="color: #6c757d; font-size: 0.875rem;">Per page:</label>
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<option value="50" <%= 'selected' if @per_page == 50 %>>50</option>
|
|
314
|
-
<option value="100" <%= 'selected' if @per_page == 100 %>>100</option>
|
|
315
|
-
</select>
|
|
316
|
-
</div>
|
|
396
|
+
<%= select_tag :per_page, options_for_select([10, 20, 50, 100], @per_page),
|
|
397
|
+
onchange: "this.form.submit()",
|
|
398
|
+
style: "padding: 0.25rem 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
399
|
+
<% end %>
|
|
317
400
|
</div>
|
|
318
401
|
<% end %>
|
|
319
402
|
<% else %>
|
|
File without changes
|
|
File without changes
|
|
@@ -20,14 +20,27 @@
|
|
|
20
20
|
|
|
21
21
|
<!-- Filter Controls -->
|
|
22
22
|
<div style="background: white; padding: 1rem 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
23
|
+
<%= form_with url: tables_path, method: :get, local: true, style: "margin-bottom: 1rem;" do %>
|
|
24
|
+
<%= hidden_field_tag :filter, @filter %>
|
|
25
|
+
<%= hidden_field_tag :page, 1 %>
|
|
26
|
+
<%= hidden_field_tag :per_page, @per_page %>
|
|
27
|
+
<div style="display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;">
|
|
28
|
+
<label style="color: #495057; font-weight: 600; font-size: 0.875rem;">Search tables:</label>
|
|
29
|
+
<%= text_field_tag :q, @search_query, placeholder: "Table name…", style: "flex: 1; min-width: 200px; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
30
|
+
<%= submit_tag "Search", style: "padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 600;" %>
|
|
31
|
+
<% if @search_query.present? %>
|
|
32
|
+
<%= link_to "Clear search", tables_path(filter: @filter, page: 1, per_page: @per_page), style: "padding: 0.5rem 1rem; background: #6c757d; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
23
36
|
<div style="display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
|
24
37
|
<label style="color: #495057; font-weight: 600; font-size: 0.875rem;">Filter:</label>
|
|
25
38
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
26
|
-
<%= link_to "All Tables", tables_path(filter: 'all', page: 1, per_page: @per_page),
|
|
39
|
+
<%= link_to "All Tables", tables_path(filter: 'all', page: 1, per_page: @per_page, q: @search_query),
|
|
27
40
|
style: "padding: 0.5rem 1rem; background: #{@filter == 'all' ? '#007bff' : '#f8f9fa'}; color: #{@filter == 'all' ? 'white' : '#495057'}; text-decoration: none; border-radius: 4px; border: 1px solid #{@filter == 'all' ? '#007bff' : '#dee2e6'}; font-size: 0.875rem; font-weight: #{@filter == 'all' ? '600' : '400'};" %>
|
|
28
|
-
<%= link_to "With Triggers", tables_path(filter: 'with_triggers', page: 1, per_page: @per_page),
|
|
41
|
+
<%= link_to "With Triggers", tables_path(filter: 'with_triggers', page: 1, per_page: @per_page, q: @search_query),
|
|
29
42
|
style: "padding: 0.5rem 1rem; background: #{@filter == 'with_triggers' ? '#28a745' : '#f8f9fa'}; color: #{@filter == 'with_triggers' ? 'white' : '#495057'}; text-decoration: none; border-radius: 4px; border: 1px solid #{@filter == 'with_triggers' ? '#28a745' : '#dee2e6'}; font-size: 0.875rem; font-weight: #{@filter == 'with_triggers' ? '600' : '400'};" %>
|
|
30
|
-
<%= link_to "Without Triggers", tables_path(filter: 'without_triggers', page: 1, per_page: @per_page),
|
|
43
|
+
<%= link_to "Without Triggers", tables_path(filter: 'without_triggers', page: 1, per_page: @per_page, q: @search_query),
|
|
31
44
|
style: "padding: 0.5rem 1rem; background: #{@filter == 'without_triggers' ? '#6c757d' : '#f8f9fa'}; color: #{@filter == 'without_triggers' ? 'white' : '#495057'}; text-decoration: none; border-radius: 4px; border: 1px solid #{@filter == 'without_triggers' ? '#6c757d' : '#dee2e6'}; font-size: 0.875rem; font-weight: #{@filter == 'without_triggers' ? '600' : '400'};" %>
|
|
32
45
|
</div>
|
|
33
46
|
</div>
|
|
@@ -123,27 +136,26 @@
|
|
|
123
136
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding: 1rem; border-top: 1px solid #dee2e6;">
|
|
124
137
|
<div>
|
|
125
138
|
<% if @page > 1 %>
|
|
126
|
-
<%= link_to "← Previous", tables_path(filter: @filter, page: @page - 1, per_page: @per_page),
|
|
139
|
+
<%= link_to "← Previous", tables_path(filter: @filter, page: @page - 1, per_page: @per_page, q: @search_query),
|
|
127
140
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
128
141
|
<% end %>
|
|
129
142
|
<% if @page < @total_pages %>
|
|
130
|
-
<%= link_to "Next →", tables_path(filter: @filter, page: @page + 1, per_page: @per_page),
|
|
143
|
+
<%= link_to "Next →", tables_path(filter: @filter, page: @page + 1, per_page: @per_page, q: @search_query),
|
|
131
144
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
132
145
|
<% end %>
|
|
133
146
|
</div>
|
|
134
147
|
<div style="color: #6c757d; font-size: 0.875rem;">
|
|
135
148
|
Page <%= @page %> of <%= @total_pages %>
|
|
136
149
|
</div>
|
|
137
|
-
|
|
150
|
+
<%= form_with url: tables_path, method: :get, local: true, style: "display: flex; align-items: center; gap: 0.5rem; margin: 0;" do %>
|
|
151
|
+
<%= hidden_field_tag :filter, @filter %>
|
|
152
|
+
<%= hidden_field_tag :page, 1 %>
|
|
153
|
+
<%= hidden_field_tag :q, @search_query if @search_query.present? %>
|
|
138
154
|
<label style="color: #6c757d; font-size: 0.875rem;">Per page:</label>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<option value="50" <%= 'selected' if @per_page == 50 %>>50</option>
|
|
144
|
-
<option value="100" <%= 'selected' if @per_page == 100 %>>100</option>
|
|
145
|
-
</select>
|
|
146
|
-
</div>
|
|
155
|
+
<%= select_tag :per_page, options_for_select([10, 20, 50, 100], @per_page),
|
|
156
|
+
onchange: "this.form.submit()",
|
|
157
|
+
style: "padding: 0.25rem 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
158
|
+
<% end %>
|
|
147
159
|
</div>
|
|
148
160
|
<% end %>
|
|
149
161
|
</div>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|