|
1 | 1 | 'use client' |
2 | 2 |
|
3 | 3 | import { useEffect, useState } from 'react' |
4 | | -import { Loader2, X } from 'lucide-react' |
5 | | -import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui' |
| 4 | +import { Loader2, MoreVertical, X } from 'lucide-react' |
| 5 | +import { |
| 6 | + Button, |
| 7 | + Dialog, |
| 8 | + DialogContent, |
| 9 | + DialogHeader, |
| 10 | + DialogTitle, |
| 11 | + DropdownMenu, |
| 12 | + DropdownMenuContent, |
| 13 | + DropdownMenuItem, |
| 14 | + DropdownMenuTrigger, |
| 15 | +} from '@/components/ui' |
6 | 16 | import { getEnv } from '@/lib/env' |
7 | 17 | import { createLogger } from '@/lib/logs/console/logger' |
8 | 18 | import { cn } from '@/lib/utils' |
@@ -89,6 +99,8 @@ export function DeployModal({ |
89 | 99 | const [previewVersion, setPreviewVersion] = useState<number | null>(null) |
90 | 100 | const [previewing, setPreviewing] = useState(false) |
91 | 101 | const [previewDeployedState, setPreviewDeployedState] = useState<WorkflowState | null>(null) |
| 102 | + const [currentPage, setCurrentPage] = useState(1) |
| 103 | + const itemsPerPage = 10 |
92 | 104 |
|
93 | 105 | const getInputFormatExample = () => { |
94 | 106 | let inputFormatExample = '' |
@@ -568,44 +580,134 @@ export function DeployModal({ |
568 | 580 | )} |
569 | 581 |
|
570 | 582 | <div className='mt-6'> |
571 | | - <div className='mb-2 font-medium text-sm'>Deployment versions</div> |
572 | | - <div className='rounded-md border'> |
573 | | - {versionsLoading ? ( |
574 | | - <div className='p-3 text-muted-foreground text-sm'>Loading…</div> |
575 | | - ) : versions.length === 0 ? ( |
576 | | - <div className='p-3 text-muted-foreground text-sm'>No deployments yet</div> |
577 | | - ) : ( |
578 | | - <ul className='divide-y'> |
579 | | - {versions.map((v) => ( |
580 | | - <li key={v.id} className='flex items-center justify-between px-3 py-2'> |
581 | | - <button |
582 | | - type='button' |
583 | | - onClick={() => openVersionPreview(v.version)} |
584 | | - className='flex items-center gap-2 text-left hover:opacity-80' |
| 583 | + <div className='mb-3 font-medium text-sm'>Deployment Versions</div> |
| 584 | + {versionsLoading ? ( |
| 585 | + <div className='rounded-md border p-4 text-center text-muted-foreground text-sm'> |
| 586 | + Loading deployments... |
| 587 | + </div> |
| 588 | + ) : versions.length === 0 ? ( |
| 589 | + <div className='rounded-md border p-4 text-center text-muted-foreground text-sm'> |
| 590 | + No deployments yet |
| 591 | + </div> |
| 592 | + ) : ( |
| 593 | + <> |
| 594 | + <div className='overflow-hidden rounded-md border'> |
| 595 | + <table className='w-full'> |
| 596 | + <thead className='border-b bg-muted/50'> |
| 597 | + <tr> |
| 598 | + <th className='w-10' /> |
| 599 | + <th className='px-4 py-2 text-left text-xs font-medium text-muted-foreground'> |
| 600 | + Version |
| 601 | + </th> |
| 602 | + <th className='px-4 py-2 text-left text-xs font-medium text-muted-foreground'> |
| 603 | + Deployed By |
| 604 | + </th> |
| 605 | + <th className='px-4 py-2 text-left text-xs font-medium text-muted-foreground'> |
| 606 | + Created |
| 607 | + </th> |
| 608 | + <th className='w-10' /> |
| 609 | + </tr> |
| 610 | + </thead> |
| 611 | + <tbody className='divide-y'> |
| 612 | + {versions |
| 613 | + .slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) |
| 614 | + .map((v) => ( |
| 615 | + <tr |
| 616 | + key={v.id} |
| 617 | + className='hover:bg-muted/30 transition-colors cursor-pointer' |
| 618 | + onClick={() => openVersionPreview(v.version)} |
| 619 | + > |
| 620 | + <td className='px-4 py-2.5'> |
| 621 | + <div |
| 622 | + className={`h-2 w-2 rounded-full ${ |
| 623 | + v.isActive ? 'bg-green-500' : 'bg-muted-foreground/40' |
| 624 | + }`} |
| 625 | + title={v.isActive ? 'Active' : 'Inactive'} |
| 626 | + /> |
| 627 | + </td> |
| 628 | + <td className='px-4 py-2.5'> |
| 629 | + <span className='font-medium text-sm'>v{v.version}</span> |
| 630 | + </td> |
| 631 | + <td className='px-4 py-2.5'> |
| 632 | + <span className='text-muted-foreground text-sm'> |
| 633 | + {v.deployedBy || 'Unknown'} |
| 634 | + </span> |
| 635 | + </td> |
| 636 | + <td className='px-4 py-2.5'> |
| 637 | + <span className='text-muted-foreground text-sm'> |
| 638 | + {new Date(v.createdAt).toLocaleDateString()}{' '} |
| 639 | + {new Date(v.createdAt).toLocaleTimeString()} |
| 640 | + </span> |
| 641 | + </td> |
| 642 | + <td |
| 643 | + className='px-4 py-2.5' |
| 644 | + onClick={(e) => e.stopPropagation()} |
| 645 | + > |
| 646 | + <DropdownMenu> |
| 647 | + <DropdownMenuTrigger asChild> |
| 648 | + <Button |
| 649 | + variant='ghost' |
| 650 | + size='icon' |
| 651 | + className='h-8 w-8' |
| 652 | + disabled={activatingVersion === v.version} |
| 653 | + > |
| 654 | + <MoreVertical className='h-4 w-4' /> |
| 655 | + </Button> |
| 656 | + </DropdownMenuTrigger> |
| 657 | + <DropdownMenuContent align='end'> |
| 658 | + <DropdownMenuItem |
| 659 | + onClick={() => activateVersion(v.version)} |
| 660 | + disabled={v.isActive || activatingVersion === v.version} |
| 661 | + > |
| 662 | + {v.isActive |
| 663 | + ? 'Active' |
| 664 | + : activatingVersion === v.version |
| 665 | + ? 'Activating...' |
| 666 | + : 'Activate'} |
| 667 | + </DropdownMenuItem> |
| 668 | + <DropdownMenuItem |
| 669 | + onClick={() => openVersionPreview(v.version)} |
| 670 | + > |
| 671 | + Inspect |
| 672 | + </DropdownMenuItem> |
| 673 | + </DropdownMenuContent> |
| 674 | + </DropdownMenu> |
| 675 | + </td> |
| 676 | + </tr> |
| 677 | + ))} |
| 678 | + </tbody> |
| 679 | + </table> |
| 680 | + </div> |
| 681 | + {versions.length > itemsPerPage && ( |
| 682 | + <div className='mt-3 flex items-center justify-between'> |
| 683 | + <span className='text-muted-foreground text-sm'> |
| 684 | + Showing{' '} |
| 685 | + {Math.min((currentPage - 1) * itemsPerPage + 1, versions.length)} -{' '} |
| 686 | + {Math.min(currentPage * itemsPerPage, versions.length)} of{' '} |
| 687 | + {versions.length} |
| 688 | + </span> |
| 689 | + <div className='flex gap-2'> |
| 690 | + <Button |
| 691 | + variant='outline' |
| 692 | + size='sm' |
| 693 | + onClick={() => setCurrentPage(currentPage - 1)} |
| 694 | + disabled={currentPage === 1} |
585 | 695 | > |
586 | | - <div |
587 | | - className={`h-2 w-2 rounded-full ${v.isActive ? 'bg-green-500' : 'bg-muted-foreground/40'}`} |
588 | | - /> |
589 | | - <div className='text-sm'>v{v.version}</div> |
590 | | - <div className='text-muted-foreground text-xs'> |
591 | | - {new Date(v.createdAt).toLocaleString()} |
592 | | - </div> |
593 | | - </button> |
594 | | - {!v.isActive && ( |
595 | | - <Button |
596 | | - size='sm' |
597 | | - variant='outline' |
598 | | - disabled={activatingVersion === v.version} |
599 | | - onClick={() => activateVersion(v.version)} |
600 | | - > |
601 | | - {activatingVersion === v.version ? 'Activating…' : 'Activate'} |
602 | | - </Button> |
603 | | - )} |
604 | | - </li> |
605 | | - ))} |
606 | | - </ul> |
607 | | - )} |
608 | | - </div> |
| 696 | + Previous |
| 697 | + </Button> |
| 698 | + <Button |
| 699 | + variant='outline' |
| 700 | + size='sm' |
| 701 | + onClick={() => setCurrentPage(currentPage + 1)} |
| 702 | + disabled={currentPage * itemsPerPage >= versions.length} |
| 703 | + > |
| 704 | + Next |
| 705 | + </Button> |
| 706 | + </div> |
| 707 | + </div> |
| 708 | + )} |
| 709 | + </> |
| 710 | + )} |
609 | 711 | </div> |
610 | 712 | </> |
611 | 713 | )} |
@@ -810,6 +912,7 @@ export function DeployModal({ |
810 | 912 | isActivating={activatingVersion === previewVersion} |
811 | 913 | selectedVersionLabel={`v${previewVersion}`} |
812 | 914 | workflowId={workflowId} |
| 915 | + isSelectedVersionActive={versions.find((v) => v.version === previewVersion)?.isActive} |
813 | 916 | /> |
814 | 917 | )} |
815 | 918 | </Dialog> |
|
0 commit comments