ultravisor-beacon-capability 1.0.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.
Files changed (38) hide show
  1. package/README.md +106 -0
  2. package/docs/.nojekyll +0 -0
  3. package/docs/README.md +103 -0
  4. package/docs/_brand.json +18 -0
  5. package/docs/_cover.md +13 -0
  6. package/docs/_sidebar.md +31 -0
  7. package/docs/_topbar.md +5 -0
  8. package/docs/_version.json +7 -0
  9. package/docs/api/README.md +44 -0
  10. package/docs/api/action-convention.md +148 -0
  11. package/docs/api/add-action.md +68 -0
  12. package/docs/api/beacon-capability.md +89 -0
  13. package/docs/api/build-action-map.md +88 -0
  14. package/docs/api/connect.md +81 -0
  15. package/docs/api/disconnect.md +50 -0
  16. package/docs/api/is-connected.md +33 -0
  17. package/docs/api/lifecycle-hooks.md +115 -0
  18. package/docs/architecture.md +237 -0
  19. package/docs/css/docuserve.css +327 -0
  20. package/docs/examples/README.md +58 -0
  21. package/docs/examples/certificate-expiry-monitor.md +212 -0
  22. package/docs/examples/docker-container-management.md +265 -0
  23. package/docs/examples/log-archive-and-upload.md +214 -0
  24. package/docs/examples/log-file-cleanup.md +199 -0
  25. package/docs/examples/mysql-maintenance.md +253 -0
  26. package/docs/examples/postgresql-aggregation.md +247 -0
  27. package/docs/examples/rest-api-health-check.md +213 -0
  28. package/docs/examples/rest-endpoint-sync.md +240 -0
  29. package/docs/examples/server-metrics-collection.md +199 -0
  30. package/docs/examples/shell-commands.md +176 -0
  31. package/docs/index.html +39 -0
  32. package/docs/quickstart.md +199 -0
  33. package/docs/retold-catalog.json +85 -0
  34. package/docs/retold-keyword-index.json +10642 -0
  35. package/package.json +33 -0
  36. package/source/Ultravisor-Beacon-Capability-ActionMap.cjs +132 -0
  37. package/source/Ultravisor-Beacon-Capability.cjs +276 -0
  38. package/test/Ultravisor-Beacon-Capability_tests.js +744 -0
