solid_queue_dashboard 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +5 -1
  4. data/Procfile.dev +2 -0
  5. data/README.md +73 -21
  6. data/app/assets/javascripts/solid_queue_dashboard/alpine.js +5 -0
  7. data/app/assets/javascripts/solid_queue_dashboard/application.js +46 -0
  8. data/app/assets/stylesheets/solid_queue_dashboard/application.css +2049 -0
  9. data/app/assets/stylesheets/solid_queue_dashboard/tailwind.css +554 -0
  10. data/app/controllers/solid_queue_dashboard/appearance_controller.rb +8 -0
  11. data/app/controllers/solid_queue_dashboard/application_controller.rb +7 -0
  12. data/app/controllers/solid_queue_dashboard/dashboard_controller.rb +8 -0
  13. data/app/controllers/solid_queue_dashboard/jobs_controller.rb +42 -0
  14. data/app/controllers/solid_queue_dashboard/processes_controller.rb +28 -0
  15. data/app/controllers/solid_queue_dashboard/recurring_tasks_controller.rb +30 -0
  16. data/app/helpers/solid_queue_dashboard/appearance_helper.rb +7 -0
  17. data/app/helpers/solid_queue_dashboard/application_helper.rb +7 -0
  18. data/app/helpers/solid_queue_dashboard/icons_helper.rb +231 -0
  19. data/app/helpers/solid_queue_dashboard/jobs_helper.rb +51 -0
  20. data/app/helpers/solid_queue_dashboard/pagination_helper.rb +38 -0
  21. data/app/helpers/solid_queue_dashboard/processes_helper.rb +35 -0
  22. data/app/helpers/solid_queue_dashboard/recurring_tasks_helper.rb +33 -0
  23. data/app/views/layouts/solid_queue_dashboard/application.html.erb +21 -0
  24. data/app/views/solid_queue_dashboard/application/_flash_messages.html.erb +38 -0
  25. data/app/views/solid_queue_dashboard/application/_footer.html.erb +11 -0
  26. data/app/views/solid_queue_dashboard/application/_navbar.html.erb +50 -0
  27. data/app/views/solid_queue_dashboard/application/_pagination.html.erb +19 -0
  28. data/app/views/solid_queue_dashboard/dashboard/index.html.erb +41 -0
  29. data/app/views/solid_queue_dashboard/jobs/_filters.html.erb +87 -0
  30. data/app/views/solid_queue_dashboard/jobs/_table.html.erb +19 -0
  31. data/app/views/solid_queue_dashboard/jobs/_table_row.html.erb +82 -0
  32. data/app/views/solid_queue_dashboard/jobs/index.html.erb +57 -0
  33. data/app/views/solid_queue_dashboard/jobs/show.html.erb +192 -0
  34. data/app/views/solid_queue_dashboard/processes/_filters.html.erb +64 -0
  35. data/app/views/solid_queue_dashboard/processes/_table.html.erb +18 -0
  36. data/app/views/solid_queue_dashboard/processes/_table_row.html.erb +32 -0
  37. data/app/views/solid_queue_dashboard/processes/index.html.erb +27 -0
  38. data/app/views/solid_queue_dashboard/processes/show.html.erb +79 -0
  39. data/app/views/solid_queue_dashboard/recurring_tasks/_filters.html.erb +26 -0
  40. data/app/views/solid_queue_dashboard/recurring_tasks/_table.html.erb +19 -0
  41. data/app/views/solid_queue_dashboard/recurring_tasks/_table_row.html.erb +31 -0
  42. data/app/views/solid_queue_dashboard/recurring_tasks/index.html.erb +21 -0
  43. data/app/views/solid_queue_dashboard/recurring_tasks/show.html.erb +129 -0
  44. data/bun.lockb +0 -0
  45. data/config/routes.rb +18 -0
  46. data/lib/solid_queue_dashboard/configuration.rb +17 -0
  47. data/lib/solid_queue_dashboard/decorators/job_decorator.rb +63 -0
  48. data/lib/solid_queue_dashboard/decorators/jobs_decorator.rb +74 -0
  49. data/lib/solid_queue_dashboard/decorators/process_decorator.rb +13 -0
  50. data/lib/solid_queue_dashboard/decorators/processes_decorator.rb +15 -0
  51. data/lib/solid_queue_dashboard/decorators/recurring_task_decorator.rb +22 -0
  52. data/lib/solid_queue_dashboard/decorators/recurring_tasks_decorator.rb +26 -0
  53. data/lib/solid_queue_dashboard/engine.rb +13 -0
  54. data/lib/solid_queue_dashboard/job.rb +26 -0
  55. data/lib/solid_queue_dashboard/process.rb +24 -0
  56. data/lib/solid_queue_dashboard/recurring_task.rb +14 -0
  57. data/lib/solid_queue_dashboard/version.rb +1 -1
  58. data/lib/solid_queue_dashboard.rb +39 -1
  59. data/package.json +13 -0
  60. data/tailwind.config.js +83 -0
  61. metadata +73 -4
