fix(ui): use 100dvh body height and coollabs auth glow

Replace min-h-screen with min-height: 100dvh on body for correct
mobile viewport height, and point the auth shell radial glow at
--color-coollabs instead of --color-accent. Add branding CSS tests.
This commit is contained in:
Andras Bacsai
2026-08-07 23:31:57 +02:00
parent f9caa5250d
commit 12b14665b5
6 changed files with 978 additions and 4 deletions
+577
View File
@@ -4132,6 +4132,73 @@
]
}
},
"\/applications\/{uuid}\/migrate": {
"post": {
"tags": [
"Applications"
],
"summary": "Migrate to Server",
"description": "Migrate an application to another destination\/server owned by the authenticated team. Stops the application, optionally transfers persistent volume data when both servers are managed by Coolify, and updates database records. Redeploy after migration completes.",
"operationId": "migrate-application-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the application.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"destination_uuid"
],
"properties": {
"destination_uuid": {
"description": "UUID of the target destination.",
"type": "string"
},
"migrate_volumes": {
"description": "Whether to transfer persistent volume data when migrating across servers.",
"type": "boolean",
"default": true
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Application migration started or completed."
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"$ref": "#\/components\/responses\/422"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/applications\/{uuid}\/storages": {
"get": {
"tags": [
@@ -7940,6 +8007,73 @@
]
}
},
"\/databases\/{uuid}\/migrate": {
"post": {
"tags": [
"Databases"
],
"summary": "Migrate to Server",
"description": "Migrate a database to another destination\/server owned by the authenticated team. Stops the database, optionally transfers persistent volume data when both servers are managed by Coolify, and updates database records. Redeploy after migration completes.",
"operationId": "migrate-database-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the database.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"destination_uuid"
],
"properties": {
"destination_uuid": {
"description": "UUID of the target destination.",
"type": "string"
},
"migrate_volumes": {
"description": "Whether to transfer persistent volume data when migrating across servers.",
"type": "boolean",
"default": true
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Database migration started or completed."
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"$ref": "#\/components\/responses\/422"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/databases\/{uuid}\/start": {
"post": {
"tags": [
@@ -15494,6 +15628,382 @@
]
}
},
"\/servers\/{uuid}\/migrate": {
"post": {
"tags": [
"Servers"
],
"summary": "Migrate server to another Coolify instance",
"description": "One-shot handoff: export this server, import+claim on the target instance (using the provided token), then disable automations here. Requires read:sensitive and write.",
"operationId": "migrate-server-between-instances",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"target_url",
"target_token"
],
"properties": {
"target_url": {
"type": "string",
"example": "https:\/\/coolify-b.example.com"
},
"target_token": {
"description": "API token on the target instance (root or write)",
"type": "string"
},
"write_remote": {
"type": "boolean",
"default": false
},
"rebind_sentinel": {
"type": "boolean",
"default": true
},
"preserve_uuids": {
"type": "boolean",
"default": true
},
"adopt_mode": {
"type": "boolean",
"default": true
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Migrated"
},
"403": {
"description": "Missing sensitive permission"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"description": "Validation or remote import failed"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/servers\/{uuid}\/export": {
"get": {
"tags": [
"Servers"
],
"summary": "Export server transfer bundle",
"description": "Export a server and all resources hosted on it as a versioned transfer bundle for moving between Coolify instances. Requires read:sensitive.",
"operationId": "export-server-transfer-bundle",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "encrypt",
"in": "query",
"description": "If true and passphrase is provided, return an encrypted envelope.",
"required": false,
"schema": {
"type": "boolean"
}
},
{
"name": "passphrase",
"in": "query",
"description": "Passphrase used when encrypt=true.",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Transfer bundle"
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"403": {
"description": "Missing sensitive permission"
},
"404": {
"$ref": "#\/components\/responses\/404"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/servers\/import": {
"post": {
"tags": [
"Servers"
],
"summary": "Import server transfer bundle",
"description": "Import a server transfer bundle into this Coolify instance (adopt mode by default).",
"operationId": "import-server-transfer-bundle",
"requestBody": {
"required": true,
"content": {
"application\/json": {
"schema": {
"properties": {
"bundle": {
"description": "Plain or encrypted transfer bundle",
"type": "object"
},
"passphrase": {
"type": [
"string",
"null"
]
},
"dry_run": {
"type": "boolean",
"default": false
},
"preserve_uuids": {
"type": "boolean",
"default": true
},
"adopt_mode": {
"description": "Import without forcing redeploy; keep statuses for adoption",
"type": "boolean",
"default": true
},
"claim": {
"description": "Automatically claim the host for this instance after import",
"type": "boolean",
"default": true
},
"write_remote": {
"description": "When claiming, write ownership file on the host via SSH",
"type": "boolean",
"default": false
},
"rebind_sentinel": {
"description": "When claiming, rebind Sentinel to this instance",
"type": "boolean",
"default": true
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Dry-run result"
},
"201": {
"description": "Imported"
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"422": {
"description": "Validation failed"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/servers\/{uuid}\/claim": {
"post": {
"tags": [
"Servers"
],
"summary": "Claim imported server",
"description": "Claim a managed host for this instance: write ownership file and rebind Sentinel.",
"operationId": "claim-server",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"properties": {
"write_remote": {
"type": "boolean",
"default": true
},
"rebind_sentinel": {
"type": "boolean",
"default": true
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Claim result"
},
"404": {
"$ref": "#\/components\/responses\/404"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/servers\/{uuid}\/transfer\/complete": {
"post": {
"tags": [
"Servers"
],
"summary": "Mark server transferred",
"description": "Source-instance step: disable automations after a successful export\/import handoff.",
"operationId": "complete-server-transfer",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"properties": {
"export_id": {
"type": [
"string",
"null"
]
},
"target_instance_url": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Marked transferred"
},
"404": {
"$ref": "#\/components\/responses\/404"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/servers\/{uuid}\/export\/mailbox": {
"post": {
"tags": [
"Servers"
],
"summary": "Write transfer bundle to server mailbox",
"description": "Write an export bundle to \/data\/coolify\/exports on the managed host for air-gapped import.",
"operationId": "export-server-transfer-mailbox",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"properties": {
"passphrase": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Mailbox write result"
},
"403": {
"description": "Missing sensitive permission"
},
"404": {
"$ref": "#\/components\/responses\/404"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/servers": {
"get": {
"tags": [
@@ -18233,6 +18743,73 @@
]
}
},
"\/services\/{uuid}\/migrate": {
"post": {
"tags": [
"Services"
],
"summary": "Migrate to Server",
"description": "Migrate a service to another destination\/server owned by the authenticated team. Stops the service, optionally transfers persistent volume data when both servers are managed by Coolify, and updates database records. Redeploy after migration completes.",
"operationId": "migrate-service-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the service.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"destination_uuid"
],
"properties": {
"destination_uuid": {
"description": "UUID of the target destination.",
"type": "string"
},
"migrate_volumes": {
"description": "Whether to transfer persistent volume data when migrating across servers.",
"type": "boolean",
"default": true
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Service migration started or completed."
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"404": {
"$ref": "#\/components\/responses\/404"
},
"422": {
"$ref": "#\/components\/responses\/422"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/services\/{uuid}\/start": {
"post": {
"tags": [
+380
View File
@@ -2692,6 +2692,51 @@ paths:
security:
-
bearerAuth: []
'/applications/{uuid}/migrate':
post:
tags:
- Applications
summary: 'Migrate to Server'
description: 'Migrate an application to another destination/server owned by the authenticated team. Stops the application, optionally transfers persistent volume data when both servers are managed by Coolify, and updates database records. Redeploy after migration completes.'
operationId: migrate-application-by-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
required:
- destination_uuid
properties:
destination_uuid:
description: 'UUID of the target destination.'
type: string
migrate_volumes:
description: 'Whether to transfer persistent volume data when migrating across servers.'
type: boolean
default: true
type: object
responses:
'200':
description: 'Application migration started or completed.'
'400':
$ref: '#/components/responses/400'
'401':
$ref: '#/components/responses/401'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/applications/{uuid}/storages':
get:
tags:
@@ -5222,6 +5267,51 @@ paths:
security:
-
bearerAuth: []
'/databases/{uuid}/migrate':
post:
tags:
- Databases
summary: 'Migrate to Server'
description: 'Migrate a database to another destination/server owned by the authenticated team. Stops the database, optionally transfers persistent volume data when both servers are managed by Coolify, and updates database records. Redeploy after migration completes.'
operationId: migrate-database-by-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the database.'
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
required:
- destination_uuid
properties:
destination_uuid:
description: 'UUID of the target destination.'
type: string
migrate_volumes:
description: 'Whether to transfer persistent volume data when migrating across servers.'
type: boolean
default: true
type: object
responses:
'200':
description: 'Database migration started or completed.'
'400':
$ref: '#/components/responses/400'
'401':
$ref: '#/components/responses/401'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/databases/{uuid}/start':
post:
tags:
@@ -9822,6 +9912,251 @@ paths:
security:
-
bearerAuth: []
'/servers/{uuid}/migrate':
post:
tags:
- Servers
summary: 'Migrate server to another Coolify instance'
description: 'One-shot handoff: export this server, import+claim on the target instance (using the provided token), then disable automations here. Requires read:sensitive and write.'
operationId: migrate-server-between-instances
parameters:
-
name: uuid
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
required:
- target_url
- target_token
properties:
target_url:
type: string
example: 'https://coolify-b.example.com'
target_token:
description: 'API token on the target instance (root or write)'
type: string
write_remote:
type: boolean
default: false
rebind_sentinel:
type: boolean
default: true
preserve_uuids:
type: boolean
default: true
adopt_mode:
type: boolean
default: true
type: object
responses:
'200':
description: Migrated
'403':
description: 'Missing sensitive permission'
'404':
$ref: '#/components/responses/404'
'422':
description: 'Validation or remote import failed'
security:
-
bearerAuth: []
'/servers/{uuid}/export':
get:
tags:
- Servers
summary: 'Export server transfer bundle'
description: 'Export a server and all resources hosted on it as a versioned transfer bundle for moving between Coolify instances. Requires read:sensitive.'
operationId: export-server-transfer-bundle
parameters:
-
name: uuid
in: path
required: true
schema:
type: string
-
name: encrypt
in: query
description: 'If true and passphrase is provided, return an encrypted envelope.'
required: false
schema:
type: boolean
-
name: passphrase
in: query
description: 'Passphrase used when encrypt=true.'
required: false
schema:
type: string
responses:
'200':
description: 'Transfer bundle'
'401':
$ref: '#/components/responses/401'
'403':
description: 'Missing sensitive permission'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/servers/import:
post:
tags:
- Servers
summary: 'Import server transfer bundle'
description: 'Import a server transfer bundle into this Coolify instance (adopt mode by default).'
operationId: import-server-transfer-bundle
requestBody:
required: true
content:
application/json:
schema:
properties:
bundle:
description: 'Plain or encrypted transfer bundle'
type: object
passphrase:
type: [string, 'null']
dry_run:
type: boolean
default: false
preserve_uuids:
type: boolean
default: true
adopt_mode:
description: 'Import without forcing redeploy; keep statuses for adoption'
type: boolean
default: true
claim:
description: 'Automatically claim the host for this instance after import'
type: boolean
default: true
write_remote:
description: 'When claiming, write ownership file on the host via SSH'
type: boolean
default: false
rebind_sentinel:
description: 'When claiming, rebind Sentinel to this instance'
type: boolean
default: true
type: object
responses:
'200':
description: 'Dry-run result'
'201':
description: Imported
'401':
$ref: '#/components/responses/401'
'422':
description: 'Validation failed'
security:
-
bearerAuth: []
'/servers/{uuid}/claim':
post:
tags:
- Servers
summary: 'Claim imported server'
description: 'Claim a managed host for this instance: write ownership file and rebind Sentinel.'
operationId: claim-server
parameters:
-
name: uuid
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
properties:
write_remote:
type: boolean
default: true
rebind_sentinel:
type: boolean
default: true
type: object
responses:
'200':
description: 'Claim result'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
'/servers/{uuid}/transfer/complete':
post:
tags:
- Servers
summary: 'Mark server transferred'
description: 'Source-instance step: disable automations after a successful export/import handoff.'
operationId: complete-server-transfer
parameters:
-
name: uuid
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
properties:
export_id:
type: [string, 'null']
target_instance_url:
type: [string, 'null']
type: object
responses:
'200':
description: 'Marked transferred'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
'/servers/{uuid}/export/mailbox':
post:
tags:
- Servers
summary: 'Write transfer bundle to server mailbox'
description: 'Write an export bundle to /data/coolify/exports on the managed host for air-gapped import.'
operationId: export-server-transfer-mailbox
parameters:
-
name: uuid
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
properties:
passphrase:
type: [string, 'null']
type: object
responses:
'200':
description: 'Mailbox write result'
'403':
description: 'Missing sensitive permission'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/servers:
get:
tags:
@@ -11507,6 +11842,51 @@ paths:
security:
-
bearerAuth: []
'/services/{uuid}/migrate':
post:
tags:
- Services
summary: 'Migrate to Server'
description: 'Migrate a service to another destination/server owned by the authenticated team. Stops the service, optionally transfers persistent volume data when both servers are managed by Coolify, and updates database records. Redeploy after migration completes.'
operationId: migrate-service-by-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the service.'
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
required:
- destination_uuid
properties:
destination_uuid:
description: 'UUID of the target destination.'
type: string
migrate_volumes:
description: 'Whether to transfer persistent volume data when migrating across servers.'
type: boolean
default: true
type: object
responses:
'200':
description: 'Service migration started or completed.'
'400':
$ref: '#/components/responses/400'
'401':
$ref: '#/components/responses/401'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/services/{uuid}/start':
post:
tags:
+3 -2
View File
@@ -257,7 +257,8 @@ body {
body {
/* overflow-x: clip (not hidden) hidden creates a scroll containment that
breaks position:sticky on settings sidebars and other in-page pins. */
@apply min-h-screen text-sm font-sans antialiased scrollbar overflow-x-clip;
min-height: 100dvh;
@apply text-sm font-sans antialiased scrollbar overflow-x-clip;
}
.coolify-monaco-editor {
@@ -981,7 +982,7 @@ html[data-theme="custom"] textarea:disabled {
min-height: 100dvh;
display: flex;
background:
radial-gradient(circle at 50% 0%, color-mix(in oklab, var(--color-accent) 9%, transparent), transparent 34rem),
radial-gradient(circle at 50% 0%, color-mix(in oklab, var(--color-coollabs) 9%, transparent), transparent 34rem),
var(--coollabs-canvas);
color: #171717;
}
+1 -1
View File
@@ -552,7 +552,7 @@
"category": "backend",
"logo": "svgs/denoKV.svg",
"minversion": "0.0.0",
"template_last_updated_at": "2026-08-07T14:41:11+02:00",
"template_last_updated_at": "2026-08-07T18:17:33+02:00",
"port": "8080"
},
"changedetection": {
+1 -1
View File
@@ -552,7 +552,7 @@
"category": "backend",
"logo": "svgs/denoKV.svg",
"minversion": "0.0.0",
"template_last_updated_at": "2026-08-07T14:41:11+02:00",
"template_last_updated_at": "2026-08-07T18:17:33+02:00",
"port": "8080"
},
"changedetection": {
+16
View File
@@ -21,6 +21,22 @@ test('login register and forgot password headers are Coolify', function () {
->not->toContain("title=\"{{ __('auth.forgot_password_heading') }}\"");
});
test('page body uses the dynamic viewport height on mobile', function () {
$styles = file_get_contents(resource_path('css/app.css'));
expect($styles)
->toMatch('/body\s*\{[^}]*min-height:\s*100dvh;/s')
->not->toMatch('/body\s*\{[^}]*@apply[^;]*min-h-screen/s');
});
test('auth pages use the Coollabs purple background glow', function () {
$styles = file_get_contents(resource_path('css/app.css'));
expect($styles)
->toMatch('/\.auth-shell\s*\{[^}]*color-mix\(in oklab, var\(--color-coollabs\) 9%, transparent\)/s')
->not->toMatch('/\.auth-shell\s*\{[^}]*color-mix\(in oklab, var\(--color-accent\) 9%, transparent\)/s');
});
test('confirm password page uses the shared auth shell', function () {
$confirm = file_get_contents(resource_path('views/auth/confirm-password.blade.php'));