@donkeylabs/cli 2.0.14 → 2.0.16
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/package.json +1 -1
- package/src/commands/config.ts +610 -0
- package/src/commands/deploy-enhanced.ts +354 -0
- package/src/commands/deploy.ts +204 -0
- package/src/commands/generate.ts +11 -13
- package/src/commands/init-enhanced.ts +1994 -0
- package/src/deployment/manager.ts +356 -0
- package/src/index.ts +47 -19
- package/templates/starter/.env.example +0 -44
- package/templates/starter/.gitignore.template +0 -4
- package/templates/starter/donkeylabs.config.ts +0 -6
- package/templates/starter/package.json +0 -21
- package/templates/starter/src/index.ts +0 -54
- package/templates/starter/src/plugins/stats/index.ts +0 -105
- package/templates/starter/src/routes/health/handlers/ping.ts +0 -22
- package/templates/starter/src/routes/health/index.ts +0 -19
- package/templates/starter/tsconfig.json +0 -27
- package/templates/sveltekit-app/.env.example +0 -59
- package/templates/sveltekit-app/README.md +0 -103
- package/templates/sveltekit-app/bun.lock +0 -683
- package/templates/sveltekit-app/donkeylabs.config.ts +0 -12
- package/templates/sveltekit-app/package.json +0 -38
- package/templates/sveltekit-app/src/app.css +0 -40
- package/templates/sveltekit-app/src/app.html +0 -12
- package/templates/sveltekit-app/src/hooks.server.ts +0 -4
- package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +0 -30
- package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +0 -3
- package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +0 -48
- package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +0 -9
- package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +0 -21
- package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +0 -21
- package/templates/sveltekit-app/src/lib/components/ui/index.ts +0 -4
- package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +0 -2
- package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +0 -20
- package/templates/sveltekit-app/src/lib/permissions.ts +0 -213
- package/templates/sveltekit-app/src/lib/utils/index.ts +0 -6
- package/templates/sveltekit-app/src/routes/+layout.svelte +0 -8
- package/templates/sveltekit-app/src/routes/+page.server.ts +0 -25
- package/templates/sveltekit-app/src/routes/+page.svelte +0 -680
- package/templates/sveltekit-app/src/routes/workflows/+page.server.ts +0 -23
- package/templates/sveltekit-app/src/routes/workflows/+page.svelte +0 -522
- package/templates/sveltekit-app/src/server/events.ts +0 -28
- package/templates/sveltekit-app/src/server/index.ts +0 -124
- package/templates/sveltekit-app/src/server/plugins/auth/auth.test.ts +0 -377
- package/templates/sveltekit-app/src/server/plugins/auth/index.ts +0 -815
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/001_create_users.ts +0 -25
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/002_create_sessions.ts +0 -32
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/003_create_refresh_tokens.ts +0 -33
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/004_create_passkeys.ts +0 -60
- package/templates/sveltekit-app/src/server/plugins/auth/schema.ts +0 -65
- package/templates/sveltekit-app/src/server/plugins/demo/index.ts +0 -262
- package/templates/sveltekit-app/src/server/plugins/email/email.test.ts +0 -369
- package/templates/sveltekit-app/src/server/plugins/email/index.ts +0 -411
- package/templates/sveltekit-app/src/server/plugins/email/migrations/001_create_email_tokens.ts +0 -33
- package/templates/sveltekit-app/src/server/plugins/email/schema.ts +0 -24
- package/templates/sveltekit-app/src/server/plugins/permissions/index.ts +0 -1048
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/001_create_tenants.ts +0 -63
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/002_create_roles.ts +0 -90
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/003_create_resource_grants.ts +0 -50
- package/templates/sveltekit-app/src/server/plugins/permissions/permissions.test.ts +0 -566
- package/templates/sveltekit-app/src/server/plugins/permissions/schema.ts +0 -67
- package/templates/sveltekit-app/src/server/plugins/workflow-demo/index.ts +0 -198
- package/templates/sveltekit-app/src/server/routes/auth/auth.schemas.ts +0 -66
- package/templates/sveltekit-app/src/server/routes/auth/handlers/login.handler.ts +0 -18
- package/templates/sveltekit-app/src/server/routes/auth/handlers/logout.handler.ts +0 -16
- package/templates/sveltekit-app/src/server/routes/auth/handlers/me.handler.ts +0 -20
- package/templates/sveltekit-app/src/server/routes/auth/handlers/refresh.handler.ts +0 -17
- package/templates/sveltekit-app/src/server/routes/auth/handlers/register.handler.ts +0 -19
- package/templates/sveltekit-app/src/server/routes/auth/handlers/update-profile.handler.ts +0 -21
- package/templates/sveltekit-app/src/server/routes/auth/index.ts +0 -73
- package/templates/sveltekit-app/src/server/routes/demo.ts +0 -464
- package/templates/sveltekit-app/src/server/routes/example/example.schemas.ts +0 -22
- package/templates/sveltekit-app/src/server/routes/example/handlers/greet.handler.ts +0 -21
- package/templates/sveltekit-app/src/server/routes/example/index.ts +0 -28
- package/templates/sveltekit-app/src/server/routes/permissions/index.ts +0 -248
- package/templates/sveltekit-app/src/server/routes/tenants/index.ts +0 -339
- package/templates/sveltekit-app/static/robots.txt +0 -3
- package/templates/sveltekit-app/svelte.config.ts +0 -17
- package/templates/sveltekit-app/tsconfig.json +0 -20
- package/templates/sveltekit-app/vite.config.ts +0 -12
|
@@ -1,680 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { browser } from "$app/environment";
|
|
3
|
-
import { onMount } from "svelte";
|
|
4
|
-
import { Button } from "$lib/components/ui/button";
|
|
5
|
-
import type { Routes } from "$lib/api";
|
|
6
|
-
import {
|
|
7
|
-
Card,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardHeader,
|
|
11
|
-
CardTitle,
|
|
12
|
-
} from "$lib/components/ui/card";
|
|
13
|
-
import { Input } from "$lib/components/ui/input";
|
|
14
|
-
import { Badge } from "$lib/components/ui/badge";
|
|
15
|
-
import { createApi } from "$lib/api";
|
|
16
|
-
|
|
17
|
-
// Type for cron tasks returned by the API
|
|
18
|
-
interface CronTask {
|
|
19
|
-
id: string;
|
|
20
|
-
name: string;
|
|
21
|
-
expression: string;
|
|
22
|
-
enabled: boolean;
|
|
23
|
-
lastRun?: string;
|
|
24
|
-
nextRun?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let { data } = $props();
|
|
28
|
-
|
|
29
|
-
// Create typed API client (browser mode - no locals)
|
|
30
|
-
const client = createApi();
|
|
31
|
-
|
|
32
|
-
// SSE Events state
|
|
33
|
-
let events = $state<
|
|
34
|
-
Array<{ id: number; message: string; timestamp: string; source?: string }>
|
|
35
|
-
>([]);
|
|
36
|
-
let sseConnected = $state(false);
|
|
37
|
-
let sseClients = $state({ total: 0, byChannel: 0 });
|
|
38
|
-
|
|
39
|
-
// Counter state
|
|
40
|
-
let count = $state(data.count);
|
|
41
|
-
let counterLoading = $state(false);
|
|
42
|
-
|
|
43
|
-
// Cache state
|
|
44
|
-
let cacheKey = $state("demo-key");
|
|
45
|
-
let cacheValue = $state("Hello World");
|
|
46
|
-
let cacheTTL = $state(30000);
|
|
47
|
-
let cacheResult = $state<any>(null);
|
|
48
|
-
let cacheKeys = $state<string[]>([]);
|
|
49
|
-
|
|
50
|
-
// Jobs state
|
|
51
|
-
let jobMessage = $state("Test job");
|
|
52
|
-
let jobDelay = $state(0);
|
|
53
|
-
let jobStats = $state({ pending: 0, running: 0, completed: 0 });
|
|
54
|
-
let lastJobId = $state<string | null>(null);
|
|
55
|
-
|
|
56
|
-
// Rate Limiter state
|
|
57
|
-
let rateLimitKey = $state("demo");
|
|
58
|
-
let rateLimitMax = $state(5);
|
|
59
|
-
let rateLimitWindow = $state(60000);
|
|
60
|
-
let rateLimitResult = $state<any>(null);
|
|
61
|
-
|
|
62
|
-
// Cron state
|
|
63
|
-
let cronTasks = $state<CronTask[]>([]);
|
|
64
|
-
|
|
65
|
-
// Events (pub/sub) state
|
|
66
|
-
let eventName = $state("demo.test");
|
|
67
|
-
let eventData = $state('{"hello": "world"}');
|
|
68
|
-
|
|
69
|
-
// Audit state
|
|
70
|
-
let auditAction = $state("user.login");
|
|
71
|
-
let auditResource = $state("user");
|
|
72
|
-
let auditResourceId = $state("user-123");
|
|
73
|
-
let auditEntries = $state<Array<{
|
|
74
|
-
id: string;
|
|
75
|
-
timestamp: string;
|
|
76
|
-
action: string;
|
|
77
|
-
actor: string;
|
|
78
|
-
resource: string;
|
|
79
|
-
resourceId?: string;
|
|
80
|
-
metadata?: Record<string, any>;
|
|
81
|
-
}>>([]);
|
|
82
|
-
let lastAuditId = $state<string | null>(null);
|
|
83
|
-
|
|
84
|
-
// WebSocket state
|
|
85
|
-
let wsChannel = $state("demo");
|
|
86
|
-
let wsMessage = $state("Hello from browser!");
|
|
87
|
-
let wsClientCount = $state(0);
|
|
88
|
-
|
|
89
|
-
// Counter actions - using typed client
|
|
90
|
-
async function counterAction(
|
|
91
|
-
action: "get" | "increment" | "decrement" | "reset",
|
|
92
|
-
) {
|
|
93
|
-
counterLoading = true;
|
|
94
|
-
|
|
95
|
-
const result = await client.api.counter[action]({});
|
|
96
|
-
count = result.count;
|
|
97
|
-
counterLoading = false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Cache actions - using typed client
|
|
101
|
-
async function cacheSet() {
|
|
102
|
-
await client.api.cache.set({ key: cacheKey, value: cacheValue, ttl: cacheTTL });
|
|
103
|
-
cacheResult = { action: "set", success: true };
|
|
104
|
-
refreshCacheKeys();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function cacheGet() {
|
|
108
|
-
cacheResult = await client.api.cache.get({ key: cacheKey });
|
|
109
|
-
refreshCacheKeys();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function cacheDelete() {
|
|
113
|
-
await client.api.cache.delete({ key: cacheKey });
|
|
114
|
-
cacheResult = { action: "deleted", success: true };
|
|
115
|
-
refreshCacheKeys();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function refreshCacheKeys() {
|
|
119
|
-
const result = await client.api.cache.keys({});
|
|
120
|
-
cacheKeys = result.keys || [];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Jobs actions - using typed client
|
|
124
|
-
async function enqueueJob() {
|
|
125
|
-
const result = (await client.api.jobs.enqueue({
|
|
126
|
-
name: "demo-job",
|
|
127
|
-
data: { message: jobMessage },
|
|
128
|
-
delay: jobDelay > 0 ? jobDelay : undefined,
|
|
129
|
-
})) as { jobId: string };
|
|
130
|
-
lastJobId = result.jobId;
|
|
131
|
-
refreshJobStats();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function refreshJobStats() {
|
|
135
|
-
jobStats = (await client.api.jobs.stats({})) as {
|
|
136
|
-
pending: number;
|
|
137
|
-
running: number;
|
|
138
|
-
completed: number;
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Rate limiter actions - using typed client
|
|
143
|
-
async function checkRateLimit() {
|
|
144
|
-
rateLimitResult = await client.api.ratelimit.check({
|
|
145
|
-
key: rateLimitKey,
|
|
146
|
-
limit: rateLimitMax,
|
|
147
|
-
window: rateLimitWindow,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function resetRateLimit() {
|
|
152
|
-
await client.api.ratelimit.reset({ key: rateLimitKey });
|
|
153
|
-
rateLimitResult = { reset: true, message: "Rate limit reset" };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Cron actions - using typed client
|
|
157
|
-
async function refreshCronTasks() {
|
|
158
|
-
const result = (await client.api.cron.list({})) as { tasks: CronTask[] };
|
|
159
|
-
cronTasks = result.tasks;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Events (pub/sub) actions - using typed client
|
|
163
|
-
async function emitEvent() {
|
|
164
|
-
try {
|
|
165
|
-
const parsedData = JSON.parse(eventData);
|
|
166
|
-
await client.api.events.emit({ event: eventName, data: parsedData });
|
|
167
|
-
} catch (e) {
|
|
168
|
-
console.error("Invalid JSON:", e);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// SSE actions - using typed client
|
|
173
|
-
async function manualBroadcast() {
|
|
174
|
-
await client.api.sse.broadcast({
|
|
175
|
-
channel: "events",
|
|
176
|
-
event: "manual",
|
|
177
|
-
data: {
|
|
178
|
-
id: Date.now(),
|
|
179
|
-
message: "Manual broadcast!",
|
|
180
|
-
timestamp: new Date().toISOString(),
|
|
181
|
-
source: "manual",
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async function refreshSSEClients() {
|
|
187
|
-
sseClients = (await client.api.sse.clients({})) as {
|
|
188
|
-
total: number;
|
|
189
|
-
byChannel: number;
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Audit actions - using typed client
|
|
194
|
-
async function auditLogEntry() {
|
|
195
|
-
const result = await client.api.audit.log({
|
|
196
|
-
action: auditAction,
|
|
197
|
-
resource: auditResource,
|
|
198
|
-
resourceId: auditResourceId,
|
|
199
|
-
metadata: { browser: true, timestamp: Date.now() },
|
|
200
|
-
}) as { id: string };
|
|
201
|
-
lastAuditId = result.id;
|
|
202
|
-
refreshAuditEntries();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function refreshAuditEntries() {
|
|
206
|
-
const result = await client.api.audit.query({ limit: 10 }) as { entries: typeof auditEntries };
|
|
207
|
-
auditEntries = result.entries;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// WebSocket actions - using typed client
|
|
211
|
-
async function wsBroadcastMessage() {
|
|
212
|
-
await client.api.websocket.broadcast({
|
|
213
|
-
channel: wsChannel,
|
|
214
|
-
event: "chat",
|
|
215
|
-
data: {
|
|
216
|
-
message: wsMessage,
|
|
217
|
-
timestamp: new Date().toISOString(),
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async function wsBroadcastAll() {
|
|
223
|
-
await client.api.websocket.broadcastAll({
|
|
224
|
-
event: "announcement",
|
|
225
|
-
data: {
|
|
226
|
-
message: wsMessage,
|
|
227
|
-
timestamp: new Date().toISOString(),
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function refreshWsClients() {
|
|
233
|
-
const result = await client.api.websocket.clientCount({}) as { count: number };
|
|
234
|
-
wsClientCount = result.count;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
onMount(() => {
|
|
238
|
-
if (!browser) return;
|
|
239
|
-
|
|
240
|
-
// Initial data fetches
|
|
241
|
-
refreshCacheKeys();
|
|
242
|
-
refreshJobStats();
|
|
243
|
-
refreshCronTasks();
|
|
244
|
-
refreshSSEClients();
|
|
245
|
-
refreshAuditEntries();
|
|
246
|
-
refreshWsClients();
|
|
247
|
-
|
|
248
|
-
// SSE subscription using the typed client
|
|
249
|
-
const unsubscribe = client.sse.subscribe(
|
|
250
|
-
["events"],
|
|
251
|
-
(eventType, eventData) => {
|
|
252
|
-
// Handle all event types
|
|
253
|
-
if (
|
|
254
|
-
["cron-event", "job-completed", "internal-event", "manual"].includes(
|
|
255
|
-
eventType,
|
|
256
|
-
)
|
|
257
|
-
) {
|
|
258
|
-
// Simple prepend - CSS handles animation via :first-child or key-based animation
|
|
259
|
-
events = [{ ...eventData }, ...events].slice(0, 15);
|
|
260
|
-
|
|
261
|
-
if (eventType === "job-completed") {
|
|
262
|
-
refreshJobStats();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
// Track connection status
|
|
269
|
-
const checkConnection = setInterval(() => {
|
|
270
|
-
// The SSE subscribe auto-reconnects, so we just refresh clients
|
|
271
|
-
refreshSSEClients().then(() => {
|
|
272
|
-
sseConnected = sseClients.byChannel > 0;
|
|
273
|
-
});
|
|
274
|
-
refreshJobStats();
|
|
275
|
-
}, 5000);
|
|
276
|
-
|
|
277
|
-
// Set connected after initial subscribe
|
|
278
|
-
setTimeout(() => {
|
|
279
|
-
sseConnected = true;
|
|
280
|
-
refreshSSEClients();
|
|
281
|
-
}, 500);
|
|
282
|
-
|
|
283
|
-
return () => {
|
|
284
|
-
unsubscribe();
|
|
285
|
-
clearInterval(checkConnection);
|
|
286
|
-
};
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
function getSourceColor(
|
|
290
|
-
source?: string,
|
|
291
|
-
): "default" | "secondary" | "destructive" | "outline" | "success" {
|
|
292
|
-
switch (source) {
|
|
293
|
-
case "cron":
|
|
294
|
-
return "default";
|
|
295
|
-
case "manual":
|
|
296
|
-
return "secondary";
|
|
297
|
-
case "events":
|
|
298
|
-
return "outline";
|
|
299
|
-
default:
|
|
300
|
-
return "success";
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function getSourceLabel(source?: string) {
|
|
305
|
-
switch (source) {
|
|
306
|
-
case "cron":
|
|
307
|
-
return "CRON";
|
|
308
|
-
case "manual":
|
|
309
|
-
return "MANUAL";
|
|
310
|
-
case "events":
|
|
311
|
-
return "PUB/SUB";
|
|
312
|
-
default:
|
|
313
|
-
return "JOB";
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
</script>
|
|
317
|
-
|
|
318
|
-
<div class="min-h-screen bg-background">
|
|
319
|
-
<div class="container mx-auto max-w-7xl py-8 px-4">
|
|
320
|
-
<!-- Header -->
|
|
321
|
-
<div class="text-center mb-8">
|
|
322
|
-
<h1 class="text-3xl font-bold tracking-tight">@donkeylabs/server Demo</h1>
|
|
323
|
-
<p class="text-muted-foreground mt-2">
|
|
324
|
-
SvelteKit Adapter — All Core Services
|
|
325
|
-
</p>
|
|
326
|
-
<div class="flex gap-2 justify-center mt-3">
|
|
327
|
-
<Badge variant="outline">
|
|
328
|
-
SSR: {data.isSSR ? "Yes" : "No"} | Loaded: {data.loadedAt}
|
|
329
|
-
</Badge>
|
|
330
|
-
<a href="/workflows">
|
|
331
|
-
<Badge variant="default" class="cursor-pointer hover:bg-primary/90">
|
|
332
|
-
Try Workflows Demo
|
|
333
|
-
</Badge>
|
|
334
|
-
</a>
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
|
|
338
|
-
<!-- Grid of feature cards -->
|
|
339
|
-
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
|
|
340
|
-
<!-- Counter / RPC Demo -->
|
|
341
|
-
<Card>
|
|
342
|
-
<CardHeader>
|
|
343
|
-
<CardTitle>RPC Routes</CardTitle>
|
|
344
|
-
<CardDescription
|
|
345
|
-
>Type-safe API calls with Zod validation</CardDescription
|
|
346
|
-
>
|
|
347
|
-
</CardHeader>
|
|
348
|
-
<CardContent>
|
|
349
|
-
<div class="text-center py-4">
|
|
350
|
-
<span class="text-5xl font-bold text-primary">{count}</span>
|
|
351
|
-
</div>
|
|
352
|
-
<div class="flex gap-2 justify-center">
|
|
353
|
-
<Button
|
|
354
|
-
variant="outline"
|
|
355
|
-
size="icon"
|
|
356
|
-
onclick={() => counterAction("decrement")}
|
|
357
|
-
disabled={counterLoading}>−</Button
|
|
358
|
-
>
|
|
359
|
-
<Button
|
|
360
|
-
variant="secondary"
|
|
361
|
-
onclick={() => counterAction("get")}
|
|
362
|
-
disabled={counterLoading}>Refresh</Button
|
|
363
|
-
>
|
|
364
|
-
<Button
|
|
365
|
-
variant="outline"
|
|
366
|
-
size="icon"
|
|
367
|
-
onclick={() => counterAction("increment")}
|
|
368
|
-
disabled={counterLoading}>+</Button
|
|
369
|
-
>
|
|
370
|
-
<Button
|
|
371
|
-
variant="ghost"
|
|
372
|
-
onclick={() => counterAction("reset")}
|
|
373
|
-
disabled={counterLoading}>Reset</Button
|
|
374
|
-
>
|
|
375
|
-
</div>
|
|
376
|
-
</CardContent>
|
|
377
|
-
</Card>
|
|
378
|
-
|
|
379
|
-
<!-- Cache Demo -->
|
|
380
|
-
<Card>
|
|
381
|
-
<CardHeader>
|
|
382
|
-
<CardTitle>Cache</CardTitle>
|
|
383
|
-
<CardDescription>In-memory caching with TTL support</CardDescription>
|
|
384
|
-
</CardHeader>
|
|
385
|
-
<CardContent class="space-y-3">
|
|
386
|
-
<div class="flex gap-2">
|
|
387
|
-
<Input bind:value={cacheKey} placeholder="Key" class="flex-1" />
|
|
388
|
-
<Input bind:value={cacheValue} placeholder="Value" class="flex-1" />
|
|
389
|
-
</div>
|
|
390
|
-
<div class="flex gap-2">
|
|
391
|
-
<Button onclick={cacheSet} size="sm">Set</Button>
|
|
392
|
-
<Button onclick={cacheGet} size="sm" variant="secondary">Get</Button
|
|
393
|
-
>
|
|
394
|
-
<Button onclick={cacheDelete} size="sm" variant="outline"
|
|
395
|
-
>Delete</Button
|
|
396
|
-
>
|
|
397
|
-
</div>
|
|
398
|
-
{#if cacheResult}
|
|
399
|
-
<pre
|
|
400
|
-
class="text-xs bg-muted p-2 rounded-md overflow-auto">{JSON.stringify(
|
|
401
|
-
cacheResult,
|
|
402
|
-
null,
|
|
403
|
-
2,
|
|
404
|
-
)}</pre>
|
|
405
|
-
{/if}
|
|
406
|
-
<p class="text-xs text-muted-foreground">
|
|
407
|
-
Keys ({cacheKeys.length}): {cacheKeys.length > 0
|
|
408
|
-
? cacheKeys.join(", ")
|
|
409
|
-
: "none"}
|
|
410
|
-
</p>
|
|
411
|
-
</CardContent>
|
|
412
|
-
</Card>
|
|
413
|
-
|
|
414
|
-
<!-- Jobs Demo -->
|
|
415
|
-
<Card>
|
|
416
|
-
<CardHeader>
|
|
417
|
-
<CardTitle>Background Jobs</CardTitle>
|
|
418
|
-
<CardDescription>Async job queue with optional delay</CardDescription>
|
|
419
|
-
</CardHeader>
|
|
420
|
-
<CardContent class="space-y-3">
|
|
421
|
-
<div class="flex gap-2">
|
|
422
|
-
<Input
|
|
423
|
-
bind:value={jobMessage}
|
|
424
|
-
placeholder="Job message"
|
|
425
|
-
class="flex-1"
|
|
426
|
-
/>
|
|
427
|
-
<Input
|
|
428
|
-
bind:value={jobDelay}
|
|
429
|
-
type="number"
|
|
430
|
-
placeholder="Delay"
|
|
431
|
-
class="w-20"
|
|
432
|
-
/>
|
|
433
|
-
</div>
|
|
434
|
-
<div class="flex gap-2">
|
|
435
|
-
<Button onclick={enqueueJob} size="sm">Enqueue</Button>
|
|
436
|
-
<Button onclick={refreshJobStats} size="sm" variant="outline"
|
|
437
|
-
>Refresh</Button
|
|
438
|
-
>
|
|
439
|
-
</div>
|
|
440
|
-
{#if lastJobId}
|
|
441
|
-
<p class="text-xs text-muted-foreground">
|
|
442
|
-
Last Job: <code class="bg-muted px-1 rounded">{lastJobId}</code>
|
|
443
|
-
</p>
|
|
444
|
-
{/if}
|
|
445
|
-
<div class="flex gap-3 text-xs text-muted-foreground">
|
|
446
|
-
<span>Pending: {jobStats.pending}</span>
|
|
447
|
-
<span>Running: {jobStats.running}</span>
|
|
448
|
-
<span>Done: {jobStats.completed}</span>
|
|
449
|
-
</div>
|
|
450
|
-
</CardContent>
|
|
451
|
-
</Card>
|
|
452
|
-
|
|
453
|
-
<!-- Rate Limiter Demo -->
|
|
454
|
-
<Card>
|
|
455
|
-
<CardHeader>
|
|
456
|
-
<CardTitle>Rate Limiter</CardTitle>
|
|
457
|
-
<CardDescription>Sliding window rate limiting</CardDescription>
|
|
458
|
-
</CardHeader>
|
|
459
|
-
<CardContent class="space-y-3">
|
|
460
|
-
<div class="flex gap-2">
|
|
461
|
-
<Input bind:value={rateLimitKey} placeholder="Key" class="flex-1" />
|
|
462
|
-
<Input
|
|
463
|
-
bind:value={rateLimitMax}
|
|
464
|
-
type="number"
|
|
465
|
-
placeholder="Limit"
|
|
466
|
-
class="w-16"
|
|
467
|
-
/>
|
|
468
|
-
<Input
|
|
469
|
-
bind:value={rateLimitWindow}
|
|
470
|
-
type="number"
|
|
471
|
-
placeholder="Window"
|
|
472
|
-
class="w-20"
|
|
473
|
-
/>
|
|
474
|
-
</div>
|
|
475
|
-
<div class="flex gap-2">
|
|
476
|
-
<Button onclick={checkRateLimit} size="sm">Check</Button>
|
|
477
|
-
<Button onclick={resetRateLimit} size="sm" variant="outline"
|
|
478
|
-
>Reset</Button
|
|
479
|
-
>
|
|
480
|
-
</div>
|
|
481
|
-
{#if rateLimitResult}
|
|
482
|
-
<pre
|
|
483
|
-
class="text-xs p-2 rounded-md overflow-auto {rateLimitResult.allowed ===
|
|
484
|
-
false
|
|
485
|
-
? 'bg-destructive/10 text-destructive'
|
|
486
|
-
: 'bg-muted'}">{JSON.stringify(rateLimitResult, null, 2)}</pre>
|
|
487
|
-
{/if}
|
|
488
|
-
</CardContent>
|
|
489
|
-
</Card>
|
|
490
|
-
|
|
491
|
-
<!-- Cron Demo -->
|
|
492
|
-
<Card>
|
|
493
|
-
<CardHeader>
|
|
494
|
-
<CardTitle>Cron Jobs</CardTitle>
|
|
495
|
-
<CardDescription
|
|
496
|
-
>Scheduled tasks with cron expressions</CardDescription
|
|
497
|
-
>
|
|
498
|
-
</CardHeader>
|
|
499
|
-
<CardContent class="space-y-3">
|
|
500
|
-
<Button onclick={refreshCronTasks} size="sm" variant="outline"
|
|
501
|
-
>Refresh Tasks</Button
|
|
502
|
-
>
|
|
503
|
-
{#if cronTasks.length > 0}
|
|
504
|
-
<ul class="space-y-2">
|
|
505
|
-
{#each cronTasks as task}
|
|
506
|
-
<li class="flex items-center gap-2 text-sm">
|
|
507
|
-
<span class="font-medium">{task.name}</span>
|
|
508
|
-
<code class="text-xs bg-muted px-1 rounded"
|
|
509
|
-
>{task.expression}</code
|
|
510
|
-
>
|
|
511
|
-
<Badge
|
|
512
|
-
variant={task.enabled ? "success" : "secondary"}
|
|
513
|
-
class="text-xs"
|
|
514
|
-
>
|
|
515
|
-
{task.enabled ? "Active" : "Paused"}
|
|
516
|
-
</Badge>
|
|
517
|
-
</li>
|
|
518
|
-
{/each}
|
|
519
|
-
</ul>
|
|
520
|
-
{:else}
|
|
521
|
-
<p class="text-sm text-muted-foreground italic">
|
|
522
|
-
No scheduled tasks
|
|
523
|
-
</p>
|
|
524
|
-
{/if}
|
|
525
|
-
</CardContent>
|
|
526
|
-
</Card>
|
|
527
|
-
|
|
528
|
-
<!-- Events (Pub/Sub) Demo -->
|
|
529
|
-
<Card>
|
|
530
|
-
<CardHeader>
|
|
531
|
-
<CardTitle>Events (Pub/Sub)</CardTitle>
|
|
532
|
-
<CardDescription>Internal event system with wildcards</CardDescription
|
|
533
|
-
>
|
|
534
|
-
</CardHeader>
|
|
535
|
-
<CardContent class="space-y-3">
|
|
536
|
-
<Input
|
|
537
|
-
bind:value={eventName}
|
|
538
|
-
placeholder="Event name (e.g., demo.test)"
|
|
539
|
-
/>
|
|
540
|
-
<textarea
|
|
541
|
-
bind:value={eventData}
|
|
542
|
-
placeholder="JSON data"
|
|
543
|
-
rows="2"
|
|
544
|
-
class="flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
545
|
-
></textarea>
|
|
546
|
-
<Button onclick={emitEvent} size="sm">Emit Event</Button>
|
|
547
|
-
<p class="text-xs text-muted-foreground italic">
|
|
548
|
-
Events matching "demo.*" broadcast to SSE
|
|
549
|
-
</p>
|
|
550
|
-
</CardContent>
|
|
551
|
-
</Card>
|
|
552
|
-
|
|
553
|
-
<!-- Audit Demo -->
|
|
554
|
-
<Card>
|
|
555
|
-
<CardHeader>
|
|
556
|
-
<CardTitle>Audit Trail</CardTitle>
|
|
557
|
-
<CardDescription>Compliance logging and tracking</CardDescription>
|
|
558
|
-
</CardHeader>
|
|
559
|
-
<CardContent class="space-y-3">
|
|
560
|
-
<div class="flex gap-2">
|
|
561
|
-
<Input bind:value={auditAction} placeholder="Action" class="flex-1" />
|
|
562
|
-
<Input bind:value={auditResource} placeholder="Resource" class="flex-1" />
|
|
563
|
-
</div>
|
|
564
|
-
<Input bind:value={auditResourceId} placeholder="Resource ID (optional)" />
|
|
565
|
-
<div class="flex gap-2">
|
|
566
|
-
<Button onclick={auditLogEntry} size="sm">Log Entry</Button>
|
|
567
|
-
<Button onclick={refreshAuditEntries} size="sm" variant="outline">Refresh</Button>
|
|
568
|
-
</div>
|
|
569
|
-
{#if lastAuditId}
|
|
570
|
-
<p class="text-xs text-muted-foreground">
|
|
571
|
-
Last: <code class="bg-muted px-1 rounded">{lastAuditId}</code>
|
|
572
|
-
</p>
|
|
573
|
-
{/if}
|
|
574
|
-
{#if auditEntries.length > 0}
|
|
575
|
-
<div class="max-h-32 overflow-y-auto space-y-1">
|
|
576
|
-
{#each auditEntries.slice(0, 5) as entry}
|
|
577
|
-
<div class="text-xs p-2 bg-muted rounded flex justify-between items-center">
|
|
578
|
-
<span class="font-medium">{entry.action}</span>
|
|
579
|
-
<span class="text-muted-foreground">{entry.resource}</span>
|
|
580
|
-
<span class="text-muted-foreground">{new Date(entry.timestamp).toLocaleTimeString()}</span>
|
|
581
|
-
</div>
|
|
582
|
-
{/each}
|
|
583
|
-
</div>
|
|
584
|
-
{:else}
|
|
585
|
-
<p class="text-xs text-muted-foreground italic">No audit entries yet</p>
|
|
586
|
-
{/if}
|
|
587
|
-
</CardContent>
|
|
588
|
-
</Card>
|
|
589
|
-
|
|
590
|
-
<!-- WebSocket Demo -->
|
|
591
|
-
<Card>
|
|
592
|
-
<CardHeader>
|
|
593
|
-
<CardTitle>WebSocket</CardTitle>
|
|
594
|
-
<CardDescription>Bidirectional real-time messaging</CardDescription>
|
|
595
|
-
</CardHeader>
|
|
596
|
-
<CardContent class="space-y-3">
|
|
597
|
-
<div class="flex gap-2">
|
|
598
|
-
<Input bind:value={wsChannel} placeholder="Channel" class="w-28" />
|
|
599
|
-
<Input bind:value={wsMessage} placeholder="Message" class="flex-1" />
|
|
600
|
-
</div>
|
|
601
|
-
<div class="flex gap-2">
|
|
602
|
-
<Button onclick={wsBroadcastMessage} size="sm">Broadcast</Button>
|
|
603
|
-
<Button onclick={wsBroadcastAll} size="sm" variant="secondary">Broadcast All</Button>
|
|
604
|
-
<Button onclick={refreshWsClients} size="sm" variant="outline">Refresh</Button>
|
|
605
|
-
</div>
|
|
606
|
-
<div class="flex items-center gap-2">
|
|
607
|
-
<span class="relative flex h-2 w-2">
|
|
608
|
-
<span class="relative inline-flex rounded-full h-2 w-2 {wsClientCount > 0 ? 'bg-green-500' : 'bg-gray-400'}"></span>
|
|
609
|
-
</span>
|
|
610
|
-
<span class="text-xs text-muted-foreground">
|
|
611
|
-
{wsClientCount} WebSocket client{wsClientCount !== 1 ? 's' : ''} connected
|
|
612
|
-
</span>
|
|
613
|
-
</div>
|
|
614
|
-
<p class="text-xs text-muted-foreground italic">
|
|
615
|
-
Messages logged to audit trail automatically
|
|
616
|
-
</p>
|
|
617
|
-
</CardContent>
|
|
618
|
-
</Card>
|
|
619
|
-
</div>
|
|
620
|
-
|
|
621
|
-
<!-- SSE Events Stream - Full Width -->
|
|
622
|
-
<Card>
|
|
623
|
-
<CardHeader class="flex flex-row items-center justify-between">
|
|
624
|
-
<div>
|
|
625
|
-
<CardTitle>Live Events (SSE)</CardTitle>
|
|
626
|
-
<CardDescription>Real-time server → client push</CardDescription>
|
|
627
|
-
</div>
|
|
628
|
-
<div class="flex items-center gap-2">
|
|
629
|
-
<span class="relative flex h-3 w-3">
|
|
630
|
-
{#if sseConnected}
|
|
631
|
-
<span
|
|
632
|
-
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
|
|
633
|
-
></span>
|
|
634
|
-
<span
|
|
635
|
-
class="relative inline-flex rounded-full h-3 w-3 bg-green-500"
|
|
636
|
-
></span>
|
|
637
|
-
{:else}
|
|
638
|
-
<span
|
|
639
|
-
class="relative inline-flex rounded-full h-3 w-3 bg-gray-400"
|
|
640
|
-
></span>
|
|
641
|
-
{/if}
|
|
642
|
-
</span>
|
|
643
|
-
<span class="text-sm text-muted-foreground">
|
|
644
|
-
{sseConnected ? "Connected" : "Disconnected"} ({sseClients.byChannel}
|
|
645
|
-
clients)
|
|
646
|
-
</span>
|
|
647
|
-
</div>
|
|
648
|
-
</CardHeader>
|
|
649
|
-
<CardContent>
|
|
650
|
-
<div class="flex gap-2 mb-4">
|
|
651
|
-
<Button onclick={manualBroadcast} size="sm">Manual Broadcast</Button>
|
|
652
|
-
<Button onclick={refreshSSEClients} size="sm" variant="outline"
|
|
653
|
-
>Refresh Clients</Button
|
|
654
|
-
>
|
|
655
|
-
</div>
|
|
656
|
-
{#if events.length === 0}
|
|
657
|
-
<p class="text-sm text-muted-foreground italic">
|
|
658
|
-
Waiting for events... (cron broadcasts every 5s)
|
|
659
|
-
</p>
|
|
660
|
-
{:else}
|
|
661
|
-
<ul class="space-y-2 max-h-80 overflow-y-auto">
|
|
662
|
-
{#each events as event}
|
|
663
|
-
<li
|
|
664
|
-
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/50 animate-in slide-in-from-left-2 duration-300"
|
|
665
|
-
>
|
|
666
|
-
<Badge variant={getSourceColor(event.source)}
|
|
667
|
-
>{getSourceLabel(event.source)}</Badge
|
|
668
|
-
>
|
|
669
|
-
<span class="flex-1 text-sm font-medium">{event.message}</span>
|
|
670
|
-
<span class="text-xs text-muted-foreground"
|
|
671
|
-
>{new Date(event.timestamp).toLocaleTimeString()}</span
|
|
672
|
-
>
|
|
673
|
-
</li>
|
|
674
|
-
{/each}
|
|
675
|
-
</ul>
|
|
676
|
-
{/if}
|
|
677
|
-
</CardContent>
|
|
678
|
-
</Card>
|
|
679
|
-
</div>
|
|
680
|
-
</div>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Workflow demo page - SSR load
|
|
2
|
-
import type { PageServerLoad } from "./$types";
|
|
3
|
-
import { createApi } from "$lib/api";
|
|
4
|
-
|
|
5
|
-
export const load: PageServerLoad = async ({ locals }) => {
|
|
6
|
-
const client = createApi({ locals });
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
// Load initial workflow instances
|
|
10
|
-
const result = await (client as any).demo.workflow.list({});
|
|
11
|
-
return {
|
|
12
|
-
instances: result.instances || [],
|
|
13
|
-
loadedAt: new Date().toISOString(),
|
|
14
|
-
isSSR: true,
|
|
15
|
-
};
|
|
16
|
-
} catch (e) {
|
|
17
|
-
return {
|
|
18
|
-
instances: [],
|
|
19
|
-
loadedAt: new Date().toISOString(),
|
|
20
|
-
isSSR: true,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
};
|