@@ -0,0 +1,21 @@
1
+ <div class="card">
2
+ <div class="card-header border-b">
3
+ <h2 class="card-title">Recurring Tasks</h2>
4
+
5
+ <div class="!mt-4">
6
+ <%= render "solid_queue_dashboard/recurring_tasks/filters" %>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="card-content !p-0">
11
+ <% if @recurring_tasks.any? %>
12
+ <%= render "solid_queue_dashboard/recurring_tasks/table", recurring_tasks: @recurring_tasks %>
13
+ <% else %>
14
+ <div class="px-6 py-12">
15
+ <p class="text-muted-foreground text-center">
16
+ No recurring tasks found
17
+ </p>
18
+ </div>
19
+ <% end %>
20
+ </div>
21
+ </div>
@@ -0,0 +1,129 @@
1
+ <h1 class="text-4xl flex items-center gap-2">
2
+ <%= recurring_task_circle(@recurring_task.type, class: "size-4") %>
3
+
4
+ <span class="tracking-tighter font-bold">
5
+ Recurring Task #<%= @recurring_task.id %>
6
+ </span>
7
+ </h1>
8
+
9
+ <div class="card mt-6">
10
+ <div class="card-content pt-6 grid grid-cols-1 sm:grid-cols-2 sm:gap-16">
11
+ <div class="space-y-4">
12
+ <div class="info-line">
13
+ <span class="info-line-label">Type</span>
14
+ <div class="info-line-separator"></div>
15
+ <span class="info-line-value"><%= recurring_task_type_badge(@recurring_task.type) %></span>
16
+ </div>
17
+
18
+ <div class="info-line">
19
+ <span class="info-line-label">Key</span>
20
+ <div class="info-line-separator"></div>
21
+ <span class="info-line-value"><%= @recurring_task.key %></span>
22
+ </div>
23
+
24
+ <div class="info-line">
25
+ <span class="info-line-label">Schedule</span>
26
+ <div class="info-line-separator"></div>
27
+ <span class="info-line-value"><%= @recurring_task.schedule %></span>
28
+ </div>
29
+
30
+ <% if @recurring_task.queue_name? %>
31
+ <div class="info-line">
32
+ <span class="info-line-label">Queue Name</span>
33
+ <div class="info-line-separator"></div>
34
+ <span class="info-line-value"><%= @recurring_task.queue_name %></span>
35
+ </div>
36
+ <% end %>
37
+ </div>
38
+
39
+ <div class="space-y-4">
40
+ <% if @recurring_task.priority? %>
41
+ <div class="info-line">
42
+ <span class="info-line-label">Priority</span>
43
+ <div class="info-line-separator"></div>
44
+ <span class="info-line-value"><%= @recurring_task.priority %></span>
45
+ </div>
46
+ <% end %>
47
+
48
+ <div class="info-line">
49
+ <span class="info-line-label">Static</span>
50
+ <div class="info-line-separator"></div>
51
+ <span class="info-line-value"><%= @recurring_task.static ? "Yes" : "No" %></span>
52
+ </div>
53
+
54
+ <div class="info-line">
55
+ <span class="info-line-label">Created At</span>
56
+ <div class="info-line-separator"></div>
57
+ <span class="info-line-value" data-date><%= @recurring_task.created_at.to_fs(:database) %></span>
58
+ </div>
59
+
60
+ <div class="info-line">
61
+ <span class="info-line-label">Updated At</span>
62
+ <div class="info-line-separator"></div>
63
+ <span class="info-line-value" data-date><%= @recurring_task.updated_at.to_fs(:database) %></span>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <% if @recurring_task.command.present? %>
70
+ <div class="card mt-8 overflow-hidden">
71
+ <div class="card-header border-b border-t-4 border-t-amber-500 dark:border-t-amber-600">
72
+ <h3 class="card-title">Command</h3>
73
+ <p class="card-description">The command to be executed</p>
74
+ </div>
75
+ <div class="card-content pt-6">
76
+ <pre><%= @recurring_task.command %></pre>
77
+ </div>
78
+ </div>
79
+ <% end %>
80
+
81
+ <% if @recurring_task.class_name.present? %>
82
+ <div class="card mt-8 overflow-hidden">
83
+ <div class="card-header border-b border-t-4 border-t-blue-500 dark:border-t-blue-600">
84
+ <h3 class="card-title">Job Class</h3>
85
+ <p class="card-description">The job class to be executed</p>
86
+ </div>
87
+ <div class="card-content pt-6">
88
+ <pre><%= @recurring_task.class_name %></pre>
89
+ </div>
90
+ </div>
91
+ <% end %>
92
+
93
+ <% if @recurring_task.arguments.present? %>
94
+ <div class="card mt-8 overflow-hidden">
95
+ <div class="card-header border-b border-t-4 border-t-purple-500 dark:border-t-purple-600">
96
+ <h3 class="card-title">Arguments</h3>
97
+ <p class="card-description">The arguments to be passed to the job</p>
98
+ </div>
99
+ <div class="card-content pt-6">
100
+ <pre><%= JSON.pretty_generate(@recurring_task.arguments) %></pre>
101
+ </div>
102
+ </div>
103
+ <% end %>
104
+
105
+ <% if @recurring_task.description.present? %>
106
+ <div class="card mt-8 overflow-hidden">
107
+ <div class="card-header border-b border-t-4 border-t-amber-500 dark:border-t-amber-600">
108
+ <h3 class="card-title">Description</h3>
109
+ <p class="card-description">Additional information about this recurring task</p>
110
+ </div>
111
+ <div class="card-content pt-6">
112
+ <pre><%= @recurring_task.description %></pre>
113
+ </div>
114
+ </div>
115
+ <% end %>
116
+
117
+ <div class="card mt-8 overflow-hidden">
118
+ <div class="card-header border-b border-t-4 border-t-zinc-500 dark:border-t-zinc-600">
119
+ <h3 class="card-title">Next Runs</h3>
120
+ <p class="card-description">Based on the current schedule</p>
121
+ </div>
122
+ <div class="card-content pt-5">
123
+ <ul class="list-decimal marker:text-sm marker:text-muted-foreground ml-4 space-y-1">
124
+ <% @recurring_task.next_runs.each do |run_time| %>
125
+ <li data-date><%= run_time.strftime("%Y-%m-%d %H:%M:%S %Z") %></li>
126
+ <% end %>
127
+ </ul>
128
+ </div>
129
+ </div>
data/bun.lockb ADDED
Binary file
data/config/routes.rb ADDED
@@ -0,0 +1,18 @@
1
+ SolidQueueDashboard::Engine.routes.draw do
2
+ resources :jobs, only: [ :index, :show ] do
3
+ member do
4
+ post :retry
5
+ end
6
+ end
7
+
8
+ resources :processes, only: [ :index, :show ]
9
+
10
+ resources :recurring_tasks, path: "recurring-tasks", only: [ :index, :show ] do
11
+ member do
12
+ post :enqueue
13
+ end
14
+ end
15
+
16
+ post "appearance/toggle", to: "appearance#toggle", as: :toggle_appearance
17
+ root "dashboard#index"
18
+ end
@@ -0,0 +1,17 @@
1
+ module SolidQueueDashboard
2
+ class Configuration
3
+ attr_accessor :title
4
+
5
+ def initialize
6
+ @title = "Solid Queue Dashboard"
7
+ end
8
+ end
9
+
10
+ def self.configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def self.configure
15
+ yield(configuration)
16
+ end
17
+ end
@@ -0,0 +1,63 @@
1
+ module SolidQueueDashboard
2
+ module Decorators
3
+ class JobDecorator < SimpleDelegator
4
+ def color
5
+ Job.status_color(status)
6
+ end
7
+
8
+ def status
9
+ return @status if defined?(@status)
10
+
11
+ @status = if retried?
12
+ Job::RETRIED
13
+ elsif failed?
14
+ Job::FAILED
15
+ elsif success?
16
+ Job::SUCCESS
17
+ elsif scheduled?
18
+ Job::SCHEDULED
19
+ else
20
+ Job::PENDING
21
+ end
22
+ end
23
+
24
+ def success?
25
+ return @success if defined?(@success)
26
+ @success = finished_at.present? && !failed? && !retried?
27
+ end
28
+
29
+ def retried?
30
+ return @retried if defined?(@retried)
31
+ @retried = finished_at.present? && !failed_execution.present? &&
32
+ (arguments["executions"].to_i > 0 || execution_history.where(scheduled_at: finished_at..).any?)
33
+ end
34
+
35
+ def failed?
36
+ return @failed if defined?(@failed)
37
+ @failed = failed_execution.present?
38
+ end
39
+
40
+ def scheduled?
41
+ return @scheduled if defined?(@scheduled)
42
+ @scheduled = scheduled_at.present? && scheduled_at > Time.current
43
+ end
44
+
45
+ def pending?
46
+ return @pending if defined?(@pending)
47
+ @pending = !finished_at.present?
48
+ end
49
+
50
+ def execution_history
51
+ SolidQueue::Job.where(active_job_id: active_job_id)
52
+ end
53
+
54
+ def error_message
55
+ return @error_message if defined?(@error_message)
56
+
57
+ @error_message = failed_execution ?
58
+ "#{failed_execution.error["exception_class"]}: #{failed_execution.error["message"]}" :
59
+ nil
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,74 @@
1
+ module SolidQueueDashboard
2
+ module Decorators
3
+ class JobsDecorator < SimpleDelegator
4
+ def with_status(status)
5
+ case status.to_sym
6
+ when Job::SUCCESS
7
+ success
8
+ when Job::FAILED
9
+ failed
10
+ when Job::SCHEDULED
11
+ scheduled
12
+ when Job::PENDING
13
+ pending
14
+ when Job::RETRIED
15
+ retried
16
+ else
17
+ raise "Invalid status: #{status}"
18
+ end
19
+ end
20
+
21
+ def success
22
+ where.not(finished_at: nil)
23
+ .where.not(id: failed)
24
+ .where.not(id: retried)
25
+ end
26
+
27
+ def scheduled
28
+ where(finished_at: nil, scheduled_at: Time.current..)
29
+ end
30
+
31
+ def pending
32
+ where(finished_at: nil, scheduled_at: ..Time.current)
33
+ .where.not(id: failed)
34
+ end
35
+
36
+ def retried
37
+ where(finished_at: ..Time.current)
38
+ .where.not(id: failed)
39
+ .where(
40
+ active_job_id: SolidQueue::Job
41
+ .select(:active_job_id)
42
+ .group(:active_job_id)
43
+ .having("COUNT(*) > 1")
44
+ )
45
+ .where.not(
46
+ id: SolidQueue::Job
47
+ .select("MAX(id)")
48
+ .group(:active_job_id)
49
+ )
50
+ end
51
+
52
+ def failure_rate
53
+ success_count = success.count
54
+ retried_count = retried.count
55
+ failed_count = failed.count
56
+
57
+ total = success_count + retried_count + failed_count
58
+ return 0 if total.zero?
59
+
60
+ (failed_count + retried_count).to_f / total * 100
61
+ end
62
+
63
+ def each
64
+ super do |job|
65
+ yield JobDecorator.new(job)
66
+ end
67
+ end
68
+
69
+ def to_a
70
+ super.map { |job| JobDecorator.new(job) }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,13 @@
1
+ module SolidQueueDashboard
2
+ module Decorators
3
+ class ProcessDecorator < SimpleDelegator
4
+ def color
5
+ Process.kind_color(kind)
6
+ end
7
+
8
+ def dead?
9
+ last_heartbeat_at < Process::HEARTBEAT_DEAD_THRESHOLD.ago
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module SolidQueueDashboard
2
+ module Decorators
3
+ class ProcessesDecorator < SimpleDelegator
4
+ def each
5
+ super do |job|
6
+ yield ProcessDecorator.new(job)
7
+ end
8
+ end
9
+
10
+ def to_a
11
+ super.map { |job| ProcessDecorator.new(job) }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module SolidQueueDashboard
2
+ module Decorators
3
+ class RecurringTaskDecorator < SimpleDelegator
4
+ def type
5
+ if command.present?
6
+ RecurringTask::COMMAND
7
+ elsif class_name.present?
8
+ RecurringTask::JOB
9
+ else
10
+ "Unknown"
11
+ end
12
+ end
13
+
14
+ def next_runs(count: 5)
15
+ cron = Fugit.parse(schedule)
16
+ return [] unless cron
17
+
18
+ cron.next.take(count)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ module SolidQueueDashboard
2
+ module Decorators
3
+ class RecurringTasksDecorator < SimpleDelegator
4
+ def with_type(type)
5
+ case type.to_sym
6
+ when RecurringTask::JOB
7
+ where.not(class_name: nil)
8
+ when RecurringTask::COMMAND
9
+ where.not(command: nil)
10
+ else
11
+ raise "Unknown type: #{type}"
12
+ end
13
+ end
14
+
15
+ def each
16
+ super do |task|
17
+ yield RecurringTaskDecorator.new(task)
18
+ end
19
+ end
20
+
21
+ def to_a
22
+ super.map { |task| RecurringTaskDecorator.new(task) }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ module SolidQueueDashboard
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SolidQueueDashboard
4
+
5
+ initializer "solid_queue_dashboard.assets.precompile" do |app|
6
+ app.config.assets.precompile += %w[
7
+ solid_queue_dashboard/alpine.js
8
+ solid_queue_dashboard/application.js
9
+ solid_queue_dashboard/application.css
10
+ ]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module SolidQueueDashboard
2
+ module Job
3
+ # Constants
4
+ SUCCESS = :success
5
+ RETRIED = :retried
6
+ FAILED = :failed
7
+ PENDING = :pending
8
+ SCHEDULED = :scheduled
9
+
10
+ STATUSES = [ SUCCESS, RETRIED, FAILED, SCHEDULED, PENDING ]
11
+
12
+ STATUS_COLORS = {
13
+ SUCCESS => "green",
14
+ RETRIED => "amber",
15
+ FAILED => "red",
16
+ SCHEDULED => "blue",
17
+ PENDING => "zinc"
18
+ }
19
+
20
+ COMMAND_CLASS_NAME = "SolidQueue::RecurringJob"
21
+
22
+ def self.status_color(status)
23
+ STATUS_COLORS[status] || "zinc"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module SolidQueueDashboard
2
+ module Process
3
+ # Constants
4
+ SUPERVISOR = "Supervisor"
5
+ DISPATCHER = "Dispatcher"
6
+ WORKER = "Worker"
7
+ SCHEDULER = "Scheduler"
8
+
9
+ KINDS = [ SUPERVISOR, DISPATCHER, WORKER, SCHEDULER ]
10
+
11
+ KIND_COLORS = {
12
+ SUPERVISOR => "blue",
13
+ DISPATCHER => "green",
14
+ WORKER => "yellow",
15
+ SCHEDULER => "purple"
16
+ }
17
+
18
+ HEARTBEAT_DEAD_THRESHOLD = 1.minute
19
+
20
+ def self.kind_color(kind)
21
+ KIND_COLORS[kind] || "zinc"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module SolidQueueDashboard
2
+ module RecurringTask
3
+ # Constants
4
+ COMMAND = :command
5
+ JOB = :job
6
+
7
+ TYPES = [ COMMAND, JOB ]
8
+
9
+ TYPE_COLORS = {
10
+ COMMAND => "amber",
11
+ JOB => "sky"
12
+ }
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueDashboard
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -1,8 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rails"
3
4
  require_relative "solid_queue_dashboard/version"
