Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 1.0.0 (2025-11-11)

- New: Fuzzy find search across jobs
- New: "Run now" — run any job immediately from the index page
- New: View the Ruby implementation of each job
- New: Highlight overdue jobs at a glance
- New: Hourly health check callback via `ClockworkWebPlus.on_health_check`, with detailed overdue context
- New: Redesigned jobs table with a sleeker, modern UI

Note: Versions prior to 1.0.0 (0.x.y) correspond to the original `clockwork_web` project by ankane. See `https://github.com/ankane/clockwork_web`.

## 0.3.1 (2024-09-04)

- Improved CSP support
Expand Down
116 changes: 95 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,169 @@
# Clockwork Web
# Clockwork Web Plus

A web interface for [Clockwork](https://github.com/Rykian/clockwork)
A fully compatible drop-in enhancement to [ankane/clockwork_web](https://github.com/ankane/clockwork_web), providing a modern web interface for [Clockwork](https://github.com/Rykian/clockwork) with fuzzy search, manual job run, overdue visibility, and hourly health checks.

[View the demo](https://clockwork.dokkuapp.com/)
[![Build Status](https://github.com/chaadow/clockwork_web_plus/actions/workflows/build.yml/badge.svg)](https://github.com/chaadow/clockwork_web_plus/actions/workflows/build.yml)

## Preview

<video src="https://github.com/user-attachments/assets/d145a4d5-834d-4c0d-9f11-397272b2d013" controls muted playsinline loop>
Sorry, your browser doesn't support embedded videos. Here’s a <a href="https://github.com/user-attachments/assets/d145a4d5-834d-4c0d-9f11-397272b2d013">direct link</a>.
</video>

## Features

### Core (from `clockwork_web`)

- see list of jobs
- monitor jobs
- disable jobs
- monitor jobs ( when they were last run at)
- Temporarily disable jobs

:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
### New compared to clockwork_web

[![Build Status](https://github.com/ankane/clockwork_web/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/clockwork_web/actions)
- fuzzy find search across jobs
- run any job immediately through the `Run now` button
- view the Ruby implementation of each job
- highlight overdue jobs at a glance
- optional hourly health check callback with custom alerting

## Installation

Add this line to your application’s Gemfile:

```ruby
gem "clockwork_web"
gem "clockwork_web_plus"
```

> [!TIP]
> Already using `clockwork_web`? Keep your existing `ClockworkWeb::Engine` mount and initializers—no renaming needed. `ClockworkWebPlus` aliases `ClockworkWeb`, so it works out of the box.

And add it to your `config/routes.rb`.

```ruby
mount ClockworkWeb::Engine, at: "clockwork"
mount ClockworkWebPlus::Engine, at: "clockwork"
```

Be sure to secure the dashboard in production.
> [!IMPORTANT]
> Secure the dashboard in production. Protect access with Basic Auth, Devise, or your app’s auth layer to avoid exposing job controls and status.

To monitor and disable jobs, hook up Redis in an initializer.

```ruby
ClockworkWeb.redis = Redis.new
ClockworkWebPlus.redis = Redis.new
```

#### Basic Authentication

Set the following variables in your environment or an initializer.

```ruby
ENV["CLOCKWORK_USERNAME"] = "andrew"
ENV["CLOCKWORK_USERNAME"] = "chaadow"
ENV["CLOCKWORK_PASSWORD"] = "secret"
```

> [!NOTE]
> These are example credentials. Use environment-specific secrets and rotate them regularly.

#### Devise

```ruby
authenticate :user, ->(user) { user.admin? } do
mount ClockworkWeb::Engine, at: "clockwork"
mount ClockworkWebPlus::Engine, at: "clockwork"
end
```

> [!TIP]
> Any authentication framework works—wrap the mount with whatever guard your app already uses for admin/ops access.

## Monitoring

```ruby
ClockworkWeb.running?
ClockworkWeb.multiple?
ClockworkWebPlus.running?
ClockworkWebPlus.multiple?
```

> [!NOTE]
> `running?` reflects recent heartbeats. `multiple?` indicates multiple active Clockwork processes (based on heartbeat contention).

## Customize

Change clock path

```ruby
ClockworkWeb.clock_path = Rails.root.join("clock") # default
ClockworkWebPlus.clock_path = Rails.root.join("clock") # default
```

> [!NOTE]
> The default `clock_path` matches `clockwork_web`. Change it only if your clock file lives elsewhere.

Turn off monitoring

```ruby
ClockworkWeb.monitor = false
ClockworkWebPlus.monitor = false
```

> [!CAUTION]
> Disabling monitoring stops heartbeats and multiple-process detection. The dashboard won’t show “running” status, but other features still work.

### Overdue Jobs & Health Checks

The dashboard highlights overdue jobs based on schedule and last run. You can also configure an hourly health check to alert when jobs are overdue:

```ruby
ClockworkWebPlus.on_health_check = ->(overdue_jobs:) do
# backlog contains array of hashes with details like:
# { job:, should_have_run_at:, last_run:, period:, at: { hour:, min: } }
if overdue_jobs.any?
# send notification to Slack, email, etc.
end
end
```

> [!NOTE]
> Overdue detection uses `ClockworkWebPlus.warning_threshold` (default: 300 seconds).
> - For `@at` schedules: a job is overdue when the most recent scheduled time has passed by more than `warning_threshold` and the job hasn’t run since that time.
> - For periodic jobs (no `@at`): a job is overdue when `now > last_run + period + warning_threshold`.
>
> Example:
> ```ruby
> # consider jobs overdue 10 minutes after their expected time
> ClockworkWebPlus.warning_threshold = 600
> ```

> [!IMPORTANT]
> With Redis configured, the health check runs at most once per hour across processes. Without Redis, throttling is per-process and approximate.

## History

View the [changelog](CHANGELOG.md)

## Compatibility

This gem is a drop-in replacement for `clockwork_web`. For backward compatibility, the original namespace is aliased:

```ruby
# Both of these work:
mount ClockworkWebPlus::Engine, at: "clockwork"
mount ClockworkWeb::Engine, at: "clockwork"
```

> [!TIP]
> Adopting this gem can be as simple as swapping the gem name in your Gemfile. Your existing `ClockworkWeb` mounts and initializers continue to work unchanged.

## Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

- [Report bugs](https://github.com/ankane/clockwork_web/issues)
- Fix bugs and [submit pull requests](https://github.com/ankane/clockwork_web/pulls)
- [Report bugs](https://github.com/chaadow/clockwork_web_plus/issues)
- Fix bugs and [submit pull requests](https://github.com/chaadow/clockwork_web_plus/pulls)
- Write, clarify, or fix documentation
- Suggest or add new features

To get started with development:

```sh
git clone https://github.com/ankane/clockwork_web.git
cd clockwork_web
git clone https://github.com/chaadow/clockwork_web_plus.git
cd clockwork_web_plus
bundle install
bundle exec rake test
```
14 changes: 10 additions & 4 deletions app/views/clockwork_web/home/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@
background: #fffbeb;
}

.badge.danger {
color: var(--danger-600);
border-color: rgba(220,38,38,0.18);
background: var(--danger-50);
}

.badge.info {
color: var(--muted);
background: #f8fafc;
Expand Down Expand Up @@ -337,11 +343,11 @@
<tr class="<%= [enabled ? nil : 'row-disabled', is_overdue ? 'row-warning' : nil].compact.join(' ') %>">
<td data-col="job">
<strong><%= event.job %></strong>
<% if is_overdue %>
&nbsp;<span title="Overdue">⚠️</span>
<% end %>
<% unless enabled %>
<div class="meta">Disabled</div>
&nbsp;<span class="badge danger" title="Job is disabled">Disabled</span>
<% end %>
<% if is_overdue %>
&nbsp;<span class="badge warn" title="Job is overdue">Overdue</span>
<% end %>
</td>
<td data-col="schedule">
Expand Down
14 changes: 7 additions & 7 deletions clockwork_web.gemspec
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
require_relative "lib/clockwork_web/version"
require_relative "lib/clockwork_web_plus/version"

Gem::Specification.new do |spec|
spec.name = "clockwork_web"
spec.version = ClockworkWeb::VERSION
spec.summary = "A web interface for Clockwork"
spec.homepage = "https://github.com/ankane/clockwork_web"
spec.name = "clockwork_web_plus"
spec.version = ClockworkWebPlus::VERSION
spec.summary = "A modern web interface for Clockwork with search, run-now & health checks"
spec.homepage = "https://github.com/chedli/clockwork_web_plus"
spec.license = "MIT"

spec.author = "Andrew Kane"
spec.email = "[email protected]"
spec.authors = ["Andrew Kane", "Chedli Bourguiba"]
spec.email = ["[email protected]"]

spec.files = Dir["*.{md,txt}", "{app,config,lib}/**/*"]
spec.require_path = "lib"
Expand Down
4 changes: 2 additions & 2 deletions lib/clockwork_web.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ def self.health_check

events = Clockwork.manager.events
last_runs = ClockworkWeb.last_runs
overdue = ClockworkWeb.overdue_details(events, last_runs)
ClockworkWeb.on_health_check.call(overdue: overdue) if overdue.any?
overdue_jobs = ClockworkWeb.overdue_details(events, last_runs)
ClockworkWeb.on_health_check.call(overdue_jobs: overdue_jobs) if overdue_jobs.any?
end

# Returns the last time this event should have run before now.
Expand Down
2 changes: 1 addition & 1 deletion lib/clockwork_web/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ClockworkWeb
VERSION = "0.3.1"
VERSION = "1.0.0"
end
6 changes: 6 additions & 0 deletions lib/clockwork_web_plus.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "clockwork_web"

# New namespace that aliases the original, for easy drop-in replacement.
ClockworkWebPlus = ClockworkWeb unless defined?(ClockworkWebPlus)


5 changes: 5 additions & 0 deletions lib/clockwork_web_plus/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module ClockworkWebPlus
VERSION = "1.0.0"
end


Loading