git-watchtower 1.10.3 → 1.10.5
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.
- package/package.json +2 -2
- package/src/server/web-ui/js.js +323 -407
- package/src/server/web-ui/pure.js +137 -0
- package/src/server/web.js +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions shared between Node (server) and the web dashboard.
|
|
3
|
+
*
|
|
4
|
+
* These are extracted so they can be unit-tested in Node while still being
|
|
5
|
+
* inlined into the browser bundle by the assembly step in js.js.
|
|
6
|
+
*
|
|
7
|
+
* Every function here MUST be self-contained (no closures over external
|
|
8
|
+
* state) so it can be serialised with Function.prototype.toString() and
|
|
9
|
+
* embedded in a <script> tag.
|
|
10
|
+
*
|
|
11
|
+
* @module server/web-ui/pure
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Escape a string for safe insertion into HTML.
|
|
16
|
+
* @param {string} s
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function escHtml(s) {
|
|
20
|
+
if (!s) return '';
|
|
21
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format a date string as a relative time (e.g. "5m ago", "2d ago").
|
|
26
|
+
* @param {string} dateStr - ISO date string or any value accepted by `new Date()`
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function timeAgo(dateStr) {
|
|
30
|
+
if (!dateStr) return '';
|
|
31
|
+
var ts = new Date(dateStr).getTime();
|
|
32
|
+
if (isNaN(ts)) return '';
|
|
33
|
+
var diff = Date.now() - ts;
|
|
34
|
+
if (diff < 0) return 'now';
|
|
35
|
+
var s = Math.floor(diff / 1000);
|
|
36
|
+
if (s < 60) return s + 's ago';
|
|
37
|
+
var m = Math.floor(s / 60);
|
|
38
|
+
if (m < 60) return m + 'm ago';
|
|
39
|
+
var h = Math.floor(m / 60);
|
|
40
|
+
if (h < 24) return h + 'h ago';
|
|
41
|
+
var d = Math.floor(h / 24);
|
|
42
|
+
return d + 'd ago';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render a sparkline Unicode string as HTML bar elements.
|
|
47
|
+
* @param {string} sparkStr - String of Unicode block characters (U+2581–U+2588)
|
|
48
|
+
* @returns {string} HTML string
|
|
49
|
+
*/
|
|
50
|
+
function renderSparklineBars(sparkStr) {
|
|
51
|
+
if (!sparkStr) return '';
|
|
52
|
+
var chars = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588';
|
|
53
|
+
var html = '<div class="sparkline-bar">';
|
|
54
|
+
for (var i = 0; i < sparkStr.length; i++) {
|
|
55
|
+
var ch = sparkStr[i];
|
|
56
|
+
var idx = chars.indexOf(ch);
|
|
57
|
+
if (idx < 0) {
|
|
58
|
+
html += '<div class="spark-bar" style="height:1px"></div>';
|
|
59
|
+
} else {
|
|
60
|
+
var pct = Math.round(((idx + 1) / 8) * 100);
|
|
61
|
+
html += '<div class="spark-bar" style="height:' + pct + '%"></div>';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
html += '</div>';
|
|
65
|
+
return html;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Format a number in compact notation (e.g. 1500 → "1.5k").
|
|
70
|
+
* @param {number} n
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function fmtCompact(n) {
|
|
74
|
+
if (n < 1000) return String(n);
|
|
75
|
+
if (n < 10000) return (n / 1000).toFixed(1) + 'k';
|
|
76
|
+
if (n < 1000000) return Math.round(n / 1000) + 'k';
|
|
77
|
+
return (n / 1000000).toFixed(1) + 'm';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Filter and sort branches for display.
|
|
82
|
+
* Pure function — all dependencies passed as arguments.
|
|
83
|
+
* @param {Array} branches - Full branch list from state
|
|
84
|
+
* @param {Object} options
|
|
85
|
+
* @param {string} [options.searchQuery=''] - Filter string
|
|
86
|
+
* @param {string[]} [options.pinnedBranches=[]] - Branch names pinned to top
|
|
87
|
+
* @param {string} [options.sortOrder='default'] - 'default' | 'alpha' | 'recent'
|
|
88
|
+
* @returns {Array} Filtered and sorted branch list
|
|
89
|
+
*/
|
|
90
|
+
function getDisplayBranches(branches, options) {
|
|
91
|
+
if (!branches) return [];
|
|
92
|
+
var searchQuery = (options && options.searchQuery) || '';
|
|
93
|
+
var pinnedBranches = (options && options.pinnedBranches) || [];
|
|
94
|
+
var sortOrder = (options && options.sortOrder) || 'default';
|
|
95
|
+
|
|
96
|
+
var result = branches.slice();
|
|
97
|
+
|
|
98
|
+
if (searchQuery) {
|
|
99
|
+
var q = searchQuery.toLowerCase();
|
|
100
|
+
result = result.filter(function(b) {
|
|
101
|
+
return b.name.toLowerCase().indexOf(q) !== -1;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Build pin lookup once
|
|
106
|
+
var pinSet = {};
|
|
107
|
+
for (var i = 0; i < pinnedBranches.length; i++) pinSet[pinnedBranches[i]] = true;
|
|
108
|
+
|
|
109
|
+
if (sortOrder === 'alpha') {
|
|
110
|
+
result.sort(function(a, b) {
|
|
111
|
+
var aPin = pinSet[a.name] ? 1 : 0;
|
|
112
|
+
var bPin = pinSet[b.name] ? 1 : 0;
|
|
113
|
+
if (aPin !== bPin) return bPin - aPin;
|
|
114
|
+
return a.name.localeCompare(b.name);
|
|
115
|
+
});
|
|
116
|
+
} else if (sortOrder === 'recent') {
|
|
117
|
+
result.sort(function(a, b) {
|
|
118
|
+
var aPin = pinSet[a.name] ? 1 : 0;
|
|
119
|
+
var bPin = pinSet[b.name] ? 1 : 0;
|
|
120
|
+
if (aPin !== bPin) return bPin - aPin;
|
|
121
|
+
var aDate = a.date ? new Date(a.date).getTime() : 0;
|
|
122
|
+
var bDate = b.date ? new Date(b.date).getTime() : 0;
|
|
123
|
+
return bDate - aDate;
|
|
124
|
+
});
|
|
125
|
+
} else if (pinnedBranches.length > 0) {
|
|
126
|
+
// Default sort: only move pinned branches to top
|
|
127
|
+
result.sort(function(a, b) {
|
|
128
|
+
var aPin = pinSet[a.name] ? 1 : 0;
|
|
129
|
+
var bPin = pinSet[b.name] ? 1 : 0;
|
|
130
|
+
return bPin - aPin;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { escHtml, timeAgo, renderSparklineBars, fmtCompact, getDisplayBranches };
|
package/src/server/web.js
CHANGED
|
@@ -429,7 +429,7 @@ class WebDashboardServer {
|
|
|
429
429
|
|
|
430
430
|
// Keepalive heartbeat to prevent proxy/LB timeouts
|
|
431
431
|
const keepalive = setInterval(() => {
|
|
432
|
-
try { res.write(': keepalive
|
|
432
|
+
try { res.write(': keepalive\n\n'); } catch (e) { clearInterval(keepalive); }
|
|
433
433
|
}, SSE_KEEPALIVE_INTERVAL);
|
|
434
434
|
|
|
435
435
|
req.on('close', () => {
|