5
+ require_relative "solid_queue_dashboard/configuration"
6
+ require_relative "solid_queue_dashboard/engine"
7
+ require_relative "solid_queue_dashboard/job"
8
+ require_relative "solid_queue_dashboard/process"
9
+ require_relative "solid_queue_dashboard/recurring_task"
10
+ require_relative "solid_queue_dashboard/decorators/job_decorator"
11
+ require_relative "solid_queue_dashboard/decorators/jobs_decorator"
12
+ require_relative "solid_queue_dashboard/decorators/process_decorator"
13
+ require_relative "solid_queue_dashboard/decorators/processes_decorator"
14
+ require_relative "solid_queue_dashboard/decorators/recurring_task_decorator"
15
+ require_relative "solid_queue_dashboard/decorators/recurring_tasks_decorator"
4
16
 
5
17
  module SolidQueueDashboard
6
18
  class Error < StandardError; end
7
- # Your code goes here...
19
+
20
+ def self.job_queue_names
21
+ SolidQueue::Job.distinct.pluck(:queue_name)
22
+ end
23
+
24
+ def self.job_class_names
25
+ SolidQueue::Job.distinct.pluck(:class_name)
26
+ end
27
+
28
+ def self.decorate(object)
29
+ case object
30
+ when SolidQueue::Job
31
+ Decorators::JobDecorator.new(object)
32
+ when SolidQueue::Job.const_get(:ActiveRecord_Relation)
33
+ Decorators::JobsDecorator.new(object)
34
+ when SolidQueue::Process
35
+ Decorators::ProcessDecorator.new(object)
36
+ when SolidQueue::Process.const_get(:ActiveRecord_Relation)
37
+ Decorators::ProcessesDecorator.new(object)
38
+ when SolidQueue::RecurringTask
39
+ Decorators::RecurringTaskDecorator.new(object)
40
+ when SolidQueue::RecurringTask.const_get(:ActiveRecord_Relation)
41
+ Decorators::RecurringTasksDecorator.new(object)
42
+ else
43
+ raise Error, "Cannot decorate #{object.class}"
44
+ end
45
+ end
8
46
  end
