| ... | @@ -593,7 +593,11 @@ export function ProviderInstanceCard({ | ... | @@ -593,7 +593,11 @@ export function ProviderInstanceCard({ |
| 593 | compatibility.status !== "supported" && | 593 | compatibility.status !== "supported" && |
| 594 | compatibility.status !== "unknown"; | 594 | compatibility.status !== "unknown"; |
| 595 | const VersionAdvisoryIcon = hasCompatibilityWarning ? AlertTriangleIcon : ArrowUpCircleIcon; | 595 | const VersionAdvisoryIcon = hasCompatibilityWarning ? AlertTriangleIcon : ArrowUpCircleIcon; |
| 596 | const onRunVersionAction = versionAdvisory?.targetVersion ? onInstallRecommended : onRunUpdate; | 596 | const onRunVersionAction = readOnly |
| | 597 | ? undefined |
| | 598 | : versionAdvisory?.targetVersion |
| | 599 | ? onInstallRecommended |
| | 600 | : onRunUpdate; |
| 597 | const urlAuthAction = liveProvider?.auth.action; | 601 | const urlAuthAction = liveProvider?.auth.action; |
| 598 | const displayName = | 602 | const displayName = |
| 599 | instance.displayName?.trim() || driverOption?.label || String(instance.driver); | 603 | instance.displayName?.trim() || driverOption?.label || String(instance.driver); |
| ... | @@ -765,6 +769,101 @@ export function ProviderInstanceCard({ | ... | @@ -765,6 +769,101 @@ export function ProviderInstanceCard({ |
| 765 | ) : null} | 769 | ) : null} |
| 766 | </> | 770 | </> |
| 767 | ); | 771 | ); |
| | 772 | const versionAdvisoryNode = versionAdvisory ? ( |
| | 773 | <Popover> |
| | 774 | <Tooltip> |
| | 775 | <TooltipTrigger |
| | 776 | render={ |
| | 777 | <PopoverTrigger |
| | 778 | render={ |
| | 779 | <Button |
| | 780 | type="button" |
| | 781 | size={mode === "list" ? "icon-micro" : "icon-xs"} |
| | 782 | variant="ghost-muted" |
| | 783 | className={mode === "list" ? "pointer-events-auto relative shrink-0" : undefined} |
| | 784 | aria-label={`${versionAdvisory.title} — view details`} |
| | 785 | > |
| | 786 | <VersionAdvisoryIcon |
| | 787 | className={cn( |
| | 788 | mode === "list" && "size-3.5", |
| | 789 | hasCompatibilityWarning && "text-warning", |
| | 790 | )} |
| | 791 | /> |
| | 792 | </Button> |
| | 793 | } |
| | 794 | /> |
| | 795 | } |
| | 796 | /> |
| | 797 | <TooltipPopup side="top">{versionAdvisory.title}</TooltipPopup> |
| | 798 | </Tooltip> |
| | 799 | <PopoverPopup side="bottom" align="end" width="md" aria-label={versionAdvisory.title}> |
| | 800 | <div className="grid min-w-0 gap-3"> |
| | 801 | <div className="grid gap-0.5"> |
| | 802 | <p className="text-sm font-semibold leading-tight text-foreground"> |
| | 803 | {versionAdvisory.title} |
| | 804 | </p> |
| | 805 | <p |
| | 806 | className={cn( |
| | 807 | "text-xs leading-snug", |
| | 808 | versionAdvisory.emphasis === "strong" ? "text-warning" : "text-muted-foreground", |
| | 809 | )} |
| | 810 | > |
| | 811 | {versionAdvisory.detail} |
| | 812 | </p> |
| | 813 | </div> |
| | 814 | {onRunVersionAction ? ( |
| | 815 | <Button |
| | 816 | type="button" |
| | 817 | size="xs" |
| | 818 | variant="outline" |
| | 819 | className="w-full" |
| | 820 | disabled={isUpdating} |
| | 821 | onClick={onRunVersionAction} |
| | 822 | > |
| | 823 | {isUpdating ? <Spinner /> : <DownloadIcon />} |
| | 824 | {isUpdating |
| | 825 | ? "Updating" |
| | 826 | : versionAdvisory.targetVersion |
| | 827 | ? `Install ${getProviderVersionLabel(versionAdvisory.targetVersion)}` |
| | 828 | : "Update now"} |
| | 829 | </Button> |
| | 830 | ) : null} |
| | 831 | {onRunVersionAction && updateCommand ? ( |
| | 832 | <div className="flex items-center gap-2 text-3xs font-medium uppercase tracking-wider text-muted-foreground"> |
| | 833 | <span aria-hidden className="h-px flex-1 bg-border" /> |
| | 834 | or, update manually using |
| | 835 | <span aria-hidden className="h-px flex-1 bg-border" /> |
| | 836 | </div> |
| | 837 | ) : null} |
| | 838 | {updateCommand ? ( |
| | 839 | <div className="flex min-w-0 items-center gap-1 rounded-md border border-border/70 bg-muted/40 py-0.5 pr-0.5 pl-2"> |
| | 840 | <code className="min-w-0 flex-1 truncate font-mono text-2xs text-foreground"> |
| | 841 | {updateCommand} |
| | 842 | </code> |
| | 843 | <Tooltip> |
| | 844 | <TooltipTrigger |
| | 845 | render={ |
| | 846 | <Button |
| | 847 | type="button" |
| | 848 | size="icon-xs" |
| | 849 | variant="ghost-muted" |
| | 850 | className="shrink-0" |
| | 851 | onClick={() => copyToClipboard(updateCommand, { providerName: displayName })} |
| | 852 | aria-label="Copy update command" |
| | 853 | > |
| | 854 | <CopyIcon className="size-3" /> |
| | 855 | </Button> |
| | 856 | } |
| | 857 | /> |
| | 858 | <TooltipPopup side="top">Copy command</TooltipPopup> |
| | 859 | </Tooltip> |
| | 860 | </div> |
| | 861 | ) : null} |
| | 862 | </div> |
| | 863 | </PopoverPopup> |
| | 864 | </Popover> |
| | 865 | ) : null; |
| | 866 | |
| 768 | if (mode === "list") { | 867 | if (mode === "list") { |
| 769 | return ( | 868 | return ( |
| 770 | <div | 869 | <div |
| ... | @@ -796,49 +895,7 @@ export function ProviderInstanceCard({ | ... | @@ -796,49 +895,7 @@ export function ProviderInstanceCard({ |
| 796 | {versionLabel} | 895 | {versionLabel} |
| 797 | </code> | 896 | </code> |
| 798 | ) : null} | 897 | ) : null} |
| 799 | {versionAdvisory ? ( | 898 | {versionAdvisoryNode} |
| 800 | hasCompatibilityWarning ? ( | | |
| 801 | <Tooltip> | | |
| 802 | <TooltipTrigger | | |
| 803 | render={ | | |
| 804 | <span | | |
| 805 | tabIndex={0} | | |
| 806 | role="img" | | |
| 807 | aria-label={versionAdvisory.title} | | |
| 808 | className="pointer-events-auto relative inline-flex shrink-0 text-warning" | | |
| 809 | > | | |
| 810 | <VersionAdvisoryIcon className="size-3.5" /> | | |
| 811 | </span> | | |
| 812 | } | | |
| 813 | /> | | |
| 814 | <TooltipPopup side="top">{versionAdvisory.detail}</TooltipPopup> | | |
| 815 | </Tooltip> | | |
| 816 | ) : updateCommand ? ( | | |
| 817 | <Tooltip> | | |
| 818 | <TooltipTrigger | | |
| 819 | render={ | | |
| 820 | <Button | | |
| 821 | type="button" | | |
| 822 | size="icon-micro" | | |
| 823 | variant="ghost-muted" | | |
| 824 | className="pointer-events-auto relative shrink-0" | | |
| 825 | aria-label={`Copy ${displayName} update command`} | | |
| 826 | onClick={() => | | |
| 827 | copyToClipboard(updateCommand, { providerName: displayName }) | | |
| 828 | } | | |
| 829 | > | | |
| 830 | <ArrowUpCircleIcon className="size-3.5" /> | | |
| 831 | </Button> | | |
| 832 | } | | |
| 833 | /> | | |
| 834 | <TooltipPopup side="top">Copy update command</TooltipPopup> | | |
| 835 | </Tooltip> | | |
| 836 | ) : ( | | |
| 837 | <span role="img" aria-label="Update available" className="inline-flex shrink-0"> | | |
| 838 | <ArrowUpCircleIcon className="size-3.5 text-muted-foreground" /> | | |
| 839 | </span> | | |
| 840 | ) | | |
| 841 | ) : null} | | |
| 842 | </span> | 899 | </span> |
| 843 | <span className="mt-0.5 flex items-start gap-1.5 text-xs leading-normal text-muted-foreground/80"> | 900 | <span className="mt-0.5 flex items-start gap-1.5 text-xs leading-normal text-muted-foreground/80"> |
| 844 | {statusDotNode ? ( | 901 | {statusDotNode ? ( |
| ... | @@ -876,105 +933,12 @@ export function ProviderInstanceCard({ | ... | @@ -876,105 +933,12 @@ export function ProviderInstanceCard({ |
| 876 | </Badge> | 933 | </Badge> |
| 877 | ) : null} | 934 | ) : null} |
| 878 | {versionCodeNode} | 935 | {versionCodeNode} |
| | 936 | {versionAdvisoryNode} |
| 879 | <span | 937 | <span |
| 880 | inert={readOnly} | 938 | inert={readOnly} |
| 881 | aria-disabled={readOnly || undefined} | 939 | aria-disabled={readOnly || undefined} |
| 882 | className={cn("inline-flex items-center gap-1", readOnly && "opacity-50")} | 940 | className={cn("inline-flex items-center gap-1", readOnly && "opacity-50")} |
| 883 | > | 941 | > |
| 884 | {versionAdvisory ? ( | | |
| 885 | <Popover> | | |
| 886 | <Tooltip> | | |
| 887 | <TooltipTrigger | | |
| 888 | render={ | | |
| 889 | <PopoverTrigger | | |
| 890 | render={ | | |
| 891 | <Button | | |
| 892 | type="button" | | |
| 893 | size="icon-xs" | | |
| 894 | variant="ghost-muted" | | |
| 895 | aria-label={`${versionAdvisory.title} — view details`} | | |
| 896 | > | | |
| 897 | <VersionAdvisoryIcon | | |
| 898 | className={cn(hasCompatibilityWarning && "text-warning")} | | |
| 899 | /> | | |
| 900 | </Button> | | |
| 901 | } | | |
| 902 | /> | | |
| 903 | } | | |
| 904 | /> | | |
| 905 | <TooltipPopup side="top">{versionAdvisory.title}</TooltipPopup> | | |
| 906 | </Tooltip> | | |
| 907 | <PopoverPopup side="bottom" align="end" width="md"> | | |
| 908 | <div className="grid min-w-0 gap-3"> | | |
| 909 | <div className="grid gap-0.5"> | | |
| 910 | <p className="text-sm font-semibold leading-tight text-foreground"> | | |
| 911 | {versionAdvisory.title} | | |
| 912 | </p> | | |
| 913 | <p | | |
| 914 | className={cn( | | |
| 915 | "text-xs leading-snug", | | |
| 916 | versionAdvisory.emphasis === "strong" | | |
| 917 | ? "text-warning" | | |
| 918 | : "text-muted-foreground", | | |
| 919 | )} | | |
| 920 | > | | |
| 921 | {versionAdvisory.detail} | | |
| 922 | </p> | | |
| 923 | </div> | | |
| 924 | {onRunVersionAction ? ( | | |
| 925 | <Button | | |
| 926 | type="button" | | |
| 927 | size="xs" | | |
| 928 | variant="outline" | | |
| 929 | className="w-full" | | |
| 930 | disabled={isUpdating} | | |
| 931 | onClick={onRunVersionAction} | | |
| 932 | > | | |
| 933 | {isUpdating ? <Spinner /> : <DownloadIcon />} | | |
| 934 | {isUpdating | | |
| 935 | ? "Updating" | | |
| 936 | : versionAdvisory.targetVersion | | |
| 937 | ? `Install ${getProviderVersionLabel(versionAdvisory.targetVersion)}` | | |
| 938 | : "Update now"} | | |
| 939 | </Button> | | |
| 940 | ) : null} | | |
| 941 | {onRunVersionAction && updateCommand ? ( | | |
| 942 | <div className="flex items-center gap-2 text-3xs font-medium uppercase tracking-wider text-muted-foreground"> | | |
| 943 | <span aria-hidden className="h-px flex-1 bg-border" /> | | |
| 944 | or, update manually using | | |
| 945 | <span aria-hidden className="h-px flex-1 bg-border" /> | | |
| 946 | </div> | | |
| 947 | ) : null} | | |
| 948 | {updateCommand ? ( | | |
| 949 | <div className="flex min-w-0 items-center gap-1 rounded-md border border-border/70 bg-muted/40 py-0.5 pr-0.5 pl-2"> | | |
| 950 | <code className="min-w-0 flex-1 truncate font-mono text-2xs text-foreground"> | | |
| 951 | {updateCommand} | | |
| 952 | </code> | | |
| 953 | <Tooltip> | | |
| 954 | <TooltipTrigger | | |
| 955 | render={ | | |
| 956 | <Button | | |
| 957 | type="button" | | |
| 958 | size="icon-xs" | | |
| 959 | variant="ghost-muted" | | |
| 960 | className="shrink-0" | | |
| 961 | onClick={() => | | |
| 962 | copyToClipboard(updateCommand, { providerName: displayName }) | | |
| 963 | } | | |
| 964 | aria-label="Copy update command" | | |
| 965 | > | | |
| 966 | <CopyIcon className="size-3" /> | | |
| 967 | </Button> | | |
| 968 | } | | |
| 969 | /> | | |
| 970 | <TooltipPopup side="top">Copy command</TooltipPopup> | | |
| 971 | </Tooltip> | | |
| 972 | </div> | | |
| 973 | ) : null} | | |
| 974 | </div> | | |
| 975 | </PopoverPopup> | | |
| 976 | </Popover> | | |
| 977 | ) : null} | | |
| 978 | {titleTailNode} | 942 | {titleTailNode} |
| 979 | {onDelete ? ( | 943 | {onDelete ? ( |
| 980 | <Button | 944 | <Button |