side_bro 0.2.2 → 0.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2882e3f5f9428e6068b4a7a96b5ff253454dd60692a11eb53e1054399df0a6f4
4
- data.tar.gz: 840234c4510a1af907e837e36ab1308419438194b5a8e205a9eb194e5292644e
3
+ metadata.gz: 0c5694dd3d4c5847cffd789314b1f343d15246c0c80758173153026fad0544e8
4
+ data.tar.gz: c80886ec41afd7f842e78982b4ade79d55d9657fa7061056f99c3e9186000509
5
5
  SHA512:
6
- metadata.gz: 74635e64db2372ac7013e346a50361b126e10082da20a1c199c9f476f58f84bde3c2de46e6a7ecfe8ef088eaee94bba198b735f138add225e66fbe8f56fe7d0b
7
- data.tar.gz: f2bc7ca9cde7caed603b8ffa73abd624ca8e51c2b189145b2c8d063ee5000c8409ee31b56726d296c878a1df077d53b7521caff7d47158ab2587edda11163ea4
6
+ metadata.gz: 06f6f13e9152e3e7992a353d22a9d518f81562f627ccd7336a9718bba5039333a9217aa3ab31b0ec1b86c09776a80b83a3eb2f9392305cffcad304934b957fa1
7
+ data.tar.gz: a4ba97aa3c4fac898b94852dbd81400f8c194efd0e2c07762c926c26c0dc52f0c4c7624f8cce64f501e953701220a7aaed430cbbe5967e6c19b6cabcf644eee5
data/README.md CHANGED
@@ -48,3 +48,7 @@ end
48
48
  ## Session Secret
49
49
 
50
50
  Set `SIDE_BRO_SESSION_SECRET` env var for a stable session secret across restarts.
