sidekiq-kicks 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72e0c33ace102f6f4a134209c7fd09762663f7f82c4fa54fe5b57be53447f1c6
4
+ data.tar.gz: b285603a30cc23060265ae1a1499be62730189db88490a7c0395ff3b0e3ba3c5
5
+ SHA512:
6
+ metadata.gz: 00febc0b3841748ed46995e3ef758d35a381ae35be57ed8b06e19f9567b51507d428b2617b1e2c1d7057d5df00b1e2ed9ffa7df08f78f094ad8266fa0404b8a5
7
+ data.tar.gz: e69274c9e3635d3cdab3ac374f3565fa5049f6f67aa9356af3c5fcd1595996b50af4d80a7ee0cf126bcda4bba9b7fd656e0102c935c58d8d36b0196d411476f6
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Andrew Kodkod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Sidekiq Kicks
2
+
3
+ Extra functionality for Sidekiq's web dashboard.
4
+
5
+ ## Features
6
+
7
+ - **Reset Buttons**: Reset processed and failed counters directly from the dashboard. Clears both total counters and historical daily stats.
8
+
9
+ ## Requirements
10
+
11
+ - Ruby >= 3.1
12
+ - Sidekiq >= 8.0
13
+
14
+ ## Installation
15
+
16
+ Add to your Gemfile:
17
+
18
+ ```ruby
19
+ gem "sidekiq-kicks"
20
+ ```
21
+
22
+ Then run:
23
+
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ In your `config/routes.rb`:
31
+
32
+ ```ruby
33
+ require "sidekiq/web"
34
+ require "sidekiq/kicks/web"
35
+
36
+ mount Sidekiq::Web, at: "/sidekiq"
37
+ ```
38
+
39
+ That's it! Reset buttons (↻) will appear on the processed and failed stat boxes on the Sidekiq dashboard.
40
+
41
+ ## How It Works
42
+
43
+ - Clicking a reset button shows a confirmation dialog
44
+ - Upon confirmation, both the total counter and all daily historical stats are cleared
45
+ - The page refreshes to show the updated (zeroed) counters
46
+
47
+ ## Development
48
+
49
+ ```bash
50
+ bin/setup
51
+ bundle exec rspec
52
+ ```
53
+
54
+ ## Contributing
55
+
56
+ Bug reports and pull requests are welcome on GitHub at https://github.com/akodkod/sidekiq-kicks.
57
+
58
+ ## License
59
+
60
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Kicks
5
+ class StatsResetter
6
+ def reset_processed
7
+ reset_stat("processed")
8
+ end
9
+
10
+ def reset_failed
11
+ reset_stat("failed")
12
+ end
13
+
14
+ private
15
+
16
+ def reset_stat(name)
17
+ Sidekiq.redis do |conn|
18
+ conn.del("stat:#{name}")
19
+
20
+ keys = conn.keys("stat:#{name}:*")
21
+ conn.del(*keys) if keys.any?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Kicks
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/web"
4
+ require_relative "../kicks"
5
+
6
+ module Sidekiq
7
+ module Kicks
8
+ module Web
9
+ ASSETS_PATH = File.expand_path("../../../web/assets", __dir__)
10
+
11
+ class ScriptInjector
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ status, headers, body = @app.call(env)
18
+
19
+ if html_response?(headers) && inject_path?(env)
20
+ body = inject_script(body, env)
21
+ headers.delete("Content-Length")
22
+ end
23
+
24
+ [status, headers, body]
25
+ end
26
+
27
+ private
28
+
29
+ def html_response?(headers)
30
+ content_type = headers["Content-Type"] || headers["content-type"]
31
+ content_type&.include?("text/html")
32
+ end
33
+
34
+ def inject_path?(env)
35
+ path = env["PATH_INFO"]
36
+ ["/", ""].include?(path)
37
+ end
38
+
39
+ def inject_script(body, env)
40
+ html = body_to_string(body)
41
+
42
+ script_path = "#{root_path(env)}kicks/kicks.js"
43
+ script_tag = %(<script src="#{script_path}"></script>)
44
+
45
+ html = html.sub("</body>", "#{script_tag}</body>") if html.include?("</body>")
46
+
47
+ [html]
48
+ end
49
+
50
+ def body_to_string(body)
51
+ str = +""
52
+ body.each { |chunk| str << chunk }
53
+ body.close if body.respond_to?(:close)
54
+ str
55
+ end
56
+
57
+ def root_path(env)
58
+ "#{env['SCRIPT_NAME']}/"
59
+ end
60
+ end
61
+
62
+ def self.registered(app)
63
+ app.get("/kicks/kicks.js") do
64
+ js_content = File.read(File.join(ASSETS_PATH, "kicks.js"))
65
+ [200, { "content-type" => "application/javascript" }, [js_content]]
66
+ end
67
+
68
+ app.post("/kicks/reset_processed") do
69
+ StatsResetter.new.reset_processed
70
+ redirect root_path.to_s
71
+ end
72
+
73
+ app.post("/kicks/reset_failed") do
74
+ StatsResetter.new.reset_failed
75
+ redirect root_path.to_s
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ Sidekiq::Web.configure do |config|
83
+ config.use(Sidekiq::Kicks::Web::ScriptInjector)
84
+ config.register(
85
+ Sidekiq::Kicks::Web,
86
+ name: "kicks",
87
+ tab: nil,
88
+ index: nil,
89
+ )
90
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require_relative "kicks/version"
5
+ require_relative "kicks/stats_resetter"
6
+
7
+ module Sidekiq
8
+ module Kicks
9
+ class Error < StandardError; end
10
+ end
11
+ end
@@ -0,0 +1,83 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ function addResetButtons() {
5
+ // Sidekiq 8.0 uses .cards-container with article elements
6
+ var cardsContainer = document.querySelector(".cards-container");
7
+ if (!cardsContainer) return;
8
+
9
+ var cards = cardsContainer.querySelectorAll("article");
10
+
11
+ cards.forEach(function(card) {
12
+ var statType = null;
13
+
14
+ // Check for Sidekiq 8.0 structure (spans with specific IDs)
15
+ if (card.querySelector("#txtProcessed")) {
16
+ statType = "processed";
17
+ } else if (card.querySelector("#txtFailed")) {
18
+ statType = "failed";
19
+ }
20
+
21
+ if (statType) {
22
+ var resetBtn = document.createElement("button");
23
+ resetBtn.className = "kicks-reset-btn";
24
+ resetBtn.innerHTML = "&#x21bb;";
25
+ resetBtn.title = "Reset " + statType + " counter";
26
+ resetBtn.style.cssText = "position:absolute;top:4px;right:4px;background:transparent;border:none;cursor:pointer;font-size:14px;opacity:0.5;padding:2px 6px;border-radius:3px;";
27
+
28
+ resetBtn.addEventListener("mouseenter", function() {
29
+ this.style.opacity = "1";
30
+ this.style.background = "rgba(0,0,0,0.1)";
31
+ });
32
+
33
+ resetBtn.addEventListener("mouseleave", function() {
34
+ this.style.opacity = "0.5";
35
+ this.style.background = "transparent";
36
+ });
37
+
38
+ resetBtn.addEventListener("click", function(e) {
39
+ e.preventDefault();
40
+ e.stopPropagation();
41
+
42
+ if (confirm("Are you sure you want to reset the " + statType + " counter? This will also clear historical data.")) {
43
+ var formData = new FormData();
44
+ formData.append("authenticity_token", csrfToken());
45
+
46
+ fetch(rootPath() + "kicks/reset_" + statType, {
47
+ method: "POST",
48
+ credentials: "same-origin",
49
+ body: formData
50
+ }).then(function() {
51
+ window.location.reload();
52
+ });
53
+ }
54
+ });
55
+
56
+ card.style.position = "relative";
57
+ card.appendChild(resetBtn);
58
+ }
59
+ });
60
+ }
61
+
62
+ function rootPath() {
63
+ var path = window.location.pathname;
64
+ // Match /sidekiq or /something/sidekiq
65
+ var sidekiqMatch = path.match(/(.*\/sidekiq)\/?/);
66
+ if (sidekiqMatch) {
67
+ return sidekiqMatch[1] + "/";
68
+ }
69
+ return "/";
70
+ }
71
+
72
+ function csrfToken() {
73
+ // Sidekiq stores CSRF token in hidden form inputs, not meta tags
74
+ var input = document.querySelector('input[name="authenticity_token"]');
75
+ return input ? input.value : "";
76
+ }
77
+
78
+ if (document.readyState === "loading") {
79
+ document.addEventListener("DOMContentLoaded", addResetButtons);
80
+ } else {
81
+ addResetButtons();
82
+ }
83
+ })();
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-kicks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kodkod
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: sidekiq
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ description: Extends Sidekiq's web UI with useful features like reset buttons for
27
+ processed/failed counters
28
+ email:
29
+ - 678665+akodkod@users.noreply.github.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE.txt
35
+ - README.md
36
+ - lib/sidekiq/kicks.rb
37
+ - lib/sidekiq/kicks/stats_resetter.rb
38
+ - lib/sidekiq/kicks/version.rb
39
+ - lib/sidekiq/kicks/web.rb
40
+ - web/assets/kicks.js
41
+ homepage: https://github.com/akodkod/sidekiq-kicks
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ homepage_uri: https://github.com/akodkod/sidekiq-kicks
46
+ source_code_uri: https://github.com/akodkod/sidekiq-kicks
47
+ changelog_uri: https://github.com/akodkod/sidekiq-kicks/blob/main/CHANGELOG.md
48
+ rubygems_mfa_required: 'true'
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.1.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.6.9
64
+ specification_version: 4
65
+ summary: Extra functionality for Sidekiq's web dashboard
66
+ test_files: []