solid_queue_ui 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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