@blackasteroid/kuma-cli 1.1.0 → 1.2.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/dist/index.js +415 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27808,10 +27808,16 @@ var KumaClient = class {
|
|
|
27808
27808
|
// BUG-01 fix: addMonitor uses callback, not a separate event
|
|
27809
27809
|
// BUG-03 fix: include required fields accepted_statuscodes, maxretries, retryInterval
|
|
27810
27810
|
async addMonitor(monitor) {
|
|
27811
|
+
const autoToken = monitor.type === "push" && !monitor.pushToken ? Array.from(crypto.getRandomValues(new Uint8Array(24))).map((b) => b.toString(16).padStart(2, "0")).join("") : void 0;
|
|
27811
27812
|
const payload = {
|
|
27812
27813
|
accepted_statuscodes: ["200-299"],
|
|
27813
27814
|
maxretries: 1,
|
|
27814
27815
|
retryInterval: 60,
|
|
27816
|
+
conditions: [],
|
|
27817
|
+
rabbitmqNodes: [],
|
|
27818
|
+
kafkaProducerBrokers: [],
|
|
27819
|
+
kafkaProducerSaslOptions: { mechanism: "none" },
|
|
27820
|
+
...autoToken ? { pushToken: autoToken } : {},
|
|
27815
27821
|
...monitor
|
|
27816
27822
|
};
|
|
27817
27823
|
return new Promise((resolve, reject) => {
|
|
@@ -27828,7 +27834,11 @@ var KumaClient = class {
|
|
|
27828
27834
|
reject(new Error(result.msg ?? "Failed to add monitor"));
|
|
27829
27835
|
return;
|
|
27830
27836
|
}
|
|
27831
|
-
resolve({
|
|
27837
|
+
resolve({
|
|
27838
|
+
id: result.monitorID,
|
|
27839
|
+
// Return the token we generated so the caller has it immediately
|
|
27840
|
+
pushToken: payload.pushToken
|
|
27841
|
+
});
|
|
27832
27842
|
}
|
|
27833
27843
|
);
|
|
27834
27844
|
});
|
|
@@ -27969,6 +27979,50 @@ var KumaClient = class {
|
|
|
27969
27979
|
});
|
|
27970
27980
|
}
|
|
27971
27981
|
// ---------------------------------------------------------------------------
|
|
27982
|
+
// Tags
|
|
27983
|
+
// ---------------------------------------------------------------------------
|
|
27984
|
+
/** Get all tags defined in Kuma. Callback-based event. */
|
|
27985
|
+
async getTags() {
|
|
27986
|
+
return new Promise((resolve, reject) => {
|
|
27987
|
+
const timer = setTimeout(() => reject(new Error("getTags timeout")), 1e4);
|
|
27988
|
+
this.socket.emit(
|
|
27989
|
+
"getTags",
|
|
27990
|
+
(result) => {
|
|
27991
|
+
clearTimeout(timer);
|
|
27992
|
+
if (!result.ok) {
|
|
27993
|
+
reject(new Error(result.msg ?? "Failed to fetch tags"));
|
|
27994
|
+
return;
|
|
27995
|
+
}
|
|
27996
|
+
resolve(result.tags ?? []);
|
|
27997
|
+
}
|
|
27998
|
+
);
|
|
27999
|
+
});
|
|
28000
|
+
}
|
|
28001
|
+
/**
|
|
28002
|
+
* Add a tag to a monitor.
|
|
28003
|
+
* socket.emit("addMonitorTag", tagID, monitorID, value, callback)
|
|
28004
|
+
* value is a user-defined label string (can be empty "").
|
|
28005
|
+
*/
|
|
28006
|
+
async addMonitorTag(tagId, monitorId, value2 = "") {
|
|
28007
|
+
return new Promise((resolve, reject) => {
|
|
28008
|
+
const timer = setTimeout(() => reject(new Error("addMonitorTag timeout")), 1e4);
|
|
28009
|
+
this.socket.emit(
|
|
28010
|
+
"addMonitorTag",
|
|
28011
|
+
tagId,
|
|
28012
|
+
monitorId,
|
|
28013
|
+
value2,
|
|
28014
|
+
(result) => {
|
|
28015
|
+
clearTimeout(timer);
|
|
28016
|
+
if (!result.ok) {
|
|
28017
|
+
reject(new Error(result.msg ?? "Failed to add tag to monitor"));
|
|
28018
|
+
return;
|
|
28019
|
+
}
|
|
28020
|
+
resolve();
|
|
28021
|
+
}
|
|
28022
|
+
);
|
|
28023
|
+
});
|
|
28024
|
+
}
|
|
28025
|
+
// ---------------------------------------------------------------------------
|
|
27972
28026
|
// Notifications
|
|
27973
28027
|
// ---------------------------------------------------------------------------
|
|
27974
28028
|
/**
|
|
@@ -28072,6 +28126,45 @@ var KumaClient = class {
|
|
|
28072
28126
|
);
|
|
28073
28127
|
});
|
|
28074
28128
|
}
|
|
28129
|
+
// ---------------------------------------------------------------------------
|
|
28130
|
+
// Bulk operations
|
|
28131
|
+
// ---------------------------------------------------------------------------
|
|
28132
|
+
/**
|
|
28133
|
+
* Pause all monitors matching a filter function.
|
|
28134
|
+
* Returns a list of { id, name, ok, error? } results.
|
|
28135
|
+
*/
|
|
28136
|
+
async bulkPause(filter) {
|
|
28137
|
+
const monitorMap = await this.getMonitorList();
|
|
28138
|
+
const targets = Object.values(monitorMap).filter(filter);
|
|
28139
|
+
const results = [];
|
|
28140
|
+
for (const m of targets) {
|
|
28141
|
+
try {
|
|
28142
|
+
await this.pauseMonitor(m.id);
|
|
28143
|
+
results.push({ id: m.id, name: m.name, ok: true });
|
|
28144
|
+
} catch (e) {
|
|
28145
|
+
results.push({ id: m.id, name: m.name, ok: false, error: e.message });
|
|
28146
|
+
}
|
|
28147
|
+
}
|
|
28148
|
+
return results;
|
|
28149
|
+
}
|
|
28150
|
+
/**
|
|
28151
|
+
* Resume all monitors matching a filter function.
|
|
28152
|
+
* Returns a list of { id, name, ok, error? } results.
|
|
28153
|
+
*/
|
|
28154
|
+
async bulkResume(filter) {
|
|
28155
|
+
const monitorMap = await this.getMonitorList();
|
|
28156
|
+
const targets = Object.values(monitorMap).filter(filter);
|
|
28157
|
+
const results = [];
|
|
28158
|
+
for (const m of targets) {
|
|
28159
|
+
try {
|
|
28160
|
+
await this.resumeMonitor(m.id);
|
|
28161
|
+
results.push({ id: m.id, name: m.name, ok: true });
|
|
28162
|
+
} catch (e) {
|
|
28163
|
+
results.push({ id: m.id, name: m.name, ok: false, error: e.message });
|
|
28164
|
+
}
|
|
28165
|
+
}
|
|
28166
|
+
return results;
|
|
28167
|
+
}
|
|
28075
28168
|
disconnect() {
|
|
28076
28169
|
this.socket.disconnect();
|
|
28077
28170
|
}
|
|
@@ -30204,9 +30297,9 @@ function isJsonMode(opts) {
|
|
|
30204
30297
|
const env3 = process.env["KUMA_JSON"];
|
|
30205
30298
|
return env3 === "1" || env3 === "true" || env3 === "yes";
|
|
30206
30299
|
}
|
|
30207
|
-
function jsonOut(data) {
|
|
30300
|
+
function jsonOut(data, exitCode = 0) {
|
|
30208
30301
|
console.log(JSON.stringify({ ok: true, data }, null, 2));
|
|
30209
|
-
process.exit(
|
|
30302
|
+
process.exit(exitCode);
|
|
30210
30303
|
}
|
|
30211
30304
|
function jsonError(message, code = 1) {
|
|
30212
30305
|
console.log(JSON.stringify({ ok: false, error: message, code }, null, 2));
|
|
@@ -30339,6 +30432,12 @@ ${source_default.dim("Examples:")}
|
|
|
30339
30432
|
// src/commands/monitors.ts
|
|
30340
30433
|
var import_enquirer2 = __toESM(require_enquirer());
|
|
30341
30434
|
var { prompt: prompt2 } = import_enquirer2.default;
|
|
30435
|
+
function collect(val, prev) {
|
|
30436
|
+
return [...prev, val];
|
|
30437
|
+
}
|
|
30438
|
+
function collectInt(val, prev) {
|
|
30439
|
+
return [...prev, parseInt(val, 10)];
|
|
30440
|
+
}
|
|
30342
30441
|
var MONITOR_TYPES = [
|
|
30343
30442
|
"http",
|
|
30344
30443
|
"tcp",
|
|
@@ -30513,6 +30612,90 @@ ${source_default.dim("Examples:")}
|
|
|
30513
30612
|
}
|
|
30514
30613
|
}
|
|
30515
30614
|
);
|
|
30615
|
+
monitors.command("create").description("Create a monitor non-interactively \u2014 designed for CI/CD pipelines").requiredOption("--name <name>", "Monitor display name").requiredOption("--type <type>", "Monitor type: http, tcp, ping, dns, push, ...").option("--url <url>", "URL or hostname to monitor").option("--interval <seconds>", "Check interval in seconds (default: 60)", "60").option("--tag <tag>", "Assign a tag by name (repeatable \u2014 must already exist in Kuma)", collect, []).option("--notification-id <id>", "Assign a notification channel by ID (repeatable)", collectInt, []).option("--json", "Output as JSON ({ ok, data }) \u2014 prints monitor ID and pushToken to stdout").addHelpText(
|
|
30616
|
+
"after",
|
|
30617
|
+
`
|
|
30618
|
+
${source_default.dim("Examples:")}
|
|
30619
|
+
${source_default.cyan('kuma monitors create --type http --name "habitu.ar" --url https://habitu.ar')}
|
|
30620
|
+
${source_default.cyan('kuma monitors create --type http --name "My API" --url https://api.example.com --tag Production --tag BlackAsteroid')}
|
|
30621
|
+
${source_default.cyan(`kuma monitors create --type push --name "GH Runner" --json | jq '.data.pushToken'`)}
|
|
30622
|
+
${source_default.cyan('kuma monitors create --type tcp --name "DB" --url db.host:5432 --interval 30 --notification-id 1')}
|
|
30623
|
+
|
|
30624
|
+
${source_default.dim("Full pipeline (deploy \u2192 monitor \u2192 heartbeat):")}
|
|
30625
|
+
${source_default.cyan('RESULT=$(kuma monitors create --type push --name "runner" --json)')}
|
|
30626
|
+
${source_default.cyan("PUSH_TOKEN=$(echo $RESULT | jq -r '.data.pushToken')")}
|
|
30627
|
+
${source_default.cyan('kuma heartbeat send $PUSH_TOKEN --msg "Alive"')}
|
|
30628
|
+
`
|
|
30629
|
+
).action(async (opts) => {
|
|
30630
|
+
const config = getConfig();
|
|
30631
|
+
if (!config) requireAuth(opts);
|
|
30632
|
+
const json = isJsonMode(opts);
|
|
30633
|
+
const interval = parseInt(opts.interval ?? "60", 10);
|
|
30634
|
+
if (["http", "keyword", "tcp", "ping", "dns"].includes(opts.type) && !opts.url) {
|
|
30635
|
+
handleError(new Error(`--url is required for monitor type "${opts.type}"`), opts);
|
|
30636
|
+
}
|
|
30637
|
+
try {
|
|
30638
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
30639
|
+
const result = await client.addMonitor({
|
|
30640
|
+
name: opts.name,
|
|
30641
|
+
type: opts.type,
|
|
30642
|
+
url: opts.url,
|
|
30643
|
+
interval
|
|
30644
|
+
});
|
|
30645
|
+
const monitorId = result.id;
|
|
30646
|
+
let pushToken = result.pushToken ?? null;
|
|
30647
|
+
const tagWarnings = [];
|
|
30648
|
+
if (opts.tag.length > 0) {
|
|
30649
|
+
const allTags = await client.getTags();
|
|
30650
|
+
const tagMap = new Map(allTags.map((t) => [t.name.toLowerCase(), t]));
|
|
30651
|
+
for (const tagName of opts.tag) {
|
|
30652
|
+
const found = tagMap.get(tagName.toLowerCase());
|
|
30653
|
+
if (!found) {
|
|
30654
|
+
const warn2 = `Tag "${tagName}" not found \u2014 skipping. Create it in the Kuma UI first.`;
|
|
30655
|
+
tagWarnings.push(warn2);
|
|
30656
|
+
if (!json) {
|
|
30657
|
+
console.warn(source_default.yellow(`\u26A0\uFE0F ${warn2}`));
|
|
30658
|
+
}
|
|
30659
|
+
continue;
|
|
30660
|
+
}
|
|
30661
|
+
await client.addMonitorTag(found.id, monitorId);
|
|
30662
|
+
}
|
|
30663
|
+
}
|
|
30664
|
+
if (opts.notificationId.length > 0) {
|
|
30665
|
+
const monitorMap = await client.getMonitorList();
|
|
30666
|
+
for (const notifId of opts.notificationId) {
|
|
30667
|
+
await client.setMonitorNotification(monitorId, notifId, true, monitorMap);
|
|
30668
|
+
}
|
|
30669
|
+
}
|
|
30670
|
+
client.disconnect();
|
|
30671
|
+
if (json) {
|
|
30672
|
+
const data = {
|
|
30673
|
+
id: monitorId,
|
|
30674
|
+
name: opts.name,
|
|
30675
|
+
type: opts.type,
|
|
30676
|
+
url: opts.url ?? null,
|
|
30677
|
+
interval
|
|
30678
|
+
};
|
|
30679
|
+
if (pushToken) data.pushToken = pushToken;
|
|
30680
|
+
if (tagWarnings.length > 0) data.warnings = tagWarnings;
|
|
30681
|
+
jsonOut(data, tagWarnings.length > 0 ? 1 : 0);
|
|
30682
|
+
}
|
|
30683
|
+
success(`Monitor "${opts.name}" created (ID: ${monitorId})`);
|
|
30684
|
+
if (pushToken) {
|
|
30685
|
+
console.log(` Push token: ${source_default.cyan(pushToken)}`);
|
|
30686
|
+
console.log(` Push URL: ${source_default.dim(`${config.url}/api/push/${pushToken}`)}`);
|
|
30687
|
+
}
|
|
30688
|
+
if (opts.tag.length > 0) {
|
|
30689
|
+
const applied = opts.tag.filter((t) => !tagWarnings.some((w) => w.includes(t)));
|
|
30690
|
+
if (applied.length > 0) console.log(` Tags: ${applied.join(", ")}`);
|
|
30691
|
+
}
|
|
30692
|
+
if (tagWarnings.length > 0) {
|
|
30693
|
+
process.exit(1);
|
|
30694
|
+
}
|
|
30695
|
+
} catch (err) {
|
|
30696
|
+
handleError(err, opts);
|
|
30697
|
+
}
|
|
30698
|
+
});
|
|
30516
30699
|
monitors.command("update <id>").description("Update the name, URL, interval, or active state of a monitor").option("--name <name>", "Set a new display name").option("--url <url>", "Set a new URL or hostname").option("--interval <seconds>", "Set a new check interval (seconds)").option("--active", "Resume the monitor (mark as active)").option("--no-active", "Pause the monitor (mark as inactive)").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30517
30700
|
"after",
|
|
30518
30701
|
`
|
|
@@ -30684,6 +30867,137 @@ ${source_default.dim("Examples:")}
|
|
|
30684
30867
|
handleError(err, opts);
|
|
30685
30868
|
}
|
|
30686
30869
|
});
|
|
30870
|
+
monitors.command("bulk-pause").description("Pause all monitors matching a tag or status filter").option("--tag <tag>", "Pause all monitors with this tag").option("--status <status>", "Pause all monitors with this status: up, down, pending, maintenance").option("--dry-run", "Preview which monitors would be paused without pausing them").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30871
|
+
"after",
|
|
30872
|
+
`
|
|
30873
|
+
${source_default.dim("Examples:")}
|
|
30874
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production")} Pause all Production monitors
|
|
30875
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production --dry-run")} Preview without pausing
|
|
30876
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production --json")} Machine-readable results
|
|
30877
|
+
|
|
30878
|
+
${source_default.dim("CI/CD usage:")}
|
|
30879
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production && ./deploy.sh && kuma monitors bulk-resume --tag Production")}
|
|
30880
|
+
`
|
|
30881
|
+
).action(async (opts) => {
|
|
30882
|
+
const config = getConfig();
|
|
30883
|
+
if (!config) requireAuth(opts);
|
|
30884
|
+
const json = isJsonMode(opts);
|
|
30885
|
+
if (!opts.tag && !opts.status) {
|
|
30886
|
+
handleError(new Error("At least one of --tag or --status is required"), opts);
|
|
30887
|
+
}
|
|
30888
|
+
const STATUS_MAP = { down: 0, up: 1, pending: 2, maintenance: 3 };
|
|
30889
|
+
try {
|
|
30890
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
30891
|
+
const monitorMap = await client.getMonitorList();
|
|
30892
|
+
const all = Object.values(monitorMap);
|
|
30893
|
+
let targets = all;
|
|
30894
|
+
if (opts.tag) {
|
|
30895
|
+
const tagName = opts.tag.toLowerCase();
|
|
30896
|
+
targets = targets.filter(
|
|
30897
|
+
(m) => Array.isArray(m.tags) && m.tags.some((t) => t.name.toLowerCase() === tagName)
|
|
30898
|
+
);
|
|
30899
|
+
}
|
|
30900
|
+
if (opts.status) {
|
|
30901
|
+
const statusNum = STATUS_MAP[opts.status.toLowerCase()];
|
|
30902
|
+
if (statusNum === void 0) {
|
|
30903
|
+
client.disconnect();
|
|
30904
|
+
handleError(new Error(`Invalid status "${opts.status}". Valid: up, down, pending, maintenance`), opts);
|
|
30905
|
+
}
|
|
30906
|
+
targets = targets.filter((m) => m.heartbeat?.status === statusNum);
|
|
30907
|
+
}
|
|
30908
|
+
if (targets.length === 0) {
|
|
30909
|
+
client.disconnect();
|
|
30910
|
+
if (json) jsonOut({ affected: 0, results: [] });
|
|
30911
|
+
console.log("No monitors matched the given filters.");
|
|
30912
|
+
return;
|
|
30913
|
+
}
|
|
30914
|
+
if (opts.dryRun) {
|
|
30915
|
+
client.disconnect();
|
|
30916
|
+
const preview = targets.map((m) => ({ id: m.id, name: m.name }));
|
|
30917
|
+
if (json) jsonOut({ dryRun: true, affected: targets.length, monitors: preview });
|
|
30918
|
+
console.log(source_default.yellow(`Dry run \u2014 would pause ${targets.length} monitor(s):`));
|
|
30919
|
+
preview.forEach((m) => console.log(` ${source_default.dim(String(m.id).padStart(4))} ${m.name}`));
|
|
30920
|
+
return;
|
|
30921
|
+
}
|
|
30922
|
+
const results = await client.bulkPause((m) => targets.some((t) => t.id === m.id));
|
|
30923
|
+
client.disconnect();
|
|
30924
|
+
const failed = results.filter((r) => !r.ok);
|
|
30925
|
+
if (json) {
|
|
30926
|
+
jsonOut({ affected: results.length, failed: failed.length, results });
|
|
30927
|
+
}
|
|
30928
|
+
console.log(`Paused ${results.length - failed.length}/${results.length} monitor(s)`);
|
|
30929
|
+
if (failed.length > 0) {
|
|
30930
|
+
failed.forEach((r) => error(` Monitor ${r.id} (${r.name}): ${r.error}`));
|
|
30931
|
+
process.exit(1);
|
|
30932
|
+
}
|
|
30933
|
+
} catch (err) {
|
|
30934
|
+
handleError(err, opts);
|
|
30935
|
+
}
|
|
30936
|
+
});
|
|
30937
|
+
monitors.command("bulk-resume").description("Resume all monitors matching a tag or status filter").option("--tag <tag>", "Resume all monitors with this tag").option("--status <status>", "Resume all monitors with this status: up, down, pending, maintenance").option("--dry-run", "Preview which monitors would be resumed without resuming them").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30938
|
+
"after",
|
|
30939
|
+
`
|
|
30940
|
+
${source_default.dim("Examples:")}
|
|
30941
|
+
${source_default.cyan("kuma monitors bulk-resume --tag Production")}
|
|
30942
|
+
${source_default.cyan("kuma monitors bulk-resume --tag Production --dry-run")}
|
|
30943
|
+
${source_default.cyan("kuma monitors bulk-resume --tag Production --json")}
|
|
30944
|
+
`
|
|
30945
|
+
).action(async (opts) => {
|
|
30946
|
+
const config = getConfig();
|
|
30947
|
+
if (!config) requireAuth(opts);
|
|
30948
|
+
const json = isJsonMode(opts);
|
|
30949
|
+
if (!opts.tag && !opts.status) {
|
|
30950
|
+
handleError(new Error("At least one of --tag or --status is required"), opts);
|
|
30951
|
+
}
|
|
30952
|
+
const STATUS_MAP = { down: 0, up: 1, pending: 2, maintenance: 3 };
|
|
30953
|
+
try {
|
|
30954
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
30955
|
+
const monitorMap = await client.getMonitorList();
|
|
30956
|
+
const all = Object.values(monitorMap);
|
|
30957
|
+
let targets = all;
|
|
30958
|
+
if (opts.tag) {
|
|
30959
|
+
const tagName = opts.tag.toLowerCase();
|
|
30960
|
+
targets = targets.filter(
|
|
30961
|
+
(m) => Array.isArray(m.tags) && m.tags.some((t) => t.name.toLowerCase() === tagName)
|
|
30962
|
+
);
|
|
30963
|
+
}
|
|
30964
|
+
if (opts.status) {
|
|
30965
|
+
const statusNum = STATUS_MAP[opts.status.toLowerCase()];
|
|
30966
|
+
if (statusNum === void 0) {
|
|
30967
|
+
client.disconnect();
|
|
30968
|
+
handleError(new Error(`Invalid status "${opts.status}". Valid: up, down, pending, maintenance`), opts);
|
|
30969
|
+
}
|
|
30970
|
+
targets = targets.filter((m) => m.heartbeat?.status === statusNum);
|
|
30971
|
+
}
|
|
30972
|
+
if (targets.length === 0) {
|
|
30973
|
+
client.disconnect();
|
|
30974
|
+
if (json) jsonOut({ affected: 0, results: [] });
|
|
30975
|
+
console.log("No monitors matched the given filters.");
|
|
30976
|
+
return;
|
|
30977
|
+
}
|
|
30978
|
+
if (opts.dryRun) {
|
|
30979
|
+
client.disconnect();
|
|
30980
|
+
const preview = targets.map((m) => ({ id: m.id, name: m.name }));
|
|
30981
|
+
if (json) jsonOut({ dryRun: true, affected: targets.length, monitors: preview });
|
|
30982
|
+
console.log(source_default.yellow(`Dry run \u2014 would resume ${targets.length} monitor(s):`));
|
|
30983
|
+
preview.forEach((m) => console.log(` ${source_default.dim(String(m.id).padStart(4))} ${m.name}`));
|
|
30984
|
+
return;
|
|
30985
|
+
}
|
|
30986
|
+
const results = await client.bulkResume((m) => targets.some((t) => t.id === m.id));
|
|
30987
|
+
client.disconnect();
|
|
30988
|
+
const failed = results.filter((r) => !r.ok);
|
|
30989
|
+
if (json) {
|
|
30990
|
+
jsonOut({ affected: results.length, failed: failed.length, results });
|
|
30991
|
+
}
|
|
30992
|
+
console.log(`Resumed ${results.length - failed.length}/${results.length} monitor(s)`);
|
|
30993
|
+
if (failed.length > 0) {
|
|
30994
|
+
failed.forEach((r) => error(` Monitor ${r.id} (${r.name}): ${r.error}`));
|
|
30995
|
+
process.exit(1);
|
|
30996
|
+
}
|
|
30997
|
+
} catch (err) {
|
|
30998
|
+
handleError(err, opts);
|
|
30999
|
+
}
|
|
31000
|
+
});
|
|
30687
31001
|
monitors.command("set-notification <id>").description("Assign or remove a notification channel from a monitor").requiredOption("--notification-id <nid>", "ID of the notification channel to assign").option("--remove", "Remove the notification instead of assigning it").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30688
31002
|
"after",
|
|
30689
31003
|
`
|
|
@@ -30730,27 +31044,32 @@ ${source_default.dim("Bulk assign via pipe:")}
|
|
|
30730
31044
|
|
|
30731
31045
|
// src/commands/heartbeat.ts
|
|
30732
31046
|
function heartbeatCommand(program3) {
|
|
30733
|
-
program3.command("heartbeat
|
|
31047
|
+
const hb = program3.command("heartbeat").description("View heartbeat history or send push heartbeats to monitors").addHelpText(
|
|
31048
|
+
"after",
|
|
31049
|
+
`
|
|
31050
|
+
${source_default.dim("Subcommands:")}
|
|
31051
|
+
${source_default.cyan("heartbeat view <monitor-id>")} View recent heartbeats for a monitor
|
|
31052
|
+
${source_default.cyan("heartbeat send <push-token>")} Send a push heartbeat (for scripts / GitHub Actions)
|
|
31053
|
+
|
|
31054
|
+
${source_default.dim("Run")} ${source_default.cyan("kuma heartbeat <subcommand> --help")} ${source_default.dim("for examples.")}
|
|
31055
|
+
`
|
|
31056
|
+
);
|
|
31057
|
+
hb.command("view <monitor-id>").description("View recent heartbeats (check results) for a monitor").option("--limit <n>", "Maximum number of heartbeats to display (default: 20)", "20").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30734
31058
|
"after",
|
|
30735
31059
|
`
|
|
30736
31060
|
${source_default.dim("Examples:")}
|
|
30737
|
-
${source_default.cyan("kuma heartbeat 42")}
|
|
30738
|
-
${source_default.cyan("kuma heartbeat 42 --limit 50")}
|
|
30739
|
-
${source_default.cyan("kuma heartbeat 42 --json")}
|
|
30740
|
-
${source_default.cyan("kuma heartbeat 42 --json | jq '.data[] | select(.status == 0)'")}
|
|
31061
|
+
${source_default.cyan("kuma heartbeat view 42")}
|
|
31062
|
+
${source_default.cyan("kuma heartbeat view 42 --limit 50")}
|
|
31063
|
+
${source_default.cyan("kuma heartbeat view 42 --json")}
|
|
31064
|
+
${source_default.cyan("kuma heartbeat view 42 --json | jq '.data[] | select(.status == 0)'")}
|
|
30741
31065
|
`
|
|
30742
31066
|
).action(async (monitorId, opts) => {
|
|
30743
31067
|
const config = getConfig();
|
|
30744
31068
|
if (!config) requireAuth(opts);
|
|
30745
31069
|
const json = isJsonMode(opts);
|
|
30746
31070
|
try {
|
|
30747
|
-
const client = await createAuthenticatedClient(
|
|
30748
|
-
|
|
30749
|
-
config.token
|
|
30750
|
-
);
|
|
30751
|
-
const heartbeats = await client.getHeartbeatList(
|
|
30752
|
-
parseInt(monitorId, 10)
|
|
30753
|
-
);
|
|
31071
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
31072
|
+
const heartbeats = await client.getHeartbeatList(parseInt(monitorId, 10));
|
|
30754
31073
|
client.disconnect();
|
|
30755
31074
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
30756
31075
|
const recent = heartbeats.slice(-limit).reverse();
|
|
@@ -30762,12 +31081,12 @@ ${source_default.dim("Examples:")}
|
|
|
30762
31081
|
return;
|
|
30763
31082
|
}
|
|
30764
31083
|
const table = createTable(["Time", "Status", "Ping", "Message"]);
|
|
30765
|
-
recent.forEach((
|
|
31084
|
+
recent.forEach((hb2) => {
|
|
30766
31085
|
table.push([
|
|
30767
|
-
formatDate(
|
|
30768
|
-
statusLabel(
|
|
30769
|
-
formatPing(
|
|
30770
|
-
|
|
31086
|
+
formatDate(hb2.time),
|
|
31087
|
+
statusLabel(hb2.status),
|
|
31088
|
+
formatPing(hb2.ping),
|
|
31089
|
+
hb2.msg ?? "\u2014"
|
|
30771
31090
|
]);
|
|
30772
31091
|
});
|
|
30773
31092
|
console.log(table.toString());
|
|
@@ -30777,6 +31096,81 @@ Showing last ${recent.length} heartbeat(s)`);
|
|
|
30777
31096
|
handleError(err, opts);
|
|
30778
31097
|
}
|
|
30779
31098
|
});
|
|
31099
|
+
hb.command("send <push-token>").description("Send a push heartbeat to a Kuma push monitor (for scripts and GitHub Actions)").option("--status <status>", "Heartbeat status: up, down, maintenance (default: up)").option("--msg <message>", "Optional status message").option("--ping <ms>", "Optional response time in milliseconds").option("--url <url>", "Kuma base URL (defaults to saved login URL)").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
31100
|
+
"after",
|
|
31101
|
+
`
|
|
31102
|
+
${source_default.dim("Examples:")}
|
|
31103
|
+
${source_default.cyan("kuma heartbeat send abc123")}
|
|
31104
|
+
${source_default.cyan('kuma heartbeat send abc123 --status down --msg "Job failed"')}
|
|
31105
|
+
${source_default.cyan('kuma heartbeat send abc123 --msg "Deploy complete" --ping 42')}
|
|
31106
|
+
${source_default.cyan("kuma heartbeat send abc123 --json")}
|
|
31107
|
+
|
|
31108
|
+
${source_default.dim("GitHub Actions usage:")}
|
|
31109
|
+
${source_default.cyan("- name: Heartbeat")}
|
|
31110
|
+
${source_default.cyan(" if: always()")}
|
|
31111
|
+
${source_default.cyan(" run: kuma heartbeat send ${{ secrets.RUNNER_PUSH_TOKEN }} --status ${{ job.status == 'success' && 'up' || 'down' }}")}
|
|
31112
|
+
|
|
31113
|
+
${source_default.dim("Finding your push token:")}
|
|
31114
|
+
Create a "Push" monitor in Kuma UI. The push URL is:
|
|
31115
|
+
https://kuma.example.com/api/push/<token>
|
|
31116
|
+
Use only the <token> part.
|
|
31117
|
+
|
|
31118
|
+
Or get it from CLI: kuma monitors create --type push --name "my-runner" --json | jq '.data.pushToken'
|
|
31119
|
+
`
|
|
31120
|
+
).action(async (pushToken, opts) => {
|
|
31121
|
+
const json = isJsonMode(opts);
|
|
31122
|
+
const VALID_STATUSES = ["up", "down", "maintenance"];
|
|
31123
|
+
const statusKey = (opts.status ?? "up").toLowerCase();
|
|
31124
|
+
if (!VALID_STATUSES.includes(statusKey)) {
|
|
31125
|
+
const msg = `Invalid status "${opts.status}". Valid: up, down, maintenance`;
|
|
31126
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31127
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31128
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31129
|
+
}
|
|
31130
|
+
let baseUrl = opts.url;
|
|
31131
|
+
if (!baseUrl) {
|
|
31132
|
+
const config = getConfig();
|
|
31133
|
+
if (!config) {
|
|
31134
|
+
const msg = "No --url specified and not logged in. Run: kuma login <url> or pass --url";
|
|
31135
|
+
if (json) jsonError(msg, EXIT_CODES.AUTH);
|
|
31136
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31137
|
+
process.exit(EXIT_CODES.AUTH);
|
|
31138
|
+
}
|
|
31139
|
+
baseUrl = config.url;
|
|
31140
|
+
}
|
|
31141
|
+
const pushUrl = new URL(`${baseUrl.replace(/\/$/, "")}/api/push/${pushToken}`);
|
|
31142
|
+
pushUrl.searchParams.set("status", statusKey);
|
|
31143
|
+
if (opts.msg) pushUrl.searchParams.set("msg", opts.msg);
|
|
31144
|
+
if (opts.ping) pushUrl.searchParams.set("ping", opts.ping);
|
|
31145
|
+
try {
|
|
31146
|
+
const res = await fetch(pushUrl.toString(), {
|
|
31147
|
+
signal: AbortSignal.timeout(1e4)
|
|
31148
|
+
});
|
|
31149
|
+
if (!res.ok) {
|
|
31150
|
+
const body = await res.text().catch(() => "");
|
|
31151
|
+
const msg = `Push failed (HTTP ${res.status}): ${body || res.statusText}`;
|
|
31152
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31153
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31154
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31155
|
+
}
|
|
31156
|
+
const data = await res.json().catch(() => ({ ok: true }));
|
|
31157
|
+
if (data.ok === false) {
|
|
31158
|
+
const msg = data.msg ?? "Kuma rejected the push heartbeat";
|
|
31159
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31160
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31161
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31162
|
+
}
|
|
31163
|
+
if (json) {
|
|
31164
|
+
jsonOut({ pushToken, status: statusKey, msg: opts.msg ?? null });
|
|
31165
|
+
}
|
|
31166
|
+
success(`Push heartbeat sent (${statusKey}${opts.msg ? ` \u2014 ${opts.msg}` : ""})`);
|
|
31167
|
+
} catch (e) {
|
|
31168
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
31169
|
+
if (json) jsonError(msg, EXIT_CODES.CONNECTION);
|
|
31170
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31171
|
+
process.exit(EXIT_CODES.CONNECTION);
|
|
31172
|
+
}
|
|
31173
|
+
});
|
|
30780
31174
|
}
|
|
30781
31175
|
|
|
30782
31176
|
// src/commands/status-pages.ts
|
|
@@ -31160,7 +31554,7 @@ ${source_default.bold("Quick Start:")}
|
|
|
31160
31554
|
${source_default.cyan("kuma login https://kuma.example.com")} Authenticate (saves session)
|
|
31161
31555
|
${source_default.cyan("kuma monitors list")} List all monitors + status
|
|
31162
31556
|
${source_default.cyan('kuma monitors add --name "My API" --type http --url https://api.example.com')}
|
|
31163
|
-
${source_default.cyan("kuma heartbeat 42")}
|
|
31557
|
+
${source_default.cyan("kuma heartbeat view 42")} View recent heartbeats for monitor 42
|
|
31164
31558
|
${source_default.cyan("kuma logout")} Clear saved session
|
|
31165
31559
|
|
|
31166
31560
|
${source_default.bold("JSON / scripting mode:")}
|