@velaro/cli 1.4.86 → 1.4.88
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/bin/velaro.js +4 -0
- package/lib/api.js +15 -0
- package/lib/commands/linked-identity.js +41 -0
- package/lib/commands/status-sla.js +47 -0
- package/lib/status-sla.test.js +23 -0
- package/package.json +1 -1
package/bin/velaro.js
CHANGED
|
@@ -29,6 +29,7 @@ import { aiConfigCommand } from '../lib/commands/ai-config.js';
|
|
|
29
29
|
import { deploymentCommand } from '../lib/commands/deployment.js';
|
|
30
30
|
import { updateCommand } from '../lib/commands/update.js';
|
|
31
31
|
import { agentCommand } from '../lib/commands/agent.js';
|
|
32
|
+
import { linkedIdentityCommand } from '../lib/commands/linked-identity.js';
|
|
32
33
|
import { notificationsCommand } from '../lib/commands/notifications.js';
|
|
33
34
|
import { articleCommand } from '../lib/commands/article.js';
|
|
34
35
|
import { sharepointCommand } from '../lib/commands/sharepoint.js';
|
|
@@ -94,6 +95,7 @@ import { diagnosticsCommand } from '../lib/commands/diagnostics.js';
|
|
|
94
95
|
import { aiSpendCommand } from '../lib/commands/ai-spend.js';
|
|
95
96
|
import { statusPageCommand } from '../lib/commands/status-page.js';
|
|
96
97
|
import { statusNotifyCommand } from '../lib/commands/status-notify.js';
|
|
98
|
+
import { statusSlaCommand } from '../lib/commands/status-sla.js';
|
|
97
99
|
import { emailInboxCommand } from '../lib/commands/email-inbox.js';
|
|
98
100
|
import { callbackCommand } from '../lib/commands/callback.js';
|
|
99
101
|
import { caseCommand } from '../lib/commands/case.js';
|
|
@@ -153,6 +155,7 @@ await yargs(hideBin(process.argv))
|
|
|
153
155
|
.command(deploymentCommand)
|
|
154
156
|
.command(updateCommand)
|
|
155
157
|
.command(agentCommand)
|
|
158
|
+
.command(linkedIdentityCommand)
|
|
156
159
|
.command(notificationsCommand)
|
|
157
160
|
.command(articleCommand)
|
|
158
161
|
.command(sharepointCommand)
|
|
@@ -220,6 +223,7 @@ await yargs(hideBin(process.argv))
|
|
|
220
223
|
.command(aiSpendCommand)
|
|
221
224
|
.command(statusPageCommand)
|
|
222
225
|
.command(statusNotifyCommand)
|
|
226
|
+
.command(statusSlaCommand)
|
|
223
227
|
.command(emailInboxCommand)
|
|
224
228
|
.command(callbackCommand)
|
|
225
229
|
.command(caseCommand)
|
package/lib/api.js
CHANGED
|
@@ -90,3 +90,18 @@ export const messagingPost = (path, body) => messagingRequest('POST', path, bod
|
|
|
90
90
|
export const messagingPut = (path, body) => messagingRequest('PUT', path, body);
|
|
91
91
|
export const messagingPatch = (path, body) => messagingRequest('PATCH', path, body);
|
|
92
92
|
export const messagingDel = (path) => messagingRequest('DELETE', path);
|
|
93
|
+
|
|
94
|
+
// Same as messagingRequest but returns the raw response text (for CSV downloads).
|
|
95
|
+
export async function messagingRequestText(method, path, envOverride) {
|
|
96
|
+
const creds = await getCredentials(envOverride);
|
|
97
|
+
const res = await fetch(joinUrl(creds.messagingApiBase, path), {
|
|
98
|
+
method,
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${creds.velaroToken}`,
|
|
101
|
+
...(creds.siteId ? { 'vmsg-siteId': String(creds.siteId) } : {}),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
const text = await res.text();
|
|
105
|
+
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}${text ? `: ${text}` : ''}`);
|
|
106
|
+
return text;
|
|
107
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { messagingGet, messagingDel } from '../api.js';
|
|
2
|
+
import { runCommand } from '../run.js';
|
|
3
|
+
|
|
4
|
+
function fmtDate(value) {
|
|
5
|
+
if (!value) return '—';
|
|
6
|
+
const d = new Date(value);
|
|
7
|
+
return Number.isNaN(d.getTime()) ? String(value) : d.toISOString().replace('T', ' ').slice(0, 16) + 'Z';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const linkedIdentityCommand = {
|
|
11
|
+
command: 'linked-identity <subcommand>',
|
|
12
|
+
describe: 'See and remove the Google / Microsoft / company sign-in methods linked to your site\'s users',
|
|
13
|
+
builder: (yargs) => yargs
|
|
14
|
+
.command({
|
|
15
|
+
command: 'list',
|
|
16
|
+
describe: 'List linked sign-in methods. Use when offboarding: disabling a person does not stop a linked Google sign-in.',
|
|
17
|
+
handler: runCommand(async () => {
|
|
18
|
+
const rows = await messagingGet('/admin/security/linked-identities');
|
|
19
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
20
|
+
console.log('No linked sign-in methods.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const emailW = Math.max(...rows.map(r => (r.userEmail ?? '').length), 5);
|
|
24
|
+
for (const r of rows) {
|
|
25
|
+
console.log(`[${String(r.id).padStart(4)}] ${(r.userEmail ?? '—').padEnd(emailW)} ${(r.method ?? r.idpKind).padEnd(24)} linked ${fmtDate(r.linkedAt)} last used ${fmtDate(r.lastLoginAt)}`);
|
|
26
|
+
}
|
|
27
|
+
console.log(`\n${rows.length} linked sign-in method(s)`);
|
|
28
|
+
}),
|
|
29
|
+
})
|
|
30
|
+
.command({
|
|
31
|
+
command: 'remove <id>',
|
|
32
|
+
describe: 'Remove one linked sign-in method by id (from `list`). The person keeps any other way they can sign in.',
|
|
33
|
+
builder: (y) => y.positional('id', { describe: 'Link id from `linked-identity list`', type: 'number' }),
|
|
34
|
+
handler: runCommand(async (argv) => {
|
|
35
|
+
if (!Number.isInteger(argv.id) || argv.id <= 0) throw new Error('id must be a positive whole number.');
|
|
36
|
+
await messagingDel(`/admin/security/linked-identities/${argv.id}`);
|
|
37
|
+
console.log(`Removed linked sign-in method ${argv.id}.`);
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
.demandCommand(1),
|
|
41
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { messagingRequest, messagingRequestText } from '../api.js';
|
|
3
|
+
|
|
4
|
+
// Staff SLA report for the public status page (velaro-messaging SuperAdmin/status-sla).
|
|
5
|
+
// Endpoint is live only after velaro-messaging PR 2223 is deployed.
|
|
6
|
+
|
|
7
|
+
export function buildStatusSlaQuery(argv) {
|
|
8
|
+
const p = new URLSearchParams();
|
|
9
|
+
if (argv.from) p.set('from', argv.from);
|
|
10
|
+
if (argv.to) p.set('to', argv.to);
|
|
11
|
+
p.set('platform', argv.platform || 'v20');
|
|
12
|
+
if (argv.includeMaintenance) p.set('includeMaintenance', 'true');
|
|
13
|
+
if (argv.includeDeployments) p.set('includeDeployments', 'true');
|
|
14
|
+
if (argv.includeExcludedChecks) p.set('includeExcludedChecks', 'true');
|
|
15
|
+
if (argv.degradedDown) p.set('degradedCountsAsDown', 'true');
|
|
16
|
+
if (argv.slowMs !== undefined) p.set('slowResponseMs', String(argv.slowMs));
|
|
17
|
+
p.set('format', argv.csv ? 'csv' : 'json');
|
|
18
|
+
return p.toString();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const statusSlaCommand = {
|
|
22
|
+
command: 'status-sla',
|
|
23
|
+
describe: 'SLA / uptime report for the status page, per component (staff only)',
|
|
24
|
+
builder: (y) => y
|
|
25
|
+
.option('from', { type: 'string', demandOption: true, describe: 'Start date (YYYY-MM-DD)' })
|
|
26
|
+
.option('to', { type: 'string', demandOption: true, describe: 'End date (YYYY-MM-DD)' })
|
|
27
|
+
.option('platform', { type: 'string', default: 'v20', choices: ['v20', 'v11'] })
|
|
28
|
+
.option('include-maintenance', { type: 'boolean', default: false, describe: 'Count scheduled maintenance as downtime' })
|
|
29
|
+
.option('include-deployments', { type: 'boolean', default: false, describe: 'Count deployment windows as downtime' })
|
|
30
|
+
.option('include-excluded-checks', { type: 'boolean', default: false })
|
|
31
|
+
.option('degraded-down', { type: 'boolean', default: false, describe: 'Degraded counts as down' })
|
|
32
|
+
.option('slow-ms', { type: 'number', describe: 'Responses slower than this many ms count as slow' })
|
|
33
|
+
.option('csv', { type: 'boolean', default: false, describe: 'CSV output (stdout, or --out file)' })
|
|
34
|
+
.option('out', { type: 'string', describe: 'Write CSV to this file' })
|
|
35
|
+
.option('staging', { type: 'boolean', default: false, describe: 'Use the staging messaging API' }),
|
|
36
|
+
handler: async (argv) => {
|
|
37
|
+
const path = `SuperAdmin/status-sla?${buildStatusSlaQuery(argv)}`;
|
|
38
|
+
const env = argv.staging ? 'staging' : undefined;
|
|
39
|
+
if (argv.csv) {
|
|
40
|
+
const csv = await messagingRequestText('GET', path, env);
|
|
41
|
+
if (argv.out) { writeFileSync(argv.out, csv); console.error(`Wrote ${argv.out}`); }
|
|
42
|
+
else process.stdout.write(csv);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(JSON.stringify(await messagingRequest('GET', path, undefined, env), null, 2));
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { buildStatusSlaQuery } from './commands/status-sla.js';
|
|
4
|
+
|
|
5
|
+
test('status-sla query maps flags to endpoint params', () => {
|
|
6
|
+
const q = new URLSearchParams(buildStatusSlaQuery({
|
|
7
|
+
from: '2026-09-01', to: '2026-09-30', platform: 'v11', includeMaintenance: true,
|
|
8
|
+
degradedDown: true, slowMs: 1500, csv: true,
|
|
9
|
+
}));
|
|
10
|
+
assert.equal(q.get('from'), '2026-09-01');
|
|
11
|
+
assert.equal(q.get('platform'), 'v11');
|
|
12
|
+
assert.equal(q.get('includeMaintenance'), 'true');
|
|
13
|
+
assert.equal(q.get('degradedCountsAsDown'), 'true');
|
|
14
|
+
assert.equal(q.get('slowResponseMs'), '1500');
|
|
15
|
+
assert.equal(q.get('format'), 'csv');
|
|
16
|
+
assert.equal(q.get('includeDeployments'), null);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('status-sla query defaults to v20 json', () => {
|
|
20
|
+
const q = new URLSearchParams(buildStatusSlaQuery({ from: 'a', to: 'b' }));
|
|
21
|
+
assert.equal(q.get('platform'), 'v20');
|
|
22
|
+
assert.equal(q.get('format'), 'json');
|
|
23
|
+
});
|
package/package.json
CHANGED