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.
- package/README.md +106 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +103 -0
- package/docs/_brand.json +18 -0
- package/docs/_cover.md +13 -0
- package/docs/_sidebar.md +31 -0
- package/docs/_topbar.md +5 -0
- package/docs/_version.json +7 -0
- package/docs/api/README.md +44 -0
- package/docs/api/action-convention.md +148 -0
- package/docs/api/add-action.md +68 -0
- package/docs/api/beacon-capability.md +89 -0
- package/docs/api/build-action-map.md +88 -0
- package/docs/api/connect.md +81 -0
- package/docs/api/disconnect.md +50 -0
- package/docs/api/is-connected.md +33 -0
- package/docs/api/lifecycle-hooks.md +115 -0
- package/docs/architecture.md +237 -0
- package/docs/css/docuserve.css +327 -0
- package/docs/examples/README.md +58 -0
- package/docs/examples/certificate-expiry-monitor.md +212 -0
- package/docs/examples/docker-container-management.md +265 -0
- package/docs/examples/log-archive-and-upload.md +214 -0
- package/docs/examples/log-file-cleanup.md +199 -0
- package/docs/examples/mysql-maintenance.md +253 -0
- package/docs/examples/postgresql-aggregation.md +247 -0
- package/docs/examples/rest-api-health-check.md +213 -0
- package/docs/examples/rest-endpoint-sync.md +240 -0
- package/docs/examples/server-metrics-collection.md +199 -0
- package/docs/examples/shell-commands.md +176 -0
- package/docs/index.html +39 -0
- package/docs/quickstart.md +199 -0
- package/docs/retold-catalog.json +85 -0
- package/docs/retold-keyword-index.json +10642 -0
- package/package.json +33 -0
- package/source/Ultravisor-Beacon-Capability-ActionMap.cjs +132 -0
- package/source/Ultravisor-Beacon-Capability.cjs +276 -0
- 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
|