Files
upage-git/app/components/header/DeployTo1PanelDialog.tsx
2025-09-24 17:02:44 +08:00

202 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useStore } from '@nanostores/react';
import * as Dialog from '@radix-ui/react-dialog';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import _1PanelConnection from '~/components/header/connections/_1PanelConnection';
import { useChatDeployment } from '~/lib/hooks/useChatDeployment';
import { _1PanelConnectionStore } from '~/lib/stores/1panel';
import { DeploymentPlatformEnum } from '~/types/deployment';
interface DeployTo1PanelDialogProps {
isOpen: boolean;
deploying: boolean;
onClose: () => void;
onDeploy: (options?: { customDomain?: string; siteId?: number; protocol?: string }) => Promise<void>;
}
export function DeployTo1PanelDialog({ deploying, isOpen, onClose, onDeploy }: DeployTo1PanelDialogProps) {
const { getDeploymentByPlatform } = useChatDeployment();
const connection = useStore(_1PanelConnectionStore);
const [is1PanelConnected, setIs1PanelConnected] = useState(false);
const [showConnectionForm, setShowConnectionForm] = useState(false);
const [customDomain, setCustomDomain] = useState('');
const [proxyProtocol, setProxyProtocol] = useState('http');
useEffect(() => {
if (connection.isConnect) {
setIs1PanelConnected(true);
if (isOpen && !is1PanelConnected && !showConnectionForm) {
setShowConnectionForm(false);
}
return;
}
setIs1PanelConnected(false);
if (isOpen && !showConnectionForm) {
setShowConnectionForm(true);
}
}, [connection.isConnect, isOpen, is1PanelConnected]);
const check1PanelConnection = () => {
if (connection.isConnect) {
setIs1PanelConnected(true);
}
};
const handleDeploy = async (options?: { customDomain?: string; siteId?: number; protocol?: string }) => {
if (!connection.isConnect) {
return;
}
await onDeploy({
...options,
customDomain: customDomain || undefined,
protocol: proxyProtocol,
});
};
const toggleProtocol = () => {
setProxyProtocol(proxyProtocol === 'http' ? 'https' : 'http');
};
const handleClose = () => {
if (!deploying) {
onClose();
setShowConnectionForm(false);
}
};
const deploymentInfo = getDeploymentByPlatform(DeploymentPlatformEnum._1PANEL);
return (
<Dialog.Root open={isOpen} onOpenChange={(open) => !open && handleClose()}>
<Dialog.Portal>
<div className="fixed inset-0 flex items-center justify-center z-[100]">
<Dialog.Overlay asChild>
<motion.div
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
/>
</Dialog.Overlay>
<Dialog.Content
aria-describedby={undefined}
onEscapeKeyDown={handleClose}
onPointerDownOutside={handleClose}
className="relative z-[101]"
>
<Dialog.Title className="sr-only"> 1Panel</Dialog.Title>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ duration: 0.2 }}
className="w-[90vw] md:w-[650px] my-4"
>
<div className="bg-white dark:bg-[#0A0A0A] rounded-lg border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl max-h-[calc(85vh-2rem)] flex flex-col">
<div className="p-6 overflow-y-auto flex-1">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<img src="/icons/1panel.png" alt="1Panel" className="size-5" />
<h3 className="text-lg font-medium text-gray-900 dark:text-white">
{is1PanelConnected ? '部署到 1Panel' : '连接 1Panel 服务器'}
</h3>
</div>
<Dialog.Close
className="flex items-center justify-center size-8 rounded-full bg-transparent hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200"
onClick={handleClose}
>
<div className="i-ph:x size-4 text-gray-500 dark:text-gray-400 group-hover:text-purple-500 transition-colors" />
</Dialog.Close>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
{is1PanelConnected
? '您的项目将被部署到 1Panel。点击"部署"按钮开始部署。'
: '需要连接 1Panel 服务器才能部署项目。请在此页面完成连接。'}
</p>
{!is1PanelConnected && (
<p className="text-sm font-medium text-amber-600 dark:text-amber-500 mb-6 flex items-center gap-1.5">
<span className="i-ph:warning-circle size-4 flex-shrink-0" />
1Panel V2
</p>
)}
{is1PanelConnected && !deploymentInfo?.id && (
<div className="mb-6">
<label className="block text-sm text-gray-600 dark:text-gray-400 mb-2"></label>
<div className="flex items-center gap-2">
<button
onClick={toggleProtocol}
className="px-3 py-2 rounded-lg text-sm bg-[#F8F8F8] dark:bg-[#1A1A1A] border border-[#E5E5E5] dark:border-[#333333] text-gray-900 dark:text-white hover:bg-[#F0F0F0] dark:hover:bg-[#222222] transition-colors focus:outline-none focus:ring-1 focus:ring-upage-elements-borderColorActive"
>
{proxyProtocol}
</button>
<span className="text-gray-500 dark:text-gray-400">://</span>
<input
type="text"
value={customDomain}
onChange={(e) => setCustomDomain(e.target.value)}
placeholder="example.upage.ai"
className="flex-1 px-3 py-2 rounded-lg text-sm bg-[#F8F8F8] dark:bg-[#1A1A1A] border border-[#E5E5E5] dark:border-[#333333] text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-upage-elements-borderColorActive"
/>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">使</p>
</div>
)}
<div className="1panel-connection-wrapper">
<_1PanelConnection isDeploying={deploying} onDeploy={(siteId) => handleDeploy({ siteId })} />
</div>
</div>
<div className="p-4 border-t border-[#E5E5E5] dark:border-[#1A1A1A] bg-white dark:bg-[#0A0A0A] sticky bottom-0">
<div className="flex justify-end">
{!is1PanelConnected ? (
<motion.button
onClick={() => {
check1PanelConnection();
setTimeout(check1PanelConnection, 500);
}}
className="px-4 py-2 rounded-lg bg-upage-elements-item-backgroundAccent text-upage-elements-item-contentAccent text-sm hover:bg-upage-elements-item-backgroundAccent/90 inline-flex items-center gap-2"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<div className="i-ph:arrows-clockwise" />
</motion.button>
) : (
<motion.button
onClick={() => handleDeploy()}
disabled={deploying}
className="px-4 py-2 rounded-lg bg-[#2b5fe3] text-white text-sm hover:bg-[#2b5fe3]/90 inline-flex items-center gap-2"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{deploying ? (
<>
<div className="i-ph:spinner-gap animate-spin size-4" />
...
</>
) : (
<>
<div className="i-ph:rocket-launch size-4" />
{!!deploymentInfo?.id ? '覆盖已有网站' : '部署到 1Panel'}
</>
)}
</motion.button>
)}
</div>
</div>
</div>
</motion.div>
</Dialog.Content>
</div>
</Dialog.Portal>
</Dialog.Root>
);
}