roundhouse_ui 0.9.0 → 0.9.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.
- checksums.yaml +4 -4
- data/README.md +63 -19
- data/lib/roundhouse_ui/backends/sidekiq.rb +10 -3
- data/lib/roundhouse_ui/cancel_middleware.rb +7 -1
- data/lib/roundhouse_ui/cancellation.rb +43 -0
- data/lib/roundhouse_ui/duration_collector.rb +11 -4
- data/lib/roundhouse_ui/pause.rb +38 -3
- data/lib/roundhouse_ui/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ee71201b4424c59d9450b7eea1fec5b7447e9e40ee419a4ebc03c39bf8189d6
|
|
4
|
+
data.tar.gz: 8a33d51ba920fd11b64ae9a28160ce40e87ff0b8c315a04136b64c7aa8427bb7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7494f64a2e7cea227f13ac6037e390775f068726f81e3962ecf0bf62a87d6dffaa7c613bef9c0d2b47bf4bdc5d410e6a2073e35c3a67723485af99fd30b43691
|
|
7
|
+
data.tar.gz: 2ff606ac7ca4797069b7cbb875313fd7988a962845f4c738765a8816a48280f5c889167a31d43452342389c3d122d798b15b782ac2219ba0cfb99d4125431bc0
|
data/README.md
CHANGED
|
@@ -107,32 +107,52 @@ RoundhouseUi.configure do |c|
|
|
|
107
107
|
# No-op unless the sidekiq-failures gem is loaded. Default: off.
|
|
108
108
|
c.show_sidekiq_failures = true
|
|
109
109
|
|
|
110
|
-
# Set false to hide queue pause/resume controls
|
|
111
|
-
#
|
|
112
|
-
# Default: true.
|
|
110
|
+
# Set false to hide queue pause/resume controls entirely. Rarely needed — on
|
|
111
|
+
# Sidekiq Pro and Solid Queue pause is enforced natively, and on OSS Sidekiq
|
|
112
|
+
# installing RoundhouseUi::Fetch enforces it. Default: true.
|
|
113
113
|
# c.pause_enabled = false
|
|
114
114
|
|
|
115
115
|
# Seconds between dashboard stat polls (default 5). Raise it if polling shows
|
|
116
116
|
# up in your traces — each poll re-runs the host's auth/routing on the mount.
|
|
117
117
|
# c.poll_interval = 10
|
|
118
118
|
|
|
119
|
-
# Show the "slowest job classes" table on the Metrics page.
|
|
120
|
-
# DurationCollector middleware (see below).
|
|
119
|
+
# Show the "slowest job classes" table on the Metrics page. The flag alone shows
|
|
120
|
+
# nothing — it also needs the DurationCollector middleware (see below).
|
|
121
|
+
# Default: false.
|
|
121
122
|
# c.collect_durations = true
|
|
122
123
|
end
|
|
123
124
|
```
|
|
124
125
|
|
|
125
|
-
Every option is independent and has a safe default — set only what you need
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
Every option is independent and has a safe default — **set only what you need**. Nothing
|
|
127
|
+
here is required to mount Roundhouse.
|
|
128
|
+
|
|
129
|
+
### When to turn each one on
|
|
130
|
+
|
|
131
|
+
| Option | Default | Turn it on when | Leave it alone when |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `read_only` | `false` | **Production, almost always.** Blocks purge/retry/delete/edit *server-side*, not just in the UI — so it holds even if someone hand-crafts a request. The usual shape is `!Rails.env.development?`. | You need operators to actually fix things from the UI, and you trust everyone behind the mount. |
|
|
134
|
+
| `redact_args` | `[]` | **Any app whose job args carry secrets or PII** — args render in full on the job page. Matches keys case-insensitively as substrings, and walks nested hashes/arrays. | Args are all IDs and enum values. |
|
|
135
|
+
| `actor_resolver` | `"anonymous"` | You want the audit log to name *who* did something. One line: `->(c) { c.current_user&.email }`. | Single-operator app, or you already audit at another layer. |
|
|
136
|
+
| `allow_job_editing` | `false` | Development and debugging. **Sharp tool** — a bad edit creates an unrunnable job, and it lets the UI enqueue arbitrary classes. | Production, unless you specifically want that power and have `read_only` off anyway. |
|
|
137
|
+
| `observability` | no-op | You run an APM and want per-job deep links out to it. Ships a Datadog adapter; duck-type `job_url`/`queue_url`/`error_url` for anything else. | No APM, or you'd rather not add links that only some people can open. |
|
|
138
|
+
| `snapshot_store` | Redis | Your snapshots are large or need to outlive Redis (S3/disk). Duck-type `write`/`read`/`delete`/`ids`. | Redis is fine — which it usually is for occasional queue snapshots. |
|
|
139
|
+
| `show_sidekiq_failures` | `false` | You use the `sidekiq-failures` gem **and** run jobs with `retry: false` — those never enter Sidekiq's retry/dead sets, so this is the only way to see them. | You don't have the gem (it's a no-op then anyway). |
|
|
140
|
+
| `poll_interval` | `5` | **Raise it** if dashboard polling shows up in your traces — every poll re-runs your app's auth and routing on the mount, so a busy console adds real load. Lower it only for a livelier demo. | Default is fine for most apps. |
|
|
141
|
+
| `collect_durations` | `false` | You want "slowest job classes" on Metrics, which Sidekiq doesn't track. **Also requires installing the `DurationCollector` middleware** — the flag alone shows nothing. Costs one pipelined Redis round-trip per job. | You already get per-job timing from your APM. |
|
|
142
|
+
| `pause_enabled` | `true` | Leave it on. | **Rarely set this to `false`.** Pause is enforced natively on Sidekiq Pro and Solid Queue, and on OSS Sidekiq by installing `RoundhouseUi::Fetch` — so turning it off usually just hides a working feature. Only useful if you want the controls gone entirely. |
|
|
143
|
+
|
|
144
|
+
Two that pair with a middleware rather than working alone: `collect_durations`
|
|
145
|
+
(`DurationCollector`) and job cancellation (`CancelMiddleware`) — see
|
|
146
|
+
[Cancelling jobs](#cancelling-jobs) and [Slowest job classes](#slowest-job-classes).
|
|
128
147
|
|
|
129
148
|
## Pausing queues
|
|
130
149
|
|
|
131
|
-
>
|
|
132
|
-
>
|
|
150
|
+
> Pause is **native** — enforced with nothing to install and no warning — on both
|
|
151
|
+
> **Solid Queue** and **Sidekiq Pro/Enterprise** (see below). The fetch strategy
|
|
152
|
+
> below is only needed on **OSS Sidekiq**.
|
|
133
153
|
|
|
134
|
-
On Sidekiq, pause/resume is pure OSS. To make a pause actually stop a queue from
|
|
135
|
-
worked, install Roundhouse's fetch strategy in your Sidekiq **server** config:
|
|
154
|
+
On OSS Sidekiq, pause/resume is pure OSS. To make a pause actually stop a queue from
|
|
155
|
+
being worked, install Roundhouse's fetch strategy in your Sidekiq **server** config:
|
|
136
156
|
|
|
137
157
|
```ruby
|
|
138
158
|
# config/initializers/sidekiq.rb
|
|
@@ -146,11 +166,26 @@ all of Sidekiq's weighting/ordering. Until it's installed, the Queues page recor
|
|
|
146
166
|
but **warns that they aren't enforced** (worker and web are separate processes, so
|
|
147
167
|
Roundhouse detects whether a fetcher has reported in).
|
|
148
168
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
### Sidekiq Pro / Enterprise — nothing to install
|
|
170
|
+
|
|
171
|
+
Pro ships its own enforced pause, and Roundhouse uses it automatically. Pro reopens
|
|
172
|
+
`Sidekiq::Queue` with `pause!`/`unpause!` and *prepends* pause support onto
|
|
173
|
+
`Sidekiq::BasicFetch` (`super_fetch` honors it too), so **any Pro worker enforces
|
|
174
|
+
pauses** whether or not a fetch strategy is configured.
|
|
175
|
+
|
|
176
|
+
When Roundhouse detects Pro it delegates pause/resume to `Sidekiq::Queue#pause!`,
|
|
177
|
+
reads paused state from Pro's registry, advertises `native_pause`, and drops the
|
|
178
|
+
"not enforced" warning. So on Pro:
|
|
179
|
+
|
|
180
|
+
- **Don't** install `RoundhouseUi::Fetch` — it isn't needed, and on `super_fetch`
|
|
181
|
+
installs it would displace reliable fetch and lose its crash-recovery guarantees.
|
|
182
|
+
- **Don't** set `pause_enabled = false` — pause genuinely works; disabling it only
|
|
183
|
+
hides a feature you already have.
|
|
184
|
+
|
|
185
|
+
Roundhouse always goes through `Sidekiq::Queue#pause!` rather than writing Pro's
|
|
186
|
+
Redis key directly: Pro's fetchers read that set once at startup and afterwards
|
|
187
|
+
only update on the `pro:config` pubsub message `pause!` publishes, so a raw write
|
|
188
|
+
would leave running workers pulling the queue until they restarted.
|
|
154
189
|
|
|
155
190
|
## Surfacing sidekiq-failures
|
|
156
191
|
|
|
@@ -184,6 +219,15 @@ The **Busy** page's Cancel button flags a job's JID. A queued/scheduled/retrying
|
|
|
184
219
|
is then skipped when it would next run; a *currently running* job stops only if it
|
|
185
220
|
checks in — e.g. a long loop can `break if RoundhouseUi.cancelled?(jid)`.
|
|
186
221
|
|
|
222
|
+
**Timing and cost.** The middleware is close to free when nothing is cancelled: rather
|
|
223
|
+
than checking each job's JID against Redis, it asks "is *anything* cancelled?" from a
|
|
224
|
+
process-local gate refreshed at most every 2s, and only does the exact per-job lookup
|
|
225
|
+
while cancellations are pending. The tradeoff is that a cancellation takes **up to ~2s
|
|
226
|
+
to reach a worker process** — the UI and your workers are separate processes, so expect
|
|
227
|
+
a brief lag after clicking Cancel. Jobs already in flight are unaffected either way
|
|
228
|
+
(cancellation is cooperative), and `RoundhouseUi.cancelled?(jid)` — what a long-running
|
|
229
|
+
job polls — is never gated, so it always reads current state.
|
|
230
|
+
|
|
187
231
|
## Slowest job classes
|
|
188
232
|
|
|
189
233
|
Sidekiq doesn't track per-class durations, so Roundhouse can record them itself.
|
|
@@ -197,8 +241,8 @@ Sidekiq.configure_server do |config|
|
|
|
197
241
|
end
|
|
198
242
|
```
|
|
199
243
|
|
|
200
|
-
It's two cheap Redis writes per job (a counter + a summed-ms float) into a single
|
|
201
|
-
|
|
244
|
+
It's two cheap Redis writes per job (a counter + a summed-ms float) into a single hash,
|
|
245
|
+
pipelined into **one round-trip**, and a job failure never propagates from the collector.
|
|
202
246
|
|
|
203
247
|
## Bulk actions on a filter
|
|
204
248
|
|
|
@@ -14,10 +14,17 @@ module RoundhouseUi
|
|
|
14
14
|
def name = "Sidekiq"
|
|
15
15
|
|
|
16
16
|
# Capabilities let the UI hide what a backend can't do. Sidekiq supports
|
|
17
|
-
# all the sets/views
|
|
18
|
-
#
|
|
17
|
+
# all the sets/views. On OSS Sidekiq pause is NOT native (it needs our
|
|
18
|
+
# fetcher), so :native_pause is withheld and the "not enforced" warning
|
|
19
|
+
# applies — but Sidekiq Pro ships its own enforced pause, so there it is
|
|
20
|
+
# advertised and the warning drops away.
|
|
19
21
|
CAPABILITIES = %i[retries dead scheduled busy workers redis capsules].freeze
|
|
20
|
-
|
|
22
|
+
|
|
23
|
+
def supports?(capability)
|
|
24
|
+
return RoundhouseUi::Pause.native? if capability == :native_pause
|
|
25
|
+
|
|
26
|
+
CAPABILITIES.include?(capability)
|
|
27
|
+
end
|
|
21
28
|
|
|
22
29
|
def stats = ::Sidekiq::Stats.new
|
|
23
30
|
def queues = ::Sidekiq::Queue.all
|
|
@@ -7,9 +7,15 @@ module RoundhouseUi
|
|
|
7
7
|
# Sidekiq.configure_server do |config|
|
|
8
8
|
# config.server_middleware { |chain| chain.add RoundhouseUi::CancelMiddleware }
|
|
9
9
|
# end
|
|
10
|
+
#
|
|
11
|
+
# Hot-path cost: near zero. Cancellation.pending? answers "anything cancelled
|
|
12
|
+
# at all?" from a process-local gate (one EXISTS per CHECK_EVERY seconds, not
|
|
13
|
+
# per job); the exact per-job SISMEMBER only runs while cancellations are
|
|
14
|
+
# actually pending. State lives on the Cancellation module because Sidekiq
|
|
15
|
+
# builds a fresh middleware instance per job — ivars here wouldn't survive.
|
|
10
16
|
class CancelMiddleware
|
|
11
17
|
def call(_worker, job, _queue)
|
|
12
|
-
if RoundhouseUi::Cancellation.cancelled?(job["jid"])
|
|
18
|
+
if RoundhouseUi::Cancellation.pending? && RoundhouseUi::Cancellation.cancelled?(job["jid"])
|
|
13
19
|
RoundhouseUi::Cancellation.clear!(job["jid"])
|
|
14
20
|
return # acknowledge without running
|
|
15
21
|
end
|
|
@@ -12,6 +12,9 @@ module RoundhouseUi
|
|
|
12
12
|
module Cancellation
|
|
13
13
|
KEY = "roundhouse:cancelled"
|
|
14
14
|
TTL = 86_400 # seconds
|
|
15
|
+
CHECK_EVERY = 2.0 # seconds — max staleness of the "nothing is cancelled" gate
|
|
16
|
+
|
|
17
|
+
@gate = Mutex.new # guards @pending / @checked_at (see .pending?)
|
|
15
18
|
|
|
16
19
|
module_function
|
|
17
20
|
|
|
@@ -20,6 +23,12 @@ module RoundhouseUi
|
|
|
20
23
|
conn.call("SADD", KEY, jid.to_s)
|
|
21
24
|
conn.call("EXPIRE", KEY, TTL)
|
|
22
25
|
end
|
|
26
|
+
# Bust the local gate: the cancelling process sees its own cancel
|
|
27
|
+
# immediately; other processes converge within CHECK_EVERY.
|
|
28
|
+
@gate.synchronize do
|
|
29
|
+
@pending = true
|
|
30
|
+
@checked_at = monotonic_now
|
|
31
|
+
end
|
|
23
32
|
end
|
|
24
33
|
|
|
25
34
|
def cancelled?(jid)
|
|
@@ -33,5 +42,39 @@ module RoundhouseUi
|
|
|
33
42
|
def cancelled_jids
|
|
34
43
|
Sidekiq.redis { |conn| conn.call("SMEMBERS", KEY) }
|
|
35
44
|
end
|
|
45
|
+
|
|
46
|
+
# The middleware's hot-path gate: "is anything cancelled at all?" — almost
|
|
47
|
+
# always no, so answering it with one EXISTS per process per CHECK_EVERY
|
|
48
|
+
# (instead of a SISMEMBER per job) takes the common case to zero Redis
|
|
49
|
+
# round-trips. While cancellations are pending, the middleware still does
|
|
50
|
+
# the exact per-job SISMEMBER. A cancel issued by *another* process is
|
|
51
|
+
# invisible here for up to CHECK_EVERY — acceptable because cancellation
|
|
52
|
+
# is cooperative and racy by design (a job may already be running when the
|
|
53
|
+
# flag lands), and cancel! busts the local gate so the cancelling process
|
|
54
|
+
# itself is always exact.
|
|
55
|
+
def pending?
|
|
56
|
+
now = monotonic_now
|
|
57
|
+
@gate.synchronize do
|
|
58
|
+
if @checked_at.nil? || now - @checked_at >= CHECK_EVERY
|
|
59
|
+
@pending = any?
|
|
60
|
+
@checked_at = now
|
|
61
|
+
end
|
|
62
|
+
@pending
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def any?
|
|
67
|
+
Sidekiq.redis { |conn| conn.call("EXISTS", KEY) } == 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Forget the cached gate state (tests; or after flushing Redis by hand).
|
|
71
|
+
def reset_gate!
|
|
72
|
+
@gate.synchronize do
|
|
73
|
+
@pending = nil
|
|
74
|
+
@checked_at = nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def monotonic_now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
36
79
|
end
|
|
37
80
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module RoundhouseUi
|
|
2
2
|
# Opt-in server middleware that records per-class execution time, so the UI can
|
|
3
3
|
# answer "which job classes are slow?" — something Sidekiq doesn't track. Two
|
|
4
|
-
# cheap Redis writes per job (a counter + a summed-ms float)
|
|
5
|
-
# enable in your Sidekiq server config:
|
|
4
|
+
# cheap Redis writes per job (a counter + a summed-ms float), pipelined into a
|
|
5
|
+
# single round-trip. Off by default; enable in your Sidekiq server config:
|
|
6
6
|
#
|
|
7
7
|
# Sidekiq.configure_server do |config|
|
|
8
8
|
# config.server_middleware { |chain| chain.add RoundhouseUi::DurationCollector }
|
|
@@ -22,9 +22,16 @@ module RoundhouseUi
|
|
|
22
22
|
def record(klass, elapsed_ms)
|
|
23
23
|
return unless klass
|
|
24
24
|
|
|
25
|
+
commands = [
|
|
26
|
+
[ "HINCRBY", KEY, "#{klass}\x00count", 1 ],
|
|
27
|
+
[ "HINCRBYFLOAT", KEY, "#{klass}\x00ms", elapsed_ms ]
|
|
28
|
+
]
|
|
25
29
|
Sidekiq.redis do |conn|
|
|
26
|
-
conn.
|
|
27
|
-
|
|
30
|
+
if conn.respond_to?(:pipelined) # redis-client and redis-rb 4.5+: one round-trip
|
|
31
|
+
conn.pipelined { |pipe| commands.each { |c| pipe.call(*c) } }
|
|
32
|
+
else
|
|
33
|
+
commands.each { |c| conn.call(*c) }
|
|
34
|
+
end
|
|
28
35
|
end
|
|
29
36
|
rescue => e
|
|
30
37
|
# Metrics collection must never break a job.
|
data/lib/roundhouse_ui/pause.rb
CHANGED
|
@@ -7,30 +7,59 @@ module RoundhouseUi
|
|
|
7
7
|
# Paused queue names live in a Redis set. RoundhouseUi::Fetch consults this set
|
|
8
8
|
# and skips paused queues when pulling work, so a paused queue stops being
|
|
9
9
|
# consumed without stopping the worker process.
|
|
10
|
+
#
|
|
11
|
+
# When Sidekiq Pro is loaded we defer to *its* registry instead (see .native?).
|
|
12
|
+
# Pro reopens Sidekiq::Queue with pause!/unpause!/paused? and prepends pause
|
|
13
|
+
# support onto Sidekiq::BasicFetch, so pausing is already enforced with no
|
|
14
|
+
# Roundhouse fetcher installed — and Pro's key ("paused") is not ours
|
|
15
|
+
# ("roundhouse:paused"), so writing our own set there would do nothing.
|
|
10
16
|
module Pause
|
|
11
17
|
KEY = "roundhouse:paused"
|
|
12
18
|
FETCH_FLAG = "roundhouse:fetch_alive" # liveness beacon set by the fetcher
|
|
19
|
+
PRO_KEY = "paused" # Sidekiq Pro's own registry
|
|
13
20
|
|
|
14
21
|
module_function
|
|
15
22
|
|
|
23
|
+
# True when Sidekiq Pro's queue-pause API is available. Feature-detected on
|
|
24
|
+
# the method rather than `defined?(Sidekiq::Pro)` so it tracks the actual
|
|
25
|
+
# capability across Pro versions. Cheap (no Redis), so it isn't memoized —
|
|
26
|
+
# loading Pro mid-process would otherwise be missed.
|
|
27
|
+
def native?
|
|
28
|
+
defined?(::Sidekiq::Queue) && ::Sidekiq::Queue.method_defined?(:pause!)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Under Pro, go through Sidekiq::Queue#pause! rather than writing PRO_KEY
|
|
32
|
+
# ourselves: Pro's fetchers read that set once at startup and afterwards only
|
|
33
|
+
# update on the "pro:config" pubsub message that pause! publishes. A bare
|
|
34
|
+
# SADD would leave running workers pulling the queue until they restarted.
|
|
16
35
|
def pause!(queue)
|
|
36
|
+
return ::Sidekiq::Queue.new(queue.to_s).pause! if native?
|
|
37
|
+
|
|
17
38
|
Sidekiq.redis { |conn| conn.call("SADD", KEY, queue.to_s) }
|
|
18
39
|
end
|
|
19
40
|
|
|
20
41
|
def unpause!(queue)
|
|
42
|
+
return ::Sidekiq::Queue.new(queue.to_s).unpause! if native?
|
|
43
|
+
|
|
21
44
|
Sidekiq.redis { |conn| conn.call("SREM", KEY, queue.to_s) }
|
|
22
45
|
end
|
|
23
46
|
|
|
24
47
|
def paused?(queue)
|
|
25
|
-
Sidekiq.redis { |conn| conn.call("SISMEMBER",
|
|
48
|
+
Sidekiq.redis { |conn| conn.call("SISMEMBER", key, queue.to_s) } == 1
|
|
26
49
|
end
|
|
27
50
|
|
|
28
51
|
def paused_queues
|
|
29
|
-
Sidekiq.redis { |conn| conn.call("SMEMBERS",
|
|
52
|
+
Sidekiq.redis { |conn| conn.call("SMEMBERS", key) }.sort
|
|
30
53
|
end
|
|
31
54
|
|
|
32
55
|
def paused_set
|
|
33
|
-
Set.new(Sidekiq.redis { |conn| conn.call("SMEMBERS",
|
|
56
|
+
Set.new(Sidekiq.redis { |conn| conn.call("SMEMBERS", key) })
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Which registry reads come from. Reads are plain set lookups in both cases
|
|
60
|
+
# (no pubsub involved), so they can share one implementation.
|
|
61
|
+
def key
|
|
62
|
+
native? ? PRO_KEY : KEY
|
|
34
63
|
end
|
|
35
64
|
|
|
36
65
|
# Given the redis queue keys BasicFetch would poll (e.g. "queue:default"),
|
|
@@ -52,7 +81,13 @@ module RoundhouseUi
|
|
|
52
81
|
|
|
53
82
|
# True when a RoundhouseUi::Fetch has reported in recently — i.e. pausing
|
|
54
83
|
# will take effect. When false, the UI warns instead of pretending.
|
|
84
|
+
#
|
|
85
|
+
# Under Pro no beacon is needed: Pro prepends pause support onto
|
|
86
|
+
# Sidekiq::BasicFetch (and SuperFetch honors it too), so any Pro worker
|
|
87
|
+
# enforces pauses whether or not our fetcher is installed.
|
|
55
88
|
def fetch_installed?
|
|
89
|
+
return true if native?
|
|
90
|
+
|
|
56
91
|
Sidekiq.redis { |conn| conn.call("EXISTS", FETCH_FLAG) } == 1
|
|
57
92
|
end
|
|
58
93
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: roundhouse_ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- R.J. Robinson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|