@meetploy/cli 1.16.0 → 1.17.1
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/dashboard-dist/assets/{main-B-euJxpr.css → main-5Kt9I_hM.css} +1 -1
- package/dist/dashboard-dist/assets/main-Ch3tPHX5.js +354 -0
- package/dist/dashboard-dist/index.html +2 -2
- package/dist/dev.js +228 -1
- package/dist/index.js +684 -3
- package/package.json +1 -1
- package/dist/dashboard-dist/assets/main-duAiLjPq.js +0 -339
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Ploy Dev Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/main-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
7
|
+
<script type="module" crossorigin src="/assets/main-Ch3tPHX5.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/main-5Kt9I_hM.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/dist/dev.js
CHANGED
|
@@ -7,9 +7,9 @@ import 'esbuild';
|
|
|
7
7
|
import 'chokidar';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { parse } from 'yaml';
|
|
10
|
+
import { randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, randomBytes } from 'crypto';
|
|
10
11
|
import { serve } from '@hono/node-server';
|
|
11
12
|
import { Hono } from 'hono';
|
|
12
|
-
import { randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, randomBytes } from 'crypto';
|
|
13
13
|
import 'os';
|
|
14
14
|
import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
|
|
15
15
|
import Database from 'better-sqlite3';
|
|
@@ -49,6 +49,62 @@ function readPloyConfig(projectDir, configPath) {
|
|
|
49
49
|
}
|
|
50
50
|
return config;
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// ../emulator/dist/services/alarm-service.js
|
|
54
|
+
function createAlarmHandlers(db) {
|
|
55
|
+
const setHandler = async (c) => {
|
|
56
|
+
try {
|
|
57
|
+
const body = await c.req.json();
|
|
58
|
+
const { alarmName, id, scheduledTime, payload, intervalMs } = body;
|
|
59
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
60
|
+
const scheduledTimeSec = Math.floor(scheduledTime / 1e3);
|
|
61
|
+
db.prepare(`INSERT OR REPLACE INTO alarm_entries (id, alarm_name, scheduled_time, payload, interval_ms, status, created_at)
|
|
62
|
+
VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(id, alarmName, scheduledTimeSec, payload !== void 0 ? JSON.stringify(payload) : null, intervalMs ?? null, now);
|
|
63
|
+
return c.json({ success: true });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
66
|
+
return c.json({ success: false, error: message }, 500);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const getHandler = async (c) => {
|
|
70
|
+
try {
|
|
71
|
+
const body = await c.req.json();
|
|
72
|
+
const { alarmName, id } = body;
|
|
73
|
+
const row = db.prepare(`SELECT id, scheduled_time, payload, interval_ms FROM alarm_entries
|
|
74
|
+
WHERE alarm_name = ? AND id = ? AND status = 'pending'`).get(alarmName, id);
|
|
75
|
+
if (!row) {
|
|
76
|
+
return c.json({ alarm: null });
|
|
77
|
+
}
|
|
78
|
+
return c.json({
|
|
79
|
+
alarm: {
|
|
80
|
+
id: row.id,
|
|
81
|
+
scheduledTime: row.scheduled_time * 1e3,
|
|
82
|
+
payload: row.payload ? JSON.parse(row.payload) : void 0,
|
|
83
|
+
intervalMs: row.interval_ms ?? void 0
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
return c.json({ success: false, error: message }, 500);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const deleteHandler = async (c) => {
|
|
92
|
+
try {
|
|
93
|
+
const body = await c.req.json();
|
|
94
|
+
const { alarmName, id } = body;
|
|
95
|
+
db.prepare(`DELETE FROM alarm_entries WHERE alarm_name = ? AND id = ?`).run(alarmName, id);
|
|
96
|
+
return c.json({ success: true });
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
99
|
+
return c.json({ success: false, error: message }, 500);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
setHandler,
|
|
104
|
+
getHandler,
|
|
105
|
+
deleteHandler
|
|
106
|
+
};
|
|
107
|
+
}
|
|
52
108
|
function getDataDir(projectDir) {
|
|
53
109
|
return join(projectDir, ".ploy");
|
|
54
110
|
}
|
|
@@ -380,6 +436,8 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
380
436
|
state: config.state,
|
|
381
437
|
fs: config.fs,
|
|
382
438
|
workflow: config.workflow,
|
|
439
|
+
cron: config.cron,
|
|
440
|
+
alarm: config.alarm,
|
|
383
441
|
auth: config.auth
|
|
384
442
|
});
|
|
385
443
|
});
|
|
@@ -904,6 +962,123 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
904
962
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
905
963
|
}
|
|
906
964
|
});
|
|
965
|
+
app.get("/api/cron/:binding/executions", (c) => {
|
|
966
|
+
const binding = c.req.param("binding");
|
|
967
|
+
const cronExpression = config.cron?.[binding];
|
|
968
|
+
const limit = parseInt(c.req.query("limit") ?? "20", 10);
|
|
969
|
+
if (!cronExpression) {
|
|
970
|
+
return c.json({ error: "Cron binding not found" }, 404);
|
|
971
|
+
}
|
|
972
|
+
try {
|
|
973
|
+
const db = dbManager2.emulatorDb;
|
|
974
|
+
const executions = db.prepare(`SELECT id, cron_name, cron_expression, status, error, duration_ms, triggered_at, completed_at, created_at
|
|
975
|
+
FROM cron_executions
|
|
976
|
+
WHERE cron_name = ?
|
|
977
|
+
ORDER BY triggered_at DESC
|
|
978
|
+
LIMIT ?`).all(binding, limit);
|
|
979
|
+
return c.json({
|
|
980
|
+
cronExpression,
|
|
981
|
+
executions: executions.map((e) => ({
|
|
982
|
+
id: e.id,
|
|
983
|
+
status: e.status.toUpperCase(),
|
|
984
|
+
error: e.error,
|
|
985
|
+
durationMs: e.duration_ms,
|
|
986
|
+
triggeredAt: new Date(e.triggered_at * 1e3).toISOString(),
|
|
987
|
+
completedAt: e.completed_at ? new Date(e.completed_at * 1e3).toISOString() : null,
|
|
988
|
+
createdAt: new Date(e.created_at * 1e3).toISOString()
|
|
989
|
+
}))
|
|
990
|
+
});
|
|
991
|
+
} catch (err) {
|
|
992
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
app.get("/api/alarm/:binding/entries", (c) => {
|
|
996
|
+
const binding = c.req.param("binding");
|
|
997
|
+
const alarmName = config.alarm?.[binding];
|
|
998
|
+
const limit = parseInt(c.req.query("limit") ?? "20", 10);
|
|
999
|
+
const offset = parseInt(c.req.query("offset") ?? "0", 10);
|
|
1000
|
+
if (!alarmName) {
|
|
1001
|
+
return c.json({ error: "Alarm binding not found" }, 404);
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
const db = dbManager2.emulatorDb;
|
|
1005
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM alarm_entries WHERE alarm_name = ?`).get(alarmName).count;
|
|
1006
|
+
const entries = db.prepare(`SELECT id, scheduled_time, payload, interval_ms, status, created_at
|
|
1007
|
+
FROM alarm_entries
|
|
1008
|
+
WHERE alarm_name = ?
|
|
1009
|
+
ORDER BY scheduled_time DESC
|
|
1010
|
+
LIMIT ? OFFSET ?`).all(alarmName, limit, offset);
|
|
1011
|
+
return c.json({
|
|
1012
|
+
entries: entries.map((e) => ({
|
|
1013
|
+
id: e.id,
|
|
1014
|
+
scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
|
|
1015
|
+
payload: e.payload ? JSON.parse(e.payload) : null,
|
|
1016
|
+
intervalMs: e.interval_ms,
|
|
1017
|
+
status: e.status.toUpperCase(),
|
|
1018
|
+
createdAt: new Date(e.created_at * 1e3).toISOString()
|
|
1019
|
+
})),
|
|
1020
|
+
total,
|
|
1021
|
+
limit,
|
|
1022
|
+
offset
|
|
1023
|
+
});
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
app.get("/api/alarm/:binding/metrics", (c) => {
|
|
1029
|
+
const binding = c.req.param("binding");
|
|
1030
|
+
const alarmName = config.alarm?.[binding];
|
|
1031
|
+
if (!alarmName) {
|
|
1032
|
+
return c.json({ error: "Alarm binding not found" }, 404);
|
|
1033
|
+
}
|
|
1034
|
+
try {
|
|
1035
|
+
const db = dbManager2.emulatorDb;
|
|
1036
|
+
const metrics = { pending: 0, fired: 0 };
|
|
1037
|
+
const statusCounts = db.prepare(`SELECT status, COUNT(*) as count
|
|
1038
|
+
FROM alarm_entries
|
|
1039
|
+
WHERE alarm_name = ?
|
|
1040
|
+
GROUP BY status`).all(alarmName);
|
|
1041
|
+
for (const row of statusCounts) {
|
|
1042
|
+
if (row.status === "pending") {
|
|
1043
|
+
metrics.pending = row.count;
|
|
1044
|
+
} else if (row.status === "fired") {
|
|
1045
|
+
metrics.fired = row.count;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return c.json({ metrics });
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
app.get("/api/alarm/:binding/executions", (c) => {
|
|
1054
|
+
const binding = c.req.param("binding");
|
|
1055
|
+
const alarmName = config.alarm?.[binding];
|
|
1056
|
+
const limit = parseInt(c.req.query("limit") ?? "20", 10);
|
|
1057
|
+
if (!alarmName) {
|
|
1058
|
+
return c.json({ error: "Alarm binding not found" }, 404);
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const db = dbManager2.emulatorDb;
|
|
1062
|
+
const executions = db.prepare(`SELECT id, alarm_id, scheduled_time, status, error, duration_ms, created_at
|
|
1063
|
+
FROM alarm_executions
|
|
1064
|
+
WHERE alarm_name = ?
|
|
1065
|
+
ORDER BY created_at DESC
|
|
1066
|
+
LIMIT ?`).all(alarmName, limit);
|
|
1067
|
+
return c.json({
|
|
1068
|
+
executions: executions.map((e) => ({
|
|
1069
|
+
id: e.id,
|
|
1070
|
+
alarmId: e.alarm_id,
|
|
1071
|
+
scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
|
|
1072
|
+
status: e.status.toUpperCase(),
|
|
1073
|
+
error: e.error,
|
|
1074
|
+
durationMs: e.duration_ms,
|
|
1075
|
+
createdAt: new Date(e.created_at * 1e3).toISOString()
|
|
1076
|
+
}))
|
|
1077
|
+
});
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
907
1082
|
if (hasDashboard) {
|
|
908
1083
|
app.get("/assets/*", (c) => {
|
|
909
1084
|
const path = c.req.path;
|
|
@@ -1507,6 +1682,12 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
1507
1682
|
app.post("/fs/delete", fsHandlers.deleteHandler);
|
|
1508
1683
|
app.post("/fs/list", fsHandlers.listHandler);
|
|
1509
1684
|
}
|
|
1685
|
+
if (config.alarm) {
|
|
1686
|
+
const alarmHandlers = createAlarmHandlers(dbManager2.emulatorDb);
|
|
1687
|
+
app.post("/alarm/set", alarmHandlers.setHandler);
|
|
1688
|
+
app.post("/alarm/get", alarmHandlers.getHandler);
|
|
1689
|
+
app.post("/alarm/delete", alarmHandlers.deleteHandler);
|
|
1690
|
+
}
|
|
1510
1691
|
if (config.auth) {
|
|
1511
1692
|
const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
|
|
1512
1693
|
app.post("/auth/signup", authHandlers.signupHandler);
|
|
@@ -1654,6 +1835,52 @@ CREATE TABLE IF NOT EXISTS fs_entries (
|
|
|
1654
1835
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
1655
1836
|
PRIMARY KEY (fs_name, key)
|
|
1656
1837
|
);
|
|
1838
|
+
|
|
1839
|
+
-- Cron executions table
|
|
1840
|
+
CREATE TABLE IF NOT EXISTS cron_executions (
|
|
1841
|
+
id TEXT PRIMARY KEY,
|
|
1842
|
+
cron_name TEXT NOT NULL,
|
|
1843
|
+
cron_expression TEXT NOT NULL,
|
|
1844
|
+
status TEXT DEFAULT 'running',
|
|
1845
|
+
error TEXT,
|
|
1846
|
+
duration_ms INTEGER,
|
|
1847
|
+
triggered_at INTEGER NOT NULL,
|
|
1848
|
+
completed_at INTEGER,
|
|
1849
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
1850
|
+
);
|
|
1851
|
+
|
|
1852
|
+
CREATE INDEX IF NOT EXISTS idx_cron_executions_name
|
|
1853
|
+
ON cron_executions(cron_name, triggered_at);
|
|
1854
|
+
|
|
1855
|
+
-- Alarm entries table (durable timers, optionally recurring)
|
|
1856
|
+
CREATE TABLE IF NOT EXISTS alarm_entries (
|
|
1857
|
+
id TEXT NOT NULL,
|
|
1858
|
+
alarm_name TEXT NOT NULL,
|
|
1859
|
+
scheduled_time INTEGER NOT NULL,
|
|
1860
|
+
payload TEXT,
|
|
1861
|
+
interval_ms INTEGER,
|
|
1862
|
+
status TEXT DEFAULT 'pending',
|
|
1863
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
1864
|
+
PRIMARY KEY (alarm_name, id)
|
|
1865
|
+
);
|
|
1866
|
+
|
|
1867
|
+
CREATE INDEX IF NOT EXISTS idx_alarm_entries_status_time
|
|
1868
|
+
ON alarm_entries(status, scheduled_time);
|
|
1869
|
+
|
|
1870
|
+
-- Alarm execution log
|
|
1871
|
+
CREATE TABLE IF NOT EXISTS alarm_executions (
|
|
1872
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1873
|
+
alarm_id TEXT NOT NULL,
|
|
1874
|
+
alarm_name TEXT NOT NULL,
|
|
1875
|
+
scheduled_time INTEGER NOT NULL,
|
|
1876
|
+
status TEXT DEFAULT 'pending',
|
|
1877
|
+
error TEXT,
|
|
1878
|
+
duration_ms INTEGER,
|
|
1879
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
1880
|
+
);
|
|
1881
|
+
|
|
1882
|
+
CREATE INDEX IF NOT EXISTS idx_alarm_executions_name
|
|
1883
|
+
ON alarm_executions(alarm_name, created_at);
|
|
1657
1884
|
`;
|
|
1658
1885
|
function initializeDatabases(projectDir) {
|
|
1659
1886
|
const dataDir = ensureDataDir(projectDir);
|