@@ -0,0 +1,327 @@
1
+ /* ============================================================================
2
+ Pict Docuserve - Base Styles & Theme Variables
3
+ ============================================================================ */
4
+
5
+ /* ----------------------------------------------------------------------------
6
+ Theme variables — light defaults on :root.
7
+ Dark mode applies when either:
8
+ (a) the user explicitly selected dark via <html data-theme="dark">
9
+ (b) the user hasn't chosen anything AND the system prefers dark
10
+ An explicit <html data-theme="light"> pins the light palette regardless.
11
+ ---------------------------------------------------------------------------- */
12
+
13
+ :root
14
+ {
15
+ /* Surfaces */
16
+ --docuserve-bg: #FDFBF7;
17
+ --docuserve-bg-elevated: #FFFFFF;
18
+ --docuserve-border: #DDD6CA;
19
+ --docuserve-border-soft: #EAE3D8;
20
+
21
+ /* Text */
22
+ --docuserve-text: #2A241E;
23
+ --docuserve-text-strong: #3D3229;
24
+ --docuserve-text-muted: #5E5549;
25
+ --docuserve-text-dim: #8A7F72;
26
+
27
+ /* Accent / links */
28
+ --docuserve-accent: #2E7D74;
29
+ --docuserve-accent-hover: #236660;
30
+
31
+ /* Top bar */
32
+ --docuserve-topbar-bg: #3D3229;
33
+ --docuserve-topbar-text: #E8E0D4;
34
+ --docuserve-topbar-text-muted: #B5AA9A;
35
+ --docuserve-topbar-text-dim: #8A7F72;
36
+ --docuserve-topbar-hover-bg: #524438;
37
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.06);
38
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.08);
39
+ --docuserve-topbar-version-text: #B5AA9A;
40
+
41
+ /* Sidebar */
42
+ --docuserve-sidebar-bg: #FAF7F1;
43
+ --docuserve-sidebar-border: #DDD6CA;
44
+ --docuserve-sidebar-border-soft: #E5DED1;
45
+ --docuserve-sidebar-text: #423D37;
46
+ --docuserve-sidebar-group-title: #3D3229;
47
+ --docuserve-sidebar-module-text: #5E5549;
48
+ --docuserve-sidebar-hover-bg: #EAE3D8;
49
+ --docuserve-sidebar-hover-text: #2E7D74;
50
+ --docuserve-sidebar-active-bg: #E5DED1;
51
+ --docuserve-sidebar-active-text: #2E7D74;
52
+ --docuserve-sidebar-search-bg: #FFFFFF;
53
+ --docuserve-sidebar-search-border: #DDD6CA;
54
+
55
+ /* Inline code */
56
+ --docuserve-inline-code-bg: #F0ECE4;
57
+ --docuserve-inline-code-text: #9E3A50;
58
+
59
+ /* Code block panel */
60
+ --docuserve-code-bg: #F6F3EE;
61
+ --docuserve-code-border: #E5DED1;
62
+ --docuserve-code-gutter-bg: #EFEAE0;
63
+ --docuserve-code-gutter-border: #DDD6CA;
64
+ --docuserve-code-gutter-text: #A59986;
65
+ --docuserve-code-text: #2A241E;
66
+
67
+ /* Syntax tokens — low-chroma dark-on-light palette */
68
+ --docuserve-tok-keyword: #A03472;
69
+ --docuserve-tok-string: #1A6640;
70
+ --docuserve-tok-number: #B25A00;
71
+ --docuserve-tok-comment: #8A7F72;
72
+ --docuserve-tok-operator: #2E7D74;
73
+ --docuserve-tok-punctuation: #2A241E;
74
+ --docuserve-tok-function: #2A5DB0;
75
+ --docuserve-tok-property: #9E3A50;
76
+ --docuserve-tok-tag: #9E3A50;
77
+ --docuserve-tok-attr-name: #B25A00;
78
+ --docuserve-tok-attr-value: #1A6640;
79
+
80
+ /* Tables, blockquotes, mermaid */
81
+ --docuserve-table-header-bg: #F5F0E8;
82
+ --docuserve-table-row-alt-bg: #F9F6F0;
83
+ --docuserve-blockquote-bg: #F7F5F0;
84
+ --docuserve-blockquote-border: #2E7D74;
85
+ --docuserve-blockquote-text: #5E5549;
86
+ --docuserve-mermaid-bg: #FFFFFF;
87
+
88
+ /* Scrollbars */
89
+ --docuserve-scrollbar-track: #F5F0E8;
90
+ --docuserve-scrollbar-thumb: #D4CCBE;
91
+ --docuserve-scrollbar-thumb-hover: #B5AA9A;
92
+ }
93
+
94
+ @media (prefers-color-scheme: dark)
95
+ {
96
+ :root:not([data-theme="light"])
97
+ {
98
+ --docuserve-bg: #15120F;
99
+ --docuserve-bg-elevated: #1B1814;
100
+ --docuserve-border: #2F2823;
101
+ --docuserve-border-soft: #26211C;
102
+
103
+ --docuserve-text: #E8E0D4;
104
+ --docuserve-text-strong: #F2ECE0;
105
+ --docuserve-text-muted: #B5AA9A;
106
+ --docuserve-text-dim: #7A6F62;
107
+
108
+ --docuserve-accent: #5DB8A8;
109
+ --docuserve-accent-hover: #7FCCB8;
110
+
111
+ --docuserve-topbar-bg: #1A1612;
112
+ --docuserve-topbar-text: #E8E0D4;
113
+ --docuserve-topbar-text-muted: #B5AA9A;
114
+ --docuserve-topbar-text-dim: #7A6F62;
115
+ --docuserve-topbar-hover-bg: #2A241E;
116
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
117
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
118
+ --docuserve-topbar-version-text: #B5AA9A;
119
+
120
+ --docuserve-sidebar-bg: #1B1814;
121
+ --docuserve-sidebar-border: #2F2823;
122
+ --docuserve-sidebar-border-soft: #26211C;
123
+ --docuserve-sidebar-text: #C9C0B3;
124
+ --docuserve-sidebar-group-title: #F2ECE0;
125
+ --docuserve-sidebar-module-text: #B5AA9A;
126
+ --docuserve-sidebar-hover-bg: #2A241E;
127
+ --docuserve-sidebar-hover-text: #7FCCB8;
128
+ --docuserve-sidebar-active-bg: #2F2823;
129
+ --docuserve-sidebar-active-text: #7FCCB8;
130
+ --docuserve-sidebar-search-bg: #26211C;
131
+ --docuserve-sidebar-search-border: #2F2823;
132
+
133
+ --docuserve-inline-code-bg: #2A241E;
134
+ --docuserve-inline-code-text: #E8B07A;
135
+
136
+ --docuserve-code-bg: #1E1A17;
137
+ --docuserve-code-border: #2F2823;
138
+ --docuserve-code-gutter-bg: #17130F;
139
+ --docuserve-code-gutter-border: #2F2823;
140
+ --docuserve-code-gutter-text: #6A6052;
141
+ --docuserve-code-text: #E8E0D4;
142
+
143
+ --docuserve-tok-keyword: #C678DD;
144
+ --docuserve-tok-string: #98C379;
145
+ --docuserve-tok-number: #D19A66;
146
+ --docuserve-tok-comment: #7F848E;
147
+ --docuserve-tok-operator: #56B6C2;
148
+ --docuserve-tok-punctuation: #E8E0D4;
149
+ --docuserve-tok-function: #61AFEF;
150
+ --docuserve-tok-property: #E06C75;
151
+ --docuserve-tok-tag: #E06C75;
152
+ --docuserve-tok-attr-name: #D19A66;
153
+ --docuserve-tok-attr-value: #98C379;
154
+
155
+ --docuserve-table-header-bg: #26211C;
156
+ --docuserve-table-row-alt-bg: #1F1B17;
157
+ --docuserve-blockquote-bg: #1F1B17;
158
+ --docuserve-blockquote-border: #5DB8A8;
159
+ --docuserve-blockquote-text: #C9C0B3;
160
+ --docuserve-mermaid-bg: #E8E0D4;
161
+
162
+ --docuserve-scrollbar-track: #1B1814;
163
+ --docuserve-scrollbar-thumb: #3A322B;
164
+ --docuserve-scrollbar-thumb-hover: #524438;
165
+ }
166
+ }
167
+
168
+ :root[data-theme="dark"]
169
+ {
170
+ --docuserve-bg: #15120F;
171
+ --docuserve-bg-elevated: #1B1814;
172
+ --docuserve-border: #2F2823;
173
+ --docuserve-border-soft: #26211C;
174
+
175
+ --docuserve-text: #E8E0D4;
176
+ --docuserve-text-strong: #F2ECE0;
177
+ --docuserve-text-muted: #B5AA9A;
178
+ --docuserve-text-dim: #7A6F62;
179
+
180
+ --docuserve-accent: #5DB8A8;
181
+ --docuserve-accent-hover: #7FCCB8;
182
+
183
+ --docuserve-topbar-bg: #1A1612;
184
+ --docuserve-topbar-text: #E8E0D4;
185
+ --docuserve-topbar-text-muted: #B5AA9A;
186
+ --docuserve-topbar-text-dim: #7A6F62;
187
+ --docuserve-topbar-hover-bg: #2A241E;
188
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
189
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
190
+ --docuserve-topbar-version-text: #B5AA9A;
191
+
192
+ --docuserve-sidebar-bg: #1B1814;
193
+ --docuserve-sidebar-border: #2F2823;
194
+ --docuserve-sidebar-border-soft: #26211C;
195
+ --docuserve-sidebar-text: #C9C0B3;
196
+ --docuserve-sidebar-group-title: #F2ECE0;
197
+ --docuserve-sidebar-module-text: #B5AA9A;
198
+ --docuserve-sidebar-hover-bg: #2A241E;
199
+ --docuserve-sidebar-hover-text: #7FCCB8;
200
+ --docuserve-sidebar-active-bg: #2F2823;
201
+ --docuserve-sidebar-active-text: #7FCCB8;
202
+ --docuserve-sidebar-search-bg: #26211C;
203
+ --docuserve-sidebar-search-border: #2F2823;
204
+
205
+ --docuserve-inline-code-bg: #2A241E;
206
+ --docuserve-inline-code-text: #E8B07A;
207
+
208
+ --docuserve-code-bg: #1E1A17;
209
+ --docuserve-code-border: #2F2823;
210
+ --docuserve-code-gutter-bg: #17130F;
211
+ --docuserve-code-gutter-border: #2F2823;
212
+ --docuserve-code-gutter-text: #6A6052;
213
+ --docuserve-code-text: #E8E0D4;
214
+
215
+ --docuserve-tok-keyword: #C678DD;
216
+ --docuserve-tok-string: #98C379;
217
+ --docuserve-tok-number: #D19A66;
218
+ --docuserve-tok-comment: #7F848E;
219
+ --docuserve-tok-operator: #56B6C2;
220
+ --docuserve-tok-punctuation: #E8E0D4;
221
+ --docuserve-tok-function: #61AFEF;
222
+ --docuserve-tok-property: #E06C75;
223
+ --docuserve-tok-tag: #E06C75;
224
+ --docuserve-tok-attr-name: #D19A66;
225
+ --docuserve-tok-attr-value: #98C379;
226
+
227
+ --docuserve-table-header-bg: #26211C;
228
+ --docuserve-table-row-alt-bg: #1F1B17;
229
+ --docuserve-blockquote-bg: #1F1B17;
230
+ --docuserve-blockquote-border: #5DB8A8;
231
+ --docuserve-blockquote-text: #C9C0B3;
232
+ --docuserve-mermaid-bg: #E8E0D4;
233
+
234
+ --docuserve-scrollbar-track: #1B1814;
235
+ --docuserve-scrollbar-thumb: #3A322B;
236
+ --docuserve-scrollbar-thumb-hover: #524438;
237
+ }
238
+
239
+ /* ----------------------------------------------------------------------------
240
+ Reset and base
241
+ ---------------------------------------------------------------------------- */
242
+
243
+ *, *::before, *::after
244
+ {
245
+ box-sizing: border-box;
246
+ }
247
+
248
+ html, body
249
+ {
250
+ margin: 0;
251
+ padding: 0;
252
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
253
+ font-size: 16px;
254
+ line-height: 1.5;
255
+ color: var(--docuserve-text);
256
+ background-color: var(--docuserve-bg);
257
+ -webkit-font-smoothing: antialiased;
258
+ -moz-osx-font-smoothing: grayscale;
259
+ transition: background-color 0.15s ease, color 0.15s ease;
260
+ }
261
+
262
+ /* Typography */
263
+ h1, h2, h3, h4, h5, h6
264
+ {
265
+ margin-top: 0;
266
+ line-height: 1.3;
267
+ color: var(--docuserve-text-strong);
268
+ }
269
+
270
+ a
271
+ {
272
+ color: var(--docuserve-accent);
273
+ text-decoration: none;
274
+ }
275
+
276
+ a:hover
277
+ {
278
+ color: var(--docuserve-accent-hover);
279
+ }
280
+
281
+ /* Application container */
282
+ #Docuserve-Application-Container
283
+ {
284
+ min-height: 100vh;
285
+ }
286
+
287
+ /* Utility: scrollbar styling */
288
+ ::-webkit-scrollbar
289
+ {
290
+ width: 8px;
291
+ height: 8px;
292
+ }
293
+
294
+ ::-webkit-scrollbar-track
295
+ {
296
+ background: var(--docuserve-scrollbar-track);
297
+ }
298
+
299
+ ::-webkit-scrollbar-thumb
300
+ {
301
+ background: var(--docuserve-scrollbar-thumb);
302
+ border-radius: 4px;
303
+ }
304
+
305
+ ::-webkit-scrollbar-thumb:hover
306
+ {
307
+ background: var(--docuserve-scrollbar-thumb-hover);
308
+ }
309
+
310
+ /* Responsive adjustments */
311
+ @media (max-width: 768px)
312
+ {
313
+ html
314
+ {
315
+ font-size: 14px;
316
+ }
317
+
318
+ #Docuserve-Sidebar-Container
319
+ {
320
+ display: none;
321
+ }
322
+
323
+ .docuserve-body
324
+ {
325
+ flex-direction: column;
326
+ }
327
+ }
@@ -0,0 +1,58 @@
1
+ # Examples
2
+
3
+ > Real-world usage patterns for ultravisor-beacon-capability
4
+
5
+ Each example below is a complete, self-contained capability class that demonstrates a practical automation scenario. These replace ad-hoc scripts that developers would otherwise run manually from their workstations.
6
+
7
+ ## Examples
8
+
9
+ | Example | Description |
10
+ |---------|-------------|
11
+ | [Shell Commands](shell-commands.md) | Wrap basic shell commands (ping, uptime, whoami) as beacon actions |
12
+ | [MySQL Maintenance](mysql-maintenance.md) | Purge old records, run table optimization, export query results |
13
+ | [PostgreSQL Aggregation](postgresql-aggregation.md) | Run aggregation queries and materialized view refreshes |
14
+ | [REST API Health Check](rest-api-health-check.md) | Monitor multiple REST endpoints and report status |
15
+ | [REST Endpoint Sync](rest-endpoint-sync.md) | Fetch data from one API and push to another |
16
+ | [Log File Cleanup](log-file-cleanup.md) | Find and delete old log files by age and size |
17
+ | [Log Archive and Upload](log-archive-and-upload.md) | Compress log directories into archives and upload to S3 |
18
+ | [Server Metrics Collection](server-metrics-collection.md) | Collect CPU, memory, and disk metrics from the host |
19
+ | [Certificate Expiry Monitor](certificate-expiry-monitor.md) | Check TLS certificate expiry dates for a list of domains |
20
+ | [Docker Container Management](docker-container-management.md) | List, restart, and prune Docker containers and images |
21
+
22
+ ## Pattern
23
+
24
+ Every example follows the same structure:
25
+
26
+ ```javascript
27
+ const libBeaconCapability = require('ultravisor-beacon-capability');
28
+
29
+ class MyCapability extends libBeaconCapability
30
+ {
31
+ constructor(pFable, pOptions, pServiceHash)
32
+ {
33
+ super(pFable, pOptions, pServiceHash);
34
+ this.serviceType = 'MyCapability';
35
+ this.capabilityName = 'MyCapability';
36
+ }
37
+
38
+ // Optional: setup resources
39
+ onInitialize(fCallback) { ... }
40
+
41
+ // Optional: teardown resources
42
+ onShutdown(fCallback) { ... }
43
+
44
+ // Action with schema and description
45
+ get actionDoWork_Description() { return '...'; }
46
+ get actionDoWork_Schema() { return [...]; }
47
+ actionDoWork(pSettings, pWorkItem, fCallback, fReportProgress) { ... }
48
+ }
49
+ ```
50
+
51
+ ## Running an Example
52
+
53
+ 1. Copy the example code into a file
54
+ 2. Install dependencies: `npm install ultravisor-beacon-capability fable`
55
+ 3. Start an Ultravisor server
56
+ 4. Run: `node my-example.js`
57
+
58
+ The capability connects as a beacon and its actions appear as task types in the Ultravisor dashboard.
@@ -0,0 +1,212 @@
1
+ # Example: Certificate Expiry Monitor
2
+
3
+ A capability that checks TLS certificate expiry dates for a list of domains. Schedule it weekly in Ultravisor to get early warning before certificates expire.
4
+
5
+ ## Full Source
6
+
7
+ ```javascript
8
+ const libFable = require('fable');
9
+ const libBeaconCapability = require('ultravisor-beacon-capability');
10
+ const libTLS = require('tls');
11
+ const libNet = require('net');
12
+
13
+ class CertificateMonitor extends libBeaconCapability
14
+ {
15
+ constructor(pFable, pOptions, pServiceHash)
16
+ {
17
+ super(pFable, pOptions, pServiceHash);
18
+ this.serviceType = 'CertificateMonitor';
19
+ this.capabilityName = 'CertificateMonitor';
20
+ }
21
+
22
+ /**
23
+ * Internal: connect to a host and return certificate details.
24
+ */
25
+ _checkCert(pHost, pPort, fCallback)
26
+ {
27
+ let tmpSocket = libTLS.connect(
28
+ {
29
+ host: pHost,
30
+ port: pPort || 443,
31
+ servername: pHost,
32
+ timeout: 10000,
33
+ rejectUnauthorized: false
34
+ },
35
+ () =>
36
+ {
37
+ let tmpCert = tmpSocket.getPeerCertificate();
38
+ tmpSocket.end();
39
+
40
+ if (!tmpCert || !tmpCert.valid_to)
41
+ {
42
+ return fCallback(null, { Host: pHost, Error: 'No certificate returned' });
43
+ }
44
+
45
+ let tmpExpiry = new Date(tmpCert.valid_to);
46
+ let tmpNow = new Date();
47
+ let tmpDaysLeft = Math.floor((tmpExpiry - tmpNow) / (1000 * 60 * 60 * 24));
48
+
49
+ return fCallback(null, {
50
+ Host: pHost,
51
+ Subject: tmpCert.subject ? tmpCert.subject.CN : 'unknown',
52
+ Issuer: tmpCert.issuer ? tmpCert.issuer.O : 'unknown',
53
+ ValidFrom: tmpCert.valid_from,
54
+ ValidTo: tmpCert.valid_to,
55
+ DaysUntilExpiry: tmpDaysLeft,
56
+ Expired: tmpDaysLeft < 0,
57
+ Warning: tmpDaysLeft >= 0 && tmpDaysLeft <= 30,
58
+ SerialNumber: tmpCert.serialNumber
59
+ });
60
+ });
61
+
62
+ tmpSocket.on('error', (pError) =>
63
+ {
64
+ return fCallback(null, { Host: pHost, Error: pError.message, DaysUntilExpiry: -1, Expired: true });
65
+ });
66
+
67
+ tmpSocket.on('timeout', () =>
68
+ {
69
+ tmpSocket.destroy();
70
+ return fCallback(null, { Host: pHost, Error: 'Connection timed out', DaysUntilExpiry: -1, Expired: true });
71
+ });
72
+ }
73
+
74
+ // --- Action: CheckDomain ---
75
+
76
+ get actionCheckDomain_Description()
77
+ {
78
+ return 'Check the TLS certificate expiry for a single domain';
79
+ }
80
+
81
+ get actionCheckDomain_Schema()
82
+ {
83
+ return [
84
+ { Name: 'Host', DataType: 'String', Required: true },
85
+ { Name: 'Port', DataType: 'Integer', Required: false, Default: 443 }
86
+ ];
87
+ }
88
+
89
+ actionCheckDomain(pSettings, pWorkItem, fCallback)
90
+ {
91
+ this._checkCert(pSettings.Host, pSettings.Port, (pError, pResult) =>
92
+ {
93
+ if (pError) return fCallback(pError);
94
+
95
+ let tmpStatus = pResult.Expired ? 'EXPIRED' : (pResult.Warning ? 'WARNING' : 'OK');
96
+
97
+ return fCallback(null, {
98
+ Outputs: pResult,
99
+ Log: [`${pSettings.Host}: ${tmpStatus} (${pResult.DaysUntilExpiry} days remaining)`]
100
+ });
101
+ });
102
+ }
103
+
104
+ // --- Action: CheckMultipleDomains ---
105
+
106
+ get actionCheckMultipleDomains_Description()
107
+ {
108
+ return 'Check TLS certificate expiry for multiple domains and produce a report';
109
+ }
110
+
111
+ get actionCheckMultipleDomains_Schema()
112
+ {
113
+ return [
114
+ { Name: 'Domains', DataType: 'Array', Required: true, Description: 'Array of domain strings or { Host, Port } objects' },
115
+ { Name: 'WarningThresholdDays', DataType: 'Integer', Required: false, Default: 30 }
116
+ ];
117
+ }
118
+
119
+ actionCheckMultipleDomains(pSettings, pWorkItem, fCallback, fReportProgress)
120
+ {
121
+ let tmpDomains = pSettings.Domains || [];
122
+ let tmpThreshold = pSettings.WarningThresholdDays || 30;
123
+ let tmpResults = [];
124
+ let tmpCompleted = 0;
125
+
126
+ if (tmpDomains.length === 0)
127
+ {
128
+ return fCallback(null, {
129
+ Outputs: { Results: [], Summary: 'No domains provided' },
130
+ Log: ['No domains to check']
131
+ });
132
+ }
133
+
134
+ tmpDomains.forEach((pDomain) =>
135
+ {
136
+ let tmpHost = (typeof pDomain === 'string') ? pDomain : pDomain.Host;
137
+ let tmpPort = (typeof pDomain === 'object') ? pDomain.Port : 443;
138
+
139
+ this._checkCert(tmpHost, tmpPort, (pError, pResult) =>
140
+ {
141
+ if (pResult)
142
+ {
143
+ pResult.Warning = pResult.DaysUntilExpiry >= 0 && pResult.DaysUntilExpiry <= tmpThreshold;
144
+ }
145
+ tmpResults.push(pResult || { Host: tmpHost, Error: 'Check failed' });
146
+ tmpCompleted++;
147
+
148
+ fReportProgress({
149
+ Percent: Math.round((tmpCompleted / tmpDomains.length) * 100),
150
+ Message: `Checked ${tmpCompleted} / ${tmpDomains.length} domains`
151
+ });
152
+
153
+ if (tmpCompleted === tmpDomains.length)
154
+ {
155
+ // Sort by days until expiry (soonest first)
156
+ tmpResults.sort((pA, pB) => (pA.DaysUntilExpiry || -999) - (pB.DaysUntilExpiry || -999));
157
+
158
+ let tmpExpired = tmpResults.filter((pR) => pR.Expired).length;
159
+ let tmpWarning = tmpResults.filter((pR) => pR.Warning && !pR.Expired).length;
160
+ let tmpOK = tmpResults.filter((pR) => !pR.Expired && !pR.Warning && !pR.Error).length;
161
+
162
+ return fCallback(null, {
163
+ Outputs: {
164
+ Results: tmpResults,
165
+ ExpiredCount: tmpExpired,
166
+ WarningCount: tmpWarning,
167
+ OKCount: tmpOK,
168
+ TotalCount: tmpDomains.length,
169
+ AllHealthy: tmpExpired === 0 && tmpWarning === 0
170
+ },
171
+ Log: [
172
+ `Certificate check: ${tmpOK} OK, ${tmpWarning} warning, ${tmpExpired} expired`
173
+ ]
174
+ });
175
+ }
176
+ });
177
+ });
178
+ }
179
+ }
180
+
181
+ // --- Startup ---
182
+
183
+ let tmpFable = new libFable({ Product: 'CertificateMonitor', ProductVersion: '1.0.0' });
184
+ tmpFable.addServiceType('CertificateMonitor', CertificateMonitor);
185
+ let tmpCap = tmpFable.instantiateServiceProvider('CertificateMonitor');
186
+
187
+ tmpCap.connect(
188
+ {
189
+ ServerURL: process.env.ULTRAVISOR_URL || 'http://localhost:54321',
190
+ Name: 'certificate-monitor'
191
+ },
192
+ (pError) =>
193
+ {
194
+ if (pError) throw pError;
195
+ console.log('Certificate monitor beacon online');
196
+ });
197
+
198
+ process.on('SIGTERM', () => { tmpCap.disconnect(() => process.exit(0)); });
199
+ ```
200
+
201
+ ## Registered Task Types
202
+
203
+ - `beacon-certificatemonitor-checkdomain`
204
+ - `beacon-certificatemonitor-checkmultipledomains`
205
+
206
+ ## Key Points
207
+
208
+ - **No external dependencies** -- uses Node.js built-in `tls` module
209
+ - **Warning threshold** is configurable (default 30 days)
210
+ - **Results sorted** by soonest expiry for quick scanning
211
+ - **rejectUnauthorized: false** ensures expired or self-signed certs are still inspected (not rejected)
212
+ - Schedule `CheckMultipleDomains` weekly to catch renewals before they expire