|
94 | 94 | } |
95 | 95 | } |
96 | 96 |
|
| 97 | + let backingUp = $state<Record<string, boolean>>({}); |
| 98 | +
|
| 99 | + async function backupEntity(entityId: string, entityName: string) { |
| 100 | + if (backingUp[entityId]) return; |
| 101 | +
|
| 102 | + backingUp[entityId] = true; |
| 103 | +
|
| 104 | + try { |
| 105 | + const response = await fetch(`/api/dynamic-entities/${entityId}/backup`, { |
| 106 | + method: "POST", |
| 107 | + }); |
| 108 | +
|
| 109 | + const responseData = await response.json(); |
| 110 | +
|
| 111 | + if (!response.ok) { |
| 112 | + throw new Error(responseData.error || "Failed to backup entity"); |
| 113 | + } |
| 114 | +
|
| 115 | + alert(`Backup created: ${responseData.entity_name}`); |
| 116 | + window.location.reload(); |
| 117 | + } catch (error) { |
| 118 | + const errorMsg = |
| 119 | + error instanceof Error ? error.message : "Failed to backup entity"; |
| 120 | + alert(`Error: ${errorMsg}`); |
| 121 | + console.error("Backup error:", error); |
| 122 | + } finally { |
| 123 | + backingUp[entityId] = false; |
| 124 | + } |
| 125 | + } |
| 126 | +
|
97 | 127 | function getPropertyCount(entity: any): number { |
98 | 128 | const schema = getSchema(entity); |
99 | 129 | return schema?.properties ? Object.keys(schema.properties).length : 0; |
|
355 | 385 | <thead class="bg-gray-50 dark:bg-gray-900"> |
356 | 386 | <tr> |
357 | 387 | <th |
358 | | - class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
| 388 | + class="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
359 | 389 | > |
360 | | - Entity Name |
| 390 | + Name |
361 | 391 | </th> |
362 | 392 | <th |
363 | | - class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
| 393 | + class="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
364 | 394 | > |
365 | 395 | Description |
366 | 396 | </th> |
367 | 397 | <th |
368 | | - class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
| 398 | + class="px-3 py-3 text-center text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
369 | 399 | > |
370 | | - Properties |
| 400 | + Props |
371 | 401 | </th> |
372 | 402 | <th |
373 | | - class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
| 403 | + class="px-3 py-3 text-center text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
374 | 404 | > |
375 | | - Required Fields |
| 405 | + Req |
376 | 406 | </th> |
377 | 407 | <th |
378 | | - class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
| 408 | + class="px-3 py-3 text-center text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
379 | 409 | > |
380 | 410 | Records |
381 | 411 | </th> |
382 | 412 | <th |
383 | | - class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
384 | | - > |
385 | | - Personal |
386 | | - </th> |
387 | | - <th |
388 | | - class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
| 413 | + class="px-3 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400" |
389 | 414 | > |
390 | 415 | Actions |
391 | 416 | </th> |
|
397 | 422 | {#each filteredEntities as entity} |
398 | 423 | {@const schema = getSchema(entity)} |
399 | 424 | <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50"> |
400 | | - <td class="whitespace-nowrap px-6 py-4 text-sm font-medium"> |
| 425 | + <td class="whitespace-nowrap px-3 py-3 text-sm font-medium"> |
401 | 426 | <a |
402 | 427 | href="/dynamic-entities/system/{entity.dynamic_entity_id}" |
403 | 428 | class="text-blue-600 hover:text-blue-800 hover:underline dark:text-blue-400 dark:hover:text-blue-300" |
|
406 | 431 | </a> |
407 | 432 | </td> |
408 | 433 | <td |
409 | | - class="max-w-md px-6 py-4 text-sm text-gray-700 dark:text-gray-300" |
| 434 | + class="max-w-xs truncate px-3 py-3 text-sm text-gray-700 dark:text-gray-300" |
| 435 | + title={schema?.description || ""} |
410 | 436 | > |
411 | 437 | {schema?.description || "No description"} |
412 | 438 | </td> |
413 | 439 | <td |
414 | | - class="whitespace-nowrap px-6 py-4 text-sm text-gray-700 dark:text-gray-300" |
| 440 | + class="whitespace-nowrap px-3 py-3 text-center text-sm text-gray-700 dark:text-gray-300" |
415 | 441 | > |
416 | | - <span |
417 | | - class="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200" |
418 | | - > |
419 | | - {getPropertyCount(entity)} |
420 | | - </span> |
| 442 | + {getPropertyCount(entity)} |
421 | 443 | </td> |
422 | 444 | <td |
423 | | - class="whitespace-nowrap px-6 py-4 text-sm text-gray-700 dark:text-gray-300" |
| 445 | + class="whitespace-nowrap px-3 py-3 text-center text-sm text-gray-700 dark:text-gray-300" |
424 | 446 | > |
425 | | - <span |
426 | | - class="inline-flex items-center rounded-full bg-purple-100 px-2.5 py-0.5 text-xs font-medium text-purple-800 dark:bg-purple-900 dark:text-purple-200" |
427 | | - > |
428 | | - {getRequiredFieldsCount(entity)} |
429 | | - </span> |
| 447 | + {getRequiredFieldsCount(entity)} |
430 | 448 | </td> |
431 | | - <td class="whitespace-nowrap px-6 py-4 text-sm"> |
| 449 | + <td class="whitespace-nowrap px-3 py-3 text-center text-sm"> |
432 | 450 | <span class="font-medium text-gray-900 dark:text-gray-100"> |
433 | 451 | {entity.record_count} |
434 | 452 | </span> |
435 | 453 | </td> |
436 | | - <td class="whitespace-nowrap px-6 py-4 text-sm"> |
437 | | - {#if entity.has_personal_entity} |
438 | | - <span |
439 | | - class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200" |
440 | | - > |
441 | | - Yes |
442 | | - </span> |
443 | | - {:else} |
444 | | - <span |
445 | | - class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-200" |
446 | | - > |
447 | | - No |
448 | | - </span> |
449 | | - {/if} |
450 | | - </td> |
451 | | - <td class="whitespace-nowrap px-6 py-4 text-right text-sm"> |
| 454 | + <td class="whitespace-nowrap px-3 py-3 text-right text-sm"> |
452 | 455 | <div class="flex justify-end gap-2"> |
453 | 456 | <button |
454 | 457 | type="button" |
|
479 | 482 | /> |
480 | 483 | </svg> |
481 | 484 | </button> |
| 485 | + <button |
| 486 | + type="button" |
| 487 | + onclick={() => |
| 488 | + backupEntity( |
| 489 | + entity.dynamic_entity_id, |
| 490 | + getEntityName(entity), |
| 491 | + )} |
| 492 | + disabled={backingUp[entity.dynamic_entity_id]} |
| 493 | + class="text-green-600 hover:text-green-900 disabled:opacity-50 dark:text-green-400 dark:hover:text-green-300" |
| 494 | + title="Backup Entity (definition + data)" |
| 495 | + > |
| 496 | + {#if backingUp[entity.dynamic_entity_id]} |
| 497 | + <svg |
| 498 | + class="h-5 w-5 animate-spin" |
| 499 | + fill="none" |
| 500 | + viewBox="0 0 24 24" |
| 501 | + > |
| 502 | + <circle |
| 503 | + class="opacity-25" |
| 504 | + cx="12" |
| 505 | + cy="12" |
| 506 | + r="10" |
| 507 | + stroke="currentColor" |
| 508 | + stroke-width="4" |
| 509 | + /> |
| 510 | + <path |
| 511 | + class="opacity-75" |
| 512 | + fill="currentColor" |
| 513 | + d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" |
| 514 | + /> |
| 515 | + </svg> |
| 516 | + {:else} |
| 517 | + <svg |
| 518 | + class="h-5 w-5" |
| 519 | + fill="none" |
| 520 | + stroke="currentColor" |
| 521 | + viewBox="0 0 24 24" |
| 522 | + > |
| 523 | + <path |
| 524 | + stroke-linecap="round" |
| 525 | + stroke-linejoin="round" |
| 526 | + stroke-width="2" |
| 527 | + d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" |
| 528 | + /> |
| 529 | + </svg> |
| 530 | + {/if} |
| 531 | + </button> |
482 | 532 | <button |
483 | 533 | type="button" |
484 | 534 | onclick={() => |
|
0 commit comments