Add frontend #1
@@ -24,7 +24,7 @@ func NewUpdateSiteHandler(envCfg *envconfig.EnvConfig, siteRepo repository.SiteR
|
|||||||
return &UpdateSiteHandler{EnvCfg: envCfg, SiteRepo: siteRepo}
|
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")
|
siteId := c.Query("site")
|
||||||
if siteId == "" {
|
if siteId == "" {
|
||||||
return c.Status(400).JSON(models.APIError{
|
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 := app.Group("/api/v1", middleware.APIHostGuard(envCfg.DashboardHost))
|
||||||
api.Get("/health", handlers.HealthCheck)
|
api.Get("/health", handlers.HealthCheck)
|
||||||
|
|
||||||
api.Post("/update", updateSiteHandler.PostUpdate)
|
api.Post("/deploy", updateSiteHandler.PostDeploy)
|
||||||
|
|
||||||
api.Get("/sites", siteHandler.GetSites)
|
api.Get("/sites", siteHandler.GetSites)
|
||||||
api.Get("/sites/:id", siteHandler.GetSite)
|
api.Get("/sites/:id", siteHandler.GetSite)
|
||||||
|
|||||||
@@ -6,9 +6,18 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
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 { cn } from '@/lib/utils';
|
||||||
import { useCreateSite } from '../../hooks/api/useCreateSite';
|
import { useCreateSite } from '../../hooks/api/useCreateSite';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
const GIT_SERVERS = [
|
const GIT_SERVERS = [
|
||||||
{
|
{
|
||||||
@@ -55,6 +64,7 @@ const parseRepoUrl = (url: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NewSite = () => {
|
const NewSite = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [repoUrl, setRepoUrl] = useState('');
|
const [repoUrl, setRepoUrl] = useState('');
|
||||||
const [gitServer, setGitServer] = useState('');
|
const [gitServer, setGitServer] = useState('');
|
||||||
@@ -64,6 +74,11 @@ const NewSite = () => {
|
|||||||
const [domain, setDomain] = useState('');
|
const [domain, setDomain] = useState('');
|
||||||
const [spa, setSpa] = useState(false);
|
const [spa, setSpa] = useState(false);
|
||||||
const [urlError, setUrlError] = useState('');
|
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 createNewSite = useCreateSite();
|
||||||
|
|
||||||
const handleQuickImport = () => {
|
const handleQuickImport = () => {
|
||||||
@@ -90,7 +105,6 @@ const NewSite = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
console.log({ name, gitServer, owner, repository, branch, domain, spa });
|
|
||||||
const data = await createNewSite.mutateAsync({
|
const data = await createNewSite.mutateAsync({
|
||||||
name,
|
name,
|
||||||
git_server: gitServer,
|
git_server: gitServer,
|
||||||
@@ -103,9 +117,32 @@ const NewSite = () => {
|
|||||||
not_found_file: '',
|
not_found_file: '',
|
||||||
index_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;
|
const isValid = name && gitServer && owner && repository && branch && domain;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -278,6 +315,78 @@ const NewSite = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</Page>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user