51
+
52
+ ---
53
+
54
+ **Maintained by [Ruby Dev](https://www.rubydev.ro)** — works great with **[Dead Bro](https://www.deadbro.com)**, APM for Rails apps.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SideBro
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.3"
5
5
  end
@@ -126,11 +126,15 @@ module SideBro
126
126
  @filter_job = params["filter_job"].to_s.strip
127
127
  @filter_args = params["filter_args"].to_s.strip
128
128
  @filtering = !@filter_job.empty? || !@filter_args.empty?
129
+ @search = params["q"].to_s.strip
130
+ @searching = !@search.empty?
131
+ needle = @search.downcase
129
132
 
130
- if @filtering
133
+ if @filtering || @searching
131
134
  @jobs = @queue.lazy.select { |j|
132
135
  (@filter_job.empty? || job_display_class(j).include?(@filter_job)) &&
133
- (@filter_args.empty? || display_args(j).include?(@filter_args))
136
+ (@filter_args.empty? || display_args(j).include?(@filter_args)) &&
137
+ (!@searching || [j.jid, job_display_class(j), display_args(j)].any? { |f| f.to_s.downcase.include?(needle) })
134
138
  }.first(@per_page)
135
139
  @total = nil
136
140
  else
@@ -30,6 +30,13 @@ module SideBro
30
30
  script.end_with?("/") ? script : "#{script}/"
31
31
  end
32
32
 
33
+ # Appends the file mtime so browsers refetch an asset when it changes
34
+ def asset_path(path)
35
+ file = File.join(SideBro::Web::ASSETS_PATH, path)
36
+ version = File.exist?(file) ? File.mtime(file).to_i : SideBro::VERSION
37
+ "#{root_path}assets/#{path}?v=#{version}"
38
+ end
39
+
33
40
  def locale
34
41
  session[:locale] || "en"
35
42
  end
@@ -41,68 +41,71 @@
41
41
  var prevProcessed = null;
42
42
  var prevFailed = null;
43
43
 
44
+ // Draws in real pixels so text and lines are never stretched
45
+ function sizeSvg(svg) {
46
+ var W = Math.max(200, Math.round(svg.clientWidth));
47
+ var H = Math.max(120, Math.round(svg.clientHeight));
48
+ svg.setAttribute("viewBox", "0 0 " + W + " " + H);
49
+ return { W: W, H: H };
50
+ }
51
+
52
+ // Flat area chart: thin line over a translucent fill, four edge-aligned x labels
53
+ var PAD_T = 8, PAD_B = 26;
54
+
55
+ function drawAreaChart(svg, series, labels) {
56
+ var size = sizeSvg(svg);
57
+ var W = size.W, H = size.H, ch = H - PAD_T - PAD_B;
58
+ var len = series[0].data.length;
59
+ var all = [];
60
+ series.forEach(function (s) { all = all.concat(s.data); });
61
+ var maxY = Math.max(1, Math.max.apply(null, all)) * 1.15;
62
+ var x = function (i) { return (i / Math.max(len - 1, 1)) * W; };
63
+ var y = function (v) { return PAD_T + (1 - v / maxY) * ch; };
64
+ var base = PAD_T + ch;
65
+
66
+ var body = "";
67
+ series.forEach(function (s) {
68
+ var line = "";
69
+ s.data.forEach(function (v, i) { line += (i === 0 ? "M" : "L") + x(i).toFixed(1) + "," + y(v).toFixed(1) + " "; });
70
+ var area = line + "L" + W + "," + base + " L0," + base + " Z";
71
+ body += "<path d=\"" + area + "\" fill=\"" + s.color + "\" fill-opacity=\"0.16\"/>";
72
+ body += "<path d=\"" + line + "\" stroke=\"" + s.color + "\" stroke-width=\"1.5\" fill=\"none\" stroke-linejoin=\"round\"/>";
73
+ });
74
+
75
+ var text = "";
76
+ labels.forEach(function (l, j) {
77
+ var anchor = j === 0 ? "start" : (j === labels.length - 1 ? "end" : "middle");
78
+ var xx = (j / (labels.length - 1)) * W;
79
+ text += "<text x=\"" + xx + "\" y=\"" + (H - 6) + "\" font-size=\"10\" fill=\"#4f567c\" text-anchor=\"" + anchor + "\" font-family=\"Geist Mono\">" + l + "</text>";
80
+ });
81
+
82
+ svg.innerHTML = body + text;
83
+ }
84
+
85
+ function clock(d) {
86
+ return [d.getHours(), d.getMinutes(), d.getSeconds()].map(function (n) { return n < 10 ? "0" + n : n; }).join(":");
87
+ }
88
+
44
89
  // ---- live chart ----
45
90
  function renderLiveChart() {
46
91
  var svg = document.getElementById("liveChart");
47
92
  if (!svg) return;
48
- var W = 1200, H = 280, PAD_L = 54, PAD_R = 20, PAD_T = 18, PAD_B = 28;
49
- var cw = W - PAD_L - PAD_R, ch = H - PAD_T - PAD_B;
50
93
  var interval = getInterval();
51
94
  var totalSec = (N - 1) * interval;
52
95
  var chartWindow = document.getElementById("chartWindow");
53
96
  if (chartWindow) chartWindow.textContent = totalSec >= 60 ? Math.round(totalSec / 60) + " min" : totalSec + "s";
54
- var all = seriesProcessed.concat(seriesFailed);
55
- var maxY = Math.max(12, Math.ceil(Math.max.apply(null, all)));
56
- var x = function (i) { return PAD_L + (i / (N - 1)) * cw; };
57
- var y = function (v) { return PAD_T + (1 - v / maxY) * ch; };
58
-
59
- function makePath(s) {
60
- var d = "";
61
- s.forEach(function (v, i) { d += (i === 0 ? "M" : "L") + x(i).toFixed(1) + "," + y(v).toFixed(1) + " "; });
62
- return d;
63
- }
64
- function makeArea(s) {
65
- var d = "M" + x(0) + "," + y(0) + " ";
66
- s.forEach(function (v, i) { d += "L" + x(i).toFixed(1) + "," + y(v).toFixed(1) + " "; });
67
- d += "L" + x(N - 1) + "," + y(0) + " Z";
68
- return d;
69
- }
70
-
71
- var grid = "";
72
- for (var i = 0; i <= 4; i++) {
73
- var yy = PAD_T + (i / 4) * ch;
74
- var val = Math.round(maxY - (i / 4) * maxY);
75
- grid += "<line x1=\"" + PAD_L + "\" y1=\"" + yy + "\" x2=\"" + (W - PAD_R) + "\" y2=\"" + yy + "\" stroke=\"#1f1638\" stroke-dasharray=\"3 4\"/>";
76
- grid += "<text x=\"" + (PAD_L - 8) + "\" y=\"" + (yy + 4) + "\" font-size=\"11\" fill=\"#6a5f8a\" text-anchor=\"end\" font-family=\"JetBrains Mono\">" + val + "</text>";
77
- }
78
- var xLabels = "";
79
- for (var j = 0; j <= 4; j++) {
80
- var xx = PAD_L + (j / 4) * cw;
81
- var sec = Math.round(((4 - j) / 4) * totalSec);
82
- xLabels += "<text x=\"" + xx + "\" y=\"" + (H - 8) + "\" font-size=\"11\" fill=\"#6a5f8a\" text-anchor=\"middle\" font-family=\"JetBrains Mono\">" + (sec === 0 ? "now" : "-" + sec + "s") + "</text>";
83
- }
84
97
 
85
- svg.innerHTML =
86
- "<defs>" +
87
- "<linearGradient id=\"gradProc\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">" +
88
- "<stop offset=\"0%\" stop-color=\"#4be3ff\" stop-opacity=\"0.45\"/>" +
89
- "<stop offset=\"100%\" stop-color=\"#4be3ff\" stop-opacity=\"0\"/>" +
90
- "</linearGradient>" +
91
- "<filter id=\"glow\"><feGaussianBlur stdDeviation=\"2\"/></filter>" +
92
- "</defs>" +
93
- grid + xLabels +
94
- "<path d=\"" + makeArea(seriesProcessed) + "\" fill=\"url(#gradProc)\"/>" +
95
- "<path d=\"" + makePath(seriesProcessed) + "\" stroke=\"#4be3ff\" stroke-width=\"2\" fill=\"none\" filter=\"url(#glow)\" opacity=\"0.6\"/>" +
96
- "<path d=\"" + makePath(seriesProcessed) + "\" stroke=\"#4be3ff\" stroke-width=\"2\" fill=\"none\"/>" +
97
- "<path d=\"" + makePath(seriesFailed) + "\" stroke=\"#ff5470\" stroke-width=\"2\" fill=\"none\"/>" +
98
- "<circle cx=\"" + x(N - 1) + "\" cy=\"" + y(seriesProcessed[N - 1]) + "\" r=\"4\" fill=\"#4be3ff\"/>" +
99
- "<circle cx=\"" + x(N - 1) + "\" cy=\"" + y(seriesProcessed[N - 1]) + "\" r=\"8\" fill=\"#4be3ff\" opacity=\"0.25\"/>";
98
+ var now = Date.now();
99
+ var labels = [0, 1, 2, 3].map(function (j) { return clock(new Date(now - ((3 - j) / 3) * totalSec * 1000)); });
100
+ drawAreaChart(svg, [
101
+ { data: seriesFailed, color: "#f2708a" },
102
+ { data: seriesProcessed, color: "#7c6cf6" }
103
+ ], labels);
100
104
 
101
105
  var tip = document.getElementById("chartTip");
102
106
  svg.onmousemove = function (e) {
103
107
  var rect = svg.getBoundingClientRect();
104
- var px = (e.clientX - rect.left) / rect.width * W;
105
- var idx = Math.max(0, Math.min(N - 1, Math.round(((px - PAD_L) / cw) * (N - 1))));
108
+ var idx = Math.max(0, Math.min(N - 1, Math.round(((e.clientX - rect.left) / rect.width) * (N - 1))));
106
109
  document.getElementById("tipProc").textContent = seriesProcessed[idx].toFixed(1);
107
110
  document.getElementById("tipFail").textContent = seriesFailed[idx].toFixed(1);
108
111
  var ageSec = (N - 1 - idx) * getInterval();
@@ -117,101 +120,33 @@
117
120
  svg.onmouseleave = function () { tip.style.display = "none"; };
118
121
  }
119
122
 
120
- function formatVal(v) {
121
- if (v >= 1000000) return (v / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
122
- if (v >= 1000) return Math.round(v / 1000) + "K";
123
- return String(v);
124
- }
125
-
126
123
  // ---- history chart ----
127
124
  var currentRange = "month";
128
125
 
129
126
  function renderHistoryChart() {
130
127
  var svg = document.getElementById("historyChart");
131
128
  if (!svg) return;
132
- var W = 1200, H = 320, PAD_L = 60, PAD_R = 30, PAD_T = 20, PAD_B = 40;
133
- var cw = W - PAD_L - PAD_R, ch = H - PAD_T - PAD_B;
134
-
135
129
  var histData = window.HISTORY_DATA;
136
130
  if (!histData) return;
137
131
 
138
- // select subset based on range
139
- var allPoints = histData.processed;
140
- var allFailed = histData.failed;
141
- var allLabels = histData.labels;
142
- var total = allPoints.length;
143
-
132
+ var total = histData.processed.length;
144
133
  var sliceLen = total;
145
134
  if (currentRange === "week") sliceLen = Math.min(7, total);
146
135
  else if (currentRange === "month") sliceLen = Math.min(30, total);
147
136
  else if (currentRange === "3m") sliceLen = Math.min(90, total);
148
137
  else if (currentRange === "6m") sliceLen = Math.min(180, total);
149
138
 
150
- var points = allPoints.slice(0, sliceLen);
151
- var failed = allFailed.slice(0, sliceLen);
152
- var labels = allLabels.slice(0, sliceLen);
139
+ var points = histData.processed.slice(0, sliceLen);
140
+ var failed = histData.failed.slice(0, sliceLen);
141
+ var labels = histData.labels.slice(0, sliceLen);
153
142
  if (points.length === 0) return;
154
143
 
155
- var maxY = Math.max(1, Math.max.apply(null, points.concat(failed)));
156
- var x = function (i) { return PAD_L + (i / Math.max(points.length - 1, 1)) * cw; };
157
- var y = function (v) { return PAD_T + (1 - v / maxY) * ch; };
158
-
159
- var line = "", area = "M" + x(0) + "," + y(0) + " ";
160
- var failLine = "", failArea = "M" + x(0) + "," + y(0) + " ";
161
- points.forEach(function (v, i) {
162
- line += (i === 0 ? "M" : "L") + x(i).toFixed(1) + "," + y(v).toFixed(1) + " ";
163
- area += "L" + x(i).toFixed(1) + "," + y(v).toFixed(1) + " ";
164
- });
165
- area += "L" + x(points.length - 1) + "," + y(0) + " Z";
166
- failed.forEach(function (v, i) {
167
- failLine += (i === 0 ? "M" : "L") + x(i).toFixed(1) + "," + y(v).toFixed(1) + " ";
168
- failArea += "L" + x(i).toFixed(1) + "," + y(v).toFixed(1) + " ";
169
- });
170
- failArea += "L" + x(failed.length - 1) + "," + y(0) + " Z";
171
-
172
- var grid = "";
173
- for (var i = 0; i <= 4; i++) {
174
- var yy = PAD_T + (i / 4) * ch;
175
- var val = Math.round(maxY - (i / 4) * maxY);
176
- grid += "<line x1=\"" + PAD_L + "\" y1=\"" + yy + "\" x2=\"" + (W - PAD_R) + "\" y2=\"" + yy + "\" stroke=\"#1f1638\" stroke-dasharray=\"2 4\"/>";
177
- if (i > 0) grid += "<text x=\"" + (PAD_L - 10) + "\" y=\"" + (yy + 4) + "\" font-size=\"11\" fill=\"#6a5f8a\" text-anchor=\"end\" font-family=\"JetBrains Mono\">" + formatVal(val) + "</text>";
178
- }
179
-
180
- // label thinning
181
- var labelStep = Math.max(1, Math.floor(labels.length / 6));
182
- var xLabels = "";
183
- labels.forEach(function (l, i) {
184
- if (i % labelStep !== 0 && i !== labels.length - 1) return;
185
- var xx = x(i);
186
- xLabels += "<line x1=\"" + xx + "\" y1=\"" + PAD_T + "\" x2=\"" + xx + "\" y2=\"" + (H - PAD_B) + "\" stroke=\"#1f1638\" stroke-dasharray=\"2 4\"/>";
187
- xLabels += "<text x=\"" + xx + "\" y=\"" + (H - 14) + "\" font-size=\"12\" fill=\"#a98cff\" text-anchor=\"middle\" font-family=\"JetBrains Mono\">" + l + "</text>";
188
- });
189
-
190
- svg.innerHTML =
191
- "<defs>" +
192
- "<linearGradient id=\"gradHist\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">" +
193
- "<stop offset=\"0%\" stop-color=\"#a98cff\" stop-opacity=\"0.35\"/>" +
194
- "<stop offset=\"60%\" stop-color=\"#a98cff\" stop-opacity=\"0.05\"/>" +
195
- "<stop offset=\"100%\" stop-color=\"#a98cff\" stop-opacity=\"0\"/>" +
196
- "</linearGradient>" +
197
- "<linearGradient id=\"gradHistLine\" x1=\"0\" x2=\"1\" y1=\"0\" y2=\"0\">" +
198
- "<stop offset=\"0%\" stop-color=\"#a98cff\"/>" +
199
- "<stop offset=\"60%\" stop-color=\"#ff5cd6\"/>" +
200
- "<stop offset=\"100%\" stop-color=\"#7a3dff\"/>" +
201
- "</linearGradient>" +
202
- "<linearGradient id=\"gradFail\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">" +
203
- "<stop offset=\"0%\" stop-color=\"#ff5470\" stop-opacity=\"0.25\"/>" +
204
- "<stop offset=\"100%\" stop-color=\"#ff5470\" stop-opacity=\"0\"/>" +
205
- "</linearGradient>" +
206
- "</defs>" +
207
- grid + xLabels +
208
- "<path d=\"" + area + "\" fill=\"url(#gradHist)\"/>" +
209
- "<path d=\"" + failArea + "\" fill=\"url(#gradFail)\"/>" +
210
- "<path d=\"" + line + "\" stroke=\"url(#gradHistLine)\" stroke-width=\"2.4\" fill=\"none\" stroke-linejoin=\"round\" stroke-linecap=\"round\"/>" +
211
- "<path d=\"" + failLine + "\" stroke=\"#ff5470\" stroke-width=\"1.6\" fill=\"none\" stroke-linejoin=\"round\" stroke-linecap=\"round\"/>" +
212
- points.map(function (v, i) {
213
- return "<circle cx=\"" + x(i).toFixed(1) + "\" cy=\"" + y(v).toFixed(1) + "\" r=\"2.4\" fill=\"#170f29\" stroke=\"#a98cff\" stroke-width=\"1.5\"/>";
214
- }).join("");
144
+ var last = labels.length - 1;
145
+ var picked = [0, 1, 2, 3].map(function (j) { return labels[Math.round((j / 3) * last)]; });
146
+ drawAreaChart(svg, [
147
+ { data: failed, color: "#f2708a" },
148
+ { data: points, color: "#7c6cf6" }
149
+ ], picked);
215
150
  }
216
151
 
217
152
  // ---- live polling via fetch ----
@@ -259,4 +194,10 @@
259
194
  renderLiveChart();
260
195
  renderHistoryChart();
261
196
  restartTimer();
197
+
198
+ var resizeTimer = null;
199
+ window.addEventListener("resize", function () {
200
+ clearTimeout(resizeTimer);
201
+ resizeTimer = setTimeout(function () { renderLiveChart(); renderHistoryChart(); }, 100);
202
+ });
262
203
  })();