@hasna/uptime 0.1.3 → 0.1.4
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/CHANGELOG.md +26 -0
- package/README.md +35 -3
- package/dist/api.js +626 -4
- package/dist/cli/index.js +757 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +664 -4
- package/dist/report.d.ts +2 -7
- package/dist/report.d.ts.map +1 -1
- package/dist/service.d.ts +41 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +577 -4
- package/dist/store.d.ts +22 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +427 -4
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -15117,9 +15117,24 @@ function uptimeHostedFallbackDbPath() {
|
|
|
15117
15117
|
|
|
15118
15118
|
// src/store.ts
|
|
15119
15119
|
var SECRET_URL_PARAM_PATTERN = /(token|secret|password|passwd|api[_-]?key|access[_-]?token|auth|credential|session)/i;
|
|
15120
|
-
var REQUIRED_TABLES = [
|
|
15120
|
+
var REQUIRED_TABLES = [
|
|
15121
|
+
"schema_migrations",
|
|
15122
|
+
"monitors",
|
|
15123
|
+
"check_results",
|
|
15124
|
+
"incidents",
|
|
15125
|
+
"check_leases",
|
|
15126
|
+
"monitor_provenance",
|
|
15127
|
+
"import_batches",
|
|
15128
|
+
"probe_identities",
|
|
15129
|
+
"probe_check_jobs",
|
|
15130
|
+
"probe_submissions",
|
|
15131
|
+
"report_schedules",
|
|
15132
|
+
"report_runs",
|
|
15133
|
+
"audit_events"
|
|
15134
|
+
];
|
|
15121
15135
|
var PROBE_TABLES = new Set(["probe_identities", "probe_check_jobs", "probe_submissions"]);
|
|
15122
|
-
var
|
|
15136
|
+
var REPORT_AUDIT_TABLES = new Set(["report_schedules", "report_runs", "audit_events"]);
|
|
15137
|
+
var CURRENT_SCHEMA_VERSION = "3";
|
|
15123
15138
|
|
|
15124
15139
|
class StaleCheckResultError extends Error {
|
|
15125
15140
|
constructor(message) {
|
|
@@ -15279,6 +15294,44 @@ class UptimeStore {
|
|
|
15279
15294
|
acquired_at TEXT NOT NULL
|
|
15280
15295
|
)
|
|
15281
15296
|
`);
|
|
15297
|
+
this.db.run(`
|
|
15298
|
+
CREATE TABLE IF NOT EXISTS report_schedules (
|
|
15299
|
+
id TEXT PRIMARY KEY,
|
|
15300
|
+
name TEXT NOT NULL UNIQUE,
|
|
15301
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
15302
|
+
interval_seconds INTEGER NOT NULL,
|
|
15303
|
+
next_run_at TEXT NOT NULL,
|
|
15304
|
+
last_run_at TEXT,
|
|
15305
|
+
subject TEXT,
|
|
15306
|
+
channels_json TEXT NOT NULL,
|
|
15307
|
+
created_at TEXT NOT NULL,
|
|
15308
|
+
updated_at TEXT NOT NULL
|
|
15309
|
+
)
|
|
15310
|
+
`);
|
|
15311
|
+
this.db.run(`
|
|
15312
|
+
CREATE TABLE IF NOT EXISTS report_runs (
|
|
15313
|
+
id TEXT PRIMARY KEY,
|
|
15314
|
+
schedule_id TEXT REFERENCES report_schedules(id) ON DELETE SET NULL,
|
|
15315
|
+
status TEXT NOT NULL CHECK (status IN ('success', 'failed')),
|
|
15316
|
+
started_at TEXT NOT NULL,
|
|
15317
|
+
finished_at TEXT NOT NULL,
|
|
15318
|
+
deliveries_json TEXT NOT NULL,
|
|
15319
|
+
error TEXT,
|
|
15320
|
+
report_json TEXT
|
|
15321
|
+
)
|
|
15322
|
+
`);
|
|
15323
|
+
this.db.run(`
|
|
15324
|
+
CREATE TABLE IF NOT EXISTS audit_events (
|
|
15325
|
+
id TEXT PRIMARY KEY,
|
|
15326
|
+
action TEXT NOT NULL,
|
|
15327
|
+
resource_type TEXT,
|
|
15328
|
+
resource_id TEXT,
|
|
15329
|
+
message TEXT,
|
|
15330
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
15331
|
+
actor TEXT,
|
|
15332
|
+
created_at TEXT NOT NULL
|
|
15333
|
+
)
|
|
15334
|
+
`);
|
|
15282
15335
|
this.db.run(`
|
|
15283
15336
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
15284
15337
|
key TEXT PRIMARY KEY,
|
|
@@ -15296,6 +15349,10 @@ class UptimeStore {
|
|
|
15296
15349
|
this.db.run("CREATE INDEX IF NOT EXISTS idx_probe_submissions_probe_time ON probe_submissions(probe_id, submitted_at DESC)");
|
|
15297
15350
|
this.db.run("CREATE INDEX IF NOT EXISTS idx_probe_submissions_monitor_time ON probe_submissions(monitor_id, checked_at DESC)");
|
|
15298
15351
|
this.db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_probe_submissions_job ON probe_submissions(job_id) WHERE job_id IS NOT NULL AND job_id != ''");
|
|
15352
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_report_schedules_due ON report_schedules(enabled, next_run_at)");
|
|
15353
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_report_runs_schedule_time ON report_runs(schedule_id, started_at DESC)");
|
|
15354
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_audit_events_resource_time ON audit_events(resource_type, resource_id, created_at DESC)");
|
|
15355
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_audit_events_time ON audit_events(created_at DESC)");
|
|
15299
15356
|
}
|
|
15300
15357
|
backup(destinationPath) {
|
|
15301
15358
|
if (this.dbPath === ":memory:" && !destinationPath) {
|
|
@@ -15592,6 +15649,136 @@ class UptimeStore {
|
|
|
15592
15649
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(receipt.id, receipt.probeId, receipt.jobId, receipt.monitorId, receipt.checkResultId, receipt.nonce, receipt.checkedAt, receipt.submittedAt);
|
|
15593
15650
|
return receipt;
|
|
15594
15651
|
}
|
|
15652
|
+
createReportSchedule(input) {
|
|
15653
|
+
const normalized = normalizeReportScheduleInput(input);
|
|
15654
|
+
const now = new Date().toISOString();
|
|
15655
|
+
const schedule = {
|
|
15656
|
+
id: newId("rps"),
|
|
15657
|
+
name: normalized.name,
|
|
15658
|
+
enabled: normalized.enabled,
|
|
15659
|
+
intervalSeconds: normalized.intervalSeconds,
|
|
15660
|
+
nextRunAt: normalized.nextRunAt,
|
|
15661
|
+
lastRunAt: null,
|
|
15662
|
+
subject: normalized.subject,
|
|
15663
|
+
channels: normalized.channels,
|
|
15664
|
+
createdAt: now,
|
|
15665
|
+
updatedAt: now
|
|
15666
|
+
};
|
|
15667
|
+
this.db.query(`INSERT INTO report_schedules (
|
|
15668
|
+
id, name, enabled, interval_seconds, next_run_at, last_run_at,
|
|
15669
|
+
subject, channels_json, created_at, updated_at
|
|
15670
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(schedule.id, schedule.name, schedule.enabled ? 1 : 0, schedule.intervalSeconds, schedule.nextRunAt, schedule.lastRunAt, schedule.subject, JSON.stringify(schedule.channels), schedule.createdAt, schedule.updatedAt);
|
|
15671
|
+
return schedule;
|
|
15672
|
+
}
|
|
15673
|
+
listReportSchedules(options = {}) {
|
|
15674
|
+
const rows = options.includeDisabled ? this.db.query("SELECT * FROM report_schedules ORDER BY name ASC").all() : this.db.query("SELECT * FROM report_schedules WHERE enabled = 1 ORDER BY name ASC").all();
|
|
15675
|
+
return rows.map(reportScheduleFromRow);
|
|
15676
|
+
}
|
|
15677
|
+
listDueReportSchedules(nowIso = new Date().toISOString()) {
|
|
15678
|
+
assertIsoTimestamp(nowIso, "Report schedule due timestamp");
|
|
15679
|
+
const rows = this.db.query("SELECT * FROM report_schedules WHERE enabled = 1 AND next_run_at <= ? ORDER BY next_run_at ASC, name ASC").all(nowIso);
|
|
15680
|
+
return rows.map(reportScheduleFromRow);
|
|
15681
|
+
}
|
|
15682
|
+
getReportSchedule(idOrName) {
|
|
15683
|
+
const row = this.db.query("SELECT * FROM report_schedules WHERE id = ? OR name = ?").get(idOrName, idOrName);
|
|
15684
|
+
return row ? reportScheduleFromRow(row) : null;
|
|
15685
|
+
}
|
|
15686
|
+
updateReportSchedule(idOrName, input) {
|
|
15687
|
+
const current = this.getReportSchedule(idOrName);
|
|
15688
|
+
if (!current)
|
|
15689
|
+
throw new Error(`Report schedule not found: ${idOrName}`);
|
|
15690
|
+
const normalized = normalizeReportScheduleInput({
|
|
15691
|
+
name: input.name ?? current.name,
|
|
15692
|
+
intervalSeconds: input.intervalSeconds ?? current.intervalSeconds,
|
|
15693
|
+
nextRunAt: input.nextRunAt ?? current.nextRunAt,
|
|
15694
|
+
enabled: input.enabled ?? current.enabled,
|
|
15695
|
+
subject: input.subject === undefined ? current.subject : input.subject,
|
|
15696
|
+
channels: input.channels ?? current.channels
|
|
15697
|
+
});
|
|
15698
|
+
const updatedAt = new Date().toISOString();
|
|
15699
|
+
this.db.query(`UPDATE report_schedules SET
|
|
15700
|
+
name = ?, enabled = ?, interval_seconds = ?, next_run_at = ?,
|
|
15701
|
+
subject = ?, channels_json = ?, updated_at = ?
|
|
15702
|
+
WHERE id = ?`).run(normalized.name, normalized.enabled ? 1 : 0, normalized.intervalSeconds, normalized.nextRunAt, normalized.subject, JSON.stringify(normalized.channels), updatedAt, current.id);
|
|
15703
|
+
return this.getReportSchedule(current.id);
|
|
15704
|
+
}
|
|
15705
|
+
deleteReportSchedule(idOrName) {
|
|
15706
|
+
const current = this.getReportSchedule(idOrName);
|
|
15707
|
+
if (!current)
|
|
15708
|
+
return false;
|
|
15709
|
+
this.db.query("DELETE FROM report_schedules WHERE id = ?").run(current.id);
|
|
15710
|
+
return true;
|
|
15711
|
+
}
|
|
15712
|
+
recordReportRun(input) {
|
|
15713
|
+
const startedAt = input.startedAt ?? new Date().toISOString();
|
|
15714
|
+
const finishedAt = input.finishedAt ?? new Date().toISOString();
|
|
15715
|
+
assertIsoTimestamp(startedAt, "Report run startedAt");
|
|
15716
|
+
assertIsoTimestamp(finishedAt, "Report run finishedAt");
|
|
15717
|
+
if (input.status !== "success" && input.status !== "failed") {
|
|
15718
|
+
throw new Error("Report run status must be success or failed");
|
|
15719
|
+
}
|
|
15720
|
+
if (input.scheduleId && !this.getReportSchedule(input.scheduleId)) {
|
|
15721
|
+
throw new Error(`Report schedule not found: ${input.scheduleId}`);
|
|
15722
|
+
}
|
|
15723
|
+
const run = {
|
|
15724
|
+
id: newId("rpr"),
|
|
15725
|
+
scheduleId: input.scheduleId ?? null,
|
|
15726
|
+
status: input.status,
|
|
15727
|
+
startedAt,
|
|
15728
|
+
finishedAt,
|
|
15729
|
+
deliveries: normalizeReportDeliveries(input.deliveries ?? []),
|
|
15730
|
+
error: normalizeNullableRedactedText(input.error, "Report run error", 1000),
|
|
15731
|
+
reportJson: input.reportJson ?? null
|
|
15732
|
+
};
|
|
15733
|
+
this.db.query(`INSERT INTO report_runs (
|
|
15734
|
+
id, schedule_id, status, started_at, finished_at, deliveries_json,
|
|
15735
|
+
error, report_json
|
|
15736
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(run.id, run.scheduleId, run.status, run.startedAt, run.finishedAt, JSON.stringify(run.deliveries), run.error, run.reportJson ? JSON.stringify(run.reportJson) : null);
|
|
15737
|
+
if (run.scheduleId) {
|
|
15738
|
+
this.advanceReportSchedule(run.scheduleId, run.finishedAt);
|
|
15739
|
+
}
|
|
15740
|
+
return run;
|
|
15741
|
+
}
|
|
15742
|
+
listReportRuns(options = {}) {
|
|
15743
|
+
const limit = clampLimit(options.limit ?? 50);
|
|
15744
|
+
const rows = options.scheduleId ? this.db.query("SELECT * FROM report_runs WHERE schedule_id = ? ORDER BY started_at DESC, id DESC LIMIT ?").all(options.scheduleId, limit) : this.db.query("SELECT * FROM report_runs ORDER BY started_at DESC, id DESC LIMIT ?").all(limit);
|
|
15745
|
+
return rows.map(reportRunFromRow);
|
|
15746
|
+
}
|
|
15747
|
+
recordAuditEvent(input) {
|
|
15748
|
+
const action = normalizeAuditText(input.action, "Audit action", 160);
|
|
15749
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
15750
|
+
assertIsoTimestamp(createdAt, "Audit event createdAt");
|
|
15751
|
+
const event = {
|
|
15752
|
+
id: newId("aud"),
|
|
15753
|
+
action,
|
|
15754
|
+
resourceType: normalizeNullableAuditText(input.resourceType, "Audit resourceType", 80),
|
|
15755
|
+
resourceId: normalizeNullableAuditText(input.resourceId, "Audit resourceId", 160),
|
|
15756
|
+
message: normalizeNullableAuditText(input.message, "Audit message", 500),
|
|
15757
|
+
metadata: normalizeAuditMetadata(input.metadata ?? {}),
|
|
15758
|
+
actor: normalizeNullableAuditText(input.actor, "Audit actor", 160),
|
|
15759
|
+
createdAt
|
|
15760
|
+
};
|
|
15761
|
+
this.db.query(`INSERT INTO audit_events (
|
|
15762
|
+
id, action, resource_type, resource_id, message, metadata_json, actor, created_at
|
|
15763
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(event.id, event.action, event.resourceType, event.resourceId, event.message, JSON.stringify(event.metadata), event.actor, event.createdAt);
|
|
15764
|
+
return event;
|
|
15765
|
+
}
|
|
15766
|
+
listAuditEvents(options = {}) {
|
|
15767
|
+
const clauses = [];
|
|
15768
|
+
const args = [];
|
|
15769
|
+
if (options.resourceType) {
|
|
15770
|
+
clauses.push("resource_type = ?");
|
|
15771
|
+
args.push(options.resourceType);
|
|
15772
|
+
}
|
|
15773
|
+
if (options.resourceId) {
|
|
15774
|
+
clauses.push("resource_id = ?");
|
|
15775
|
+
args.push(options.resourceId);
|
|
15776
|
+
}
|
|
15777
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
15778
|
+
args.push(clampLimit(options.limit ?? 50));
|
|
15779
|
+
const rows = this.db.query(`SELECT * FROM audit_events ${where} ORDER BY created_at DESC, id DESC LIMIT ?`).all(...args);
|
|
15780
|
+
return rows.map(auditEventFromRow);
|
|
15781
|
+
}
|
|
15595
15782
|
acquireCheckLease(monitorId, owner, ttlMs) {
|
|
15596
15783
|
const now = new Date;
|
|
15597
15784
|
const nowIso = now.toISOString();
|
|
@@ -15774,6 +15961,18 @@ class UptimeStore {
|
|
|
15774
15961
|
closeOpenIncident(monitorId, closedAt) {
|
|
15775
15962
|
this.db.query("UPDATE incidents SET status = 'closed', closed_at = ? WHERE monitor_id = ? AND status = 'open'").run(closedAt, monitorId);
|
|
15776
15963
|
}
|
|
15964
|
+
advanceReportSchedule(scheduleId, finishedAt) {
|
|
15965
|
+
const schedule = this.getReportSchedule(scheduleId);
|
|
15966
|
+
if (!schedule)
|
|
15967
|
+
throw new Error(`Report schedule not found: ${scheduleId}`);
|
|
15968
|
+
const finishedMs = Date.parse(finishedAt);
|
|
15969
|
+
let nextMs = Math.max(Date.parse(schedule.nextRunAt), finishedMs);
|
|
15970
|
+
do {
|
|
15971
|
+
nextMs += schedule.intervalSeconds * 1000;
|
|
15972
|
+
} while (nextMs <= finishedMs);
|
|
15973
|
+
const nextRunAt = new Date(nextMs).toISOString();
|
|
15974
|
+
this.db.query("UPDATE report_schedules SET last_run_at = ?, next_run_at = ?, updated_at = ? WHERE id = ?").run(finishedAt, nextRunAt, finishedAt, schedule.id);
|
|
15975
|
+
}
|
|
15777
15976
|
ensureColumn(table, name, definition) {
|
|
15778
15977
|
const columns = this.db.query(`PRAGMA table_info(${table})`).all();
|
|
15779
15978
|
if (!columns.some((column) => column.name === name)) {
|
|
@@ -15852,9 +16051,10 @@ function verifyBackupFile(backupPath) {
|
|
|
15852
16051
|
const missingTables = REQUIRED_TABLES.filter((table) => !tableExists(db, table));
|
|
15853
16052
|
const schemaVersion = missingTables.includes("schema_migrations") ? null : db.query("SELECT value FROM schema_migrations WHERE key = 'schema_version'").get()?.value ?? null;
|
|
15854
16053
|
const currentOk = missingTables.length === 0 && schemaVersion === CURRENT_SCHEMA_VERSION;
|
|
15855
|
-
const restorableV1 = schemaVersion === "1" && missingTables.every((table) => PROBE_TABLES.has(table));
|
|
16054
|
+
const restorableV1 = schemaVersion === "1" && missingTables.every((table) => PROBE_TABLES.has(table) || REPORT_AUDIT_TABLES.has(table));
|
|
16055
|
+
const restorableV2 = schemaVersion === "2" && missingTables.every((table) => REPORT_AUDIT_TABLES.has(table));
|
|
15856
16056
|
return {
|
|
15857
|
-
ok: integrity === "ok" && (currentOk || restorableV1),
|
|
16057
|
+
ok: integrity === "ok" && (currentOk || restorableV1 || restorableV2),
|
|
15858
16058
|
backupPath,
|
|
15859
16059
|
integrity,
|
|
15860
16060
|
schemaVersion,
|
|
@@ -16018,6 +16218,175 @@ function normalizeScheduleSlot(value) {
|
|
|
16018
16218
|
rejectControlCharacters2(slot, "Probe job scheduleSlot");
|
|
16019
16219
|
return slot;
|
|
16020
16220
|
}
|
|
16221
|
+
function normalizeReportScheduleInput(input) {
|
|
16222
|
+
const name = input.name?.trim();
|
|
16223
|
+
if (!name)
|
|
16224
|
+
throw new Error("Report schedule name is required");
|
|
16225
|
+
rejectControlCharacters2(name, "Report schedule name");
|
|
16226
|
+
const intervalSeconds = boundedInteger2(input.intervalSeconds, "intervalSeconds", MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS);
|
|
16227
|
+
const nextRunAt = input.nextRunAt ?? new Date().toISOString();
|
|
16228
|
+
assertIsoTimestamp(nextRunAt, "Report schedule nextRunAt");
|
|
16229
|
+
const enabled = normalizeEnabled(input.enabled);
|
|
16230
|
+
const subject = normalizeNullableBoundedText(input.subject, "Report schedule subject", 200);
|
|
16231
|
+
const channels = normalizeReportChannels(input.channels);
|
|
16232
|
+
return { name, intervalSeconds, nextRunAt, enabled, subject, channels };
|
|
16233
|
+
}
|
|
16234
|
+
function normalizeReportChannels(channels) {
|
|
16235
|
+
if (!channels || typeof channels !== "object")
|
|
16236
|
+
throw new Error("Report schedule channels are required");
|
|
16237
|
+
const normalized = {};
|
|
16238
|
+
if (channels.email !== undefined)
|
|
16239
|
+
normalized.email = normalizeChannelTarget(channels.email, "email", ["apiUrl", "from", "to", "subject", "providerId"]);
|
|
16240
|
+
if (channels.sms !== undefined)
|
|
16241
|
+
normalized.sms = normalizeChannelTarget(channels.sms, "sms", ["apiUrl", "from", "to"]);
|
|
16242
|
+
if (channels.logs !== undefined)
|
|
16243
|
+
normalized.logs = normalizeChannelTarget(channels.logs, "logs", ["apiUrl", "projectId", "environment", "service"]);
|
|
16244
|
+
if (!normalized.email && !normalized.sms && !normalized.logs) {
|
|
16245
|
+
throw new Error("Report schedule requires at least one channel");
|
|
16246
|
+
}
|
|
16247
|
+
return normalized;
|
|
16248
|
+
}
|
|
16249
|
+
function normalizeChannelTarget(value, channel, allowedKeys) {
|
|
16250
|
+
if (value === false || value == null)
|
|
16251
|
+
return false;
|
|
16252
|
+
if (value === true)
|
|
16253
|
+
return true;
|
|
16254
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
16255
|
+
throw new Error(`Report schedule ${channel} channel must be true or an object`);
|
|
16256
|
+
}
|
|
16257
|
+
const record2 = value;
|
|
16258
|
+
const normalized = {};
|
|
16259
|
+
for (const [key, rawValue] of Object.entries(record2)) {
|
|
16260
|
+
if (!allowedKeys.includes(key)) {
|
|
16261
|
+
if (/key|token|secret|password|credential|auth/i.test(key)) {
|
|
16262
|
+
throw new Error("Report schedules must not persist API keys or tokens; use environment variables or cloud channel refs");
|
|
16263
|
+
}
|
|
16264
|
+
throw new Error(`Unsupported report schedule ${channel} channel field: ${key}`);
|
|
16265
|
+
}
|
|
16266
|
+
if (rawValue === undefined || rawValue === null || rawValue === "")
|
|
16267
|
+
continue;
|
|
16268
|
+
if (key === "apiUrl" && Array.isArray(rawValue)) {
|
|
16269
|
+
throw new Error(`Report schedule ${channel}.${key} must be a string`);
|
|
16270
|
+
}
|
|
16271
|
+
if (Array.isArray(rawValue)) {
|
|
16272
|
+
const items = rawValue.map((item) => normalizeBoundedText(String(item), `Report schedule ${channel}.${key}`, 300));
|
|
16273
|
+
if (items.length > 0)
|
|
16274
|
+
normalized[key] = items;
|
|
16275
|
+
} else if (typeof rawValue === "string" || typeof rawValue === "number") {
|
|
16276
|
+
normalized[key] = key === "apiUrl" ? normalizeHttpIntegrationUrl(String(rawValue)) : normalizeBoundedText(String(rawValue), `Report schedule ${channel}.${key}`, 500);
|
|
16277
|
+
} else {
|
|
16278
|
+
throw new Error(`Report schedule ${channel}.${key} must be a string or string array`);
|
|
16279
|
+
}
|
|
16280
|
+
}
|
|
16281
|
+
return Object.keys(normalized).length > 0 ? normalized : true;
|
|
16282
|
+
}
|
|
16283
|
+
function normalizeHttpIntegrationUrl(value) {
|
|
16284
|
+
const parsed = new URL(value.trim());
|
|
16285
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
16286
|
+
throw new Error("Report schedule integration API URL must use http or https");
|
|
16287
|
+
}
|
|
16288
|
+
if (parsed.username || parsed.password) {
|
|
16289
|
+
throw new Error("Report schedule integration API URL must not include credentials");
|
|
16290
|
+
}
|
|
16291
|
+
for (const key of parsed.searchParams.keys()) {
|
|
16292
|
+
if (SECRET_URL_PARAM_PATTERN.test(key)) {
|
|
16293
|
+
throw new Error("Report schedule integration API URL must not include secret query parameters");
|
|
16294
|
+
}
|
|
16295
|
+
}
|
|
16296
|
+
parsed.hash = "";
|
|
16297
|
+
return parsed.toString();
|
|
16298
|
+
}
|
|
16299
|
+
function normalizeReportDeliveries(deliveries) {
|
|
16300
|
+
return deliveries.map((delivery) => {
|
|
16301
|
+
if (delivery.channel !== "email" && delivery.channel !== "sms" && delivery.channel !== "logs") {
|
|
16302
|
+
throw new Error("Report delivery channel must be email, sms, or logs");
|
|
16303
|
+
}
|
|
16304
|
+
return {
|
|
16305
|
+
channel: delivery.channel,
|
|
16306
|
+
ok: Boolean(delivery.ok),
|
|
16307
|
+
status: delivery.status,
|
|
16308
|
+
id: delivery.id === undefined ? undefined : normalizeRedactedText(String(delivery.id), "Report delivery id", 300),
|
|
16309
|
+
error: delivery.error === undefined ? undefined : normalizeRedactedText(String(delivery.error), "Report delivery error", 1000)
|
|
16310
|
+
};
|
|
16311
|
+
});
|
|
16312
|
+
}
|
|
16313
|
+
function normalizeAuditText(value, label, maxLength) {
|
|
16314
|
+
return normalizeBoundedText(value ?? "", label, maxLength);
|
|
16315
|
+
}
|
|
16316
|
+
function normalizeNullableAuditText(value, label, maxLength) {
|
|
16317
|
+
return normalizeNullableBoundedText(value, label, maxLength);
|
|
16318
|
+
}
|
|
16319
|
+
function normalizeNullableBoundedText(value, label, maxLength) {
|
|
16320
|
+
if (value == null)
|
|
16321
|
+
return null;
|
|
16322
|
+
const normalized = normalizeRedactedText(value, label, maxLength);
|
|
16323
|
+
return normalized || null;
|
|
16324
|
+
}
|
|
16325
|
+
function normalizeBoundedText(value, label, maxLength) {
|
|
16326
|
+
const normalized = value.trim();
|
|
16327
|
+
rejectControlCharacters2(normalized, label);
|
|
16328
|
+
if (normalized.length > maxLength)
|
|
16329
|
+
throw new Error(`${label} is too long`);
|
|
16330
|
+
return normalized;
|
|
16331
|
+
}
|
|
16332
|
+
function normalizeNullableRedactedText(value, label, maxLength) {
|
|
16333
|
+
if (value == null)
|
|
16334
|
+
return null;
|
|
16335
|
+
const normalized = normalizeRedactedText(value, label, maxLength);
|
|
16336
|
+
return normalized || null;
|
|
16337
|
+
}
|
|
16338
|
+
function normalizeRedactedText(value, label, maxLength) {
|
|
16339
|
+
return normalizeBoundedText(redactSecretString(value), label, maxLength);
|
|
16340
|
+
}
|
|
16341
|
+
function normalizeAuditMetadata(value) {
|
|
16342
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
16343
|
+
throw new Error("Audit metadata must be an object");
|
|
16344
|
+
}
|
|
16345
|
+
return redactAuditSecrets(JSON.parse(JSON.stringify(value)));
|
|
16346
|
+
}
|
|
16347
|
+
function redactAuditSecrets(value) {
|
|
16348
|
+
if (Array.isArray(value))
|
|
16349
|
+
return value.map(redactAuditSecrets);
|
|
16350
|
+
if (typeof value === "string")
|
|
16351
|
+
return redactSecretString(value);
|
|
16352
|
+
if (!value || typeof value !== "object")
|
|
16353
|
+
return value;
|
|
16354
|
+
const output = {};
|
|
16355
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
16356
|
+
output[key] = /key|token|secret|password|credential|auth/i.test(key) ? "[REDACTED]" : redactAuditSecrets(nested);
|
|
16357
|
+
}
|
|
16358
|
+
return output;
|
|
16359
|
+
}
|
|
16360
|
+
function redactSecretString(value) {
|
|
16361
|
+
let output = value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]");
|
|
16362
|
+
output = output.replace(/https?:\/\/[^\s"'<>]+/gi, (match) => redactUrlString(match));
|
|
16363
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(output))
|
|
16364
|
+
return output;
|
|
16365
|
+
return redactUrlString(output);
|
|
16366
|
+
}
|
|
16367
|
+
function redactUrlString(value) {
|
|
16368
|
+
let trailing = "";
|
|
16369
|
+
let candidate = value;
|
|
16370
|
+
while (/[),.;\]]$/.test(candidate)) {
|
|
16371
|
+
trailing = `${candidate.slice(-1)}${trailing}`;
|
|
16372
|
+
candidate = candidate.slice(0, -1);
|
|
16373
|
+
}
|
|
16374
|
+
try {
|
|
16375
|
+
const parsed = new URL(candidate);
|
|
16376
|
+
if (parsed.username)
|
|
16377
|
+
parsed.username = "[REDACTED]";
|
|
16378
|
+
if (parsed.password)
|
|
16379
|
+
parsed.password = "[REDACTED]";
|
|
16380
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
16381
|
+
if (SECRET_URL_PARAM_PATTERN.test(key))
|
|
16382
|
+
parsed.searchParams.set(key, "[REDACTED]");
|
|
16383
|
+
}
|
|
16384
|
+
parsed.hash = "";
|
|
16385
|
+
return `${parsed.toString()}${trailing}`;
|
|
16386
|
+
} catch {
|
|
16387
|
+
return value;
|
|
16388
|
+
}
|
|
16389
|
+
}
|
|
16021
16390
|
function assertIsoTimestamp(value, label) {
|
|
16022
16391
|
if (!Number.isFinite(Date.parse(value))) {
|
|
16023
16392
|
throw new Error(`${label} must be an ISO timestamp`);
|
|
@@ -16117,12 +16486,66 @@ function probeCheckJobFromRow(row) {
|
|
|
16117
16486
|
updatedAt: row.updated_at
|
|
16118
16487
|
};
|
|
16119
16488
|
}
|
|
16489
|
+
function reportScheduleFromRow(row) {
|
|
16490
|
+
return {
|
|
16491
|
+
id: row.id,
|
|
16492
|
+
name: row.name,
|
|
16493
|
+
enabled: Boolean(row.enabled),
|
|
16494
|
+
intervalSeconds: row.interval_seconds,
|
|
16495
|
+
nextRunAt: row.next_run_at,
|
|
16496
|
+
lastRunAt: row.last_run_at,
|
|
16497
|
+
subject: row.subject,
|
|
16498
|
+
channels: parseReportChannels(row.channels_json),
|
|
16499
|
+
createdAt: row.created_at,
|
|
16500
|
+
updatedAt: row.updated_at
|
|
16501
|
+
};
|
|
16502
|
+
}
|
|
16503
|
+
function reportRunFromRow(row) {
|
|
16504
|
+
return {
|
|
16505
|
+
id: row.id,
|
|
16506
|
+
scheduleId: row.schedule_id,
|
|
16507
|
+
status: row.status,
|
|
16508
|
+
startedAt: row.started_at,
|
|
16509
|
+
finishedAt: row.finished_at,
|
|
16510
|
+
deliveries: parseReportDeliveries(row.deliveries_json),
|
|
16511
|
+
error: row.error,
|
|
16512
|
+
reportJson: parseRecord(row.report_json)
|
|
16513
|
+
};
|
|
16514
|
+
}
|
|
16515
|
+
function auditEventFromRow(row) {
|
|
16516
|
+
return {
|
|
16517
|
+
id: row.id,
|
|
16518
|
+
action: row.action,
|
|
16519
|
+
resourceType: row.resource_type,
|
|
16520
|
+
resourceId: row.resource_id,
|
|
16521
|
+
message: row.message,
|
|
16522
|
+
metadata: parseRecord(row.metadata_json) ?? {},
|
|
16523
|
+
actor: row.actor,
|
|
16524
|
+
createdAt: row.created_at
|
|
16525
|
+
};
|
|
16526
|
+
}
|
|
16120
16527
|
function parseEvidence(value) {
|
|
16121
16528
|
if (!value)
|
|
16122
16529
|
return null;
|
|
16123
16530
|
const parsed = parseJson(value);
|
|
16124
16531
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
16125
16532
|
}
|
|
16533
|
+
function parseReportChannels(value) {
|
|
16534
|
+
const parsed = parseJson(value);
|
|
16535
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
16536
|
+
return {};
|
|
16537
|
+
return parsed;
|
|
16538
|
+
}
|
|
16539
|
+
function parseReportDeliveries(value) {
|
|
16540
|
+
const parsed = parseJson(value);
|
|
16541
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
16542
|
+
}
|
|
16543
|
+
function parseRecord(value) {
|
|
16544
|
+
if (!value)
|
|
16545
|
+
return null;
|
|
16546
|
+
const parsed = parseJson(value);
|
|
16547
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
16548
|
+
}
|
|
16126
16549
|
function parseJson(value) {
|
|
16127
16550
|
try {
|
|
16128
16551
|
return JSON.parse(value);
|
|
@@ -16444,6 +16867,7 @@ class UptimeService {
|
|
|
16444
16867
|
checkRunner;
|
|
16445
16868
|
leaseOwner = `svc_${randomUUID3().replace(/-/g, "").slice(0, 18)}`;
|
|
16446
16869
|
inFlightChecks = new Set;
|
|
16870
|
+
inFlightReportSchedules = new Set;
|
|
16447
16871
|
constructor(options = {}) {
|
|
16448
16872
|
this.store = options.store ?? new UptimeStore({ mode: "local", ...options });
|
|
16449
16873
|
this.checkRunner = options.checkRunner ?? runMonitorCheck;
|
|
@@ -16537,6 +16961,115 @@ class UptimeService {
|
|
|
16537
16961
|
}
|
|
16538
16962
|
return sendUptimeReport(this.summary(), options);
|
|
16539
16963
|
}
|
|
16964
|
+
createReportSchedule(input) {
|
|
16965
|
+
const store = this.reportStore();
|
|
16966
|
+
const schedule = store.createReportSchedule(input);
|
|
16967
|
+
this.audit("report_schedule.create", "report_schedule", schedule.id, `Created report schedule ${schedule.name}`, {
|
|
16968
|
+
name: schedule.name,
|
|
16969
|
+
enabled: schedule.enabled,
|
|
16970
|
+
intervalSeconds: schedule.intervalSeconds,
|
|
16971
|
+
channels: enabledReportChannels(schedule)
|
|
16972
|
+
});
|
|
16973
|
+
return schedule;
|
|
16974
|
+
}
|
|
16975
|
+
listReportSchedules(options = {}) {
|
|
16976
|
+
return this.reportStore().listReportSchedules(options);
|
|
16977
|
+
}
|
|
16978
|
+
getReportSchedule(idOrName) {
|
|
16979
|
+
return this.reportStore().getReportSchedule(idOrName);
|
|
16980
|
+
}
|
|
16981
|
+
updateReportSchedule(idOrName, input) {
|
|
16982
|
+
const store = this.reportStore();
|
|
16983
|
+
const schedule = store.updateReportSchedule(idOrName, input);
|
|
16984
|
+
this.audit("report_schedule.update", "report_schedule", schedule.id, `Updated report schedule ${schedule.name}`, {
|
|
16985
|
+
name: schedule.name,
|
|
16986
|
+
enabled: schedule.enabled,
|
|
16987
|
+
intervalSeconds: schedule.intervalSeconds,
|
|
16988
|
+
channels: enabledReportChannels(schedule)
|
|
16989
|
+
});
|
|
16990
|
+
return schedule;
|
|
16991
|
+
}
|
|
16992
|
+
deleteReportSchedule(idOrName) {
|
|
16993
|
+
const store = this.reportStore();
|
|
16994
|
+
const schedule = store.getReportSchedule(idOrName);
|
|
16995
|
+
const deleted = store.deleteReportSchedule(idOrName);
|
|
16996
|
+
if (deleted && schedule) {
|
|
16997
|
+
this.audit("report_schedule.delete", "report_schedule", schedule.id, `Deleted report schedule ${schedule.name}`, {
|
|
16998
|
+
name: schedule.name
|
|
16999
|
+
});
|
|
17000
|
+
}
|
|
17001
|
+
return deleted;
|
|
17002
|
+
}
|
|
17003
|
+
listReportRuns(options = {}) {
|
|
17004
|
+
return this.reportStore().listReportRuns(options);
|
|
17005
|
+
}
|
|
17006
|
+
listAuditEvents(options = {}) {
|
|
17007
|
+
return this.reportStore().listAuditEvents(options);
|
|
17008
|
+
}
|
|
17009
|
+
recordAuditEvent(input) {
|
|
17010
|
+
return this.reportStore().recordAuditEvent(input);
|
|
17011
|
+
}
|
|
17012
|
+
async runReportSchedule(idOrName, options = {}) {
|
|
17013
|
+
const store = this.reportStore();
|
|
17014
|
+
const schedule = store.getReportSchedule(idOrName);
|
|
17015
|
+
if (!schedule)
|
|
17016
|
+
throw new Error(`Report schedule not found: ${idOrName}`);
|
|
17017
|
+
if (!schedule.enabled)
|
|
17018
|
+
throw new Error(`Report schedule is disabled: ${schedule.name}`);
|
|
17019
|
+
if (this.inFlightReportSchedules.has(schedule.id))
|
|
17020
|
+
throw new Error(`Report schedule already running: ${schedule.name}`);
|
|
17021
|
+
this.inFlightReportSchedules.add(schedule.id);
|
|
17022
|
+
try {
|
|
17023
|
+
const startedAt = new Date().toISOString();
|
|
17024
|
+
let deliveries = [];
|
|
17025
|
+
let error51 = null;
|
|
17026
|
+
let reportJson = null;
|
|
17027
|
+
try {
|
|
17028
|
+
const report = this.buildReport({ subject: schedule.subject ?? undefined });
|
|
17029
|
+
reportJson = report.json;
|
|
17030
|
+
deliveries = await this.sendReport({
|
|
17031
|
+
subject: schedule.subject ?? undefined,
|
|
17032
|
+
email: schedule.channels.email,
|
|
17033
|
+
sms: schedule.channels.sms,
|
|
17034
|
+
logs: schedule.channels.logs,
|
|
17035
|
+
fetchImpl: options.fetchImpl
|
|
17036
|
+
});
|
|
17037
|
+
const failed = deliveries.filter((delivery) => !delivery.ok);
|
|
17038
|
+
if (failed.length > 0) {
|
|
17039
|
+
error51 = failed.map((delivery) => `${delivery.channel}: ${delivery.error ?? delivery.status ?? "failed"}`).join("; ");
|
|
17040
|
+
}
|
|
17041
|
+
} catch (caught) {
|
|
17042
|
+
error51 = caught instanceof Error ? caught.message : String(caught);
|
|
17043
|
+
}
|
|
17044
|
+
const finishedAt = new Date().toISOString();
|
|
17045
|
+
const run = store.recordReportRun({
|
|
17046
|
+
scheduleId: schedule.id,
|
|
17047
|
+
status: error51 ? "failed" : "success",
|
|
17048
|
+
startedAt,
|
|
17049
|
+
finishedAt,
|
|
17050
|
+
deliveries,
|
|
17051
|
+
error: error51,
|
|
17052
|
+
reportJson
|
|
17053
|
+
});
|
|
17054
|
+
this.audit("report_schedule.run", "report_schedule", schedule.id, `Ran report schedule ${schedule.name}`, {
|
|
17055
|
+
runId: run.id,
|
|
17056
|
+
status: run.status,
|
|
17057
|
+
deliveryChannels: run.deliveries.map((delivery) => ({ channel: delivery.channel, ok: delivery.ok }))
|
|
17058
|
+
});
|
|
17059
|
+
return run;
|
|
17060
|
+
} finally {
|
|
17061
|
+
this.inFlightReportSchedules.delete(schedule.id);
|
|
17062
|
+
}
|
|
17063
|
+
}
|
|
17064
|
+
async runDueReportSchedules(now = new Date, options = {}) {
|
|
17065
|
+
const store = this.reportStore();
|
|
17066
|
+
const schedules = store.listDueReportSchedules(now.toISOString());
|
|
17067
|
+
const runs = [];
|
|
17068
|
+
for (const schedule of schedules) {
|
|
17069
|
+
runs.push(await this.runReportSchedule(schedule.id, options));
|
|
17070
|
+
}
|
|
17071
|
+
return runs;
|
|
17072
|
+
}
|
|
16540
17073
|
async checkMonitor(idOrName) {
|
|
16541
17074
|
if (this.store.mode === "hosted")
|
|
16542
17075
|
throw new Error("hosted checks require check_jobs and probes");
|
|
@@ -16595,6 +17128,9 @@ class UptimeService {
|
|
|
16595
17128
|
this.runDueChecks().catch((error51) => {
|
|
16596
17129
|
console.error(error51 instanceof Error ? error51.message : String(error51));
|
|
16597
17130
|
});
|
|
17131
|
+
this.runDueReportSchedules(new Date, { fetchImpl: options.reportFetchImpl }).catch((error51) => {
|
|
17132
|
+
console.error(error51 instanceof Error ? error51.message : String(error51));
|
|
17133
|
+
});
|
|
16598
17134
|
}, tickMs);
|
|
16599
17135
|
return {
|
|
16600
17136
|
stop: () => clearInterval(timer)
|
|
@@ -16654,6 +17190,40 @@ class UptimeService {
|
|
|
16654
17190
|
}
|
|
16655
17191
|
return store;
|
|
16656
17192
|
}
|
|
17193
|
+
reportStore() {
|
|
17194
|
+
if (this.store.mode === "hosted") {
|
|
17195
|
+
throw new Error("hosted report schedules require cloud channel refs, workspace stores, and audit logging");
|
|
17196
|
+
}
|
|
17197
|
+
const store = this.store;
|
|
17198
|
+
const required2 = [
|
|
17199
|
+
"createReportSchedule",
|
|
17200
|
+
"listReportSchedules",
|
|
17201
|
+
"listDueReportSchedules",
|
|
17202
|
+
"getReportSchedule",
|
|
17203
|
+
"updateReportSchedule",
|
|
17204
|
+
"deleteReportSchedule",
|
|
17205
|
+
"recordReportRun",
|
|
17206
|
+
"listReportRuns",
|
|
17207
|
+
"recordAuditEvent",
|
|
17208
|
+
"listAuditEvents"
|
|
17209
|
+
];
|
|
17210
|
+
for (const method of required2) {
|
|
17211
|
+
if (typeof store[method] !== "function") {
|
|
17212
|
+
throw new Error("report scheduling requires a report-capable store");
|
|
17213
|
+
}
|
|
17214
|
+
}
|
|
17215
|
+
return store;
|
|
17216
|
+
}
|
|
17217
|
+
audit(action, resourceType, resourceId, message, metadata) {
|
|
17218
|
+
this.reportStore().recordAuditEvent({
|
|
17219
|
+
action,
|
|
17220
|
+
resourceType,
|
|
17221
|
+
resourceId,
|
|
17222
|
+
message,
|
|
17223
|
+
metadata,
|
|
17224
|
+
actor: "local"
|
|
17225
|
+
});
|
|
17226
|
+
}
|
|
16657
17227
|
submitProbeResultInTransaction(input) {
|
|
16658
17228
|
const store = this.probeStore();
|
|
16659
17229
|
const probe = store.getProbeIdentity(input.probeId);
|
|
@@ -16743,6 +17313,9 @@ class MonitorCheckBusyError extends Error {
|
|
|
16743
17313
|
this.name = "MonitorCheckBusyError";
|
|
16744
17314
|
}
|
|
16745
17315
|
}
|
|
17316
|
+
function enabledReportChannels(schedule) {
|
|
17317
|
+
return ["email", "sms", "logs"].filter((channel) => Boolean(schedule.channels[channel]));
|
|
17318
|
+
}
|
|
16746
17319
|
function validateProbeSubmission(input) {
|
|
16747
17320
|
if (!input.jobId.trim())
|
|
16748
17321
|
throw new Error("Probe submission jobId is required");
|
|
@@ -16833,6 +17406,21 @@ function createMcpServer(options = {}) {
|
|
|
16833
17406
|
description: "Recent downtime incidents.",
|
|
16834
17407
|
mimeType: "application/json"
|
|
16835
17408
|
}, async (uri) => jsonResource(uri, service.listIncidents({ limit: 100 })));
|
|
17409
|
+
server.registerResource("uptime_report_schedules", "uptime://report-schedules", {
|
|
17410
|
+
title: "Open Uptime Report Schedules",
|
|
17411
|
+
description: "Scheduled uptime reports and delivery channel configuration.",
|
|
17412
|
+
mimeType: "application/json"
|
|
17413
|
+
}, async (uri) => jsonResource(uri, service.listReportSchedules({ includeDisabled: true })));
|
|
17414
|
+
server.registerResource("uptime_report_runs", "uptime://report-runs", {
|
|
17415
|
+
title: "Open Uptime Report Runs",
|
|
17416
|
+
description: "Recent scheduled report delivery runs.",
|
|
17417
|
+
mimeType: "application/json"
|
|
17418
|
+
}, async (uri) => jsonResource(uri, service.listReportRuns({ limit: 100 })));
|
|
17419
|
+
server.registerResource("uptime_audit_events", "uptime://audit-events", {
|
|
17420
|
+
title: "Open Uptime Audit Events",
|
|
17421
|
+
description: "Recent local audit events.",
|
|
17422
|
+
mimeType: "application/json"
|
|
17423
|
+
}, async (uri) => jsonResource(uri, service.listAuditEvents({ limit: 100 })));
|
|
16836
17424
|
server.registerTool("uptime_create_monitor", {
|
|
16837
17425
|
title: "Create an uptime monitor",
|
|
16838
17426
|
description: "Create an HTTP or TCP uptime monitor in the local Open Uptime store.",
|
|
@@ -16939,6 +17527,56 @@ function createMcpServer(options = {}) {
|
|
|
16939
17527
|
logs: args.logs,
|
|
16940
17528
|
timeoutMs: args.timeoutMs
|
|
16941
17529
|
})));
|
|
17530
|
+
server.registerTool("uptime_create_report_schedule", {
|
|
17531
|
+
title: "Create a scheduled uptime report",
|
|
17532
|
+
description: "Create a local scheduled uptime report. Persistent schedules do not accept API keys; configure Mailery/Open Logs credentials through environment variables.",
|
|
17533
|
+
inputSchema: {
|
|
17534
|
+
name: exports_external.string(),
|
|
17535
|
+
intervalSeconds: exports_external.number().int().min(MIN_INTERVAL_SECONDS).max(MAX_INTERVAL_SECONDS),
|
|
17536
|
+
nextRunAt: exports_external.string().optional(),
|
|
17537
|
+
enabled: exports_external.boolean().optional(),
|
|
17538
|
+
subject: exports_external.string().nullable().optional(),
|
|
17539
|
+
channels: reportScheduleChannelsSchema()
|
|
17540
|
+
}
|
|
17541
|
+
}, async (args) => jsonResult(service.createReportSchedule(args)));
|
|
17542
|
+
server.registerTool("uptime_list_report_schedules", {
|
|
17543
|
+
title: "List scheduled uptime reports",
|
|
17544
|
+
description: "List local scheduled uptime reports.",
|
|
17545
|
+
inputSchema: {
|
|
17546
|
+
includeDisabled: exports_external.boolean().optional()
|
|
17547
|
+
}
|
|
17548
|
+
}, async (args) => jsonResult(service.listReportSchedules({ includeDisabled: args.includeDisabled })));
|
|
17549
|
+
server.registerTool("uptime_run_report_schedule", {
|
|
17550
|
+
title: "Run one scheduled uptime report",
|
|
17551
|
+
description: "Run a local scheduled uptime report now and record the run.",
|
|
17552
|
+
inputSchema: {
|
|
17553
|
+
idOrName: exports_external.string()
|
|
17554
|
+
}
|
|
17555
|
+
}, async (args) => jsonResult(await service.runReportSchedule(args.idOrName)));
|
|
17556
|
+
server.registerTool("uptime_run_due_report_schedules", {
|
|
17557
|
+
title: "Run due scheduled uptime reports",
|
|
17558
|
+
description: "Run all due local scheduled uptime reports and record runs.",
|
|
17559
|
+
inputSchema: {
|
|
17560
|
+
now: exports_external.string().optional()
|
|
17561
|
+
}
|
|
17562
|
+
}, async (args) => jsonResult(await service.runDueReportSchedules(args.now ? new Date(args.now) : new Date)));
|
|
17563
|
+
server.registerTool("uptime_report_runs", {
|
|
17564
|
+
title: "List scheduled report runs",
|
|
17565
|
+
description: "List local scheduled report run history.",
|
|
17566
|
+
inputSchema: {
|
|
17567
|
+
scheduleId: exports_external.string().optional(),
|
|
17568
|
+
limit: exports_external.number().int().min(1).max(MAX_RESULT_LIMIT).optional()
|
|
17569
|
+
}
|
|
17570
|
+
}, async (args) => jsonResult(service.listReportRuns({ scheduleId: args.scheduleId, limit: args.limit })));
|
|
17571
|
+
server.registerTool("uptime_audit_events", {
|
|
17572
|
+
title: "List audit events",
|
|
17573
|
+
description: "List recent local audit events.",
|
|
17574
|
+
inputSchema: {
|
|
17575
|
+
resourceType: exports_external.string().optional(),
|
|
17576
|
+
resourceId: exports_external.string().optional(),
|
|
17577
|
+
limit: exports_external.number().int().min(1).max(MAX_RESULT_LIMIT).optional()
|
|
17578
|
+
}
|
|
17579
|
+
}, async (args) => jsonResult(service.listAuditEvents({ resourceType: args.resourceType, resourceId: args.resourceId, limit: args.limit })));
|
|
16942
17580
|
server.registerTool("uptime_import_preview", {
|
|
16943
17581
|
title: "Preview an uptime inventory import",
|
|
16944
17582
|
description: "Preview monitor candidates from manual, projects, servers, domains, or deployment records without writing.",
|
|
@@ -17056,6 +17694,28 @@ function jsonResource(uri, value) {
|
|
|
17056
17694
|
function errorResult(message) {
|
|
17057
17695
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
17058
17696
|
}
|
|
17697
|
+
function reportScheduleChannelsSchema() {
|
|
17698
|
+
return exports_external.object({
|
|
17699
|
+
email: exports_external.union([exports_external.literal(true), exports_external.object({
|
|
17700
|
+
apiUrl: exports_external.string().url().optional(),
|
|
17701
|
+
from: exports_external.string().optional(),
|
|
17702
|
+
to: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
17703
|
+
subject: exports_external.string().optional(),
|
|
17704
|
+
providerId: exports_external.string().optional()
|
|
17705
|
+
}).strict()]).optional(),
|
|
17706
|
+
sms: exports_external.union([exports_external.literal(true), exports_external.object({
|
|
17707
|
+
apiUrl: exports_external.string().url().optional(),
|
|
17708
|
+
from: exports_external.string().optional(),
|
|
17709
|
+
to: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional()
|
|
17710
|
+
}).strict()]).optional(),
|
|
17711
|
+
logs: exports_external.union([exports_external.literal(true), exports_external.object({
|
|
17712
|
+
apiUrl: exports_external.string().url().optional(),
|
|
17713
|
+
projectId: exports_external.string().optional(),
|
|
17714
|
+
environment: exports_external.string().optional(),
|
|
17715
|
+
service: exports_external.string().optional()
|
|
17716
|
+
}).strict()]).optional()
|
|
17717
|
+
});
|
|
17718
|
+
}
|
|
17059
17719
|
if (import.meta.main) {
|
|
17060
17720
|
main().catch((error51) => {
|
|
17061
17721
|
console.error(error51 instanceof Error ? error51.message : String(error51));
|