diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 735f182f..8ee87203 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: fail-fast: true matrix: php: [ 8.4 ] - node-version: [ "20.x" ] + node-version: [ "22.x" ] steps: - uses: actions/checkout@v4 @@ -46,10 +46,13 @@ jobs: - name: Set up the .env file run: touch .env + - name: Generate Application Key + run: php artisan key:generate + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20.x" + node-version: "22.x" - name: Install NPM Dependencies run: npm install --force diff --git a/app/Actions/Site/UpdateLoadBalancer.php b/app/Actions/Site/UpdateLoadBalancer.php index a628e9d8..f3b58197 100644 --- a/app/Actions/Site/UpdateLoadBalancer.php +++ b/app/Actions/Site/UpdateLoadBalancer.php @@ -22,7 +22,7 @@ public function update(Site $site, array $input): void foreach ($input['servers'] as $server) { $loadBalancerServer = new LoadBalancerServer([ 'load_balancer_id' => $site->id, - 'ip' => $server['server'], + 'ip' => $server['ip'], 'port' => $server['port'], 'weight' => $server['weight'], 'backup' => (bool) $server['backup'], @@ -43,7 +43,7 @@ public static function rules(Site $site): array 'required', 'array', ], - 'servers.*.server' => [ + 'servers.*.ip' => [ 'required', Rule::exists('servers', 'local_ip') ->where('project_id', $site->project->id), diff --git a/resources/js/pages/application/components/load-balancer.tsx b/resources/js/pages/application/components/load-balancer.tsx index d0d5a375..83e80aee 100644 --- a/resources/js/pages/application/components/load-balancer.tsx +++ b/resources/js/pages/application/components/load-balancer.tsx @@ -6,8 +6,8 @@ import Container from '@/components/container'; import HeaderContainer from '@/components/header-container'; import Heading from '@/components/heading'; import { Button } from '@/components/ui/button'; -import { BookOpenIcon, LoaderCircleIcon } from 'lucide-react'; -import { FormEvent } from 'react'; +import { BookOpenIcon, LoaderCircleIcon, XIcon } from 'lucide-react'; +import React, { FormEvent } from 'react'; import { LoadBalancerServer } from '@/types/load-balancer-server'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Form, FormField, FormFields } from '@/components/ui/form'; @@ -15,6 +15,9 @@ import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import InputError from '@/components/ui/input-error'; import FormSuccessful from '@/components/form-successful'; +import ServerSelect from '@/pages/servers/components/server-select'; +import { Input } from '@/components/ui/input'; +import { Switch } from '@/components/ui/switch'; export default function LoadBalancer() { const page = usePage<{ @@ -26,26 +29,42 @@ export default function LoadBalancer() { const form = useForm<{ method: 'round-robin' | 'least-connections' | 'ip-hash'; servers: { - server: string; - port: string; + load_balancer_id: number; + ip: string; + port: number; weight: string; backup: boolean; }[]; }>({ method: 'round-robin', - servers: [], + servers: page.props.loadBalancerServers, }); + const addServer = () => { + const newServer: LoadBalancerServer = { + load_balancer_id: 0, + ip: '', + port: 80, + weight: '100', + backup: false, + created_at: '', + updated_at: '', + }; + + form.setData('servers', [...form.data.servers, newServer]); + }; + const submit = (e: FormEvent) => { e.preventDefault(); form.post(route('application.update-load-balancer', { server: page.props.server.id, site: page.props.site.id }), { - onSuccess: () => { - form.reset(); - }, preserveScroll: true, }); }; + const getFieldError = (field: string): string | undefined => { + return form.errors[field as keyof typeof form.errors]; + }; + return ( @@ -90,6 +109,99 @@ export default function LoadBalancer() { + + {form.data.servers.map((item, index) => ( +
+ { + const updatedServers = [...form.data.servers]; + updatedServers.splice(index, 1); + form.setData('servers', updatedServers); + }} + /> +
+ + + { + const updatedServers = [...form.data.servers]; + updatedServers[index] = { + ...updatedServers[index], + ip: server ? server.local_ip || '' : '', + }; + form.setData('servers', updatedServers); + }} + /> + + + + + + { + const updatedServers = [...form.data.servers]; + updatedServers[index] = { + ...updatedServers[index], + port: parseInt(e.target.value, 10), + }; + form.setData('servers', updatedServers); + }} + /> + + + + + + { + const updatedServers = [...form.data.servers]; + updatedServers[index] = { + ...updatedServers[index], + weight: e.target.value, + }; + form.setData('servers', updatedServers); + }} + /> + + + + + + { + const updatedServers = [...form.data.servers]; + updatedServers[index] = { + ...updatedServers[index], + backup: checked, + }; + form.setData('servers', updatedServers); + }} + className="mt-2" + /> + + +
+
+ ))} + + + Add server to the load balancer + diff --git a/resources/js/pages/scripts/components/execute.tsx b/resources/js/pages/scripts/components/execute.tsx index 035e2787..3049bdc5 100644 --- a/resources/js/pages/scripts/components/execute.tsx +++ b/resources/js/pages/scripts/components/execute.tsx @@ -55,7 +55,7 @@ export default function Execute({ script, children }: { script: Script; children { - form.setData('server', value.id.toString()); + form.setData('server', value ? value.id.toString() : ''); setServer(value); }} /> diff --git a/resources/js/pages/servers/components/server-select.tsx b/resources/js/pages/servers/components/server-select.tsx index cce3d3ca..404d53f3 100644 --- a/resources/js/pages/servers/components/server-select.tsx +++ b/resources/js/pages/servers/components/server-select.tsx @@ -8,7 +8,19 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command import { cn } from '@/lib/utils'; import axios from 'axios'; -export default function ServerSelect({ value, onValueChange }: { value: string; onValueChange: (selectedServer: Server) => void }) { +export default function ServerSelect({ + value, + valueBy = 'id', + onValueChange, + id, + prefetch, +}: { + value: string; + valueBy?: keyof Server; + onValueChange: (selectedServer?: Server) => void; + id?: string; + prefetch?: boolean; +}) { const [query, setQuery] = useState(''); const [open, setOpen] = useState(false); const [selected, setSelected] = useState(value); @@ -27,7 +39,7 @@ export default function ServerSelect({ value, onValueChange }: { value: string; const response = await axios.get(route('servers.json', { query: query })); return response.data; }, - enabled: false, + enabled: prefetch, }); const onOpenChange = (open: boolean) => { @@ -47,12 +59,12 @@ export default function ServerSelect({ value, onValueChange }: { value: string; } }, [query, open, refetch]); - const selectedServer = servers.find((server) => server.id === parseInt(selected)); + const selectedServer = servers.find((server) => String(server[valueBy] as Server[keyof Server]) === selected); return ( - @@ -63,25 +75,21 @@ export default function ServerSelect({ value, onValueChange }: { value: string; {isFetching ? 'Searching...' : query === '' ? 'Start typing to search servers' : 'No servers found.'} - {servers.map((server) => ( + {servers.map((server: Server) => ( { const newSelected = currentValue === selected ? '' : currentValue; setSelected(newSelected); setOpen(false); - if (newSelected) { - const server = servers.find((s) => s.id.toString() === newSelected); - if (server) { - onValueChange(server); - } - } + const server = servers.find((s) => String(s[valueBy] as Server[keyof Server]) === newSelected); + onValueChange(server); }} className="truncate" > {server.name} ({server.ip}) - + ))} diff --git a/resources/js/pages/sites/components/create-site.tsx b/resources/js/pages/sites/components/create-site.tsx index 9021cc6c..c11c5875 100644 --- a/resources/js/pages/sites/components/create-site.tsx +++ b/resources/js/pages/sites/components/create-site.tsx @@ -104,7 +104,7 @@ export default function CreateSite({ server, children }: { server?: Server; chil {server === undefined && ( - form.setData('server', value.id.toString())} /> + form.setData('server', value ? value.id.toString() : '')} /> )} diff --git a/resources/js/types/load-balancer-server.d.ts b/resources/js/types/load-balancer-server.d.ts index e5c1b536..4e71f87b 100644 --- a/resources/js/types/load-balancer-server.d.ts +++ b/resources/js/types/load-balancer-server.d.ts @@ -1,9 +1,9 @@ export interface LoadBalancerServer { load_balancer_id: number; - ip: number; + ip: string; port: number; - weight: boolean; - backup: string; + weight: string; + backup: boolean; created_at: string; updated_at: string;