Add frontend #1
@@ -24,7 +24,7 @@ func NewUpdateSiteHandler(envCfg *envconfig.EnvConfig, siteRepo repository.SiteR
|
||||
return &UpdateSiteHandler{EnvCfg: envCfg, SiteRepo: siteRepo}
|
||||
}
|
||||
|
||||
func (h *UpdateSiteHandler) PostUpdate(c fiber.Ctx) error {
|
||||
func (h *UpdateSiteHandler) PostDeploy(c fiber.Ctx) error {
|
||||
siteId := c.Query("site")
|
||||
if siteId == "" {
|
||||
return c.Status(400).JSON(models.APIError{
|
||||
@@ -24,7 +24,7 @@ func Register(app *fiber.App, cfg *config.Config, envCfg *envconfig.EnvConfig, d
|
||||
api := app.Group("/api/v1", middleware.APIHostGuard(envCfg.DashboardHost))
|
||||
api.Get("/health", handlers.HealthCheck)
|
||||
|
||||
api.Post("/update", updateSiteHandler.PostUpdate)
|
||||
api.Post("/deploy", updateSiteHandler.PostDeploy)
|
||||
|
||||
api.Get("/sites", siteHandler.GetSites)
|
||||
api.Get("/sites/:id", siteHandler.GetSite)
|
||||
|
||||
@@ -6,9 +6,18 @@ import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Plus, Zap } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Plus, Zap, Copy, Check, AlertTriangle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useCreateSite } from '../../hooks/api/useCreateSite';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
const GIT_SERVERS = [
|
||||
{
|
||||
@@ -55,6 +64,7 @@ const parseRepoUrl = (url: string) => {
|
||||
};
|
||||
|
||||
const NewSite = () => {
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState('');
|
||||
const [repoUrl, setRepoUrl] = useState('');
|
||||
const [gitServer, setGitServer] = useState('');
|
||||
@@ -64,6 +74,11 @@ const NewSite = () => {
|
||||
const [domain, setDomain] = useState('');
|
||||
const [spa, setSpa] = useState(false);
|
||||
const [urlError, setUrlError] = useState('');
|
||||
const [showTokenModal, setShowTokenModal] = useState(false);
|
||||
const [deployToken, setDeployToken] = useState('');
|
||||
const [createdSiteId, setCreatedSiteId] = useState('');
|
||||
const [copiedToken, setCopiedToken] = useState(false);
|
||||
const [copiedCurl, setCopiedCurl] = useState(false);
|
||||
const createNewSite = useCreateSite();
|
||||
|
||||
const handleQuickImport = () => {
|
||||
@@ -90,7 +105,6 @@ const NewSite = () => {
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
console.log({ name, gitServer, owner, repository, branch, domain, spa });
|
||||
const data = await createNewSite.mutateAsync({
|
||||
name,
|
||||
git_server: gitServer,
|
||||
@@ -103,9 +117,32 @@ const NewSite = () => {
|
||||
not_found_file: '',
|
||||
index_file: '',
|
||||
});
|
||||
console.log('Created site:', data);
|
||||
|
||||
setCreatedSiteId(data.site.id);
|
||||
setDeployToken(data.raw_deploy_token);
|
||||
setShowTokenModal(true);
|
||||
};
|
||||
|
||||
const handleCopy = async (text: string, type: 'token' | 'curl') => {
|
||||
await navigator.clipboard.writeText(text);
|
||||
if (type === 'token') {
|
||||
setCopiedToken(true);
|
||||
setTimeout(() => setCopiedToken(false), 2000);
|
||||
} else {
|
||||
setCopiedCurl(true);
|
||||
setTimeout(() => setCopiedCurl(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setShowTokenModal(false);
|
||||
navigate(`/sites/${createdSiteId}`);
|
||||
};
|
||||
|
||||
const curlExample = `curl -X POST \\
|
||||
"${window.location.origin}/api/v1/deploy?site=${createdSiteId}" \\
|
||||
-H "Authorization: Bearer ${deployToken}"`;
|
||||
|
||||
const isValid = name && gitServer && owner && repository && branch && domain;
|
||||
|
||||
return (
|
||||
@@ -278,6 +315,78 @@ const NewSite = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog open={showTokenModal} onOpenChange={setShowTokenModal}>
|
||||
<DialogContent
|
||||
className="sm:max-w-lg"
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg">Site Created Successfully</DialogTitle>
|
||||
<DialogDescription>
|
||||
Your site has been created. Use the deploy token below to trigger
|
||||
deployments via the API.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3">
|
||||
<AlertTriangle className="w-4 h-4 text-amber-500 mt-0.5 shrink-0" />
|
||||
<p className="text-sm text-amber-500">
|
||||
Make sure to copy your deploy token now. You won't be able to see it
|
||||
again.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Deploy Token</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input readOnly value={deployToken} className="font-mono text-sm" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => handleCopy(deployToken, 'token')}
|
||||
>
|
||||
{copiedToken ? (
|
||||
<Check className="w-4 h-4 text-success" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Usage</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Trigger a deployment by sending a POST request to the deploy
|
||||
endpoint with your token in the Authorization header:
|
||||
</p>
|
||||
<div className="relative">
|
||||
<pre className="rounded-lg border bg-muted p-3 text-sm font-mono overflow-x-auto whitespace-pre-wrap break-all">
|
||||
{curlExample}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute top-2 right-2 h-7 w-7"
|
||||
onClick={() => handleCopy(curlExample, 'curl')}
|
||||
>
|
||||
{copiedCurl ? (
|
||||
<Check className="w-3.5 h-3.5 text-success" />
|
||||
) : (
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={handleModalClose}>I've saved my token</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user