data/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "private": true,
3
+ "scripts": {
4
+ "build": "tailwindcss -i ./app/assets/stylesheets/solid_queue_dashboard/tailwind.css -o ./app/assets/stylesheets/solid_queue_dashboard/application.css",
5
+ "watch": "tailwindcss -i ./app/assets/stylesheets/solid_queue_dashboard/tailwind.css -o ./app/assets/stylesheets/solid_queue_dashboard/application.css --watch"
6
+ },
7
+ "devDependencies": {
8
+ "@tailwindcss/forms": "^0.5.9",
9
+ "@tailwindcss/typography": "^0.5.15",
10
+ "tailwindcss": "^3.4.13",
11
+ "tailwindcss-animate": "^1.0.7"
12
+ }
13
+ }
@@ -0,0 +1,83 @@
1
+ const { fontFamily } = require("tailwindcss/defaultTheme")
2
+
3
+ /** @type {import('tailwindcss').Config} */
4
+ module.exports = {
5
+ darkMode: ["class"],
6
+ content: [
7
+ "./app/views/**/*.html.erb",
8
+ "./app/helpers/**/*.rb",
9
+ "./app/javascript/**/*.js",
10
+ ],
11
+ theme: {
12
+ container: {
13
+ center: true,
14
+ padding: "2rem",
15
+ screens: {
16
+ "2xl": "1400px",
17
+ },
18
+ },
19
+ extend: {
20
+ colors: {
21
+ border: "hsl(var(--border))",
22
+ input: "hsl(var(--input))",
23
+ ring: "hsl(var(--ring))",
24
+ background: "hsl(var(--background))",
25
+ foreground: "hsl(var(--foreground))",
26
+ primary: {
27
+ DEFAULT: "hsl(var(--primary))",
28
+ foreground: "hsl(var(--primary-foreground))",
29
+ },
30
+ secondary: {
31
+ DEFAULT: "hsl(var(--secondary))",
32
+ foreground: "hsl(var(--secondary-foreground))",
33
+ },
34
+ destructive: {
35
+ DEFAULT: "hsl(var(--destructive))",
36
+ foreground: "hsl(var(--destructive-foreground))",
37
+ },
38
+ muted: {
39
+ DEFAULT: "hsl(var(--muted))",
40
+ foreground: "hsl(var(--muted-foreground))",
41
+ },
42
+ accent: {
43
+ DEFAULT: "hsl(var(--accent))",
44
+ foreground: "hsl(var(--accent-foreground))",
45
+ },
46
+ popover: {
47
+ DEFAULT: "hsl(var(--popover))",
48
+ foreground: "hsl(var(--popover-foreground))",
49
+ },
50
+ card: {
51
+ DEFAULT: "hsl(var(--card))",
52
+ foreground: "hsl(var(--card-foreground))",
53
+ },
54
+ },
55
+ borderRadius: {
56
+ lg: `var(--radius)`,
57
+ md: `calc(var(--radius) - 2px)`,
58
+ sm: "calc(var(--radius) - 4px)",
59
+ },
60
+ opacity: {
61
+ 7.5: "0.075",
62
+ },
63
+ keyframes: {
64
+ "accordion-down": {
65
+ from: { height: "0" },
66
+ to: { height: "var(--radix-accordion-content-height)" },
67
+ },
68
+ "accordion-up": {
69
+ from: { height: "var(--radix-accordion-content-height)" },
70
+ to: { height: "0" },
71
+ },
72
+ },
73
+ animation: {
74
+ "accordion-down": "accordion-down 0.2s ease-out",
75
+ "accordion-up": "accordion-up 0.2s ease-out",
76
+ },
77
+ },
78
+ },
79
+ plugins: [
80
+ require("tailwindcss-animate"),
81
+ require("@tailwindcss/forms")({ strategy: "class" }),
82
+ ],
83
+ }