solid_queue_ui 0.0.1

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +42 -0
  4. data/Rakefile +3 -0
  5. data/lib/solid_queue_ui/config.rb +26 -0
  6. data/lib/solid_queue_ui/rails.rb +11 -0
  7. data/lib/solid_queue_ui/railtie.rb +4 -0
  8. data/lib/solid_queue_ui/version.rb +3 -0
  9. data/lib/solid_queue_ui/web/action.rb +93 -0
  10. data/lib/solid_queue_ui/web/application.rb +126 -0
  11. data/lib/solid_queue_ui/web/csrf_protection.rb +180 -0
  12. data/lib/solid_queue_ui/web/database.rb +17 -0
  13. data/lib/solid_queue_ui/web/helpers.rb +201 -0
  14. data/lib/solid_queue_ui/web/router.rb +102 -0
  15. data/lib/solid_queue_ui/web.rb +145 -0
  16. data/lib/solid_queue_ui.rb +22 -0
  17. data/lib/tasks/solid_queue_ui_tasks.rake +4 -0
  18. data/solid_queue_ui.gemspec +42 -0
  19. data/web/assets/javascripts/application.js +177 -0
  20. data/web/assets/javascripts/chart.min.js +13 -0
  21. data/web/assets/stylesheets/application.css +37 -0
  22. data/web/assets/stylesheets/application.css.scss +15 -0
  23. data/web/views/_footer.html.erb +1 -0
  24. data/web/views/_nav.html.erb +11 -0
  25. data/web/views/dashboard.html.erb +33 -0
  26. data/web/views/jobs.html.erb +41 -0
  27. data/web/views/layout.html.erb +32 -0
  28. data/web/views/solid_queue_ui/application/_flashes.html.erb +8 -0
  29. data/web/views/solid_queue_ui/application/_index_header.html.erb +28 -0
  30. data/web/views/solid_queue_ui/application/_javascript.html.erb +13 -0
  31. data/web/views/solid_queue_ui/application/_navigation.html.erb +11 -0
  32. data/web/views/solid_queue_ui/application/_stylesheet.html.erb +5 -0
  33. data/web/views/solid_queue_ui/application/index.html.erb +21 -0
  34. data/web/views/solid_queue_ui/application/show.html.erb +57 -0
  35. metadata +127 -0
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "set"
5
+ require "yaml"
6
+ require "cgi"
7
+
8
+ module SolidQueueUi
9
+ # This is not a public API
10
+ module WebHelpers
11
+ def strings(lang)
12
+ @strings ||= {}
13
+
14
+ @strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
15
+ find_locale_files(lang).each do |file|
16
+ strs = YAML.safe_load(File.read(file))
17
+ global.merge!(strs[lang])
18
+ end
19
+ end
20
+ end
21
+
22
+ def to_json(x)
23
+ SolidQueueUi.dump_json(x)
24
+ end
25
+
26
+ def singularize(str, count)
27
+ if count == 1 && str.respond_to?(:singularize) # rails
28
+ str.singularize
29
+ else
30
+ str
31
+ end
32
+ end
33
+
34
+ def clear_caches
35
+ @strings = nil
36
+ @locale_files = nil
37
+ @available_locales = nil
38
+ end
39
+
40
+ def locale_files
41
+ @locale_files ||= settings.locales.flat_map { |path|
42
+ Dir["#{path}/*.yml"]
43
+ }
44
+ end
45
+
46
+ def available_locales
47
+ @available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq
48
+ end
49
+
50
+ def find_locale_files(lang)
51
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
52
+ end
53
+
54
+ def search(jobset, substr)
55
+ resultset = jobset.scan(substr).to_a
56
+ @current_page = 1
57
+ @count = @total_size = resultset.size
58
+ resultset
59
+ end
60
+
61
+ def filtering(which)
62
+ erb(:filtering, locals: {which: which})
63
+ end
64
+
65
+ def filter_link(jid, within = "retries")
66
+ if within.nil?
67
+ ::Rack::Utils.escape_html(jid)
68
+ else
69
+ "<a href='#{root_path}filter/#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
70
+ end
71
+ end
72
+
73
+ def display_tags(job, within = "retries")
74
+ job.tags.map { |tag|
75
+ "<span class='label label-info jobtag'>#{filter_link(tag, within)}</span>"
76
+ }.join(" ")
77
+ end
78
+
79
+ # This view helper provide ability display you html code in
80
+ # to head of page. Example:
81
+ #
82
+ # <% add_to_head do %>
83
+ # <link rel="stylesheet" .../>
84
+ # <meta .../>
85
+ # <% end %>
86
+ #
87
+ def add_to_head
88
+ @head_html ||= []
89
+ @head_html << yield.dup if block_given?
90
+ end
91
+
92
+ def display_custom_head
93
+ @head_html.join if defined?(@head_html)
94
+ end
95
+
96
+ def text_direction
97
+ get_locale["TextDirection"] || "ltr"
98
+ end
99
+
100
+ def rtl?
101
+ text_direction == "rtl"
102
+ end
103
+
104
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
105
+ def user_preferred_languages
106
+ languages = env["HTTP_ACCEPT_LANGUAGE"]
107
+ languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
108
+ locale, quality = language.split(";q=", 2)
109
+ locale = nil if locale == "*" # Ignore wildcards
110
+ quality = quality ? quality.to_f : 1.0
111
+ [locale, quality]
112
+ }.sort { |(_, left), (_, right)|
113
+ right <=> left
114
+ }.map(&:first).compact
115
+ end
116
+
117
+ # Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
118
+ # this method will try to best match the available locales to the user's preferred languages.
119
+ #
120
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
121
+ def locale
122
+ @locale ||= begin
123
+ matched_locale = user_preferred_languages.map { |preferred|
124
+ preferred_language = preferred.split("-", 2).first
125
+
126
+ lang_group = available_locales.select { |available|
127
+ preferred_language == available.split("-", 2).first
128
+ }
129
+
130
+ lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
131
+ }.compact.first
132
+
133
+ matched_locale || "en"
134
+ end
135
+ end
136
+
137
+ def get_locale
138
+ strings(locale)
139
+ end
140
+
141
+ def t(msg, options = {})
142
+ string = get_locale[msg] || strings("en")[msg] || msg
143
+ if options.empty?
144
+ string
145
+ else
146
+ string % options
147
+ end
148
+ end
149
+
150
+ def sort_direction_label
151
+ (params[:direction] == "asc") ? "&uarr;" : "&darr;"
152
+ end
153
+
154
+
155
+ def root_path
156
+ "#{env["SCRIPT_NAME"]}/"
157
+ end
158
+
159
+ def current_path
160
+ @current_path ||= request.path_info.gsub(/^\//, "")
161
+ end
162
+
163
+ # def current_status
164
+ # (workset.size == 0) ? "idle" : "active"
165
+ # end
166
+
167
+ def relative_time(time)
168
+ stamp = time.getutc.iso8601
169
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
170
+ end
171
+
172
+ SAFE_QPARAMS = %w[page direction]
173
+
174
+ # Merge options with current params, filter safe params, and stringify to query string
175
+ def qparams(options)
176
+ stringified_options = options.transform_keys(&:to_s)
177
+
178
+ to_query_string(params.merge(stringified_options))
179
+ end
180
+
181
+ def to_query_string(params)
182
+ params.map { |key, value|
183
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
184
+ }.compact.join("&")
185
+ end
186
+
187
+ def csrf_tag
188
+ "<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
189
+ end
190
+
191
+
192
+ def product_version
193
+ "SolidQueueUi v#{SolidQueueUi::VERSION}"
194
+ end
195
+
196
+ def server_utc_time
197
+ Time.now.utc.strftime("%H:%M:%S UTC")
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ module SolidQueueUi
6
+ module WebRouter
7
+ GET = "GET"
8
+ DELETE = "DELETE"
9
+ POST = "POST"
10
+ PUT = "PUT"
11
+ PATCH = "PATCH"
12
+ HEAD = "HEAD"
13
+
14
+ ROUTE_PARAMS = "rack.route_params"
15
+ REQUEST_METHOD = "REQUEST_METHOD"
16
+ PATH_INFO = "PATH_INFO"
17
+
18
+ def head(path, &block)
19
+ route(HEAD, path, &block)
20
+ end
21
+
22
+ def get(path, &block)
23
+ route(GET, path, &block)
24
+ end
25
+
26
+ def post(path, &block)
27
+ route(POST, path, &block)
28
+ end
29
+
30
+ def put(path, &block)
31
+ route(PUT, path, &block)
32
+ end
33
+
34
+ def patch(path, &block)
35
+ route(PATCH, path, &block)
36
+ end
37
+
38
+ def delete(path, &block)
39
+ route(DELETE, path, &block)
40
+ end
41
+
42
+ def route(method, path, &block)
43
+ @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
44
+
45
+ @routes[method] << WebRoute.new(method, path, block)
46
+ end
47
+
48
+ def match(env)
49
+ request_method = env[REQUEST_METHOD]
50
+ path_info = ::Rack::Utils.unescape env[PATH_INFO]
51
+
52
+ path_info = "/" if path_info == ""
53
+
54
+ @routes[request_method].each do |route|
55
+ params = route.match(request_method, path_info)
56
+ if params
57
+ env[ROUTE_PARAMS] = params
58
+
59
+ return WebAction.new(env, route.block)
60
+ end
61
+ end
62
+
63
+ nil
64
+ end
65
+ end
66
+
67
+ class WebRoute
68
+ attr_accessor :request_method, :pattern, :block, :name
69
+
70
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
71
+
72
+ def initialize(request_method, pattern, block)
73
+ @request_method = request_method
74
+ @pattern = pattern
75
+ @block = block
76
+ end
77
+
78
+ def matcher
79
+ @matcher ||= compile
80
+ end
81
+
82
+ def compile
83
+ if pattern.match?(NAMED_SEGMENTS_PATTERN)
84
+ p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
85
+
86
+ Regexp.new("\\A#{p}\\Z")
87
+ else
88
+ pattern
89
+ end
90
+ end
91
+
92
+ def match(request_method, path)
93
+ case matcher
94
+ when String
95
+ {} if path == matcher
96
+ else
97
+ path_match = path.match(matcher)
98
+ path_match&.named_captures&.transform_keys(&:to_sym)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,145 @@
1
+ require "erb"
2
+
3
+ require "solid_queue_ui"
4
+ require "solid_queue_ui/web/helpers"
5
+
6
+ require "solid_queue_ui/web/router"
7
+ require "solid_queue_ui/web/action"
8
+ require "solid_queue_ui/web/application"
9
+ require "solid_queue_ui/web/csrf_protection"
10
+
11
+ module SolidQueueUi
12
+ class Web
13
+ ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
14
+ VIEWS = "#{ROOT}/views"
15
+ LOCALES = ["#{ROOT}/locales"]
16
+ LAYOUT = "#{VIEWS}/layout.html.erb"
17
+ ASSETS = "#{ROOT}/assets"
18
+
19
+ DEFAULT_TABS = {
20
+ "Dashboard" => "",
21
+ }
22
+
23
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
24
+ CONTENT_LANGUAGE = "Content-Language"
25
+ CONTENT_SECURITY_POLICY = "Content-Security-Policy"
26
+ LOCATION = "Location"
27
+ X_CASCADE = "X-Cascade"
28
+ else
29
+ CONTENT_LANGUAGE = "content-language"
30
+ CONTENT_SECURITY_POLICY = "content-security-policy"
31
+ LOCATION = "location"
32
+ X_CASCADE = "x-cascade"
33
+ end
34
+
35
+ class << self
36
+ def settings
37
+ self
38
+ end
39
+
40
+ def default_tabs
41
+ DEFAULT_TABS
42
+ end
43
+
44
+ def custom_job_info_rows
45
+ @custom_job_info_rows ||= []
46
+ end
47
+
48
+ def locales
49
+ @locales ||= LOCALES
50
+ end
51
+
52
+ def views
53
+ @views ||= VIEWS
54
+ end
55
+
56
+ def middlewares
57
+ @middlewares ||= []
58
+ end
59
+
60
+ def use(*args, &block)
61
+ middlewares << [args, block]
62
+ end
63
+
64
+ def set(attribute, value)
65
+ send(:"#{attribute}=", value)
66
+ end
67
+
68
+ attr_accessor :app_url
69
+ attr_writer :locales, :views
70
+ end
71
+
72
+ def self.inherited(child)
73
+ child.app_url = app_url
74
+ end
75
+
76
+ def settings
77
+ self.class.settings
78
+ end
79
+
80
+ def middlewares
81
+ @middlewares ||= self.class.middlewares
82
+ end
83
+
84
+ def use(*args, &block)
85
+ middlewares << [args, block]
86
+ end
87
+
88
+ def call(env)
89
+ app.call(env)
90
+ end
91
+
92
+ def self.call(env)
93
+ @app ||= new
94
+ @app.call(env)
95
+ end
96
+
97
+ def app
98
+ @app ||= build
99
+ end
100
+
101
+ def enable(*opts)
102
+ opts.each { |key| set(key, true) }
103
+ end
104
+
105
+ def disable(*opts)
106
+ opts.each { |key| set(key, false) }
107
+ end
108
+
109
+ def set(attribute, value)
110
+ send(:"#{attribute}=", value)
111
+ end
112
+
113
+ def self.register(extension)
114
+ extension.registered(WebApplication)
115
+ end
116
+
117
+ private
118
+
119
+ def build
120
+ klass = self.class
121
+ m = middlewares
122
+
123
+ rules = []
124
+ rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SOLID_QUEUE_UI_WEB_TESTING"]
125
+
126
+ ::Rack::Builder.new do
127
+ use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
128
+ root: ASSETS,
129
+ cascade: true,
130
+ header_rules: rules
131
+ m.each { |middleware, block| use(*middleware, &block) }
132
+ use SolidQueueUi::Web::CsrfProtection unless $TESTING
133
+ run WebApplication.new(klass)
134
+ end
135
+ end
136
+ end
137
+
138
+ SolidQueueUi::WebApplication.helpers WebHelpers
139
+
140
+ SolidQueueUi::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
141
+ def _render
142
+ #{ERB.new(File.read(Web::LAYOUT)).src}
143
+ end
144
+ RUBY
145
+ end
@@ -0,0 +1,22 @@
1
+ require "solid_queue_ui/version"
2
+ require "solid_queue_ui/railtie"
3
+
4
+ module SolidQueueUi
5
+ NAME = "SolidQueueUi"
6
+
7
+ def self.load_json(string)
8
+ JSON.parse(string)
9
+ end
10
+
11
+ def self.dump_json(object)
12
+ JSON.generate(object)
13
+ end
14
+
15
+ def self.freeze!
16
+ @frozen = true
17
+ @config_blocks = nil
18
+ end
19
+
20
+ end
21
+
22
+ require "solid_queue_ui/rails" if defined?(::Rails::Engine)
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :solid_queue_ui do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/solid_queue_ui/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "solid_queue_ui"
7
+ spec.version = SolidQueueUi::VERSION
8
+ spec.authors = ["Claude Ayitey"]
9
+ spec.email = ["code@ayitey.me"]
10
+
11
+ spec.summary = "A user interface for the solid_queue gem."
12
+ spec.description = "this plugin provides a user interface to the recently-released solid_queue gem which is a backend for handing jobs in Rails apps."
13
+ spec.homepage = "https://github.com/claudey/solid_queue_ui"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["source_code_uri"] = "https://github.com/claudey/solid_queue_ui"
18
+ spec.metadata["changelog_uri"] = "https://github.com/claudey/solid_queue_ui/changelog.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "solid_queue"
33
+ spec.add_dependency "activerecord", ">= 6.0", "< 8.0"
34
+ spec.add_dependency "sassc-rails", "~> 2.1"
35
+
36
+
37
+ # Uncomment to register a new dependency of your gem
38
+ # spec.add_dependency "example-gem", "~> 1.0"
39
+
40
+ # For more information and examples about making a new gem, check out our
41
+ # guide at: https://bundler.io/guides/creating_gem.html
42
+ end