the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add cid field to sandboxes

+830 -10
+1
apps/api/scripts/seed.ts
··· 66 66 provider: "daytona", 67 67 status: "STOPPED", 68 68 uri: sandbox.uri, 69 + cid: sandbox.cid, 69 70 publicKey: env.PUBLIC_KEY, 70 71 vcpus: sandbox.value.vcpus, 71 72 memory: sandbox.value.memory,
+4 -1
apps/api/src/schema/sandboxes.ts
··· 9 9 import users from "./users"; 10 10 11 11 const sandboxes = pgTable("sandboxes", { 12 - id: text("id").primaryKey().default(sql`sandbox_id()`), 12 + id: text("id") 13 + .primaryKey() 14 + .default(sql`sandbox_id()`), 13 15 base: text("base"), 14 16 name: text("name").unique().notNull(), 15 17 displayName: text("display_name"), 16 18 uri: text("uri").unique(), 19 + cid: text("cid").unique(), 17 20 repo: text("repo"), 18 21 provider: text("provider").default("cloudflare").notNull(), 19 22 description: text("description"),
+26 -1
apps/api/src/xrpc/io/pocketenv/sandbox/createSandbox.ts
··· 10 10 import { TID } from "@atproto/common"; 11 11 import schema from "schema"; 12 12 import { eq } from "drizzle-orm"; 13 + import { 14 + validateMain, 15 + type Main, 16 + } from "lexicon/types/com/atproto/repo/strongRef"; 13 17 14 18 export default function (server: Server, ctx: Context) { 15 19 const createSandbox = async (input: HandlerInput, auth: HandlerAuth) => { ··· 36 40 `Writing ${chalk.greenBright("io.pocketenv.sandbox")} record...`, 37 41 ); 38 42 43 + const base = await ctx.db 44 + .select() 45 + .from(schema.sandboxes) 46 + .where(eq(schema.sandboxes.name, "openclaw")) 47 + .execute() 48 + .then(([row]) => row); 49 + 50 + const baseRef = validateMain({ 51 + uri: base?.uri, 52 + cid: base?.cid, 53 + }); 54 + 55 + if (!baseRef.success) { 56 + consola.error( 57 + "Failed to validate base record reference:", 58 + baseRef.error, 59 + ); 60 + throw new Error("Invalid base record reference"); 61 + } 62 + 39 63 const record: Sandbox.Record = { 40 64 $type: "io.pocketenv.sandbox", 41 65 name: res.data.name, ··· 43 67 vcpus: res.data.vcpus, 44 68 memory: res.data.memory, 45 69 disk: res.data.disk, 70 + base: baseRef.value as Main, 46 71 createdAt: new Date().toISOString(), 47 72 }; 48 73 ··· 64 89 65 90 await ctx.db 66 91 .update(schema.sandboxes) 67 - .set({ uri: data.uri }) 92 + .set({ uri: data.uri, cid: data.cid }) 68 93 .where(eq(schema.sandboxes.id, res.data.id)) 69 94 .execute(); 70 95
+2
apps/cf-sandbox/drizzle/0012_happy_mysterio.sql
··· 1 + ALTER TABLE "sandboxes" ADD COLUMN "cid" text;--> statement-breakpoint 2 + ALTER TABLE "sandboxes" ADD CONSTRAINT "sandboxes_cid_unique" UNIQUE("cid");
+757
apps/cf-sandbox/drizzle/meta/0012_snapshot.json
··· 1 + { 2 + "id": "bbc292d8-d78f-4887-934c-7de4b2f52f6b", 3 + "prevId": "91e8a6f6-d86c-49d0-b476-e8dd8abb9d1e", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.authorized_keys": { 8 + "name": "authorized_keys", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": false 23 + }, 24 + "public_key": { 25 + "name": "public_key", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "created_at": { 31 + "name": "created_at", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + } 37 + }, 38 + "indexes": {}, 39 + "foreignKeys": { 40 + "authorized_keys_sandbox_id_sandboxes_id_fk": { 41 + "name": "authorized_keys_sandbox_id_sandboxes_id_fk", 42 + "tableFrom": "authorized_keys", 43 + "tableTo": "sandboxes", 44 + "columnsFrom": [ 45 + "sandbox_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "no action", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": {}, 55 + "uniqueConstraints": {}, 56 + "policies": {}, 57 + "checkConstraints": {}, 58 + "isRLSEnabled": false 59 + }, 60 + "public.sandbox_secrets": { 61 + "name": "sandbox_secrets", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "xata_id()" 70 + }, 71 + "sandbox_id": { 72 + "name": "sandbox_id", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "secret_id": { 78 + "name": "secret_id", 79 + "type": "text", 80 + "primaryKey": false, 81 + "notNull": true 82 + } 83 + }, 84 + "indexes": { 85 + "unique_sandbox_secret": { 86 + "name": "unique_sandbox_secret", 87 + "columns": [ 88 + { 89 + "expression": "sandbox_id", 90 + "isExpression": false, 91 + "asc": true, 92 + "nulls": "last" 93 + }, 94 + { 95 + "expression": "secret_id", 96 + "isExpression": false, 97 + "asc": true, 98 + "nulls": "last" 99 + } 100 + ], 101 + "isUnique": true, 102 + "concurrently": false, 103 + "method": "btree", 104 + "with": {} 105 + } 106 + }, 107 + "foreignKeys": { 108 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 109 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 110 + "tableFrom": "sandbox_secrets", 111 + "tableTo": "sandboxes", 112 + "columnsFrom": [ 113 + "sandbox_id" 114 + ], 115 + "columnsTo": [ 116 + "id" 117 + ], 118 + "onDelete": "no action", 119 + "onUpdate": "no action" 120 + }, 121 + "sandbox_secrets_secret_id_secrets_id_fk": { 122 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 123 + "tableFrom": "sandbox_secrets", 124 + "tableTo": "secrets", 125 + "columnsFrom": [ 126 + "secret_id" 127 + ], 128 + "columnsTo": [ 129 + "id" 130 + ], 131 + "onDelete": "no action", 132 + "onUpdate": "no action" 133 + } 134 + }, 135 + "compositePrimaryKeys": {}, 136 + "uniqueConstraints": {}, 137 + "policies": {}, 138 + "checkConstraints": {}, 139 + "isRLSEnabled": false 140 + }, 141 + "public.sandbox_variables": { 142 + "name": "sandbox_variables", 143 + "schema": "", 144 + "columns": { 145 + "id": { 146 + "name": "id", 147 + "type": "text", 148 + "primaryKey": true, 149 + "notNull": true, 150 + "default": "xata_id()" 151 + }, 152 + "sandbox_id": { 153 + "name": "sandbox_id", 154 + "type": "text", 155 + "primaryKey": false, 156 + "notNull": true 157 + }, 158 + "variable_id": { 159 + "name": "variable_id", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": true 163 + } 164 + }, 165 + "indexes": { 166 + "unique_sandbox_variables": { 167 + "name": "unique_sandbox_variables", 168 + "columns": [ 169 + { 170 + "expression": "sandbox_id", 171 + "isExpression": false, 172 + "asc": true, 173 + "nulls": "last" 174 + }, 175 + { 176 + "expression": "variable_id", 177 + "isExpression": false, 178 + "asc": true, 179 + "nulls": "last" 180 + } 181 + ], 182 + "isUnique": true, 183 + "concurrently": false, 184 + "method": "btree", 185 + "with": {} 186 + } 187 + }, 188 + "foreignKeys": { 189 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 190 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 191 + "tableFrom": "sandbox_variables", 192 + "tableTo": "sandboxes", 193 + "columnsFrom": [ 194 + "sandbox_id" 195 + ], 196 + "columnsTo": [ 197 + "id" 198 + ], 199 + "onDelete": "no action", 200 + "onUpdate": "no action" 201 + }, 202 + "sandbox_variables_variable_id_variables_id_fk": { 203 + "name": "sandbox_variables_variable_id_variables_id_fk", 204 + "tableFrom": "sandbox_variables", 205 + "tableTo": "variables", 206 + "columnsFrom": [ 207 + "variable_id" 208 + ], 209 + "columnsTo": [ 210 + "id" 211 + ], 212 + "onDelete": "no action", 213 + "onUpdate": "no action" 214 + } 215 + }, 216 + "compositePrimaryKeys": {}, 217 + "uniqueConstraints": {}, 218 + "policies": {}, 219 + "checkConstraints": {}, 220 + "isRLSEnabled": false 221 + }, 222 + "public.sandbox_volumes": { 223 + "name": "sandbox_volumes", 224 + "schema": "", 225 + "columns": { 226 + "id": { 227 + "name": "id", 228 + "type": "text", 229 + "primaryKey": true, 230 + "notNull": true, 231 + "default": "xata_id()" 232 + }, 233 + "sandbox_id": { 234 + "name": "sandbox_id", 235 + "type": "text", 236 + "primaryKey": false, 237 + "notNull": true 238 + }, 239 + "volume_id": { 240 + "name": "volume_id", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true 244 + } 245 + }, 246 + "indexes": {}, 247 + "foreignKeys": { 248 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 249 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 250 + "tableFrom": "sandbox_volumes", 251 + "tableTo": "sandboxes", 252 + "columnsFrom": [ 253 + "sandbox_id" 254 + ], 255 + "columnsTo": [ 256 + "id" 257 + ], 258 + "onDelete": "no action", 259 + "onUpdate": "no action" 260 + }, 261 + "sandbox_volumes_volume_id_volumes_id_fk": { 262 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 263 + "tableFrom": "sandbox_volumes", 264 + "tableTo": "volumes", 265 + "columnsFrom": [ 266 + "volume_id" 267 + ], 268 + "columnsTo": [ 269 + "id" 270 + ], 271 + "onDelete": "no action", 272 + "onUpdate": "no action" 273 + } 274 + }, 275 + "compositePrimaryKeys": {}, 276 + "uniqueConstraints": {}, 277 + "policies": {}, 278 + "checkConstraints": {}, 279 + "isRLSEnabled": false 280 + }, 281 + "public.sandboxes": { 282 + "name": "sandboxes", 283 + "schema": "", 284 + "columns": { 285 + "id": { 286 + "name": "id", 287 + "type": "text", 288 + "primaryKey": true, 289 + "notNull": true, 290 + "default": "sandbox_id()" 291 + }, 292 + "base": { 293 + "name": "base", 294 + "type": "text", 295 + "primaryKey": false, 296 + "notNull": false 297 + }, 298 + "name": { 299 + "name": "name", 300 + "type": "text", 301 + "primaryKey": false, 302 + "notNull": true 303 + }, 304 + "display_name": { 305 + "name": "display_name", 306 + "type": "text", 307 + "primaryKey": false, 308 + "notNull": false 309 + }, 310 + "uri": { 311 + "name": "uri", 312 + "type": "text", 313 + "primaryKey": false, 314 + "notNull": false 315 + }, 316 + "cid": { 317 + "name": "cid", 318 + "type": "text", 319 + "primaryKey": false, 320 + "notNull": false 321 + }, 322 + "repo": { 323 + "name": "repo", 324 + "type": "text", 325 + "primaryKey": false, 326 + "notNull": false 327 + }, 328 + "provider": { 329 + "name": "provider", 330 + "type": "text", 331 + "primaryKey": false, 332 + "notNull": true, 333 + "default": "'cloudflare'" 334 + }, 335 + "description": { 336 + "name": "description", 337 + "type": "text", 338 + "primaryKey": false, 339 + "notNull": false 340 + }, 341 + "logo": { 342 + "name": "logo", 343 + "type": "text", 344 + "primaryKey": false, 345 + "notNull": false 346 + }, 347 + "readme": { 348 + "name": "readme", 349 + "type": "text", 350 + "primaryKey": false, 351 + "notNull": false 352 + }, 353 + "public_key": { 354 + "name": "public_key", 355 + "type": "text", 356 + "primaryKey": false, 357 + "notNull": true 358 + }, 359 + "user_id": { 360 + "name": "user_id", 361 + "type": "text", 362 + "primaryKey": false, 363 + "notNull": false 364 + }, 365 + "instance_type": { 366 + "name": "instance_type", 367 + "type": "text", 368 + "primaryKey": false, 369 + "notNull": false 370 + }, 371 + "vcpus": { 372 + "name": "vcpus", 373 + "type": "integer", 374 + "primaryKey": false, 375 + "notNull": false 376 + }, 377 + "memory": { 378 + "name": "memory", 379 + "type": "integer", 380 + "primaryKey": false, 381 + "notNull": false 382 + }, 383 + "disk": { 384 + "name": "disk", 385 + "type": "integer", 386 + "primaryKey": false, 387 + "notNull": false 388 + }, 389 + "status": { 390 + "name": "status", 391 + "type": "text", 392 + "primaryKey": false, 393 + "notNull": true 394 + }, 395 + "keep_alive": { 396 + "name": "keep_alive", 397 + "type": "boolean", 398 + "primaryKey": false, 399 + "notNull": true, 400 + "default": false 401 + }, 402 + "sleep_after": { 403 + "name": "sleep_after", 404 + "type": "text", 405 + "primaryKey": false, 406 + "notNull": false 407 + }, 408 + "sandbox_id": { 409 + "name": "sandbox_id", 410 + "type": "text", 411 + "primaryKey": false, 412 + "notNull": false 413 + }, 414 + "installs": { 415 + "name": "installs", 416 + "type": "integer", 417 + "primaryKey": false, 418 + "notNull": true, 419 + "default": 0 420 + }, 421 + "started_at": { 422 + "name": "started_at", 423 + "type": "timestamp", 424 + "primaryKey": false, 425 + "notNull": false 426 + }, 427 + "created_at": { 428 + "name": "created_at", 429 + "type": "timestamp", 430 + "primaryKey": false, 431 + "notNull": true, 432 + "default": "now()" 433 + }, 434 + "updated_at": { 435 + "name": "updated_at", 436 + "type": "timestamp", 437 + "primaryKey": false, 438 + "notNull": true, 439 + "default": "now()" 440 + } 441 + }, 442 + "indexes": {}, 443 + "foreignKeys": { 444 + "sandboxes_user_id_users_id_fk": { 445 + "name": "sandboxes_user_id_users_id_fk", 446 + "tableFrom": "sandboxes", 447 + "tableTo": "users", 448 + "columnsFrom": [ 449 + "user_id" 450 + ], 451 + "columnsTo": [ 452 + "id" 453 + ], 454 + "onDelete": "no action", 455 + "onUpdate": "no action" 456 + } 457 + }, 458 + "compositePrimaryKeys": {}, 459 + "uniqueConstraints": { 460 + "sandboxes_name_unique": { 461 + "name": "sandboxes_name_unique", 462 + "nullsNotDistinct": false, 463 + "columns": [ 464 + "name" 465 + ] 466 + }, 467 + "sandboxes_uri_unique": { 468 + "name": "sandboxes_uri_unique", 469 + "nullsNotDistinct": false, 470 + "columns": [ 471 + "uri" 472 + ] 473 + }, 474 + "sandboxes_cid_unique": { 475 + "name": "sandboxes_cid_unique", 476 + "nullsNotDistinct": false, 477 + "columns": [ 478 + "cid" 479 + ] 480 + } 481 + }, 482 + "policies": {}, 483 + "checkConstraints": {}, 484 + "isRLSEnabled": false 485 + }, 486 + "public.secrets": { 487 + "name": "secrets", 488 + "schema": "", 489 + "columns": { 490 + "id": { 491 + "name": "id", 492 + "type": "text", 493 + "primaryKey": true, 494 + "notNull": true, 495 + "default": "secret_id()" 496 + }, 497 + "name": { 498 + "name": "name", 499 + "type": "text", 500 + "primaryKey": false, 501 + "notNull": true 502 + }, 503 + "value": { 504 + "name": "value", 505 + "type": "text", 506 + "primaryKey": false, 507 + "notNull": true 508 + }, 509 + "created_at": { 510 + "name": "created_at", 511 + "type": "timestamp", 512 + "primaryKey": false, 513 + "notNull": true, 514 + "default": "now()" 515 + } 516 + }, 517 + "indexes": {}, 518 + "foreignKeys": {}, 519 + "compositePrimaryKeys": {}, 520 + "uniqueConstraints": {}, 521 + "policies": {}, 522 + "checkConstraints": {}, 523 + "isRLSEnabled": false 524 + }, 525 + "public.snapshots": { 526 + "name": "snapshots", 527 + "schema": "", 528 + "columns": { 529 + "id": { 530 + "name": "id", 531 + "type": "text", 532 + "primaryKey": true, 533 + "notNull": true, 534 + "default": "snapshot_id()" 535 + }, 536 + "slug": { 537 + "name": "slug", 538 + "type": "text", 539 + "primaryKey": false, 540 + "notNull": true 541 + }, 542 + "created_at": { 543 + "name": "created_at", 544 + "type": "timestamp", 545 + "primaryKey": false, 546 + "notNull": true, 547 + "default": "now()" 548 + } 549 + }, 550 + "indexes": {}, 551 + "foreignKeys": {}, 552 + "compositePrimaryKeys": {}, 553 + "uniqueConstraints": { 554 + "snapshots_slug_unique": { 555 + "name": "snapshots_slug_unique", 556 + "nullsNotDistinct": false, 557 + "columns": [ 558 + "slug" 559 + ] 560 + } 561 + }, 562 + "policies": {}, 563 + "checkConstraints": {}, 564 + "isRLSEnabled": false 565 + }, 566 + "public.users": { 567 + "name": "users", 568 + "schema": "", 569 + "columns": { 570 + "id": { 571 + "name": "id", 572 + "type": "text", 573 + "primaryKey": true, 574 + "notNull": true, 575 + "default": "xata_id()" 576 + }, 577 + "did": { 578 + "name": "did", 579 + "type": "text", 580 + "primaryKey": false, 581 + "notNull": true 582 + }, 583 + "display_name": { 584 + "name": "display_name", 585 + "type": "text", 586 + "primaryKey": false, 587 + "notNull": false 588 + }, 589 + "handle": { 590 + "name": "handle", 591 + "type": "text", 592 + "primaryKey": false, 593 + "notNull": true 594 + }, 595 + "avatar": { 596 + "name": "avatar", 597 + "type": "text", 598 + "primaryKey": false, 599 + "notNull": false 600 + }, 601 + "created_at": { 602 + "name": "created_at", 603 + "type": "timestamp", 604 + "primaryKey": false, 605 + "notNull": true, 606 + "default": "now()" 607 + }, 608 + "updated_at": { 609 + "name": "updated_at", 610 + "type": "timestamp", 611 + "primaryKey": false, 612 + "notNull": true, 613 + "default": "now()" 614 + } 615 + }, 616 + "indexes": {}, 617 + "foreignKeys": {}, 618 + "compositePrimaryKeys": {}, 619 + "uniqueConstraints": { 620 + "users_did_unique": { 621 + "name": "users_did_unique", 622 + "nullsNotDistinct": false, 623 + "columns": [ 624 + "did" 625 + ] 626 + }, 627 + "users_handle_unique": { 628 + "name": "users_handle_unique", 629 + "nullsNotDistinct": false, 630 + "columns": [ 631 + "handle" 632 + ] 633 + } 634 + }, 635 + "policies": {}, 636 + "checkConstraints": {}, 637 + "isRLSEnabled": false 638 + }, 639 + "public.variables": { 640 + "name": "variables", 641 + "schema": "", 642 + "columns": { 643 + "id": { 644 + "name": "id", 645 + "type": "text", 646 + "primaryKey": true, 647 + "notNull": true, 648 + "default": "variable_id()" 649 + }, 650 + "name": { 651 + "name": "name", 652 + "type": "text", 653 + "primaryKey": false, 654 + "notNull": true 655 + }, 656 + "value": { 657 + "name": "value", 658 + "type": "text", 659 + "primaryKey": false, 660 + "notNull": true 661 + }, 662 + "created_at": { 663 + "name": "created_at", 664 + "type": "timestamp", 665 + "primaryKey": false, 666 + "notNull": true, 667 + "default": "now()" 668 + }, 669 + "updated_at": { 670 + "name": "updated_at", 671 + "type": "timestamp", 672 + "primaryKey": false, 673 + "notNull": true, 674 + "default": "now()" 675 + } 676 + }, 677 + "indexes": {}, 678 + "foreignKeys": {}, 679 + "compositePrimaryKeys": {}, 680 + "uniqueConstraints": {}, 681 + "policies": {}, 682 + "checkConstraints": {}, 683 + "isRLSEnabled": false 684 + }, 685 + "public.volumes": { 686 + "name": "volumes", 687 + "schema": "", 688 + "columns": { 689 + "id": { 690 + "name": "id", 691 + "type": "text", 692 + "primaryKey": true, 693 + "notNull": true, 694 + "default": "volume_id()" 695 + }, 696 + "slug": { 697 + "name": "slug", 698 + "type": "text", 699 + "primaryKey": false, 700 + "notNull": true 701 + }, 702 + "size": { 703 + "name": "size", 704 + "type": "integer", 705 + "primaryKey": false, 706 + "notNull": true 707 + }, 708 + "size_unit": { 709 + "name": "size_unit", 710 + "type": "text", 711 + "primaryKey": false, 712 + "notNull": true 713 + }, 714 + "created_at": { 715 + "name": "created_at", 716 + "type": "timestamp", 717 + "primaryKey": false, 718 + "notNull": true, 719 + "default": "now()" 720 + }, 721 + "updated_at": { 722 + "name": "updated_at", 723 + "type": "timestamp", 724 + "primaryKey": false, 725 + "notNull": true, 726 + "default": "now()" 727 + } 728 + }, 729 + "indexes": {}, 730 + "foreignKeys": {}, 731 + "compositePrimaryKeys": {}, 732 + "uniqueConstraints": { 733 + "volumes_slug_unique": { 734 + "name": "volumes_slug_unique", 735 + "nullsNotDistinct": false, 736 + "columns": [ 737 + "slug" 738 + ] 739 + } 740 + }, 741 + "policies": {}, 742 + "checkConstraints": {}, 743 + "isRLSEnabled": false 744 + } 745 + }, 746 + "enums": {}, 747 + "schemas": {}, 748 + "sequences": {}, 749 + "roles": {}, 750 + "policies": {}, 751 + "views": {}, 752 + "_meta": { 753 + "columns": {}, 754 + "schemas": {}, 755 + "tables": {} 756 + } 757 + }
+7
apps/cf-sandbox/drizzle/meta/_journal.json
··· 85 85 "when": 1771520023773, 86 86 "tag": "0011_nappy_magdalene", 87 87 "breakpoints": true 88 + }, 89 + { 90 + "idx": 12, 91 + "version": "7", 92 + "when": 1771532126862, 93 + "tag": "0012_happy_mysterio", 94 + "breakpoints": true 88 95 } 89 96 ] 90 97 }
+1
apps/cf-sandbox/src/schema/sandboxes.ts
··· 16 16 name: text("name").unique().notNull(), 17 17 displayName: text("display_name"), 18 18 uri: text("uri").unique(), 19 + cid: text("cid").unique(), 19 20 repo: text("repo"), 20 21 provider: text("provider").default("cloudflare").notNull(), 21 22 description: text("description"),
+1
apps/sandbox/src/schema/sandboxes.ts
··· 15 15 base: text("base"), 16 16 name: text("name").unique().notNull(), 17 17 uri: text("uri").unique(), 18 + cid: text("cid").unique(), 18 19 repo: text("repo"), 19 20 provider: text("provider").default("cloudflare").notNull(), 20 21 description: text("description"),
+31 -8
apps/web/src/pages/sandbox/Sandbox.tsx
··· 19 19 function New() { 20 20 const profile = useAtomValue(profileAtom); 21 21 const queryClient = useQueryClient(); 22 + const [displayLoading, setDisplayLoading] = useState(false); 22 23 const { mutate: stopSandbox } = useStopSandboxMutation(); 23 24 const { mutate: startSandbox } = useStartSandboxMutation(); 24 25 const isAuthenticated = !!localStorage.getItem("token"); ··· 135 136 ((profile && data?.sandbox?.owner?.did === profile.did) || 136 137 !data?.sandbox?.owner) && ( 137 138 <button 138 - onClick={() => 139 + onClick={() => { 140 + if (displayLoading) return; 141 + setDisplayLoading(true); 139 142 stopSandbox(data!.sandbox!.id, { 140 143 onSuccess: async () => { 141 144 await queryClient.invalidateQueries({ ··· 144 147 await queryClient.invalidateQueries({ 145 148 queryKey: ["sandbox", data?.sandbox?.uri], 146 149 }); 150 + setDisplayLoading(false); 147 151 }, 148 - }) 149 - } 152 + onError: () => { 153 + setDisplayLoading(false); 154 + }, 155 + }); 156 + }} 150 157 className="btn btn-outline btn-lg hover:text-white" 151 158 > 152 - <span className="icon-[tabler--player-stop-filled] size-5 shrink-0"></span> 159 + {!displayLoading && ( 160 + <span className="icon-[tabler--player-stop-filled] size-5 shrink-0"></span> 161 + )} 162 + {displayLoading && ( 163 + <span className="loading loading-spinner loading-sm"></span> 164 + )} 153 165 Stop Sandbox 154 166 </button> 155 167 )} ··· 157 169 ((profile && data?.sandbox?.owner?.did === profile.did) || 158 170 !data?.sandbox?.owner) && ( 159 171 <button 160 - onClick={() => 172 + onClick={() => { 173 + if (displayLoading) return; 174 + setDisplayLoading(true); 161 175 startSandbox(data!.sandbox!.id, { 162 176 onSuccess: async () => { 163 177 await queryClient.invalidateQueries({ ··· 166 180 await queryClient.invalidateQueries({ 167 181 queryKey: ["sandbox", data?.sandbox?.uri], 168 182 }); 183 + setDisplayLoading(false); 169 184 }, 170 - }) 171 - } 185 + onError: () => { 186 + setDisplayLoading(false); 187 + }, 188 + }); 189 + }} 172 190 className="btn btn-outline btn-lg hover:text-white" 173 191 > 174 - <span className="icon-[tabler--player-play-filled] size-5 shrink-0"></span> 192 + {!displayLoading && ( 193 + <span className="icon-[tabler--player-play-filled] size-5 shrink-0"></span> 194 + )} 195 + {displayLoading && ( 196 + <span className="loading loading-spinner loading-sm"></span> 197 + )} 175 198 Start Sandbox 176 199 </button> 177 200 )}