mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-19 04:15:57 +00:00
feat(v5): add app configuration inspector
Add a sheet-based dashboard inspector with overview, networking, and advanced tabs for applications. Switch Caddy proxy actions to the generic Flux ingress apply and stop payloads.
This commit is contained in:
1 parent
a42785189e
commit
22e3d6960e
8 files changed
+317
-34
No files matched your search
@@ -184,15 +184,16 @@ it('generates http-only caddy routes for application ingress', function () {
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->with(
|
||||
'100.64.0.10',
|
||||
'caddy',
|
||||
Mockery::on(fn (string $caddyfile): bool => str_contains($caddyfile, 'import apps/*.caddy')),
|
||||
Mockery::on(fn (array $apps): bool => count($apps) === 1
|
||||
&& str_contains($apps[0]['caddyfile'], 'http://app.example.com {')
|
||||
&& ! str_contains($apps[0]['caddyfile'], 'https://')
|
||||
&& str_contains($apps[0]['caddyfile'], 'reverse_proxy coolify-v5-nginx-test.default.coolify.internal:3000'))
|
||||
&& str_contains($apps[0]['config'], 'http://app.example.com {')
|
||||
&& ! str_contains($apps[0]['config'], 'https://')
|
||||
&& str_contains($apps[0]['config'], 'reverse_proxy coolify-v5-nginx-test.default.coolify.internal:3000'))
|
||||
)
|
||||
->andReturn('Caddy ingress applied.');
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
@@ -3241,7 +3242,7 @@ it('rejects application ingress when server ingress is disabled', function () {
|
||||
]);
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient->shouldNotReceive('applyCaddyIngress');
|
||||
$fluxClient->shouldNotReceive('applyIngress');
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
|
||||
$this
|
||||
@@ -3305,10 +3306,11 @@ it('enables application ingress without publishing domains by default', function
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->with(
|
||||
'100.64.0.10',
|
||||
'caddy',
|
||||
Mockery::on(fn (string $caddyfile): bool => str_contains($caddyfile, 'import apps/*.caddy')),
|
||||
[]
|
||||
)
|
||||
@@ -3371,7 +3373,7 @@ it('validates application ingress domains', function () {
|
||||
]);
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient->shouldNotReceive('applyCaddyIngress');
|
||||
$fluxClient->shouldNotReceive('applyIngress');
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
|
||||
$this
|
||||
@@ -3430,14 +3432,15 @@ it('enables application ingress with explicit domains and port', function () {
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->with(
|
||||
'100.64.0.10',
|
||||
'caddy',
|
||||
Mockery::on(fn (string $caddyfile): bool => str_contains($caddyfile, 'import apps/*.caddy')),
|
||||
Mockery::on(fn (array $apps): bool => count($apps) === 1
|
||||
&& str_contains($apps[0]['caddyfile'], 'http://app.example.com {')
|
||||
&& str_contains($apps[0]['caddyfile'], 'reverse_proxy coolify-v5-nginx-test.default.coolify.internal:3000'))
|
||||
&& str_contains($apps[0]['config'], 'http://app.example.com {')
|
||||
&& str_contains($apps[0]['config'], 'reverse_proxy coolify-v5-nginx-test.default.coolify.internal:3000'))
|
||||
)
|
||||
->andReturn('Caddy ingress applied.');
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
@@ -3502,7 +3505,7 @@ it('returns flux error details when application ingress sync fails', function ()
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->andThrow(new RuntimeException('start Caddy ingress: podman exited with status 125'));
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
@@ -3572,15 +3575,16 @@ it('syncs caddy ingress routes through flux when enabling ingress on an installe
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->with(
|
||||
'100.64.0.10',
|
||||
'caddy',
|
||||
Mockery::on(fn (string $caddyfile): bool => str_contains($caddyfile, 'import apps/*.caddy')),
|
||||
Mockery::on(fn (array $apps): bool => count($apps) === 1
|
||||
&& str_contains($apps[0]['caddyfile'], 'http://nginx.example.com {')
|
||||
&& str_contains($apps[0]['caddyfile'], 'http://www.nginx.example.com {')
|
||||
&& str_contains($apps[0]['caddyfile'], 'reverse_proxy coolify-v5-nginx-test.default.coolify.internal:8080'))
|
||||
&& str_contains($apps[0]['config'], 'http://nginx.example.com {')
|
||||
&& str_contains($apps[0]['config'], 'http://www.nginx.example.com {')
|
||||
&& str_contains($apps[0]['config'], 'reverse_proxy coolify-v5-nginx-test.default.coolify.internal:8080'))
|
||||
)
|
||||
->andReturn('Caddy ingress applied.');
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
@@ -3651,7 +3655,7 @@ it('returns flux error details when server ingress activation fails', function (
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->andThrow(new RuntimeException('validate Caddyfile: unrecognized directive'));
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
@@ -4076,6 +4080,8 @@ it('defines the v5 dashboard page as a shadcn styled canvas shell', function ()
|
||||
->not->toContain('function csrfToken()')
|
||||
->toContain("import { csrfToken } from '@/lib/csrf';")
|
||||
->toContain("import { Button } from '@/components/ui/button';")
|
||||
->toContain("import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet';")
|
||||
->toContain("import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';")
|
||||
->not->toContain("fetch('/v5/clusters'")
|
||||
->toContain('<AppNavbar')
|
||||
->toContain('bg-background text-foreground')
|
||||
@@ -4087,6 +4093,20 @@ it('defines the v5 dashboard page as a shadcn styled canvas shell', function ()
|
||||
->toContain('server_id: selectedNginxServerId || null')
|
||||
->toContain('Center')
|
||||
->toContain('Delete')
|
||||
->toContain('App configuration')
|
||||
->toContain('openApplicationInspector')
|
||||
->toContain('selectedInspectorApplication')
|
||||
->toContain('onDoubleClick={(event) => openApplicationInspector(event, application)}')
|
||||
->toContain('open={selectedInspectorApplication !== null}')
|
||||
->toContain('<SheetContent side="right" className="w-full overflow-y-auto bg-background sm:rounded-l-xl sm:border data-[side=right]:sm:!inset-y-4 data-[side=right]:sm:!h-auto data-[side=right]:sm:!w-[45vw] data-[side=right]:sm:!max-w-[45vw]"')
|
||||
->toContain('showCloseButton blurOverlay={false}')
|
||||
->toContain('<SheetHeader className="p-6 pb-4">')
|
||||
->toContain('<div className="flex flex-1 flex-col gap-6 px-6 pb-6">')
|
||||
->toContain('<Tabs defaultValue="overview"')
|
||||
->toContain('<TabsTrigger value="overview">Overview</TabsTrigger>')
|
||||
->toContain('<TabsTrigger value="networking">Networking</TabsTrigger>')
|
||||
->toContain('<TabsTrigger value="advanced">Advanced</TabsTrigger>')
|
||||
->toContain('Double-click an application card to open configuration.')
|
||||
->toContain("method: 'DELETE'")
|
||||
->toContain('removeApplication')
|
||||
->toContain('useEffect(() => {')
|
||||
@@ -4106,6 +4126,10 @@ it('defines the v5 dashboard page as a shadcn styled canvas shell', function ()
|
||||
->not->toContain("import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';")
|
||||
->not->toContain("fetch('/v5/selection'");
|
||||
|
||||
expect(file_get_contents($sheetPath))
|
||||
->toContain('blurOverlay = true')
|
||||
->toContain('<SheetOverlay blur={blurOverlay} />');
|
||||
|
||||
expect($navbar)
|
||||
->toContain("import { Link, router, usePage } from '@inertiajs/react';")
|
||||
->toContain("import { cn } from '@/lib/utils';")
|
||||
|
||||
@@ -110,10 +110,11 @@ it('applies caddy ingress configuration through flux instead of ssh', function (
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('applyCaddyIngress')
|
||||
->shouldReceive('applyIngress')
|
||||
->once()
|
||||
->with(
|
||||
'100.64.0.10',
|
||||
'caddy',
|
||||
Mockery::on(fn (string $caddyfile): bool => str_contains($caddyfile, 'respond /coolify-health 200')),
|
||||
[]
|
||||
)
|
||||
@@ -144,9 +145,9 @@ it('stops caddy ingress through flux instead of ssh', function () {
|
||||
|
||||
$fluxClient = Mockery::mock(FluxClient::class);
|
||||
$fluxClient
|
||||
->shouldReceive('stopCaddyIngress')
|
||||
->shouldReceive('stopIngress')
|
||||
->once()
|
||||
->with('100.64.0.10')
|
||||
->with('100.64.0.10', 'caddy')
|
||||
->andReturn('Caddy ingress stopped.');
|
||||
app()->instance(FluxClient::class, $fluxClient);
|
||||
|
||||
@@ -173,7 +174,7 @@ it('includes flux error response details when dispatch returns a non success sta
|
||||
'Content-Length: '.strlen($body)."\r\n".
|
||||
"\r\n".
|
||||
$body,
|
||||
fn () => (new FluxClient)->applyCaddyIngress('100.64.0.10', 'example.com { respond "ok" }')
|
||||
fn () => (new FluxClient)->applyIngress('100.64.0.10', 'caddy', 'example.com { respond "ok" }')
|
||||
);
|
||||
})->throws(RuntimeException::class, 'start Caddy ingress: podman exited with status 125');
|
||||
|
||||
@@ -184,7 +185,7 @@ it('uses a friendly message when flux returns an invalid http response', functio
|
||||
|
||||
withFakeFluxSocket(
|
||||
'',
|
||||
fn () => (new FluxClient)->applyCaddyIngress('100.64.0.10', 'example.com { respond "ok" }')
|
||||
fn () => (new FluxClient)->applyIngress('100.64.0.10', 'caddy', 'example.com { respond "ok" }')
|
||||
);
|
||||
})->throws(RuntimeException::class, 'Flux did not return a response before the timeout.');
|
||||
|
||||
|
||||
Reference in new issue
Block a user