Merge branch 'next' into feature/s3-path-prefix
|
|
@ -1,6 +1,6 @@
|
|||
# Coolify Configuration
|
||||
APP_ENV=local
|
||||
APP_NAME="Coolify Development"
|
||||
APP_NAME=Coolify
|
||||
APP_ID=development
|
||||
APP_KEY=
|
||||
APP_URL=http://localhost
|
||||
|
|
|
|||
8533
CHANGELOG.md
36
CLAUDE.md
|
|
@ -10,42 +10,6 @@ This file provides guidance to **Claude Code** (claude.ai/code) when working wit
|
|||
|
||||
Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization.
|
||||
|
||||
## Git Worktree Shared Dependencies
|
||||
|
||||
This repository uses git worktrees for parallel development with **automatic shared dependency setup** via Conductor.
|
||||
|
||||
### How It Works
|
||||
|
||||
The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically:
|
||||
1. Creates symlinks from worktree's `node_modules` and `vendor` to the main repository's directories
|
||||
2. All worktrees share the same dependencies from the main repository
|
||||
3. This happens automatically when Conductor creates a new worktree
|
||||
|
||||
### Benefits
|
||||
|
||||
- **Save disk space**: Only one copy of dependencies across all worktrees
|
||||
- **Faster setup**: No need to run `npm install` or `composer install` for each worktree
|
||||
- **Consistent versions**: All worktrees use the same dependency versions
|
||||
- **Auto-configured**: Handled by Conductor's setup script
|
||||
- **Simple**: Uses the main repo's existing directories, no extra folders
|
||||
|
||||
### Manual Setup (If Needed)
|
||||
|
||||
If you need to set up symlinks manually or for non-Conductor worktrees:
|
||||
|
||||
```bash
|
||||
# From the worktree directory
|
||||
rm -rf node_modules vendor
|
||||
ln -sf ../../node_modules node_modules
|
||||
ln -sf ../../vendor vendor
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
- Dependencies are shared from the main repository (`$CONDUCTOR_ROOT_PATH`)
|
||||
- Run `npm install` or `composer install` from the main repo or any worktree to update all
|
||||
- If different branches need different dependency versions, this won't work - remove symlinks and use separate directories
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Frontend Development
|
||||
|
|
|
|||
|
|
@ -145,6 +145,12 @@ class GetContainersStatus
|
|||
$this->applicationContainerStatuses->put($applicationId, collect());
|
||||
}
|
||||
$containerName = data_get($labels, 'com.docker.compose.service');
|
||||
// Fallback for Docker Swarm which uses different labels
|
||||
if (! $containerName && $this->server->isSwarm()) {
|
||||
$containerName = data_get($labels, 'coolify.serviceName')
|
||||
?? data_get($labels, 'coolify.name')
|
||||
?? data_get($labels, 'com.docker.stack.namespace');
|
||||
}
|
||||
if ($containerName) {
|
||||
$this->applicationContainerStatuses->get($applicationId)->put($containerName, $containerStatus);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,11 +117,11 @@ class InstallDocker
|
|||
private function getDebianDockerInstallCommand(): string
|
||||
{
|
||||
return "curl --max-time 300 --retry 3 https://releases.rancher.com/install-docker/{$this->dockerVersion}.sh | sh || curl --max-time 300 --retry 3 https://get.docker.com | sh -s -- --version {$this->dockerVersion} || (".
|
||||
'install -m 0755 -d /etc/apt/keyrings && '.
|
||||
'curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && '.
|
||||
'chmod a+r /etc/apt/keyrings/docker.asc && '.
|
||||
'. /etc/os-release && '.
|
||||
'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list && '.
|
||||
'install -m 0755 -d /etc/apt/keyrings && '.
|
||||
'curl -fsSL https://download.docker.com/linux/${ID}/gpg -o /etc/apt/keyrings/docker.asc && '.
|
||||
'chmod a+r /etc/apt/keyrings/docker.asc && '.
|
||||
'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list && '.
|
||||
'apt-get update && '.
|
||||
'apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin'.
|
||||
')';
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class CleanupNames extends Command
|
|||
{--backup : Create database backup before changes}
|
||||
{--force : Skip confirmation prompt}';
|
||||
|
||||
protected $description = 'Sanitize name fields by removing invalid characters (keeping only letters, numbers, spaces, dashes, underscores, dots, slashes, colons, parentheses)';
|
||||
protected $description = 'Sanitize name fields by removing dangerous characters';
|
||||
|
||||
protected array $modelsToClean = [
|
||||
'Project' => Project::class,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,17 @@ class ApplicationsController extends Controller
|
|||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Applications'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'tag',
|
||||
in: 'query',
|
||||
description: 'Filter applications by tag name.',
|
||||
required: false,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
)
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
|
|
@ -94,13 +105,19 @@ class ApplicationsController extends Controller
|
|||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$applications = collect();
|
||||
$applications->push($projects->pluck('applications')->flatten());
|
||||
$applications = $applications->flatten();
|
||||
$applications = $applications->map(function ($application) {
|
||||
return $this->removeSensitiveData($application);
|
||||
});
|
||||
|
||||
$tagName = $request->query('tag');
|
||||
|
||||
$applications = Application::ownedByCurrentTeamAPI($teamId)
|
||||
->when($tagName, function ($query, $tagName) {
|
||||
$query->whereHas('tags', function ($query) use ($tagName) {
|
||||
$query->where('name', $tagName);
|
||||
});
|
||||
})
|
||||
->get()
|
||||
->map(function ($application) {
|
||||
return $this->removeSensitiveData($application);
|
||||
});
|
||||
|
||||
return response()->json($applications);
|
||||
}
|
||||
|
|
@ -193,6 +210,7 @@ class ApplicationsController extends Controller
|
|||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -344,6 +362,7 @@ class ApplicationsController extends Controller
|
|||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -495,6 +514,7 @@ class ApplicationsController extends Controller
|
|||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -630,6 +650,7 @@ class ApplicationsController extends Controller
|
|||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -762,6 +783,7 @@ class ApplicationsController extends Controller
|
|||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -856,6 +878,7 @@ class ApplicationsController extends Controller
|
|||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -932,7 +955,7 @@ class ApplicationsController extends Controller
|
|||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain'];
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled'];
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
|
|
@ -977,6 +1000,7 @@ class ApplicationsController extends Controller
|
|||
$isStatic = $request->is_static;
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$customNginxConfiguration = $request->custom_nginx_configuration;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true);
|
||||
|
||||
if (! is_null($customNginxConfiguration)) {
|
||||
if (! isBase64Encoded($customNginxConfiguration)) {
|
||||
|
|
@ -1093,6 +1117,10 @@ class ApplicationsController extends Controller
|
|||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isContainerLabelEscapeEnabled)) {
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
$application->refresh();
|
||||
// Auto-generate domain if requested and no custom domain provided
|
||||
if ($autogenerateDomain && blank($fqdn)) {
|
||||
|
|
@ -1259,6 +1287,10 @@ class ApplicationsController extends Controller
|
|||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isContainerLabelEscapeEnabled)) {
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($application->settings->is_container_label_readonly_enabled) {
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->save();
|
||||
|
|
@ -1393,6 +1425,10 @@ class ApplicationsController extends Controller
|
|||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isContainerLabelEscapeEnabled)) {
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($application->settings->is_container_label_readonly_enabled) {
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->save();
|
||||
|
|
@ -1492,6 +1528,10 @@ class ApplicationsController extends Controller
|
|||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isContainerLabelEscapeEnabled)) {
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($application->settings->is_container_label_readonly_enabled) {
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->save();
|
||||
|
|
@ -1590,6 +1630,10 @@ class ApplicationsController extends Controller
|
|||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isContainerLabelEscapeEnabled)) {
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($application->settings->is_container_label_readonly_enabled) {
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->save();
|
||||
|
|
@ -1617,7 +1661,7 @@ class ApplicationsController extends Controller
|
|||
'domains' => data_get($application, 'fqdn'),
|
||||
]))->setStatusCode(201);
|
||||
} elseif ($type === 'dockercompose') {
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw', 'force_domain_override'];
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw', 'force_domain_override', 'is_container_label_escape_enabled'];
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
|
@ -1681,6 +1725,9 @@ class ApplicationsController extends Controller
|
|||
$service->server_id = $server->id;
|
||||
$service->destination_id = $destination->id;
|
||||
$service->destination_type = $destination->getMorphClass();
|
||||
if (isset($isContainerLabelEscapeEnabled)) {
|
||||
$service->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
}
|
||||
$service->save();
|
||||
|
||||
$service->parse(isNew: true);
|
||||
|
|
@ -1718,7 +1765,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1786,7 +1832,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
@ -1888,7 +1933,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
|
|
@ -1975,7 +2019,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2051,6 +2094,7 @@ class ApplicationsController extends Controller
|
|||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -2136,7 +2180,7 @@ class ApplicationsController extends Controller
|
|||
$this->authorize('update', $application);
|
||||
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override'];
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
|
|
@ -2317,6 +2361,7 @@ class ApplicationsController extends Controller
|
|||
$isStatic = $request->is_static;
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$useBuildServer = $request->use_build_server;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled');
|
||||
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
|
|
@ -2333,6 +2378,11 @@ class ApplicationsController extends Controller
|
|||
$application->settings->save();
|
||||
}
|
||||
|
||||
if ($request->has('is_container_label_escape_enabled')) {
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$data = $request->all();
|
||||
|
|
@ -2386,7 +2436,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2472,7 +2521,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2662,7 +2710,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2703,10 +2750,8 @@ class ApplicationsController extends Controller
|
|||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Environment variables updated.'],
|
||||
]
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
|
||||
)
|
||||
),
|
||||
]
|
||||
|
|
@ -2872,7 +2917,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -3038,7 +3082,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
@ -3048,7 +3091,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -3131,7 +3173,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
@ -3247,7 +3288,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -3324,7 +3364,6 @@ class ApplicationsController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -411,7 +411,6 @@ class CloudProviderTokensController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -117,7 +117,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -182,7 +181,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -245,7 +243,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -614,7 +611,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -832,7 +828,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
@ -842,7 +837,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2104,7 +2098,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
|
|
@ -2193,7 +2186,7 @@ class DatabasesController extends Controller
|
|||
in: 'path',
|
||||
required: true,
|
||||
description: 'UUID of the backup configuration to delete',
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
schema: new OA\Schema(type: 'string')
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'delete_s3',
|
||||
|
|
@ -2310,14 +2303,14 @@ class DatabasesController extends Controller
|
|||
in: 'path',
|
||||
required: true,
|
||||
description: 'UUID of the backup configuration',
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
schema: new OA\Schema(type: 'string')
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'execution_uuid',
|
||||
in: 'path',
|
||||
required: true,
|
||||
description: 'UUID of the backup execution to delete',
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
schema: new OA\Schema(type: 'string')
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'delete_s3',
|
||||
|
|
@ -2430,7 +2423,7 @@ class DatabasesController extends Controller
|
|||
in: 'path',
|
||||
required: true,
|
||||
description: 'UUID of the backup configuration',
|
||||
schema: new OA\Schema(type: 'string', format: 'uuid')
|
||||
schema: new OA\Schema(type: 'string')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
|
|
@ -2527,7 +2520,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2608,7 +2600,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -2689,7 +2680,6 @@ class DatabasesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -538,7 +538,6 @@ class DeployController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
|
|||
|
|
@ -285,7 +285,6 @@ class ProjectController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -406,7 +405,6 @@ class ProjectController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -699,7 +699,6 @@ class ServersController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -681,7 +681,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -871,7 +870,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -952,7 +950,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1069,7 +1066,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1200,7 +1196,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1315,7 +1310,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
@ -1325,7 +1319,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1404,7 +1397,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1485,7 +1477,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
|
|
@ -1566,7 +1557,6 @@ class ServicesController extends Controller
|
|||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class Gitea extends Controller
|
|||
if ($x_gitea_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ApplicationPullRequestUpdateJob;
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
use App\Jobs\ProcessGithubPullRequestWebhook;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\PrivateKey;
|
||||
use Exception;
|
||||
|
|
@ -54,6 +51,8 @@ class Github extends Controller
|
|||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
$before_sha = data_get($payload, 'before');
|
||||
$after_sha = data_get($payload, 'after', data_get($payload, 'pull_request.head.sha'));
|
||||
$author_association = data_get($payload, 'pull_request.author_association');
|
||||
}
|
||||
if (! $branch) {
|
||||
|
|
@ -69,7 +68,7 @@ class Github extends Controller
|
|||
if ($x_github_event === 'pull_request') {
|
||||
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
return response("Nothing to do. No applications found for repo $full_name and branch '$base_branch'.");
|
||||
}
|
||||
}
|
||||
$applicationsByServer = $applications->groupBy(function ($app) {
|
||||
|
|
@ -102,7 +101,7 @@ class Github extends Controller
|
|||
if ($x_github_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -152,96 +151,35 @@ class Github extends Controller
|
|||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||
if ($application->isPRDeployable()) {
|
||||
// Check if PR deployments from public contributors are restricted
|
||||
if (! $application->settings->is_pr_deployments_public_enabled) {
|
||||
$trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
|
||||
if (! in_array($author_association, $trustedAssociations)) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'PR deployments are restricted to repository members and contributors. Author association: '.$author_association,
|
||||
]);
|
||||
// Check if PR deployments are enabled (but allow 'closed' action to cleanup)
|
||||
if (! $application->isPRDeployable() && $action !== 'closed') {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
'docker_compose_domains' => $application->docker_compose_domains,
|
||||
]);
|
||||
$pr_app->generate_preview_fqdn_compose();
|
||||
} else {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
$pr_app->generate_preview_fqdn();
|
||||
}
|
||||
}
|
||||
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $pull_request_id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
commit: data_get($payload, 'head.sha', 'HEAD'),
|
||||
is_webhook: true,
|
||||
git_type: 'github'
|
||||
);
|
||||
if ($result['status'] === 'queue_full') {
|
||||
return response($result['message'], 429)->header('Retry-After', 60);
|
||||
} elseif ($result['status'] === 'skipped') {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => $result['message'],
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment queued.',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($action === 'closed') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
// Use comprehensive cleanup that cancels active deployments,
|
||||
// kills helper containers, and removes all PR containers
|
||||
CleanupPreviewDeployment::run($application, $pull_request_id, $found);
|
||||
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment closed.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No preview deployment found.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
ProcessGithubPullRequestWebhook::dispatch(
|
||||
applicationId: $application->id,
|
||||
githubAppId: null,
|
||||
action: $action,
|
||||
pullRequestId: $pull_request_id,
|
||||
pullRequestHtmlUrl: $pull_request_html_url,
|
||||
beforeSha: $before_sha,
|
||||
afterSha: $after_sha,
|
||||
commitSha: data_get($payload, 'pull_request.head.sha', 'HEAD'),
|
||||
authorAssociation: $author_association,
|
||||
fullName: $full_name,
|
||||
);
|
||||
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'queued',
|
||||
'message' => 'PR webhook received, processing queued.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -304,6 +242,8 @@ class Github extends Controller
|
|||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
$before_sha = data_get($payload, 'before');
|
||||
$after_sha = data_get($payload, 'after', data_get($payload, 'pull_request.head.sha'));
|
||||
$author_association = data_get($payload, 'pull_request.author_association');
|
||||
}
|
||||
if (! $id || ! $branch) {
|
||||
|
|
@ -344,7 +284,7 @@ class Github extends Controller
|
|||
if ($x_github_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
@ -386,86 +326,37 @@ class Github extends Controller
|
|||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||
if ($application->isPRDeployable()) {
|
||||
// Check if PR deployments from public contributors are restricted
|
||||
if (! $application->settings->is_pr_deployments_public_enabled) {
|
||||
$trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
|
||||
if (! in_array($author_association, $trustedAssociations)) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'PR deployments are restricted to repository members and contributors. Author association: '.$author_association,
|
||||
]);
|
||||
// Check if PR deployments are enabled (but allow 'closed' action to cleanup)
|
||||
if (! $application->isPRDeployable() && $action !== 'closed') {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
}
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $pull_request_id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
commit: data_get($payload, 'head.sha', 'HEAD'),
|
||||
is_webhook: true,
|
||||
git_type: 'github'
|
||||
);
|
||||
if ($result['status'] === 'queue_full') {
|
||||
return response($result['message'], 429)->header('Retry-After', 60);
|
||||
} elseif ($result['status'] === 'skipped') {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'skipped',
|
||||
'message' => $result['message'],
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment queued.',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($action === 'closed' || $action === 'close') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
// Delete the PR comment on GitHub (GitHub-specific feature)
|
||||
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
||||
|
||||
// Use comprehensive cleanup that cancels active deployments,
|
||||
// kills helper containers, and removes all PR containers
|
||||
CleanupPreviewDeployment::run($application, $pull_request_id, $found);
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment closed.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No preview deployment found.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
ProcessGithubPullRequestWebhook::dispatch(
|
||||
applicationId: $application->id,
|
||||
githubAppId: $github_app->id,
|
||||
action: $action,
|
||||
pullRequestId: $pull_request_id,
|
||||
pullRequestHtmlUrl: $pull_request_html_url,
|
||||
beforeSha: $before_sha,
|
||||
afterSha: $after_sha,
|
||||
commitSha: data_get($payload, 'pull_request.head.sha', 'HEAD'),
|
||||
authorAssociation: $author_association,
|
||||
fullName: $full_name,
|
||||
);
|
||||
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'queued',
|
||||
'message' => 'PR webhook received, processing queued.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class Gitlab extends Controller
|
|||
}
|
||||
foreach ($applications as $application) {
|
||||
$webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
|
||||
if ($webhook_secret !== $x_gitlab_token) {
|
||||
if (! hash_equals($webhook_secret ?? '', $x_gitlab_token ?? '')) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
|
|
@ -122,7 +122,7 @@ class Gitlab extends Controller
|
|||
if ($x_gitlab_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||
if ($is_watch_path_triggered || blank($application->watch_paths)) {
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $application,
|
||||
|
|
|
|||
|
|
@ -87,9 +87,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
private bool $use_build_server = false;
|
||||
|
||||
// Save original server between phases
|
||||
private Server $original_server;
|
||||
|
||||
private Server $mainServer;
|
||||
|
||||
private bool $is_this_additional_server = false;
|
||||
|
|
@ -325,18 +322,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
if ($buildServers->count() === 0) {
|
||||
$this->application_deployment_queue->addLogEntry('No suitable build server found. Using the deployment server.');
|
||||
$this->build_server = $this->server;
|
||||
$this->original_server = $this->server;
|
||||
} else {
|
||||
$this->build_server = $buildServers->random();
|
||||
$this->application_deployment_queue->build_server_id = $this->build_server->id;
|
||||
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
||||
$this->original_server = $this->server;
|
||||
$this->use_build_server = true;
|
||||
}
|
||||
} else {
|
||||
// Set build server & original_server to the same as deployment server
|
||||
$this->build_server = $this->server;
|
||||
$this->original_server = $this->server;
|
||||
}
|
||||
$this->detectBuildKitCapabilities();
|
||||
$this->decide_what_to_do();
|
||||
|
|
@ -937,7 +930,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
{
|
||||
if ($this->preserveRepository) {
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
}
|
||||
if (str($this->configuration_dir)->isNotEmpty()) {
|
||||
$this->execute_remote_command(
|
||||
|
|
@ -960,7 +953,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
}
|
||||
if (isset($this->docker_compose_base64)) {
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
}
|
||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||
|
||||
|
|
@ -1342,7 +1335,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
// Also create in configuration directory
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"touch $this->configuration_dir/.env",
|
||||
|
|
@ -1359,7 +1352,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
} else {
|
||||
// For non-Docker Compose deployments, clean up any existing .env files
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
'command' => "rm -f $this->configuration_dir/.env",
|
||||
|
|
@ -1407,7 +1400,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
// Write .env file to configuration directory
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/.env > /dev/null",
|
||||
|
|
@ -1744,7 +1737,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
} else {
|
||||
if ($this->use_build_server) {
|
||||
$this->write_deployment_configurations();
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
}
|
||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty() || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||
|
|
@ -1913,7 +1906,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
private function create_workdir()
|
||||
{
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->server = $this->mainServer;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
'command' => "mkdir -p {$this->configuration_dir}",
|
||||
|
|
@ -2563,7 +2556,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
if (! is_null($this->application->limits_cpuset)) {
|
||||
data_set($docker_compose, 'services.'.$this->container_name.'.cpuset', $this->application->limits_cpuset);
|
||||
}
|
||||
if ($this->server->isSwarm()) {
|
||||
if ($this->mainServer->isSwarm()) {
|
||||
data_forget($docker_compose, 'services.'.$this->container_name.'.container_name');
|
||||
data_forget($docker_compose, 'services.'.$this->container_name.'.expose');
|
||||
data_forget($docker_compose, 'services.'.$this->container_name.'.restart');
|
||||
|
|
@ -2613,7 +2606,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
} else {
|
||||
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
||||
}
|
||||
if ($this->original_server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
||||
if ($this->mainServer->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$this->container_name]['logging'] = generate_fluentd_configuration();
|
||||
}
|
||||
if ($this->application->settings->is_gpu_enabled) {
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||
ProcessStatus::CLOSED => '', // Already handled above, but included for completeness
|
||||
};
|
||||
$this->build_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/environment/{$this->application->environment->uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
$application_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/environment/{$this->application->environment->uuid}/application/{$this->application->uuid}/logs";
|
||||
|
||||
$this->body .= '[Open Build Logs]('.$this->build_logs_url.")\n\n\n";
|
||||
$this->body .= '[Open Build Logs]('.$this->build_logs_url.') | [Open Application Logs]('.$application_logs_url.")\n\n\n";
|
||||
$this->body .= 'Last updated at: '.now()->toDateTimeString().' CET';
|
||||
if ($this->preview->pull_request_issue_comment_id) {
|
||||
$this->update_comment();
|
||||
|
|
|
|||
152
app/Jobs/ProcessGithubPullRequestWebhook.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\GithubApp;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
public int $timeout = 60;
|
||||
|
||||
public array $backoff = [30, 60, 120];
|
||||
|
||||
public function __construct(
|
||||
public int $applicationId,
|
||||
public ?int $githubAppId,
|
||||
public string $action,
|
||||
public int $pullRequestId,
|
||||
public string $pullRequestHtmlUrl,
|
||||
public ?string $beforeSha,
|
||||
public ?string $afterSha,
|
||||
public string $commitSha,
|
||||
public ?string $authorAssociation,
|
||||
public string $fullName,
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$application = Application::find($this->applicationId);
|
||||
if (! $application) {
|
||||
return;
|
||||
}
|
||||
|
||||
$githubApp = $this->githubAppId ? GithubApp::find($this->githubAppId) : null;
|
||||
|
||||
if ($this->action === 'closed' || $this->action === 'close') {
|
||||
$this->handleClosedAction($application);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->action === 'opened' || $this->action === 'synchronize' || $this->action === 'reopened') {
|
||||
$this->handleOpenAction($application, $githubApp);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleClosedAction(Application $application): void
|
||||
{
|
||||
$found = ApplicationPreview::where('application_id', $application->id)
|
||||
->where('pull_request_id', $this->pullRequestId)
|
||||
->first();
|
||||
|
||||
if ($found) {
|
||||
ApplicationPullRequestUpdateJob::dispatchSync(
|
||||
application: $application,
|
||||
preview: $found,
|
||||
status: ProcessStatus::CLOSED
|
||||
);
|
||||
|
||||
CleanupPreviewDeployment::run($application, $this->pullRequestId, $found);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleOpenAction(Application $application, ?GithubApp $githubApp): void
|
||||
{
|
||||
if (! $application->isPRDeployable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PR deployments from public contributors are restricted
|
||||
if (! $application->settings->is_pr_deployments_public_enabled) {
|
||||
$trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
|
||||
if (! in_array($this->authorAssociation, $trustedAssociations)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get changed files for watch path filtering
|
||||
$changed_files = collect();
|
||||
$repository_parts = explode('/', $this->fullName);
|
||||
$owner = $repository_parts[0] ?? '';
|
||||
$repo = $repository_parts[1] ?? '';
|
||||
|
||||
if ($this->action === 'synchronize' && $this->beforeSha && $this->afterSha) {
|
||||
// For synchronize events, get files changed between before and after commits
|
||||
$changed_files = collect(getGithubCommitRangeFiles($githubApp, $owner, $repo, $this->beforeSha, $this->afterSha));
|
||||
} elseif ($this->action === 'opened' || $this->action === 'reopened') {
|
||||
// For opened/reopened events, get all files in the PR
|
||||
$changed_files = collect(getGithubPullRequestFiles($githubApp, $owner, $repo, $this->pullRequestId));
|
||||
}
|
||||
|
||||
// Apply watch path filtering
|
||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||
if (! $is_watch_path_triggered && ! blank($application->watch_paths)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create ApplicationPreview if not exists
|
||||
$found = ApplicationPreview::where('application_id', $application->id)
|
||||
->where('pull_request_id', $this->pullRequestId)
|
||||
->first();
|
||||
|
||||
if (! $found) {
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
$preview = ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $this->pullRequestId,
|
||||
'pull_request_html_url' => $this->pullRequestHtmlUrl,
|
||||
'docker_compose_domains' => $application->docker_compose_domains,
|
||||
]);
|
||||
$preview->generate_preview_fqdn_compose();
|
||||
} else {
|
||||
$preview = ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $this->pullRequestId,
|
||||
'pull_request_html_url' => $this->pullRequestHtmlUrl,
|
||||
]);
|
||||
$preview->generate_preview_fqdn();
|
||||
}
|
||||
}
|
||||
|
||||
// Queue the deployment
|
||||
$deployment_uuid = new Cuid2;
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $this->pullRequestId,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
commit: $this->commitSha,
|
||||
is_webhook: true,
|
||||
git_type: 'github'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -232,8 +232,6 @@ class General extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'gitRepository.required' => 'The Git Repository field is required.',
|
||||
'gitBranch.required' => 'The Git Branch field is required.',
|
||||
'buildPack.required' => 'The Build Pack field is required.',
|
||||
|
|
|
|||
|
|
@ -168,21 +168,20 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->dbUrlPublic = $this->database->external_db_url;
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
|
|
|||
|
|
@ -178,21 +178,20 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->dbUrlPublic = $this->database->external_db_url;
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class Import extends Component
|
|||
|
||||
public ?int $activityId = null;
|
||||
|
||||
public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
|
||||
public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}';
|
||||
|
||||
public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
|
||||
|
||||
|
|
@ -231,10 +231,10 @@ for pid in $(mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT id FROM info
|
|||
mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "KILL $pid" 2>/dev/null || true
|
||||
done && \
|
||||
mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT CONCAT('DROP DATABASE IF EXISTS \`',schema_name,'\`;') FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys');" | mariadb -u root -p$MARIADB_ROOT_PASSWORD && \
|
||||
mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`default\`;" && \
|
||||
(gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mariadb -u root -p$MARIADB_ROOT_PASSWORD default
|
||||
mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`${MARIADB_DATABASE:-default}\`;" && \
|
||||
(gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mariadb -u root -p$MARIADB_ROOT_PASSWORD ${MARIADB_DATABASE:-default}
|
||||
EOD;
|
||||
$this->restoreCommandText = $this->mariadbRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mariadb -u root -p$MARIADB_ROOT_PASSWORD default';
|
||||
$this->restoreCommandText = $this->mariadbRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mariadb -u root -p$MARIADB_ROOT_PASSWORD ${MARIADB_DATABASE:-default}';
|
||||
} else {
|
||||
$this->mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE';
|
||||
}
|
||||
|
|
@ -247,10 +247,10 @@ for pid in $(mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT id FROM informat
|
|||
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "KILL $pid" 2>/dev/null || true
|
||||
done && \
|
||||
mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT CONCAT('DROP DATABASE IF EXISTS \`',schema_name,'\`;') FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys');" | mysql -u root -p$MYSQL_ROOT_PASSWORD && \
|
||||
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`default\`;" && \
|
||||
(gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mysql -u root -p$MYSQL_ROOT_PASSWORD default
|
||||
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`${MYSQL_DATABASE:-default}\`;" && \
|
||||
(gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mysql -u root -p$MYSQL_ROOT_PASSWORD ${MYSQL_DATABASE:-default}
|
||||
EOD;
|
||||
$this->restoreCommandText = $this->mysqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mysql -u root -p$MYSQL_ROOT_PASSWORD default';
|
||||
$this->restoreCommandText = $this->mysqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mysql -u root -p$MYSQL_ROOT_PASSWORD ${MYSQL_DATABASE:-default}';
|
||||
} else {
|
||||
$this->mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
|
||||
}
|
||||
|
|
@ -259,13 +259,13 @@ EOD;
|
|||
case 'postgresql':
|
||||
if ($value === true) {
|
||||
$this->postgresqlRestoreCommand = <<<'EOD'
|
||||
psql -U $POSTGRES_USER -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && \
|
||||
psql -U $POSTGRES_USER -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate" | xargs -I {} dropdb -U $POSTGRES_USER --if-exists {} && \
|
||||
createdb -U $POSTGRES_USER postgres
|
||||
psql -U ${POSTGRES_USER} -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && \
|
||||
psql -U ${POSTGRES_USER} -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate" | xargs -I {} dropdb -U ${POSTGRES_USER} --if-exists {} && \
|
||||
createdb -U ${POSTGRES_USER} ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}
|
||||
EOD;
|
||||
$this->restoreCommandText = $this->postgresqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | psql -U $POSTGRES_USER postgres';
|
||||
$this->restoreCommandText = $this->postgresqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | psql -U ${POSTGRES_USER} -d ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}';
|
||||
} else {
|
||||
$this->postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
|
||||
$this->postgresqlRestoreCommand = 'pg_restore -U ${POSTGRES_USER} -d ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -739,7 +739,7 @@ EOD;
|
|||
case 'mariadb':
|
||||
$restoreCommand = $this->mariadbRestoreCommand;
|
||||
if ($this->dumpAll) {
|
||||
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mariadb -u root -p\$MARIADB_ROOT_PASSWORD";
|
||||
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mariadb -u root -p\$MARIADB_ROOT_PASSWORD \${MARIADB_DATABASE:-default}";
|
||||
} else {
|
||||
$restoreCommand .= " < {$tmpPath}";
|
||||
}
|
||||
|
|
@ -748,7 +748,7 @@ EOD;
|
|||
case 'mysql':
|
||||
$restoreCommand = $this->mysqlRestoreCommand;
|
||||
if ($this->dumpAll) {
|
||||
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mysql -u root -p\$MYSQL_ROOT_PASSWORD";
|
||||
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mysql -u root -p\$MYSQL_ROOT_PASSWORD \${MYSQL_DATABASE:-default}";
|
||||
} else {
|
||||
$restoreCommand .= " < {$tmpPath}";
|
||||
}
|
||||
|
|
@ -757,7 +757,7 @@ EOD;
|
|||
case 'postgresql':
|
||||
$restoreCommand = $this->postgresqlRestoreCommand;
|
||||
if ($this->dumpAll) {
|
||||
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \$POSTGRES_USER postgres";
|
||||
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \${POSTGRES_USER} -d \${POSTGRES_DB:\${POSTGRES_USER:-postgres}}";
|
||||
} else {
|
||||
$restoreCommand .= " {$tmpPath}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,21 +185,20 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->dbUrlPublic = $this->database->external_db_url;
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
|
|
|||
|
|
@ -91,8 +91,6 @@ class General extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'mariadbRootPassword.required' => 'The Root Password field is required.',
|
||||
'mariadbUser.required' => 'The MariaDB User field is required.',
|
||||
'mariadbPassword.required' => 'The MariaDB Password field is required.',
|
||||
|
|
@ -234,22 +232,23 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,8 +91,6 @@ class General extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
|
||||
'mongoInitdbRootPassword.required' => 'The Root Password field is required.',
|
||||
'mongoInitdbDatabase.required' => 'The MongoDB Database field is required.',
|
||||
|
|
@ -237,22 +235,23 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,8 +94,6 @@ class General extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'mysqlRootPassword.required' => 'The Root Password field is required.',
|
||||
'mysqlUser.required' => 'The MySQL User field is required.',
|
||||
'mysqlPassword.required' => 'The MySQL Password field is required.',
|
||||
|
|
@ -241,22 +239,23 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,8 +106,6 @@ class General extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'postgresUser.required' => 'The Postgres User field is required.',
|
||||
'postgresPassword.required' => 'The Postgres Password field is required.',
|
||||
'postgresDb.required' => 'The Postgres Database field is required.',
|
||||
|
|
@ -288,22 +286,23 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,8 +88,6 @@ class General extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'redisUsername.required' => 'The Redis Username field is required.',
|
||||
|
|
@ -227,21 +225,20 @@ class General extends Component
|
|||
|
||||
return;
|
||||
}
|
||||
if ($this->isPublic) {
|
||||
if (! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
if ($this->isPublic && ! str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->isPublic = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->syncData(true);
|
||||
if ($this->isPublic) {
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->dbUrlPublic = $this->database->external_db_url;
|
||||
$this->syncData(true);
|
||||
} catch (\Throwable $e) {
|
||||
$this->isPublic = ! $this->isPublic;
|
||||
$this->syncData(true);
|
||||
|
|
|
|||
|
|
@ -52,8 +52,6 @@ class StackForm extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'dockerComposeRaw.required' => 'The Docker Compose Raw field is required.',
|
||||
'dockerCompose.required' => 'The Docker Compose field is required.',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -63,20 +63,30 @@ class All extends Component
|
|||
|
||||
public function getEnvironmentVariablesProperty()
|
||||
{
|
||||
if ($this->is_env_sorting_enabled === false) {
|
||||
return $this->resource->environment_variables()->orderBy('order')->get();
|
||||
$query = $this->resource->environment_variables()
|
||||
->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END");
|
||||
|
||||
if ($this->is_env_sorting_enabled) {
|
||||
$query->orderBy('key');
|
||||
} else {
|
||||
$query->orderBy('order');
|
||||
}
|
||||
|
||||
return $this->resource->environment_variables;
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function getEnvironmentVariablesPreviewProperty()
|
||||
{
|
||||
if ($this->is_env_sorting_enabled === false) {
|
||||
return $this->resource->environment_variables_preview()->orderBy('order')->get();
|
||||
$query = $this->resource->environment_variables_preview()
|
||||
->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END");
|
||||
|
||||
if ($this->is_env_sorting_enabled) {
|
||||
$query->orderBy('key');
|
||||
} else {
|
||||
$query->orderBy('order');
|
||||
}
|
||||
|
||||
return $this->resource->environment_variables_preview;
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function getDevView()
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ class Show extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'privateKeyValue.required' => 'The Private Key field is required.',
|
||||
'privateKeyValue.string' => 'The Private Key must be a valid string.',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ class Index extends Component
|
|||
#[Validate('nullable|string|max:255')]
|
||||
public ?string $instance_name = null;
|
||||
|
||||
#[Validate('nullable|string')]
|
||||
#[Validate('nullable|ipv4')]
|
||||
public ?string $public_ipv4 = null;
|
||||
|
||||
#[Validate('nullable|string')]
|
||||
#[Validate('nullable|ipv6')]
|
||||
public ?string $public_ipv6 = null;
|
||||
|
||||
#[Validate('required|string|timezone')]
|
||||
|
|
|
|||
|
|
@ -53,8 +53,6 @@ class Form extends Component
|
|||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'region.required' => 'The Region field is required.',
|
||||
'region.max' => 'The Region may not be greater than 255 characters.',
|
||||
'key.required' => 'The Access Key field is required.',
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ class Index extends Component
|
|||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -845,15 +845,7 @@ class Application extends BaseModel
|
|||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->where('is_preview', false)
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN is_required = true THEN 1
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
->where('is_preview', false);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ class ApplicationDeploymentQueue extends Model
|
|||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'finished_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
|
|
|
|||
|
|
@ -15,12 +15,6 @@ use Visus\Cuid2\Cuid2;
|
|||
'uuid' => ['type' => 'string'],
|
||||
'name' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string'],
|
||||
'environments' => new OA\Property(
|
||||
property: 'environments',
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/Environment'),
|
||||
description: 'The environments of the project.'
|
||||
),
|
||||
]
|
||||
)]
|
||||
class Project extends BaseModel
|
||||
|
|
|
|||
|
|
@ -270,15 +270,6 @@ class Server extends BaseModel
|
|||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false)->whereRelation('settings', 'force_disabled', false);
|
||||
}
|
||||
|
||||
public static function destinationsByServer(string $server_id)
|
||||
{
|
||||
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
||||
$standaloneDocker = collect($server->standaloneDockers->all());
|
||||
$swarmDocker = collect($server->swarmDockers->all());
|
||||
|
||||
return $standaloneDocker->concat($swarmDocker);
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
return $this->hasOne(ServerSetting::class);
|
||||
|
|
|
|||
|
|
@ -515,6 +515,31 @@ class Service extends BaseModel
|
|||
}
|
||||
$fields->put('RabbitMQ', $data->toArray());
|
||||
break;
|
||||
case $image->is('registry'):
|
||||
$data = collect([]);
|
||||
$registry_user = $this->environment_variables()->where('key', 'SERVICE_USER_REGISTRY')->first();
|
||||
$registry_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_REGISTRY')->first();
|
||||
if ($registry_user) {
|
||||
$data = $data->merge([
|
||||
'Registry User' => [
|
||||
'key' => data_get($registry_user, 'key'),
|
||||
'value' => data_get($registry_user, 'value'),
|
||||
'rules' => 'required',
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($registry_password) {
|
||||
$data = $data->merge([
|
||||
'Registry Password' => [
|
||||
'key' => data_get($registry_password, 'key'),
|
||||
'value' => data_get($registry_password, 'value'),
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Docker Registry', $data->toArray());
|
||||
break;
|
||||
case $image->contains('tolgee'):
|
||||
$data = collect([]);
|
||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
|
||||
|
|
@ -1433,15 +1458,7 @@ class Service extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN is_required = true THEN 1
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
|
||||
public function workdir()
|
||||
|
|
|
|||
|
|
@ -295,15 +295,7 @@ class StandaloneClickhouse extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
|
||||
public function runtime_environment_variables()
|
||||
|
|
|
|||
|
|
@ -324,14 +324,6 @@ class StandaloneDragonfly extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,14 +324,6 @@ class StandaloneKeydb extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,15 +289,7 @@ class StandaloneMariadb extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
|
||||
public function runtime_environment_variables()
|
||||
|
|
|
|||
|
|
@ -349,14 +349,6 @@ class StandaloneMongodb extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,14 +328,6 @@ class StandaloneMysql extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,15 +323,7 @@ class StandalonePostgresql extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
|
|
|
|||
|
|
@ -374,14 +374,6 @@ class StandaloneRedis extends BaseModel
|
|||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN LOWER(key) LIKE 'service_%' THEN 1
|
||||
WHEN is_required = true AND (value IS NULL OR value = '') THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
LOWER(key) ASC
|
||||
");
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ class ChangelogService
|
|||
$html = preg_replace('/<a([^>]*)>/', '<a$1 class="text-blue-500 hover:text-blue-600 underline" target="_blank" rel="noopener">', $html);
|
||||
|
||||
// Convert plain URLs to clickable links (that aren't already in <a> tags)
|
||||
$html = preg_replace('/(?<!href="|href=\')(?<!>)(?<!\/)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/', '<a href="$1" class="text-blue-500 hover:text-blue-600 underline" target="_blank" rel="noopener">$1</a>', $html);
|
||||
$html = preg_replace('/(?<!href="|href=\')(?<!src="|src=\')(?<!>)(?<!\/)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/', '<a href="$1" class="text-blue-500 hover:text-blue-600 underline" target="_blank" rel="noopener">$1</a>', $html);
|
||||
|
||||
// Strong/bold text
|
||||
$html = preg_replace('/<strong[^>]*>/', '<strong class="font-semibold dark:text-white">', $html);
|
||||
|
|
|
|||
|
|
@ -8,16 +8,14 @@ namespace App\Support;
|
|||
class ValidationPatterns
|
||||
{
|
||||
/**
|
||||
* Pattern for names (allows letters, numbers, spaces, dashes, underscores, dots, slashes, colons, parentheses)
|
||||
* Matches CleanupNames::sanitizeName() allowed characters
|
||||
*/
|
||||
public const NAME_PATTERN = '/^[a-zA-Z0-9\s\-_.:\/()]+$/';
|
||||
* Pattern for names excluding all dangerous characters
|
||||
*/
|
||||
public const NAME_PATTERN = '/^[\p{L}\p{M}\p{N}\s\-_.]+$/u';
|
||||
|
||||
/**
|
||||
* Pattern for descriptions (allows more characters including quotes, commas, etc.)
|
||||
* More permissive than names but still restricts dangerous characters
|
||||
* Pattern for descriptions excluding all dangerous characters with some additional allowed characters
|
||||
*/
|
||||
public const DESCRIPTION_PATTERN = '/^[a-zA-Z0-9\s\-_.:\/()\'\",.!?@#%&+=\[\]{}|~`*]+$/';
|
||||
public const DESCRIPTION_PATTERN = '/^[\p{L}\p{M}\p{N}\s\-_.,!?()\'\"+=*]+$/u';
|
||||
|
||||
/**
|
||||
* Get validation rules for name fields
|
||||
|
|
@ -66,7 +64,7 @@ class ValidationPatterns
|
|||
public static function nameMessages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => 'The name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
|
||||
'name.regex' => "The name may only contain letters (including Unicode), numbers, spaces, dashes (-), underscores (_) and dots (.).",
|
||||
'name.min' => 'The name must be at least :min characters.',
|
||||
'name.max' => 'The name may not be greater than :max characters.',
|
||||
];
|
||||
|
|
@ -78,12 +76,12 @@ class ValidationPatterns
|
|||
public static function descriptionMessages(): array
|
||||
{
|
||||
return [
|
||||
'description.regex' => 'The description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
|
||||
'description.regex' => "The description may only contain letters (including Unicode), numbers, spaces, and common punctuation (- _ . , ! ? ( ) ' \" + = *).",
|
||||
'description.max' => 'The description may not be greater than :max characters.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get combined validation messages for both name and description fields
|
||||
*/
|
||||
public static function combinedMessages(): array
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ function sharedDataApplications()
|
|||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_custom_start_command' => 'string|nullable',
|
||||
'docker_compose_custom_build_command' => 'string|nullable',
|
||||
'is_container_label_escape_enabled' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -179,4 +180,5 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
|
|||
$request->offsetUnset('is_static');
|
||||
$request->offsetUnset('force_domain_override');
|
||||
$request->offsetUnset('autogenerate_domain');
|
||||
$request->offsetUnset('is_container_label_escape_enabled');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@ function format_docker_envs_to_json($rawOutput)
|
|||
}
|
||||
function checkMinimumDockerEngineVersion($dockerVersion)
|
||||
{
|
||||
$majorDockerVersion = str($dockerVersion)->before('.')->value();
|
||||
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.')->value();
|
||||
$majorDockerVersion = (int) str($dockerVersion)->before('.')->value();
|
||||
$requiredDockerVersion = (int) str(config('constants.docker.minimum_required_version'))->before('.')->value();
|
||||
if ($majorDockerVersion < $requiredDockerVersion) {
|
||||
$dockerVersion = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,3 +162,54 @@ function loadRepositoryByPage(GithubApp $source, string $token, int $page)
|
|||
'repositories' => $json['repositories'],
|
||||
];
|
||||
}
|
||||
function getGithubCommitRangeFiles(?GithubApp $source, string $owner, string $repo, string $beforeSha, string $afterSha): array
|
||||
{
|
||||
try {
|
||||
if (! $source) {
|
||||
// Manual webhooks don't have GitHub App authentication
|
||||
// Return empty array so watch paths are ignored (current behavior)
|
||||
return [];
|
||||
}
|
||||
|
||||
$endpoint = "/repos/{$owner}/{$repo}/compare/{$beforeSha}...{$afterSha}";
|
||||
$response = githubApi($source, $endpoint, 'get', null, false);
|
||||
|
||||
if (! $response) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = collect(data_get($response, 'data.files', []));
|
||||
|
||||
return $files->pluck('filename')->filter()->values()->toArray();
|
||||
} catch (Exception $e) {
|
||||
ray('Error fetching GitHub commit range files: '.$e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getGithubPullRequestFiles(?GithubApp $source, string $owner, string $repo, int $pullRequestId): array
|
||||
{
|
||||
try {
|
||||
if (! $source) {
|
||||
// Manual webhooks don't have GitHub App authentication
|
||||
// Return empty array so watch paths are ignored (current behavior)
|
||||
return [];
|
||||
}
|
||||
|
||||
$endpoint = "/repos/{$owner}/{$repo}/pulls/{$pullRequestId}/files";
|
||||
$response = githubApi($source, $endpoint, 'get', null, false);
|
||||
|
||||
if (! $response) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = collect(data_get($response, 'data', []));
|
||||
|
||||
return $files->pluck('filename')->filter()->values()->toArray();
|
||||
} catch (Exception $e) {
|
||||
ray('Error fetching GitHub PR files: '.$e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -989,6 +989,9 @@ function generateEnvValue(string $command, Service|Application|null $service = n
|
|||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
case 'LOWERCASEUSER':
|
||||
$generatedValue = Str::lower(Str::random(16));
|
||||
break;
|
||||
case 'SUPABASEANON':
|
||||
$signingKey = $service->environment_variables()->where('key', 'SERVICE_PASSWORD_JWT')->first();
|
||||
if (is_null($signingKey)) {
|
||||
|
|
@ -1187,7 +1190,7 @@ function get_public_ips()
|
|||
$ipv4 = $first->output();
|
||||
if ($ipv4) {
|
||||
$ipv4 = trim($ipv4);
|
||||
$validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
|
||||
$validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
if ($validate_ipv4 == false) {
|
||||
echo "Invalid ipv4: $ipv4\n";
|
||||
|
||||
|
|
@ -1202,7 +1205,7 @@ function get_public_ips()
|
|||
$ipv6 = $second->output();
|
||||
if ($ipv6) {
|
||||
$ipv6 = trim($ipv6);
|
||||
$validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
|
||||
$validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
if ($validate_ipv6 == false) {
|
||||
echo "Invalid ipv6: $ipv6\n";
|
||||
|
||||
|
|
|
|||
|
|
@ -13,67 +13,67 @@
|
|||
"require": {
|
||||
"php": "^8.4",
|
||||
"danharrin/livewire-rate-limiting": "^2.1.0",
|
||||
"doctrine/dbal": "^4.3.0",
|
||||
"guzzlehttp/guzzle": "^7.9.3",
|
||||
"laravel/fortify": "^1.27.0",
|
||||
"laravel/framework": "^12.20.0",
|
||||
"laravel/horizon": "^5.33.1",
|
||||
"laravel/pail": "^1.2.3",
|
||||
"laravel/prompts": "^0.3.6|^0.3.6|^0.3.6",
|
||||
"laravel/sanctum": "^4.1.2",
|
||||
"laravel/socialite": "^5.21.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"doctrine/dbal": "^4.4.1",
|
||||
"guzzlehttp/guzzle": "^7.10.0",
|
||||
"laravel/fortify": "^1.33.0",
|
||||
"laravel/framework": "^12.44.0",
|
||||
"laravel/horizon": "^5.41.0",
|
||||
"laravel/pail": "^1.2.4",
|
||||
"laravel/prompts": "^0.3.8|^0.3.8|^0.3.8",
|
||||
"laravel/sanctum": "^4.2.1",
|
||||
"laravel/socialite": "^5.24.0",
|
||||
"laravel/tinker": "^2.10.2",
|
||||
"laravel/ui": "^4.6.1",
|
||||
"lcobucci/jwt": "^5.5.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.29",
|
||||
"lcobucci/jwt": "^5.6.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.30.1",
|
||||
"league/flysystem-sftp-v3": "^3.30",
|
||||
"livewire/livewire": "^3.6.4",
|
||||
"livewire/livewire": "^3.7.3",
|
||||
"log1x/laravel-webfonts": "^2.0.1",
|
||||
"lorisleiva/laravel-actions": "^2.9.0",
|
||||
"lorisleiva/laravel-actions": "^2.9.1",
|
||||
"nubs/random-name-generator": "^2.2",
|
||||
"phpseclib/phpseclib": "^3.0.46",
|
||||
"phpseclib/phpseclib": "^3.0.48",
|
||||
"pion/laravel-chunk-upload": "^1.5.6",
|
||||
"poliander/cron": "^3.2.1",
|
||||
"purplepixie/phpdns": "^2.2",
|
||||
"poliander/cron": "^3.3.0",
|
||||
"purplepixie/phpdns": "^2.3.6",
|
||||
"pusher/pusher-php-server": "^7.2.7",
|
||||
"resend/resend-laravel": "^0.20.0",
|
||||
"sentry/sentry-laravel": "^4.15.1",
|
||||
"sentry/sentry-laravel": "^4.20.0",
|
||||
"socialiteproviders/authentik": "^5.2",
|
||||
"socialiteproviders/clerk": "^5.0",
|
||||
"socialiteproviders/clerk": "^5.1",
|
||||
"socialiteproviders/discord": "^4.2",
|
||||
"socialiteproviders/google": "^4.1",
|
||||
"socialiteproviders/infomaniak": "^4.0",
|
||||
"socialiteproviders/microsoft-azure": "^5.2",
|
||||
"socialiteproviders/zitadel": "^4.2",
|
||||
"spatie/laravel-activitylog": "^4.10.2",
|
||||
"spatie/laravel-data": "^4.17.0",
|
||||
"spatie/laravel-markdown": "^2.7",
|
||||
"spatie/laravel-ray": "^1.40.2",
|
||||
"spatie/laravel-data": "^4.18.0",
|
||||
"spatie/laravel-markdown": "^2.7.1",
|
||||
"spatie/laravel-ray": "^1.43.2",
|
||||
"spatie/laravel-schemaless-attributes": "^2.5.1",
|
||||
"spatie/url": "^2.4",
|
||||
"stevebauman/purify": "^6.3.1",
|
||||
"stripe/stripe-php": "^16.6.0",
|
||||
"symfony/yaml": "^7.3.1",
|
||||
"symfony/yaml": "^7.4.1",
|
||||
"visus/cuid2": "^4.1.0",
|
||||
"yosymfony/toml": "^1.0.4",
|
||||
"zircote/swagger-php": "^5.1.4"
|
||||
"zircote/swagger-php": "^5.7.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.15.4",
|
||||
"driftingly/rector-laravel": "^2.0.5",
|
||||
"barryvdh/laravel-debugbar": "^3.16.3",
|
||||
"driftingly/rector-laravel": "^2.1.9",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"laravel/boost": "^1.1",
|
||||
"laravel/dusk": "^8.3.3",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/telescope": "^5.10",
|
||||
"laravel/boost": "^1.8.7",
|
||||
"laravel/dusk": "^8.3.4",
|
||||
"laravel/pint": "^1.26",
|
||||
"laravel/telescope": "^5.16",
|
||||
"mockery/mockery": "^1.6.12",
|
||||
"nunomaduro/collision": "^8.8.2",
|
||||
"pestphp/pest": "^3.8.2",
|
||||
"phpstan/phpstan": "^2.1.18",
|
||||
"rector/rector": "^2.1.2",
|
||||
"serversideup/spin": "^3.0.2",
|
||||
"nunomaduro/collision": "^8.8.3",
|
||||
"pestphp/pest": "^4.3.0",
|
||||
"phpstan/phpstan": "^2.1.33",
|
||||
"rector/rector": "^2.3.0",
|
||||
"serversideup/spin": "^3.1.1",
|
||||
"spatie/laravel-ignition": "^2.9.1",
|
||||
"symfony/http-client": "^7.3.1"
|
||||
"symfony/http-client": "^7.4.3"
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
|
|
@ -109,11 +109,6 @@
|
|||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-install-cmd": [
|
||||
"cp -r 'hooks/' '.git/hooks/'",
|
||||
"php -r \"copy('hooks/pre-commit', '.git/hooks/pre-commit');\"",
|
||||
"php -r \"chmod('.git/hooks/pre-commit', 0777);\""
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
||||
"Illuminate\\Foundation\\ComposerScripts::postUpdate"
|
||||
|
|
|
|||
4127
composer.lock
generated
|
|
@ -48,6 +48,9 @@ return [
|
|||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
'options' => [
|
||||
PDO::PGSQL_ATTR_DISABLE_PREPARES => env('DB_DISABLE_PREPARES', false),
|
||||
],
|
||||
],
|
||||
|
||||
'testing' => [
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ services:
|
|||
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
healthcheck:
|
||||
test: curl -sf http://127.0.0.1:8080/api/health || exit 1
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
- .:/var/www/html/:cached
|
||||
- dev_backups_data:/var/www/html/storage/app/backups
|
||||
|
|
@ -32,6 +37,11 @@ services:
|
|||
POSTGRES_PASSWORD: "${DB_PASSWORD:-password}"
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
POSTGRES_HOST_AUTH_METHOD: "trust"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
- dev_postgres_data:/var/lib/postgresql/data
|
||||
redis:
|
||||
|
|
@ -40,6 +50,11 @@ services:
|
|||
- "${FORWARD_REDIS_PORT:-6379}:6379"
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
- dev_redis_data:/data
|
||||
soketi:
|
||||
|
|
@ -61,6 +76,11 @@ services:
|
|||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "curl -fsS http://127.0.0.1:6001/ready && curl -fsS http://127.0.0.1:6002/ready || exit 1" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
|
||||
vite:
|
||||
image: node:24-alpine
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
#!/bin/sh
|
||||
# Detect whether /dev/tty is available & functional
|
||||
if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then
|
||||
exec </dev/tty
|
||||
fi
|
||||
|
||||
# Generate service templates and OpenAPI documentation
|
||||
echo "🔄 Generating service templates..."
|
||||
php artisan generate:services
|
||||
|
||||
echo "📚 Generating OpenAPI documentation..."
|
||||
php artisan generate:openapi
|
||||
|
||||
# Add the generated files to the commit
|
||||
git add templates/service-templates*.json
|
||||
git add openapi.json openapi.yaml
|
||||
|
||||
echo "✅ Generated files have been added to the commit"
|
||||
|
||||
# Get list of stashed PHP files
|
||||
stashed_files=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')
|
||||
|
||||
# If there are no stashed PHP files, exit early
|
||||
if [ -z "$stashed_files" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Set files variable to only include stashed PHP files
|
||||
files="$stashed_files"
|
||||
|
||||
$(pwd)/vendor/bin/pint $files -q
|
||||
if [ $? -eq 0 ]; then
|
||||
git add $files
|
||||
fi
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"scripts": {
|
||||
"setup": "cp $JEAN_ROOT_PATH/.env . && mkdir -p .claude && cp $JEAN_ROOT_PATH/.claude/settings.local.json .claude/settings.local.json"
|
||||
"setup": "cp $JEAN_ROOT_PATH/.env . && mkdir -p .claude && cp $JEAN_ROOT_PATH/.claude/settings.local.json .claude/settings.local.json",
|
||||
"run": "docker rm -f coolify coolify-minio-init coolify-realtime coolify-minio coolify-testing-host coolify-redis coolify-db coolify-mail coolify-vite; spin up; spin down"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
"auth.login.gitlab": "Entrar com Gitlab",
|
||||
"auth.login.google": "Entrar com Google",
|
||||
"auth.login.infomaniak": "Entrar com Infomaniak",
|
||||
"auth.login.zitadel": "Entrar com Zitadel",
|
||||
"auth.already_registered": "Já tem uma conta?",
|
||||
"auth.confirm_password": "Confirmar senha",
|
||||
"auth.forgot_password_link": "Esqueceu a senha?",
|
||||
|
|
@ -40,4 +41,4 @@
|
|||
"resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.",
|
||||
"database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.",
|
||||
"warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https <span class='dark:text-red-500 text-red-500 font-bold'>NÃO</span> é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará). <br><br>Use seu próprio domínio em vez disso."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
lang/pt.json
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"auth.login": "Entrar",
|
||||
"auth.login.authentik": "Entrar com Authentik",
|
||||
"auth.login.azure": "Entrar com Microsoft",
|
||||
"auth.login.bitbucket": "Entrar com Bitbucket",
|
||||
"auth.login.clerk": "Entrar com Clerk",
|
||||
|
|
@ -8,6 +9,7 @@
|
|||
"auth.login.gitlab": "Entrar com Gitlab",
|
||||
"auth.login.google": "Entrar com Google",
|
||||
"auth.login.infomaniak": "Entrar com Infomaniak",
|
||||
"auth.login.zitadel": "Entrar com Zitadel",
|
||||
"auth.already_registered": "Já tem uma conta?",
|
||||
"auth.confirm_password": "Confirmar senha",
|
||||
"auth.forgot_password_link": "Esqueceu a senha?",
|
||||
|
|
@ -30,5 +32,13 @@
|
|||
"input.code": "Código único",
|
||||
"input.recovery_code": "Código de recuperação",
|
||||
"button.save": "Salvar",
|
||||
"repository.url": "<span class='text-helper'>Exemplos</span><br>Para repositórios públicos, use <span class='text-helper'>https://...</span>.<br>Para repositórios privados, use <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>a branch main</span> será selecionada<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>a branch nodejs-fastify</span> será selecionada.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>a branch main</span> será selecionada.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>a branch main</span> será selecionada."
|
||||
}
|
||||
"repository.url": "<span class='text-helper'>Exemplos</span><br>Para repositórios públicos, use <span class='text-helper'>https://...</span>.<br>Para repositórios privados, use <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>a branch main</span> será selecionada<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>a branch nodejs-fastify</span> será selecionada.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>a branch main</span> será selecionada.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>a branch main</span> será selecionada.",
|
||||
"service.stop": "Este serviço será parado.",
|
||||
"resource.docker_cleanup": "Executar limpeza do Docker (remover imagens não utilizadas e cache de build).",
|
||||
"resource.non_persistent": "Todos os dados não persistentes serão excluídos.",
|
||||
"resource.delete_volumes": "Excluir permanentemente todos os volumes associados a este recurso.",
|
||||
"resource.delete_connected_networks": "Excluir permanentemente todas as redes não predefinidas associadas a este recurso.",
|
||||
"resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.",
|
||||
"database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.",
|
||||
"warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https <span class='dark:text-red-500 text-red-500 font-bold'>NÃO</span> é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará). <br><br>Use seu próprio domínio em vez disso."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"auth.login": "登录",
|
||||
"auth.login.authentik": "使用 Authentik 登录",
|
||||
"auth.login.azure": "使用 Microsoft 登录",
|
||||
"auth.login.bitbucket": "使用 Bitbucket 登录",
|
||||
"auth.login.clerk": "使用 Clerk 登录",
|
||||
|
|
@ -8,6 +9,7 @@
|
|||
"auth.login.gitlab": "使用 Gitlab 登录",
|
||||
"auth.login.google": "使用 Google 登录",
|
||||
"auth.login.infomaniak": "使用 Infomaniak 登录",
|
||||
"auth.login.zitadel": "使用 Zitadel 登录",
|
||||
"auth.already_registered": "已经注册?",
|
||||
"auth.confirm_password": "确认密码",
|
||||
"auth.forgot_password_link": "忘记密码?",
|
||||
|
|
@ -30,5 +32,13 @@
|
|||
"input.code": "验证码",
|
||||
"input.recovery_code": "恢复码",
|
||||
"button.save": "保存",
|
||||
"repository.url": "<span class='text-helper'>示例</span><br>对于公共代码仓库,请使用 <span class='text-helper'>https://...</span>。<br>对于私有代码仓库,请使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支将被选择<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支将被选择。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支将被选择。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支将被选择"
|
||||
"repository.url": "<span class='text-helper'>示例</span><br>对于公共代码仓库,请使用 <span class='text-helper'>https://...</span>。<br>对于私有代码仓库,请使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支将被选择<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支将被选择。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支将被选择。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支将被选择",
|
||||
"service.stop": "此服务将被停止。",
|
||||
"resource.docker_cleanup": "运行 Docker 清理(删除未使用的镜像和构建缓存)。",
|
||||
"resource.non_persistent": "所有非持久性数据将被删除。",
|
||||
"resource.delete_volumes": "永久删除与此资源关联的所有卷。",
|
||||
"resource.delete_connected_networks": "永久删除与此资源关联的所有非预定义网络。",
|
||||
"resource.delete_configurations": "永久删除服务器上的所有配置文件。",
|
||||
"database.delete_backups_locally": "所有备份将从本地存储中永久删除。",
|
||||
"warning.sslipdomain": "您的配置已保存,但不建议将 sslip 域与 https 一起使用,因为 Let's Encrypt 服务器对此公共域有速率限制(SSL 证书验证将失败)。<br><br>请改用您自己的域名。"
|
||||
}
|
||||
190
openapi.json
|
|
@ -19,6 +19,17 @@
|
|||
"summary": "List",
|
||||
"description": "List all applications.",
|
||||
"operationId": "list-applications",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tag",
|
||||
"in": "query",
|
||||
"description": "Filter applications by tag name.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Get all applications.",
|
||||
|
|
@ -366,6 +377,11 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -781,6 +797,11 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1196,6 +1217,11 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1540,6 +1566,11 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1867,6 +1898,11 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -2028,6 +2064,11 @@
|
|||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -2134,8 +2175,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2180,8 +2220,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -2272,8 +2311,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2552,6 +2590,11 @@
|
|||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
},
|
||||
"is_container_label_escape_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -2661,8 +2704,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -2725,8 +2767,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2774,8 +2815,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2863,8 +2903,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2958,8 +2997,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3017,13 +3055,10 @@
|
|||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Environment variables updated."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#\/components\/schemas\/EnvironmentVariable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3060,8 +3095,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -3070,8 +3104,7 @@
|
|||
"description": "UUID of the environment variable.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3124,8 +3157,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -3202,8 +3234,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3256,8 +3287,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3521,8 +3551,7 @@
|
|||
"description": "UUID of the cloud provider token.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3730,8 +3759,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3777,8 +3805,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3910,8 +3937,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3957,8 +3983,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -4049,8 +4074,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -4271,8 +4295,7 @@
|
|||
"description": "UUID of the backup configuration to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -4340,8 +4363,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -4350,8 +4372,7 @@
|
|||
"description": "UUID of the backup configuration.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -5528,8 +5549,7 @@
|
|||
"description": "UUID of the backup configuration",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -5538,8 +5558,7 @@
|
|||
"description": "UUID of the backup execution to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -5618,8 +5637,7 @@
|
|||
"description": "UUID of the backup configuration",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -5688,8 +5706,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -5742,8 +5759,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -5796,8 +5812,7 @@
|
|||
"description": "UUID of the database.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -6109,8 +6124,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -7522,8 +7536,7 @@
|
|||
"description": "UUID of the application.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -7577,8 +7590,7 @@
|
|||
"description": "UUID of the project.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -8415,8 +8427,7 @@
|
|||
"description": "UUID of the server.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9126,8 +9137,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9244,8 +9254,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9293,8 +9302,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9385,8 +9393,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9483,8 +9490,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9588,8 +9594,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -9598,8 +9603,7 @@
|
|||
"description": "UUID of the environment variable.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9652,8 +9656,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9706,8 +9709,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -9760,8 +9762,7 @@
|
|||
"description": "UUID of the service.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -10608,13 +10609,6 @@
|
|||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"environments": {
|
||||
"description": "The environments of the project.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#\/components\/schemas\/Environment"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
|
|||
89
openapi.yaml
|
|
@ -14,6 +14,14 @@ paths:
|
|||
summary: List
|
||||
description: 'List all applications.'
|
||||
operationId: list-applications
|
||||
parameters:
|
||||
-
|
||||
name: tag
|
||||
in: query
|
||||
description: 'Filter applications by tag name.'
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all applications.'
|
||||
|
|
@ -269,6 +277,10 @@ paths:
|
|||
type: boolean
|
||||
default: true
|
||||
description: "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -539,6 +551,10 @@ paths:
|
|||
type: boolean
|
||||
default: true
|
||||
description: "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -809,6 +825,10 @@ paths:
|
|||
type: boolean
|
||||
default: true
|
||||
description: "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -1026,6 +1046,10 @@ paths:
|
|||
type: boolean
|
||||
default: true
|
||||
description: "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -1234,6 +1258,10 @@ paths:
|
|||
type: boolean
|
||||
default: true
|
||||
description: "If true and domains is empty, auto-generate a domain using the server's wildcard domain or sslip.io fallback. Default: true."
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -1318,6 +1346,10 @@ paths:
|
|||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -1360,7 +1392,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get application by UUID.'
|
||||
|
|
@ -1391,7 +1422,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: delete_configurations
|
||||
in: query
|
||||
|
|
@ -1456,7 +1486,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Application updated.'
|
||||
required: true
|
||||
|
|
@ -1661,6 +1690,10 @@ paths:
|
|||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
is_container_label_escape_enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
|
|
@ -1705,7 +1738,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: lines
|
||||
in: query
|
||||
|
|
@ -1748,7 +1780,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'All environment variables by application UUID.'
|
||||
|
|
@ -1781,7 +1812,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Env created.'
|
||||
required: true
|
||||
|
|
@ -1840,7 +1870,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Env updated.'
|
||||
required: true
|
||||
|
|
@ -1903,7 +1932,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Bulk envs updated.'
|
||||
required: true
|
||||
|
|
@ -1923,9 +1951,9 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Environment variables updated.' }
|
||||
type: object
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/EnvironmentVariable'
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
|
|
@ -1950,7 +1978,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: env_uuid
|
||||
in: path
|
||||
|
|
@ -1958,7 +1985,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Environment variable deleted.'
|
||||
|
|
@ -1992,7 +2018,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: force
|
||||
in: query
|
||||
|
|
@ -2041,7 +2066,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Stop application.'
|
||||
|
|
@ -2075,7 +2099,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Restart application.'
|
||||
|
|
@ -2219,7 +2242,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Cloud provider token deleted.'
|
||||
|
|
@ -2350,7 +2372,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all backups for a database'
|
||||
|
|
@ -2382,7 +2403,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Backup configuration data'
|
||||
required: true
|
||||
|
|
@ -2471,7 +2491,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all databases'
|
||||
|
|
@ -2503,7 +2522,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: delete_configurations
|
||||
in: query
|
||||
|
|
@ -2568,7 +2586,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
|
|
@ -2730,7 +2747,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: delete_s3
|
||||
in: query
|
||||
|
|
@ -2773,7 +2789,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: scheduled_backup_uuid
|
||||
in: path
|
||||
|
|
@ -2781,7 +2796,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Database backup configuration data'
|
||||
required: true
|
||||
|
|
@ -3628,7 +3642,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: execution_uuid
|
||||
in: path
|
||||
|
|
@ -3636,7 +3649,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: delete_s3
|
||||
in: query
|
||||
|
|
@ -3687,7 +3699,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'List of backup executions'
|
||||
|
|
@ -3717,7 +3728,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Start database.'
|
||||
|
|
@ -3751,7 +3761,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Stop database.'
|
||||
|
|
@ -3785,7 +3794,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Restart database.'
|
||||
|
|
@ -3970,7 +3978,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: skip
|
||||
in: query
|
||||
|
|
@ -4777,7 +4784,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Project deleted.'
|
||||
|
|
@ -4812,7 +4818,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Project updated.'
|
||||
required: true
|
||||
|
|
@ -5348,7 +5353,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Server deleted.'
|
||||
|
|
@ -5739,7 +5743,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Service updated.'
|
||||
required: true
|
||||
|
|
@ -5815,7 +5818,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'All environment variables by service UUID.'
|
||||
|
|
@ -5848,7 +5850,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Env created.'
|
||||
required: true
|
||||
|
|
@ -5909,7 +5910,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Env updated.'
|
||||
required: true
|
||||
|
|
@ -5974,7 +5974,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Bulk envs updated.'
|
||||
required: true
|
||||
|
|
@ -6023,7 +6022,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: env_uuid
|
||||
in: path
|
||||
|
|
@ -6031,7 +6029,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Environment variable deleted.'
|
||||
|
|
@ -6065,7 +6062,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Start service.'
|
||||
|
|
@ -6099,7 +6095,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Stop service.'
|
||||
|
|
@ -6133,7 +6128,6 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: latest
|
||||
in: query
|
||||
|
|
@ -6734,11 +6728,6 @@ components:
|
|||
type: string
|
||||
description:
|
||||
type: string
|
||||
environments:
|
||||
description: 'The environments of the project.'
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Environment'
|
||||
type: object
|
||||
Server:
|
||||
description: 'Server model'
|
||||
|
|
|
|||
1186
package-lock.json
generated
18
package.json
|
|
@ -7,17 +7,17 @@
|
|||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "4.1.10",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"axios": "1.9.0",
|
||||
"laravel-echo": "2.1.5",
|
||||
"laravel-vite-plugin": "1.3.0",
|
||||
"postcss": "8.5.5",
|
||||
"@tailwindcss/postcss": "4.1.18",
|
||||
"@vitejs/plugin-vue": "6.0.3",
|
||||
"axios": "1.13.2",
|
||||
"laravel-echo": "2.2.7",
|
||||
"laravel-vite-plugin": "2.0.1",
|
||||
"postcss": "8.5.6",
|
||||
"pusher-js": "8.4.0",
|
||||
"tailwind-scrollbar": "4.0.2",
|
||||
"tailwindcss": "4.1.10",
|
||||
"vite": "6.4.1",
|
||||
"vue": "3.5.16"
|
||||
"tailwindcss": "4.1.18",
|
||||
"vite": "7.3.0",
|
||||
"vue": "3.5.26"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/forms": "0.5.10",
|
||||
|
|
|
|||
BIN
public/seaweedfs.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
3
public/svgs/autobase.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="677" height="603" viewBox="0 0 677 603" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M646.296 373.547C633.734 389.485 617.744 402.39 599.514 411.304C581.284 420.219 561.281 424.914 540.988 425.043H302.307C292.153 425.042 282.407 429.04 275.178 436.172C267.95 443.304 263.821 452.996 263.686 463.149V547.344L291.493 505.633C293.506 503.135 296.378 501.478 299.548 500.987C302.717 500.495 305.957 501.205 308.63 502.977C311.304 504.749 313.22 507.455 314.003 510.566C314.787 513.676 314.38 516.967 312.864 519.794L261.369 597.037C260.305 598.619 258.896 599.939 257.249 600.899C255.488 601.916 253.49 602.451 251.456 602.451C249.422 602.451 247.424 601.916 245.663 600.899C244.482 600.134 243.438 599.176 242.573 598.067L191.078 520.824C189.561 517.997 189.155 514.706 189.938 511.596C190.721 508.485 192.637 505.779 195.311 504.007C197.985 502.235 201.224 501.525 204.394 502.016C207.564 502.508 210.436 504.165 212.448 506.663L237.938 547.601V463.149C237.894 454.625 239.557 446.178 242.83 438.307C246.102 430.435 250.917 423.299 256.992 417.318C262.984 411.402 270.109 406.756 277.94 403.659C285.771 400.562 294.147 399.078 302.565 399.295H540.988C557.37 399.328 573.551 395.691 588.344 388.652C603.136 381.612 616.164 371.348 626.47 358.614C636.823 345.736 644.162 330.704 647.949 314.62C651.736 298.537 651.875 281.809 648.355 265.665C642.531 238.717 626.587 215.031 603.812 199.494C587.241 188.108 567.854 181.508 547.778 180.418C527.702 179.328 507.714 183.79 490.008 193.314C483.391 236.071 460.398 274.576 425.896 300.682C424.543 301.696 423.004 302.434 421.367 302.854C419.729 303.273 418.025 303.366 416.351 303.127C414.677 302.888 413.067 302.322 411.613 301.46C410.158 300.599 408.887 299.459 407.873 298.107C406.858 296.754 406.12 295.215 405.701 293.578C405.281 291.94 405.188 290.236 405.427 288.562C405.666 286.888 406.233 285.278 407.094 283.824C407.955 282.369 409.095 281.098 410.447 280.084C425.732 268.541 438.501 254.003 447.975 237.357C457.449 220.711 463.428 202.308 465.547 183.273C465.896 178.129 465.896 172.968 465.547 167.824C466.042 139.22 457.636 111.17 441.492 87.5527C425.347 63.9356 402.262 45.9193 375.431 35.9962C353.756 28.0027 330.469 25.3733 307.558 28.3325C284.647 31.2916 262.792 39.7515 243.86 52.9897C225.925 66.1997 211.372 83.4685 201.393 103.383C191.413 123.298 186.29 145.292 186.443 167.567C186.85 169.825 186.712 172.148 186.04 174.342C185.368 176.537 184.182 178.539 182.581 180.183C181.041 181.778 179.13 182.966 177.017 183.64C174.905 184.315 172.659 184.454 170.48 184.045C156.294 179.969 141.432 178.791 126.781 180.582C112.131 182.373 97.9904 187.095 85.2037 194.468C72.417 201.84 61.2457 211.712 52.3559 223.494C43.466 235.276 37.0396 248.728 33.4598 263.047C29.8801 277.367 29.2202 292.26 31.5195 306.84C33.8189 321.419 39.0304 335.387 46.8433 347.909C54.6563 360.431 64.9107 371.252 76.9953 379.727C89.0798 388.201 102.747 394.156 117.182 397.235H174.084C184.354 397.003 194.172 392.966 201.634 385.906C205.125 382.238 207.855 377.915 209.667 373.187C211.479 368.458 212.336 363.418 212.191 358.356V274.419L184.383 316.388C183.537 317.966 182.37 319.351 180.957 320.453C179.545 321.554 177.918 322.349 176.181 322.787C174.444 323.224 172.635 323.294 170.869 322.992C169.104 322.69 167.421 322.023 165.927 321.034C164.434 320.044 163.164 318.754 162.198 317.246C161.232 315.737 160.591 314.044 160.317 312.274C160.042 310.504 160.14 308.696 160.605 306.966C161.069 305.236 161.889 303.622 163.013 302.227L214.508 224.984C215.652 223.389 217.16 222.09 218.906 221.193C220.652 220.297 222.587 219.83 224.55 219.83C226.512 219.83 228.447 220.297 230.193 221.193C231.939 222.09 233.447 223.389 234.591 224.984L286.086 302.227C287.21 303.622 288.03 305.236 288.495 306.966C288.959 308.696 289.057 310.504 288.783 312.274C288.508 314.044 287.868 315.737 286.901 317.246C285.935 318.754 284.665 320.044 283.172 321.034C281.679 322.023 279.995 322.69 278.23 322.992C276.464 323.294 274.655 323.224 272.918 322.787C271.181 322.349 269.554 321.554 268.142 320.453C266.729 319.351 265.563 317.966 264.716 316.388L237.938 274.419V358.356C237.948 366.837 236.268 375.235 232.997 383.06C229.726 390.885 224.928 397.979 218.885 403.93C206.53 415.758 190.157 422.473 173.054 422.725H114.607H111.775C76.2658 416.238 44.7881 395.911 24.2665 366.215C3.74485 336.519 -4.13957 299.887 2.34768 264.378C8.83494 228.868 29.1625 197.391 58.8584 176.869C88.5544 156.347 125.186 148.463 160.696 154.95C162.564 129.144 170.409 104.126 183.61 81.8728C196.812 59.6199 215.007 40.7424 236.76 26.7319C258.512 12.7214 283.225 3.96203 308.945 1.14597C334.666 -1.67008 360.688 1.53437 384.957 10.5061C416.254 21.9448 443.308 42.6731 462.497 69.9149C481.686 97.1567 492.093 129.611 492.325 162.932C512.957 154.996 535.219 152.232 557.166 154.88C579.113 157.528 600.078 165.508 618.231 178.123C646.523 197.399 666.338 226.799 673.588 260.258C677.684 279.985 677.361 300.375 672.642 319.962C667.924 339.55 658.926 357.85 646.296 373.547Z" fill="#FF5722"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
4
public/svgs/booklore.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="126" height="126" viewBox="0 0 126 126" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M59 4.79297C71.5051 11.5557 80 24.7854 80 40C80 40.5959 79.987 41.1888 79.9609 41.7783C79.8609 44.0406 81.7355 46 84 46C106.091 46 124 63.9086 124 86C124 108.091 106.091 126 84 126H10C4.47715 126 0 121.523 0 116V39.0068L0.0126953 38.9941C0.357624 25.0252 7.86506 12.8347 19 5.95215V63.832C19 64.8345 20.0676 65.4391 20.9121 64.9902L21.0771 64.8867L38.2227 52.3428C38.6819 52.0068 39.3064 52.0068 39.7656 52.3428L56.9229 64.8945L57.0879 64.998C57.9324 65.447 59 64.8423 59 63.8398V4.79297Z" fill="#818cf8"/>
|
||||
<path d="M40 0C43.8745 0 47.6199 0.552381 51.1631 1.58008V50.9697L44.3926 46.0176L44.0879 45.8037C40.9061 43.6679 36.7098 43.7393 33.5957 46.0176L26.8369 50.9619V2.21875C30.9593 0.782634 35.3881 0 40 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 842 B |
BIN
public/svgs/calibre-web-automated-with-downloader.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
35
public/svgs/cloudreve.svg
Normal file
|
After Width: | Height: | Size: 130 KiB |
1
public/svgs/esphome.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M489.4 226.7 278.6 16c-12.5-12.5-32.8-12.5-45.3 0L22.6 226.7C10.2 239.2 0 263.8 0 281.3v192c0 17.6 14.4 32 32 32h125.9V186.8c0-7.1 5.7-12.8 12.8-12.8h170.7c7.1 0 12.8 5.7 12.8 12.8V238c0 7.1-5.7 12.8-12.8 12.8H234.7v25.6h106.7c7.1 0 12.8 5.7 12.8 12.8v51.2c0 7.1-5.7 12.8-12.8 12.8H234.7v25.6h106.7c7.1 0 12.8 5.7 12.8 12.8v51.2c0 7.1-5.7 12.8-12.8 12.8H221.9c-7.1 0-12.8-5.7-12.8-12.8s5.7-12.8 12.8-12.8h106.7v-25.6H221.9c-7.1 0-12.8-5.7-12.8-12.8v-51.2c0-7.1 5.7-12.8 12.8-12.8h106.7V302H221.9c-7.1 0-12.8-5.7-12.8-12.8V238c0-7.1 5.7-12.8 12.8-12.8h106.7v-25.6H183.5v305.8H480c17.6 0 32-14.4 32-32v-192c0-17.6-10.2-42.3-22.6-54.7" style="fill:#d1d1d1"/></svg>
|
||||
|
After Width: | Height: | Size: 753 B |
7
public/svgs/hatchet.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="194" height="194" viewBox="0 0 194 194" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M152.845 0H40.2223C18.0081 0 0 18.0081 0 40.2223V152.845C0 175.059 18.0081 193.067 40.2223 193.067H152.845C175.059 193.067 193.067 175.059 193.067 152.845V40.2223C193.067 18.0081 175.059 0 152.845 0Z" fill="#3F16E4"/>
|
||||
<path d="M47.0231 102.4L60.3522 114.615L107.151 60.9115L93.8217 48.6967L47.0231 102.4Z" fill="#FFFEFE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.0037 117.497L44.1061 105.795L60.3985 120.592L64.7456 115.701L64.7455 115.701H64.7457L83.1695 116.164L74.7153 125.692L60.7149 141.758L60.4073 141.476L34.0037 117.497Z" fill="#FFFEFE"/>
|
||||
<path d="M147.029 90.4922L133.7 78.2773L86.9013 131.98L100.23 144.195L147.029 90.4922Z" fill="#FFFEFE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M129.307 77.1908L110.883 76.7278L119.326 67.2114L133.337 51.1333L133.772 51.532L160.047 75.395L149.945 87.0971L133.653 72.3011L129.307 77.1908L129.307 77.1908Z" fill="#FFFEFE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1,008 B |
BIN
public/svgs/nocobase.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
22
public/svgs/redmine.svg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/svgs/sftpgo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/svgs/silverbullet.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
194
public/svgs/trailbase.svg
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46667 135.46667"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="logo.svg"
|
||||
xml:space="preserve"
|
||||
inkscape:export-filename="logo_512.webp"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.0485567"
|
||||
inkscape:cx="316.14885"
|
||||
inkscape:cy="254.63573"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1131"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g43570-56-7"
|
||||
showguides="false" /><defs
|
||||
id="defs2"><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath16302"><circle
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:2.13168;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
id="circle16304"
|
||||
cx="420.85474"
|
||||
cy="71.167152"
|
||||
r="57.454071"
|
||||
transform="scale(-1,1)" /></clipPath><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath16302-3"><circle
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:2.13168;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
id="circle16304-6"
|
||||
cx="420.85474"
|
||||
cy="71.167152"
|
||||
r="57.454071"
|
||||
transform="scale(-1,1)" /></clipPath><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath16302-36-7"><circle
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:2.13168;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
id="circle16304-75-5"
|
||||
cx="420.85474"
|
||||
cy="71.167152"
|
||||
r="57.454071"
|
||||
transform="scale(-1,1)" /></clipPath><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath656"><path
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:1.85208;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -432.00265,67.318698 c -1.73616,0.139667 -3.50154,0.246024 -4.3977,0.178677 -1.81069,-4.111525 1.52048,-8.953296 4.44978,-8.84645 6.20224,0.226235 4.32785,8.328991 -0.0521,8.667773 z"
|
||||
id="path658"
|
||||
sodipodi:nodetypes="ccscc" /></clipPath><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath953"><circle
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:2.13168;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
id="circle955"
|
||||
cx="420.85474"
|
||||
cy="71.167152"
|
||||
r="57.454071"
|
||||
transform="scale(-1,1)" /></clipPath></defs><g
|
||||
id="g43570-56-7"
|
||||
transform="matrix(1.1789127,0,0,1.1789127,563.88433,-16.166525)"
|
||||
style="stroke-width:0.5"><g
|
||||
id="g951"
|
||||
inkscape:label="all"
|
||||
clip-path="url(#clipPath953)"><circle
|
||||
style="fill:#0073aa;fill-opacity:1;stroke:none;stroke-width:1.06584;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
id="path11937-0-2-5"
|
||||
cx="420.85474"
|
||||
cy="71.167152"
|
||||
transform="scale(-1,1)"
|
||||
inkscape:label="circle"
|
||||
r="57.454071" /><g
|
||||
id="g16298-9-3"
|
||||
clip-path="none"
|
||||
inkscape:label="squirrel"
|
||||
style="stroke-width:0.5"><path
|
||||
style="display:inline;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.66502;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m -394.37046,131.47753 c 5.12125,-4.34385 11.67689,-7.38652 14.74179,-13.66074 7.51252,-10.78724 9.93689,-23.023486 7.08569,-35.021399 -3.59312,-12.873181 -10.70247,-24.576135 -20.0541,-34.075166 -6.49968,-8.652975 -17.65011,-20.955638 -5.00797,-28.104256 -6.30691,-4.056153 -18.75609,-4.63811 -25.70383,-1.749206 -15.25854,5.960156 -26.03291,19.153286 -31.66631,33.953367 -3.2501,8.538654 -0.75393,16.12354 -1.76923,25.046648 -0.35669,4.145274 17.319,8.98466 17.65317,13.107967 13.49706,14.623365 15.84552,37.512255 36.89824,39.290795 2.60748,0.40399 5.21497,0.80799 7.82245,1.21199 z"
|
||||
id="path41116-1-5"
|
||||
sodipodi:nodetypes="ccccccsccccc"
|
||||
inkscape:label="tail" /><path
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.66502;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m -404.67281,53.96564 c -15.1265,-12.030261 -8.8751,-28.360639 7.06776,-33.349671 -6.30691,-4.056153 -18.75609,-4.63811 -25.70383,-1.749206 -15.25854,5.960156 -26.03291,19.153286 -31.66631,33.953367 -3.2501,8.538654 -0.75393,16.12354 -1.76923,25.046648 -0.35669,4.145274 17.319,8.98466 17.65317,13.107967 26.5708,-16.773958 21.84372,-0.325359 34.41844,-37.009105 z"
|
||||
id="path3419-6"
|
||||
sodipodi:nodetypes="cccsccc"
|
||||
inkscape:label="tail_highlight" /><path
|
||||
style="opacity:1;fill:#0073aa;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m -457.72736,69.431518 c 3.3896,-13.264962 8.17328,-20.647211 18.28329,-28.817159 2.54315,-3.757231 5.89263,-9.337896 8.31222,-10.306417 2.7432,1.237322 3.70892,3.928026 5.27209,6.366975 l 6.12486,-1.227345 c 1.63581,-5.260125 3.12238,-8.447075 7.62011,-10.654554 6.27135,4.105499 10.78712,9.441913 8.73926,18.138952 13.09917,13.464505 16.44363,20.397287 24.60428,39.658663 0.49305,9.347413 -0.27747,21.544127 -1.46969,29.794347 0,0 -1.69258,6.25854 -2.47729,5.72446 -0.7847,-0.53409 -4.76437,-5.18721 -4.76437,-5.18721 l -14.10706,-45.69118 -26.40483,-16.864179 c 0,0 -13.84655,5.577975 -14.887,5.946963 -1.04044,0.368988 -13.53338,18.760194 -13.53338,18.760194 z"
|
||||
id="path41710-2-2"
|
||||
sodipodi:nodetypes="cccccccccscccscc"
|
||||
inkscape:label="outline" /><path
|
||||
style="display:inline;fill:#ffffff;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -384.87624,121.37652 c 3.75488,-12.90478 3.79893,-25.678538 3.46109,-38.414706 -3.68701,-9.015838 -6.90659,-16.25675 -11.00759,-22.767045 -33.4486,11.590743 -41.50958,10.306557 -48.19197,20.239302 -0.97945,7.933407 -6.13515,12.228544 -0.80529,12.978815 -3.93992,6.715814 -7.95535,17.147064 -7.099,28.870934 4.52456,2.43084 9.62565,4.76081 14.50144,6.38118 4.0208,1.03673 8.17605,1.37868 12.37812,1.36613 3.83891,0.3095 7.67953,0.82426 11.5103,0.15208 4.55232,-0.17052 8.85841,-1.80004 13.22085,-2.94852 2.98304,-1.36738 4.01069,-1.56562 6.99373,-2.933 z"
|
||||
id="path11884-0-6-7-9"
|
||||
sodipodi:nodetypes="cccccccccccc"
|
||||
inkscape:label="body" /><path
|
||||
id="path12948-3-0-1"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -396.08075,90.674118 c -0.0102,-2.186513 -0.31014,-3.657934 0.73553,-8.452944 -0.56307,-1.566319 -1.3733,-3.116694 -4.37726,-4.129161 l -27.88609,-5.756495 -13.8158,21.085237 c -0.37346,0.632177 -1.5594,2.73663 -2.3232,4.495613 l 7.33211,2.007166 3.04952,16.392586 c -1.01658,5.79059 -1.71085,11.2009 1.8533,14.8264 l 35.36206,2.99202 c -0.42715,-3.20821 -4.12887,-11.82958 -2.52045,-19.80255 z"
|
||||
inkscape:label="body shade"
|
||||
sodipodi:nodetypes="cccccccccccc"
|
||||
transform="translate(-1.6924549e-5)" /><g
|
||||
id="g682"
|
||||
inkscape:label="head"
|
||||
style="stroke-width:0.5"><path
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -389.17548,65.820707 c -4.29469,-8.08968 -9.26583,-14.81462 -16.99742,-21.788301 2.53845,-7.601334 -0.90679,-11.83714 -5.95942,-15.95915 -3.96817,2.763543 -4.35541,5.263903 -5.51422,9.53362 l -10.07786,1.87569 c -0.60659,-1.465688 -1.69634,-5.47392 -3.58709,-5.947589 -2.69409,2.292964 -3.62617,7.287264 -9.06298,11.06401 -10.71811,8.957439 -16.27586,23.967124 -17.44664,36.400021 -0.60434,3.593749 1.28377,5.083628 2.17454,7.084879 4.0042,4.344485 8.87315,4.771188 14.22657,5.328999 12.38671,1.290659 47.91435,-12.996238 52.24452,-27.592179 z"
|
||||
id="path593"
|
||||
sodipodi:nodetypes="cccccccccsc" /><path
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -432.00265,67.318696 c -1.73616,0.139669 -3.50154,0.246026 -4.3977,0.17868 -1.81069,-4.111526 1.52048,-8.953299 4.44978,-8.84645 6.20224,0.226233 4.32785,8.328992 -0.0521,8.66777 z"
|
||||
id="path11890-1-0-3-2"
|
||||
sodipodi:nodetypes="ccscc" /><circle
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.17961;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
id="path16232-2-7"
|
||||
cx="-435.367"
|
||||
cy="65.691788"
|
||||
r="2.1497555"
|
||||
clip-path="url(#clipPath656)"
|
||||
transform="translate(-1.6924549e-5)" /><path
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -412.33041,46.837035 c 3.38872,-4.79066 4.27837,-5.677734 3.5344,-11.123199 -1.28741,-2.525126 -3.55942,-0.0091 -3.5778,0.37371 -0.0793,0.268859 0.62604,5.692511 0.0434,10.749489 z"
|
||||
id="path11888-6-2-9-0"
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:label="path11888-6-2-9"
|
||||
transform="translate(-1.6924549e-5)" /><path
|
||||
style="opacity:1;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m -439.13116,43.583622 c 3.72307,-1.614419 7.3675,-3.483388 11.41474,-4.048403 l -1.36077,-3.617316 c -0.80217,-1.389627 -1.75454,-2.490926 -2.27688,-2.366713 -0.0768,0.01826 -0.1989,0.05969 -1.03532,1.145831 -1.08724,1.411848 -2.19455,3.571613 -3.52156,5.397558 -1.46888,2.021146 -2.98564,3.50213 -3.22021,3.489043 z"
|
||||
id="path26944-3-9"
|
||||
sodipodi:nodetypes="cccsssc"
|
||||
transform="translate(-1.6924549e-5)" /><path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -406.08838,44.108528 -3.38124,5.263692 6.11582,-2.679375 z"
|
||||
id="path13150-5-2-3"
|
||||
sodipodi:nodetypes="cccc" /><g
|
||||
id="g665"
|
||||
inkscape:label="nose"
|
||||
transform="translate(-1.6924549e-5)"
|
||||
style="stroke-width:0.5"><path
|
||||
style="opacity:1;fill:#0073aa;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m -457.87586,83.179939 c 0,0 -1.17624,2.935047 1.46367,5.058495 4.10609,-3.375529 4.34075,-6.079122 3.99985,-6.604356 -0.61922,-0.0073 -3.50156,0.755828 -5.46352,1.545857 z"
|
||||
id="path31425-0-6"
|
||||
sodipodi:nodetypes="ccccc" /><path
|
||||
style="opacity:1;fill:#0073aa;fill-opacity:1;stroke:none;stroke-width:0.887605;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m -454.56334,85.363491 c 0.18842,1.165661 1.1436,2.897617 2.73115,3.885001 -0.0329,0.217466 -0.86381,0.708773 -0.86381,0.708773 0,0 -2.31741,-1.087639 -3.14466,-3.035718"
|
||||
id="path33246-2-0"
|
||||
sodipodi:nodetypes="cccc" /></g></g><g
|
||||
id="g16795-61-6"
|
||||
transform="translate(0,-2.6458334)"
|
||||
inkscape:label="acorn"
|
||||
style="display:inline;stroke-width:0.5"><path
|
||||
style="fill:#0273aa;fill-opacity:1;stroke:none;stroke-width:1.06653;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -398.79932,116.98984 c 2.14816,-3.18843 5.3282,-9.00453 6.86159,-16.2357 -1.68801,-8.452686 -5.41474,-9.528969 -12.67201,-13.175064 l -6.77667,-2.1655 c -0.59411,-4.916754 3.66346,-6.007316 1.95818,-7.562799 -0.60935,-0.60799 -0.85772,-1.152153 -2.92807,-0.747044 -0.68887,2.828557 -1.5891,4.882655 -1.38413,7.856743 -10.88291,-0.324825 -19.86773,1.21596 -23.75555,10.917323 l 0.59462,6.157521 c 0.40774,4.06454 0.90942,8.36039 1.19673,10.7182 2.51007,9.53776 7.2756,12.84946 15.14952,17.9473 10.80658,-1.60382 18.0178,-8.23239 21.75579,-13.71098 z"
|
||||
id="path11886-3-5-8-2"
|
||||
sodipodi:nodetypes="cccccccccccc" /><path
|
||||
style="display:inline;fill:#01557e;fill-opacity:1;stroke:none;stroke-width:0.92604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40;stroke-dasharray:none;paint-order:normal"
|
||||
d="m -437.24943,98.587152 c 15.47059,5.160838 28.853,6.210908 44.45035,5.528858 l -0.33269,1.00136 c -2.84453,8.48885 -7.11438,16.19984 -14.78081,20.77251 -4.22299,2.87641 -8.95937,4.24265 -12.64118,4.80585 4.90186,-1.9411 6.28154,-3.13013 9.44127,-5.62107 7.30314,-5.75735 10.53897,-13.79201 -7.2632,-17.54634 -2.04827,-0.7546 -6.97065,-1.79557 -8.95696,-2.4842 -3.78691,-1.31289 -5.87451,-2.69569 -8.49494,-4.19919 -0.64857,-0.55614 -1.05123,-1.499972 -1.42184,-2.257778 z"
|
||||
id="path14904-79-6"
|
||||
sodipodi:nodetypes="cccccscscc" /><path
|
||||
style="fill:#01557e;fill-opacity:1;stroke-width:0.44886;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40"
|
||||
d="m -412.38484,74.540497 c 1.04648,1.02564 2.03773,1.53722 3.32802,1.217522 0.0656,0.210395 0.0231,0.418915 -0.0326,0.627204 -1.5334,2.409182 -2.45327,4.552421 -2.21329,7.284672 l 0.16083,1.055961 c -0.9361,0.0984 -1.87578,0.258567 -2.4535,-0.468121 0.75107,0.18109 0.90263,0.01389 1.42464,-0.485892 -0.17483,-2.661716 0.30288,-4.511654 1.49152,-6.770675 -0.34469,-1.051553 -1.02053,-1.564634 -1.70561,-2.460671 z"
|
||||
id="path567-1"
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
transform="translate(-1.6924549e-5,2.6458344)" /></g><path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.253293;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40"
|
||||
d="m -425.34578,109.54285 c -4.28572,-3.13939 -8.22729,-6.28553 -13.56434,-7.79875 l -2.74389,-0.63156 -1.54247,5.90823 3.47191,4.95663 c 4.23416,-0.0815 11.40165,1.60574 14.77029,-0.71646 0.83261,-0.45672 0.27535,-1.182 -0.3915,-1.71809 z"
|
||||
id="path2086-8"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
transform="translate(-1.6924549e-5,-2.6458352)"
|
||||
inkscape:label="hand right" /><path
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.44886;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:40"
|
||||
d="m -395.66357,114.61895 c -3.65971,-0.56995 -7.76016,-0.93577 -11.11157,-1.77569 -2.18796,-0.93216 -3.00632,-2.3589 -1.70861,-3.71925 0.37365,-0.39735 5.69577,-2.25753 8.58864,-3.25262 2.85206,-1.18646 5.57388,-2.652 8.32495,-4.05099 l 3.67759,-2.614861 2.4911,6.808421 -2.29694,7.91659 -3.92475,1.47713 z"
|
||||
id="path2472-7"
|
||||
sodipodi:nodetypes="cccccccccc"
|
||||
inkscape:label="hand left" /></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -15,14 +15,23 @@
|
|||
@if ($type === 'password')
|
||||
<div class="relative" x-data="{ type: 'password' }">
|
||||
@if ($allowToPeak)
|
||||
<div x-on:click="changePasswordFieldType"
|
||||
<div x-on:click="changePasswordFieldType; type = type === 'password' ? 'text' : 'password'"
|
||||
class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer dark:hover:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
{{-- Eye icon (shown when password is hidden) --}}
|
||||
<svg x-show="type === 'password'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
||||
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
|
||||
</svg>
|
||||
{{-- Eye-off icon (shown when password is visible) --}}
|
||||
<svg x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
|
||||
<path d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
<input autocomplete="{{ $autocomplete }}" value="{{ $value }}"
|
||||
|
|
|
|||
|
|
@ -500,8 +500,8 @@
|
|||
@endif
|
||||
@endif
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-forms.input placeholder="3000:3000" id="portsMappings" label="Ports Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host."
|
||||
<x-forms.input placeholder="3000:3000" id="portsMappings" label="Port Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Format:</span> host:container<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span> 3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host."
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@endif
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
|
|
|
|||
|
|
@ -45,18 +45,11 @@
|
|||
@if ($repositories->count() > 0)
|
||||
<div class="flex flex-col gap-2 pb-6">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.select class="w-full" label="Repository" wire:model="selected_repository_id">
|
||||
<x-forms.datalist class="w-full" label="Repository" placeholder="Search repositories..." wire:model.live="selected_repository_id">
|
||||
@foreach ($repositories as $repo)
|
||||
@if ($loop->first)
|
||||
<option selected value="{{ data_get($repo, 'id') }}">
|
||||
{{ data_get($repo, 'name') }}
|
||||
</option>
|
||||
@else
|
||||
<option value="{{ data_get($repo, 'id') }}">{{ data_get($repo, 'name') }}
|
||||
</option>
|
||||
@endif
|
||||
<option value="{{ data_get($repo, 'id') }}">{{ data_get($repo, 'name') }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
</x-forms.datalist>
|
||||
</div>
|
||||
<x-forms.button wire:click.prevent="loadBranches"> Load Repository </x-forms.button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
@endcan
|
||||
@else
|
||||
@can('createAnyResource')
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" {{ wireNavigate() }}
|
||||
class="button">+
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
{{ wireNavigate() }} class="button">+
|
||||
New</a>
|
||||
@endcan
|
||||
@can('createAnyResource')
|
||||
|
|
@ -40,16 +40,18 @@
|
|||
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
||||
{{ $project->name }}</a>
|
||||
<button type="button" @click.stop="toggle()" class="px-1 text-warning">
|
||||
<svg class="w-3 h-3 transition-transform" :class="{ 'rotate-90': projectOpen }" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
<svg class="w-3 h-3 transition-transform" :class="{ 'rotate-90': projectOpen }"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div x-show="projectOpen" @click.outside="close()" x-transition:enter="transition ease-out duration-200"
|
||||
<div x-show="projectOpen" @click.outside="close()"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75" x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-20 top-full mt-1 w-56 -ml-2 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
@foreach ($projects as $proj)
|
||||
<a href="{{ route('project.show', ['project_uuid' => $proj->uuid]) }}"
|
||||
|
|
@ -62,9 +64,27 @@
|
|||
</div>
|
||||
</li>
|
||||
@php
|
||||
$allEnvironments = $project->environments()->with(['applications', 'services'])->get();
|
||||
$allEnvironments = $project
|
||||
->environments()
|
||||
->with(['applications', 'services'])
|
||||
->get();
|
||||
@endphp
|
||||
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout); this.envOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.envOpen = false; this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openEnv(id) { clearTimeout(this.closeTimeout); clearTimeout(this.envTimeout); this.activeEnv = id }, closeEnv() { this.envTimeout = setTimeout(() => { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openRes(id) { clearTimeout(this.envTimeout); clearTimeout(this.resTimeout); this.activeRes = id }, closeRes() { this.resTimeout = setTimeout(() => { this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openMenu(id) { clearTimeout(this.resTimeout); clearTimeout(this.menuTimeout); this.activeMenuEnv = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenuEnv = null; }, 100) } }">
|
||||
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null;
|
||||
this.activeRes = null;
|
||||
this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout);
|
||||
this.envOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.envOpen = false;
|
||||
this.activeEnv = null;
|
||||
this.activeRes = null;
|
||||
this.activeMenuEnv = null; }, 100) }, openEnv(id) { clearTimeout(this.closeTimeout);
|
||||
clearTimeout(this.envTimeout);
|
||||
this.activeEnv = id }, closeEnv() { this.envTimeout = setTimeout(() => { this.activeEnv = null;
|
||||
this.activeRes = null;
|
||||
this.activeMenuEnv = null; }, 100) }, openRes(id) { clearTimeout(this.envTimeout);
|
||||
clearTimeout(this.resTimeout);
|
||||
this.activeRes = id }, closeRes() { this.resTimeout = setTimeout(() => { this.activeRes = null;
|
||||
this.activeMenuEnv = null; }, 100) }, openMenu(id) { clearTimeout(this.resTimeout);
|
||||
clearTimeout(this.menuTimeout);
|
||||
this.activeMenuEnv = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenuEnv = null; }, 100) } }">
|
||||
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
||||
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||
href="{{ route('project.resource.index', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => $environment->uuid]) }}">
|
||||
|
|
@ -73,40 +93,61 @@
|
|||
<button type="button" @click.stop="toggle()" class="px-1 text-warning">
|
||||
<svg class="w-3 h-3 transition-transform" :class="{ 'rotate-90': envOpen }" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Environment Dropdown Container -->
|
||||
<div x-show="envOpen" @click.outside="close()" x-transition:enter="transition ease-out duration-200"
|
||||
<div x-show="envOpen" @click.outside="close()"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75" x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-20 top-full mt-1 left-0 sm:left-auto max-w-[calc(100vw-1rem)]" x-init="$nextTick(() => { const rect = $el.getBoundingClientRect(); if (rect.right > window.innerWidth) { $el.style.left = 'auto'; $el.style.right = '0'; } })">
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-20 top-full mt-1 left-0 sm:left-auto max-w-[calc(100vw-1rem)]"
|
||||
x-init="$nextTick(() => { const rect = $el.getBoundingClientRect(); if (rect.right > window.innerWidth) { $el.style.left = 'auto';
|
||||
$el.style.right = '0'; } })">
|
||||
<!-- Environment List -->
|
||||
<div class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
<div
|
||||
class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
@foreach ($allEnvironments as $env)
|
||||
@php
|
||||
$envResources = collect()
|
||||
->merge($env->applications->map(fn($app) => ['type' => 'application', 'resource' => $app]))
|
||||
->merge($env->databases()->map(fn($db) => ['type' => 'database', 'resource' => $db]))
|
||||
->merge($env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]));
|
||||
->merge(
|
||||
$env->applications->map(
|
||||
fn($app) => ['type' => 'application', 'resource' => $app],
|
||||
),
|
||||
)
|
||||
->merge(
|
||||
$env
|
||||
->databases()
|
||||
->map(fn($db) => ['type' => 'database', 'resource' => $db]),
|
||||
)
|
||||
->merge(
|
||||
$env->services->map(
|
||||
fn($svc) => ['type' => 'service', 'resource' => $svc],
|
||||
),
|
||||
);
|
||||
@endphp
|
||||
<div @mouseenter="openEnv('{{ $env->uuid }}'); envPositions['{{ $env->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)" @mouseleave="closeEnv()">
|
||||
<div @mouseenter="openEnv('{{ $env->uuid }}'); envPositions['{{ $env->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||
@mouseleave="closeEnv()">
|
||||
<a href="{{ route('project.resource.index', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => $env->uuid]) }}"
|
||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $env->uuid === $environment->uuid ? 'dark:text-warning font-semibold' : '' }}"
|
||||
title="{{ $env->name }}">
|
||||
<span class="truncate">{{ $env->name }}</span>
|
||||
@if ($envResources->count() > 0)
|
||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="border-t border-neutral-200 dark:border-coolgray-200 mt-1 pt-1">
|
||||
<a href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}" {{ wireNavigate() }}
|
||||
<a href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}"
|
||||
{{ wireNavigate() }}
|
||||
class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
|
|
@ -122,23 +163,35 @@
|
|||
@foreach ($allEnvironments as $env)
|
||||
@php
|
||||
$envResources = collect()
|
||||
->merge($env->applications->map(fn($app) => ['type' => 'application', 'resource' => $app]))
|
||||
->merge($env->databases()->map(fn($db) => ['type' => 'database', 'resource' => $db]))
|
||||
->merge($env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]));
|
||||
->merge(
|
||||
$env->applications->map(
|
||||
fn($app) => ['type' => 'application', 'resource' => $app],
|
||||
),
|
||||
)
|
||||
->merge(
|
||||
$env
|
||||
->databases()
|
||||
->map(fn($db) => ['type' => 'database', 'resource' => $db]),
|
||||
)
|
||||
->merge(
|
||||
$env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]),
|
||||
);
|
||||
@endphp
|
||||
@if ($envResources->count() > 0)
|
||||
<div x-show="activeEnv === '{{ $env->uuid }}'" x-cloak
|
||||
x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||
@mouseenter="openEnv('{{ $env->uuid }}')" @mouseleave="closeEnv()"
|
||||
:style="'position: absolute; left: 100%; top: ' + (envPositions['{{ $env->uuid }}'] || 0) + 'px; z-index: 30;'"
|
||||
:style="'position: absolute; left: 100%; top: ' + (envPositions[
|
||||
'{{ $env->uuid }}'] || 0) + 'px; z-index: 30;'"
|
||||
class="flex flex-col sm:flex-row items-start pl-1">
|
||||
<div class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
<div
|
||||
class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
@foreach ($envResources as $envResource)
|
||||
@php
|
||||
$resType = $envResource['type'];
|
||||
$res = $envResource['resource'];
|
||||
$resRoute = match($resType) {
|
||||
$resRoute = match ($resType) {
|
||||
'application' => route('project.application.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $env->uuid,
|
||||
|
|
@ -155,16 +208,28 @@
|
|||
'database_uuid' => $res->uuid,
|
||||
]),
|
||||
};
|
||||
$resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') && $res->additional_servers()->count() > 0;
|
||||
$resServerName = $resHasMultipleServers ? null : data_get($res, 'destination.server.name');
|
||||
$resHasMultipleServers =
|
||||
$resType === 'application' &&
|
||||
method_exists($res, 'additional_servers') &&
|
||||
$res->additional_servers()->count() > 0;
|
||||
$resServerName = $resHasMultipleServers
|
||||
? null
|
||||
: data_get($res, 'destination.server.name');
|
||||
@endphp
|
||||
<div @mouseenter="openRes('{{ $env->uuid }}-{{ $res->uuid }}'); resPositions['{{ $env->uuid }}-{{ $res->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)" @mouseleave="closeRes()">
|
||||
<div @mouseenter="openRes('{{ $env->uuid }}-{{ $res->uuid }}'); resPositions['{{ $env->uuid }}-{{ $res->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||
@mouseleave="closeRes()">
|
||||
<a href="{{ $resRoute }}"
|
||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200"
|
||||
title="{{ $res->name }}{{ $resServerName ? ' ('.$resServerName.')' : '' }}">
|
||||
<span class="truncate">{{ $res->name }}@if($resServerName) <span class="text-xs text-neutral-400">({{ $resServerName }})</span>@endif</span>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
title="{{ $res->name }}{{ $resServerName ? ' (' . $resServerName . ')' : '' }}">
|
||||
<span class="truncate">{{ $res->name }}@if ($resServerName)
|
||||
<span
|
||||
class="text-xs text-neutral-400">({{ $resServerName }})</span>
|
||||
@endif
|
||||
</span>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -190,112 +255,189 @@
|
|||
$resKey = $env->uuid . '-' . $res->uuid;
|
||||
@endphp
|
||||
<div x-show="activeRes === '{{ $resKey }}'" x-cloak
|
||||
x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@mouseenter="openRes('{{ $resKey }}')" @mouseleave="closeRes()"
|
||||
:style="'position: absolute; left: 100%; top: ' + (resPositions['{{ $resKey }}'] || 0) + 'px; z-index: 40;'"
|
||||
:style="'position: absolute; left: 100%; top: ' + (resPositions[
|
||||
'{{ $resKey }}'] || 0) + 'px; z-index: 40;'"
|
||||
class="flex flex-col sm:flex-row items-start pl-1">
|
||||
<!-- Main Menu List -->
|
||||
<div class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200">
|
||||
<div
|
||||
class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200">
|
||||
@if ($resType === 'application')
|
||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)" @mouseleave="closeMenu()">
|
||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||
@mouseleave="closeMenu()">
|
||||
<a href="{{ route('project.application.configuration', $resParams) }}"
|
||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||
<span>Configuration</span>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="4"
|
||||
d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ route('project.application.deployment.index', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Deployments</a>
|
||||
<a href="{{ route('project.application.logs', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||
<a href="{{ route('project.application.deployment.index', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Deployments</a>
|
||||
<a href="{{ route('project.application.logs', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||
@can('canAccessTerminal')
|
||||
<a href="{{ route('project.application.command', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Terminal</a>
|
||||
<a href="{{ route('project.application.command', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Terminal</a>
|
||||
@endcan
|
||||
@elseif ($resType === 'service')
|
||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)" @mouseleave="closeMenu()">
|
||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||
@mouseleave="closeMenu()">
|
||||
<a href="{{ route('project.service.configuration', $resParams) }}"
|
||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||
<span>Configuration</span>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="4"
|
||||
d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ route('project.service.logs', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||
<a href="{{ route('project.service.logs', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||
@can('canAccessTerminal')
|
||||
<a href="{{ route('project.service.command', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Terminal</a>
|
||||
<a href="{{ route('project.service.command', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Terminal</a>
|
||||
@endcan
|
||||
@else
|
||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)" @mouseleave="closeMenu()">
|
||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||
@mouseleave="closeMenu()">
|
||||
<a href="{{ route('project.database.configuration', $resParams) }}"
|
||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||
<span>Configuration</span>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M9 5l7 7-7 7"></path>
|
||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="4"
|
||||
d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ route('project.database.logs', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||
<a href="{{ route('project.database.logs', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||
@can('canAccessTerminal')
|
||||
<a href="{{ route('project.database.command', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Terminal</a>
|
||||
<a href="{{ route('project.database.command', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Terminal</a>
|
||||
@endcan
|
||||
@if (
|
||||
$res->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||
$res->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$res->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$res->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a href="{{ route('project.database.backup.index', $resParams) }}" class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Backups</a>
|
||||
$res->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$res->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$res->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a href="{{ route('project.database.backup.index', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Backups</a>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Configuration Sub-menu (4th level) -->
|
||||
<div x-show="activeMenuEnv === '{{ $resKey }}-config'" x-cloak
|
||||
x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@mouseenter="openMenu('{{ $resKey }}-config')" @mouseleave="closeMenu()"
|
||||
:style="'position: absolute; left: 100%; top: ' + (menuPositions['{{ $resKey }}-config'] || 0) + 'px; z-index: 50;'"
|
||||
@mouseenter="openMenu('{{ $resKey }}-config')"
|
||||
@mouseleave="closeMenu()"
|
||||
:style="'position: absolute; left: 100%; top: ' + (menuPositions[
|
||||
'{{ $resKey }}-config'] || 0) + 'px; z-index: 50;'"
|
||||
class="pl-1">
|
||||
<div class="w-52 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
@if ($resType === 'application')
|
||||
<a href="{{ route('project.application.configuration', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||
<a href="{{ route('project.application.environment-variables', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment Variables</a>
|
||||
<a href="{{ route('project.application.persistent-storage', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent Storage</a>
|
||||
<a href="{{ route('project.application.source', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Source</a>
|
||||
<a href="{{ route('project.application.servers', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||
<a href="{{ route('project.application.scheduled-tasks.show', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled Tasks</a>
|
||||
<a href="{{ route('project.application.webhooks', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||
<a href="{{ route('project.application.preview-deployments', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Preview Deployments</a>
|
||||
<a href="{{ route('project.application.healthcheck', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Healthcheck</a>
|
||||
<a href="{{ route('project.application.rollback', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Rollback</a>
|
||||
<a href="{{ route('project.application.resource-limits', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource Limits</a>
|
||||
<a href="{{ route('project.application.resource-operations', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource Operations</a>
|
||||
<a href="{{ route('project.application.metrics', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||
<a href="{{ route('project.application.tags', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||
<a href="{{ route('project.application.advanced', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Advanced</a>
|
||||
<a href="{{ route('project.application.danger', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger Zone</a>
|
||||
@elseif ($resType === 'service')
|
||||
<a href="{{ route('project.service.configuration', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||
<a href="{{ route('project.service.environment-variables', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment Variables</a>
|
||||
<a href="{{ route('project.service.storages', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Storages</a>
|
||||
<a href="{{ route('project.service.scheduled-tasks.show', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled Tasks</a>
|
||||
<a href="{{ route('project.service.webhooks', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||
<a href="{{ route('project.service.resource-operations', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource Operations</a>
|
||||
<a href="{{ route('project.service.tags', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||
<a href="{{ route('project.service.danger', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger Zone</a>
|
||||
@else
|
||||
<a href="{{ route('project.database.configuration', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||
<a href="{{ route('project.database.environment-variables', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment Variables</a>
|
||||
<a href="{{ route('project.database.servers', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||
<a href="{{ route('project.database.persistent-storage', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent Storage</a>
|
||||
<a href="{{ route('project.database.webhooks', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||
<a href="{{ route('project.database.resource-limits', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource Limits</a>
|
||||
<a href="{{ route('project.database.resource-operations', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource Operations</a>
|
||||
<a href="{{ route('project.database.metrics', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||
<a href="{{ route('project.database.tags', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||
<a href="{{ route('project.database.danger', $resParams) }}" class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger Zone</a>
|
||||
@endif
|
||||
<div
|
||||
class="w-52 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||
@if ($resType === 'application')
|
||||
<a href="{{ route('project.application.configuration', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||
<a href="{{ route('project.application.environment-variables', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||
Variables</a>
|
||||
<a href="{{ route('project.application.persistent-storage', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
||||
Storage</a>
|
||||
<a href="{{ route('project.application.source', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Source</a>
|
||||
<a href="{{ route('project.application.servers', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||
<a href="{{ route('project.application.scheduled-tasks.show', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
||||
Tasks</a>
|
||||
<a href="{{ route('project.application.webhooks', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||
<a href="{{ route('project.application.preview-deployments', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Preview
|
||||
Deployments</a>
|
||||
<a href="{{ route('project.application.healthcheck', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Healthcheck</a>
|
||||
<a href="{{ route('project.application.rollback', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Rollback</a>
|
||||
<a href="{{ route('project.application.resource-limits', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||
Limits</a>
|
||||
<a href="{{ route('project.application.resource-operations', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||
Operations</a>
|
||||
<a href="{{ route('project.application.metrics', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||
<a href="{{ route('project.application.tags', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||
<a href="{{ route('project.application.advanced', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Advanced</a>
|
||||
<a href="{{ route('project.application.danger', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||
Zone</a>
|
||||
@elseif ($resType === 'service')
|
||||
<a href="{{ route('project.service.configuration', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||
<a href="{{ route('project.service.environment-variables', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||
Variables</a>
|
||||
<a href="{{ route('project.service.storages', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Storages</a>
|
||||
<a href="{{ route('project.service.scheduled-tasks.show', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
||||
Tasks</a>
|
||||
<a href="{{ route('project.service.webhooks', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||
<a href="{{ route('project.service.resource-operations', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||
Operations</a>
|
||||
<a href="{{ route('project.service.tags', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||
<a href="{{ route('project.service.danger', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||
Zone</a>
|
||||
@else
|
||||
<a href="{{ route('project.database.configuration', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||
<a href="{{ route('project.database.environment-variables', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||
Variables</a>
|
||||
<a href="{{ route('project.database.servers', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||
<a href="{{ route('project.database.persistent-storage', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
||||
Storage</a>
|
||||
<a href="{{ route('project.database.webhooks', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||
<a href="{{ route('project.database.resource-limits', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||
Limits</a>
|
||||
<a href="{{ route('project.database.resource-operations', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||
Operations</a>
|
||||
<a href="{{ route('project.database.metrics', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||
<a href="{{ route('project.database.tags', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||
<a href="{{ route('project.database.danger', $resParams) }}"
|
||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||
Zone</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -311,8 +453,8 @@
|
|||
</div>
|
||||
@if ($environment->isEmpty())
|
||||
@can('createAnyResource')
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" {{ wireNavigate() }}
|
||||
class="items-center justify-center coolbox">+ Add Resource</a>
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
{{ wireNavigate() }} class="items-center justify-center coolbox">+ Add Resource</a>
|
||||
@else
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-8 text-center border border-dashed border-neutral-300 dark:border-coolgray-300 rounded-lg">
|
||||
|
|
@ -375,6 +517,8 @@
|
|||
</div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||
<div class="max-w-full px-4 pt-1 truncate box-description">Server: <span
|
||||
x-text="item.destination?.server?.name || 'Unknown'"></span></div>
|
||||
<template x-if="item.server_status == false">
|
||||
<div class="px-4 text-xs font-bold text-error">Server is unreachable or
|
||||
misconfigured
|
||||
|
|
@ -425,6 +569,8 @@
|
|||
</div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||
<div class="max-w-full px-4 pt-1 truncate box-description">Server: <span
|
||||
x-text="item.destination?.server?.name || 'Unknown'"></span></div>
|
||||
<template x-if="item.server_status == false">
|
||||
<div class="px-4 text-xs font-bold text-error">Server is unreachable or
|
||||
misconfigured
|
||||
|
|
@ -475,6 +621,8 @@
|
|||
</div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||
<div class="max-w-full px-4 pt-1 truncate box-description">Server: <span
|
||||
x-text="item.destination?.server?.name || 'Unknown'"></span></div>
|
||||
<template x-if="item.server_status == false">
|
||||
<div class="px-4 text-xs font-bold text-error">Server is unreachable or
|
||||
misconfigured
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@
|
|||
// Parse timestamp from log line (ISO 8601 format: 2025-12-04T11:48:39.136764033Z)
|
||||
$timestamp = '';
|
||||
$logContent = $line;
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2}:\d{2})(?:\.(\d+))?Z?\s*(.*)$/', $line, $matches)) {
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2}:\d{2})(?:\.(\d+))?Z?\s(.*)$/', $line, $matches)) {
|
||||
$year = $matches[1];
|
||||
$month = $matches[2];
|
||||
$day = $matches[3];
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)->values(),
|
||||
),
|
||||
projects: @js(
|
||||
$projects->map(
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ if [ -z "$LATEST_REALTIME_VERSION" ]; then
|
|||
fi
|
||||
|
||||
case "$OS_TYPE" in
|
||||
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine) ;;
|
||||
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine | postmarketos) ;;
|
||||
*)
|
||||
echo "This script only supports Debian, Redhat, Arch Linux, Alpine Linux, or SLES based operating systems for now."
|
||||
exit
|
||||
|
|
@ -370,7 +370,7 @@ else
|
|||
arch)
|
||||
pacman -Sy --noconfirm --needed curl wget git jq openssl >/dev/null || true
|
||||
;;
|
||||
alpine)
|
||||
alpine | postmarketos)
|
||||
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
|
||||
apk update >/dev/null
|
||||
apk add curl wget git jq openssl >/dev/null
|
||||
|
|
@ -437,7 +437,7 @@ if [ "$SSH_DETECTED" = "false" ]; then
|
|||
systemctl enable sshd >/dev/null 2>&1
|
||||
systemctl start sshd >/dev/null 2>&1
|
||||
;;
|
||||
alpine)
|
||||
alpine | postmarketos)
|
||||
apk add openssh >/dev/null
|
||||
rc-update add sshd default >/dev/null 2>&1
|
||||
service sshd start >/dev/null 2>&1
|
||||
|
|
@ -558,7 +558,7 @@ if ! [ -x "$(command -v docker)" ]; then
|
|||
systemctl start docker >/dev/null 2>&1
|
||||
systemctl enable docker >/dev/null 2>&1
|
||||
;;
|
||||
"alpine")
|
||||
"alpine" | "postmarketos")
|
||||
apk add docker docker-cli-compose >/dev/null 2>&1
|
||||
rc-update add docker default >/dev/null 2>&1
|
||||
service docker start >/dev/null 2>&1
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
services:
|
||||
activepieces:
|
||||
image: "ghcr.io/activepieces/activepieces:0.21.0" # Released on March 13 2024
|
||||
image: ghcr.io/activepieces/activepieces:0.75.0
|
||||
environment:
|
||||
- SERVICE_URL_ACTIVEPIECES
|
||||
- AP_API_KEY=$SERVICE_PASSWORD_64_APIKEY
|
||||
|
|
@ -40,7 +40,7 @@ services:
|
|||
timeout: 20s
|
||||
retries: 10
|
||||
postgres:
|
||||
image: 'postgres:14.4'
|
||||
image: postgres:18-alpine
|
||||
environment:
|
||||
- POSTGRES_DB=${POSTGRES_DB:-activepieces}
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
|
||||
|
|
@ -54,7 +54,7 @@ services:
|
|||
timeout: 20s
|
||||
retries: 10
|
||||
redis:
|
||||
image: 'redis:7.0.7'
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- "redis_data:/data"
|
||||
healthcheck:
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
|
||||
services:
|
||||
authentik-server:
|
||||
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.4}
|
||||
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.10.3}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
- SERVICE_URL_AUTHENTIKSERVER_9000
|
||||
- AUTHENTIK_REDIS__HOST=${REDIS_HOST:-redis}
|
||||
- AUTHENTIK_POSTGRESQL__HOST=${POSTGRES_HOST:-postgresql}
|
||||
- AUTHENTIK_POSTGRESQL__USER=${SERVICE_USER_POSTGRESQL}
|
||||
- AUTHENTIK_POSTGRESQL__NAME=${POSTGRES_DB:-authentik}
|
||||
|
|
@ -33,14 +32,11 @@ services:
|
|||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
authentik-worker:
|
||||
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.4}
|
||||
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.10.3}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
- AUTHENTIK_REDIS__HOST=${REDIS_HOST:-redis}
|
||||
- AUTHENTIK_POSTGRESQL__HOST=${POSTGRES_HOST:-postgresql}
|
||||
- AUTHENTIK_POSTGRESQL__USER=${SERVICE_USER_POSTGRESQL}
|
||||
- AUTHENTIK_POSTGRESQL__NAME=${POSTGRES_DB:-authentik}
|
||||
|
|
@ -70,8 +66,6 @@ services:
|
|||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
postgresql:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
|
|
@ -86,14 +80,3 @@ services:
|
|||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
|
||||
- POSTGRES_DB=authentik
|
||||
redis:
|
||||
image: redis:alpine
|
||||
command: --save 60 1 --loglevel warning
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
volumes:
|
||||
- redis:/data
|
||||
|
|
|
|||
59
templates/compose/autobase.yaml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# documentation: https://autobase.tech/docs/
|
||||
# slogan: Autobase for PostgreSQL® is an open-source alternative to cloud-managed databases (self-hosted DBaaS).
|
||||
# tags: database, postgres, automation, self-hosted, dbaas
|
||||
# logo: svgs/autobase.svg
|
||||
# port: 80
|
||||
|
||||
services:
|
||||
autobase:
|
||||
image: autobase/console_ui:2.4.1
|
||||
platform: linux/amd64
|
||||
environment:
|
||||
- SERVICE_FQDN_AUTOBASE_80
|
||||
- PG_CONSOLE_AUTHORIZATION_TOKEN=${SERVICE_PASSWORD_UI}
|
||||
- PG_CONSOLE_API_HOST=autobase-api
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "http://localhost:80/" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
depends_on:
|
||||
autobase-api:
|
||||
condition: service_healthy
|
||||
|
||||
autobase-db:
|
||||
image: autobase/console_db:2.4.1
|
||||
platform: linux/amd64
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
|
||||
volumes:
|
||||
- autobase-db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
autobase-api:
|
||||
image: autobase/console_api:2.4.1
|
||||
platform: linux/amd64
|
||||
environment:
|
||||
- PG_CONSOLE_DB_HOST=autobase-db
|
||||
- PG_CONSOLE_DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
|
||||
- PG_CONSOLE_AUTHORIZATION_TOKEN=${SERVICE_PASSWORD_UI}
|
||||
- PG_CONSOLE_ENCRYPTIONKEY=${SERVICE_BASE64_ENCRYPTIONKEY}
|
||||
- PG_CONSOLE_LOGGER_LEVEL=${PG_CONSOLE_LOGGER_LEVEL:-info}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /tmp/ansible:/tmp/ansible
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS",
|
||||
"-H", "accept: application/json",
|
||||
"-H", "Authorization: Bearer ${SERVICE_PASSWORD_UI}",
|
||||
"http://localhost:8080/api/v1/version"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
depends_on:
|
||||
autobase-db:
|
||||
condition: service_healthy
|
||||
48
templates/compose/booklore.yaml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# documentation: https://booklore.org/docs/getting-started
|
||||
# slogan: Booklore is an open-source library management system for your digital book collection.
|
||||
# tags: media, books, kobo, epub, ebook, KOreader
|
||||
# logo: svgs/booklore.svg
|
||||
# port: 80
|
||||
|
||||
services:
|
||||
booklore:
|
||||
image: booklore/booklore:v1.16.5
|
||||
environment:
|
||||
- SERVICE_URL_BOOKLORE_80
|
||||
- USER_ID=${BOOKLORE_USER_ID:-0}
|
||||
- GROUP_ID=${BOOKLORE_GROUP_ID:-0}
|
||||
- TZ=${TZ:-UTC}
|
||||
- DATABASE_URL=jdbc:mariadb://mariadb:3306/${MARIADB_DATABASE:-booklore-db}
|
||||
- DATABASE_USERNAME=${SERVICE_USER_MARIADB}
|
||||
- DATABASE_PASSWORD=${SERVICE_PASSWORD_MARIADB}
|
||||
- BOOKLORE_PORT=80
|
||||
volumes:
|
||||
- booklore-data:/app/data
|
||||
- booklore-books:/books
|
||||
- type: bind
|
||||
source: ~/booklore
|
||||
target: /bookdrop
|
||||
is_directory: true
|
||||
healthcheck:
|
||||
test: "wget --no-verbose --tries=1 --spider http://localhost/login || exit 1"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
|
||||
mariadb:
|
||||
image: mariadb:12
|
||||
environment:
|
||||
- MARIADB_USER=${SERVICE_USER_MARIADB}
|
||||
- MARIADB_PASSWORD=${SERVICE_PASSWORD_MARIADB}
|
||||
- MARIADB_ROOT_PASSWORD=${SERVICE_PASSWORD_MARIADBROOT}
|
||||
- MARIADB_DATABASE=${MARIADB_DATABASE:-booklore-db}
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
|
@ -43,8 +43,8 @@ services:
|
|||
- SELF_HOSTED=1
|
||||
- PORT=4003
|
||||
- CLUSTER_PORT=10000
|
||||
- API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASE
|
||||
- JWT_SECRET=$SERVICE_BASE64_64_BUDIBASE
|
||||
- API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASEAPI
|
||||
- JWT_SECRET=$SERVICE_BASE64_64_BUDIBASEJWT
|
||||
- MINIO_ACCESS_KEY=$SERVICE_USER_MINIO
|
||||
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_MINIO
|
||||
- MINIO_URL=http://minio-service:9000
|
||||
|
|
|
|||
45
templates/compose/calibre-web-automated-book-downloader.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# documentation: https://github.com/calibrain/calibre-web-automated-book-downloader
|
||||
# slogan: An intuitive web interface for searching and requesting book downloads, designed to work seamlessly with Calibre-Web-Automated.
|
||||
# tags: calibre,calibre-web,ebook,library,epub,ereader,kindle,book,reader,download,downloader
|
||||
# logo: svgs/calibre-web-automated-with-downloader.png
|
||||
# port: 8083
|
||||
|
||||
services:
|
||||
calibre-web-automated:
|
||||
image: crocodilestick/calibre-web-automated:latest
|
||||
environment:
|
||||
- SERVICE_URL_CWA_8083
|
||||
- PUID=${PUID:-1000}
|
||||
- PGID=${PGID:-1000}
|
||||
- TZ=${TZ:-UTC}
|
||||
- NETWORK_SHARE_MODE=${NETWORK_SHARE_MODE:-false}
|
||||
volumes:
|
||||
- cwa-config:/config
|
||||
- cwa-book-ingest:/cwa-book-ingest
|
||||
- calibre-library:/calibre-library
|
||||
- calibre-plugins:/config/.config/calibre/plugins
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- 'curl -fs http://localhost:8083 || exit 1'
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
calibre-web-downloader:
|
||||
image: ghcr.io/calibrain/calibre-web-automated-book-downloader:latest
|
||||
environment:
|
||||
- SERVICE_URL_DOWNLOADER_8084
|
||||
- FLASK_PORT=${FLASK_PORT:-8084}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- BOOK_LANGUAGE=${BOOK_LANGUAGE:-en}
|
||||
- USE_BOOK_TITLE=${USE_BOOK_TITLE:-true}
|
||||
- TZ=${TZ:-America/New_York}
|
||||
- APP_ENV=${APP_ENV:-prod}
|
||||
- UID=${UID:-1000}
|
||||
- GID=${GID:-100}
|
||||
- CWA_DB_PATH=${CWA_DB_PATH:-/cwa-config/app.db}
|
||||
volumes:
|
||||
- cwa-book-ingest:/cwa-book-ingest
|
||||
- cwa-config:/cwa-config
|
||||
57
templates/compose/cloudreve.yaml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# documentation: https://docs.cloudreve.org/
|
||||
# slogan: A self-hosted file management and sharing system.
|
||||
# category: storage
|
||||
# tags: file sharing,cloud storage,self-hosted,open-source
|
||||
# logo: svgs/cloudreve.svg
|
||||
# port: 5212
|
||||
|
||||
services:
|
||||
cloudreve:
|
||||
image: cloudreve/cloudreve:4.10.1
|
||||
environment:
|
||||
- SERVICE_URL_CLOUDREVE_5212
|
||||
- CR_CONF_Database.Type=postgres
|
||||
- CR_CONF_Database.Host=postgres
|
||||
- CR_CONF_Database.User=${SERVICE_USER_POSTGRES}
|
||||
- CR_CONF_Database.Password=${SERVICE_PASSWORD_POSTGRES}
|
||||
- CR_CONF_Database.Name=${POSTGRES_DB:-cloudreve-db}
|
||||
- CR_CONF_Database.Port=5432
|
||||
- CR_CONF_Redis.Server=redis:6379
|
||||
- CR_CONF_Redis.Password=${SERVICE_PASSWORD_REDIS}
|
||||
volumes:
|
||||
- cloudreve-data:/cloudreve/data
|
||||
healthcheck:
|
||||
test: ["CMD", "nc", "-z", "localhost", "5212"]
|
||||
interval: 20s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-cloudreve-db}
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${SERVICE_PASSWORD_REDIS}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# documentation: https://docs.docker.com/registry/
|
||||
# documentation: https://distribution.github.io/distribution/
|
||||
# slogan: The Docker Registry lets you distribute Docker images.
|
||||
# category: devtools
|
||||
# tags: registry,images,docker
|
||||
|
|
@ -7,20 +7,35 @@
|
|||
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
image: registry:3
|
||||
environment:
|
||||
- SERVICE_URL_REGISTRY_5000
|
||||
- USERNAME=${SERVICE_USER_REGISTRY}
|
||||
- PASSWORD=${SERVICE_PASSWORD_REGISTRY}
|
||||
- REGISTRY_AUTH=htpasswd
|
||||
- REGISTRY_AUTH_HTPASSWD_REALM=Registry
|
||||
- REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
|
||||
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.password
|
||||
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/data
|
||||
command: ["/entrypoint.sh"]
|
||||
volumes:
|
||||
- registry-data:/var/lib/registry
|
||||
- type: bind
|
||||
source: ./auth/registry.password
|
||||
target: /auth/registry.password
|
||||
isDirectory: false
|
||||
content: >-
|
||||
testuser:$2y$05$/o2JvmI2bhExXIt6Oqxa7ekYB7v3scj1wFEf6tBslJvJOMoPQL.Gy
|
||||
source: ./etc/entrypoint.sh
|
||||
target: /entrypoint.sh
|
||||
mode: "0755"
|
||||
content: |
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
if [ -z "$USERNAME" ] || [ -z "$PASSWORD" ]; then
|
||||
echo "Error: USERNAME and PASSWORD environment variables must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
apk add --no-cache apache2-utils
|
||||
mkdir -p "$(dirname "$REGISTRY_AUTH_HTPASSWD_PATH")"
|
||||
chmod 755 "$(dirname "$REGISTRY_AUTH_HTPASSWD_PATH")"
|
||||
htpasswd -Bbc "$REGISTRY_AUTH_HTPASSWD_PATH" "$USERNAME" "$PASSWORD"
|
||||
registry serve /etc/docker/registry/config.yml
|
||||
- type: bind
|
||||
source: ./config/config.yml
|
||||
target: /etc/docker/registry/config.yml
|
||||
|
|
@ -45,7 +60,13 @@ services:
|
|||
enabled: true
|
||||
interval: 10s
|
||||
threshold: 3
|
||||
- type: bind
|
||||
source: ./data
|
||||
target: /data
|
||||
isDirectory: true
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wget
|
||||
- "-q"
|
||||
- "--spider"
|
||||
- "http://localhost:5000/"
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
|
|
|
|||
19
templates/compose/esphome.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# documentation: https://esphome.io/guides/getting_started_command_line/
|
||||
# slogan: Smart Home Made Simple.
|
||||
# category: automation
|
||||
# tags: home, smart, assistant, microcontroller
|
||||
# logo: svgs/esphome.svg
|
||||
# port: 6052
|
||||
|
||||
services:
|
||||
esphome:
|
||||
image: ghcr.io/esphome/esphome:2025.12.4
|
||||
volumes:
|
||||
- esp_config:/config
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
privileged: true
|
||||
network_mode: host
|
||||
environment:
|
||||
- SERVICE_URL_ESPHOME_6052
|
||||
- USERNAME=${SERVICE_USER_ADMIN}
|
||||
- PASSWORD=${SERVICE_PASSWORD_ADMIN}
|
||||
|
|
@ -5,17 +5,12 @@
|
|||
# logo: svgs/evolution-api.svg
|
||||
# port: 8080
|
||||
|
||||
version: '3.8'
|
||||
services:
|
||||
api:
|
||||
image: 'evoapicloud/evolution-api:latest' # Change to specific version if needed.
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
- postgres
|
||||
image: evoapicloud/evolution-api:v2.3.7
|
||||
environment:
|
||||
- SERVICE_URL_EVO_8080
|
||||
- SERVER_URL=$SERVICE_URL_EVO
|
||||
- SERVER_URL=${SERVICE_URL_EVO}
|
||||
- DB_TYPE=${DB_TYPE:-postgresdb}
|
||||
- 'DB_POSTGRESDB_DATABASE=${POSTGRES_DB:-postgres}'
|
||||
- DB_POSTGRESDB_HOST=${DB_POSTGRESDB_HOST:-postgres}
|
||||
|
|
@ -137,15 +132,38 @@ services:
|
|||
volumes:
|
||||
- 'evolution_instances:/evolution/instances'
|
||||
expose:
|
||||
- 8080
|
||||
- "8080"
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- >
|
||||
node -e "require('http')
|
||||
.get('http://127.0.0.1:8080/',r=>process.exit(r.statusCode>=200&&r.statusCode<300?0:1))
|
||||
.on('error',()=>process.exit(1))"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 60s
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
redis:
|
||||
image: 'redis:latest'
|
||||
command: "redis-server --port 6379 --appendonly yes\n"
|
||||
restart: always
|
||||
image: redis:7-alpine
|
||||
command: redis-server --port 6379 --appendonly yes
|
||||
volumes:
|
||||
- 'evolution_redis:/data'
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-h", "127.0.0.1", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
start_period: 5s
|
||||
|
||||
postgres:
|
||||
image: 'postgres:16-alpine'
|
||||
image: postgres:16-alpine
|
||||
command:
|
||||
- postgres
|
||||
- '-c'
|
||||
|
|
@ -155,10 +173,13 @@ services:
|
|||
- 'POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}'
|
||||
- 'POSTGRES_DB=${POSTGRES_DB:-postgres}'
|
||||
- 'AUTHENTICATION_API_KEY=${SERVICE_PASSWORD_AUTHENTICATIONAPIKEY}'
|
||||
restart: always
|
||||
volumes:
|
||||
- 'postgres_data:/var/lib/postgresql/data'
|
||||
volumes:
|
||||
evolution_instances: null
|
||||
evolution_redis: null
|
||||
postgres_data: null
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} -h 127.0.0.1'
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ services:
|
|||
- POSTGRES_DB=${POSTGRESQL_DATABASE:-freshrss}
|
||||
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||
- POSTGRES_HOST=postgresql
|
||||
- POSTGRES_HOST=freshrss-db
|
||||
volumes:
|
||||
- freshrss-data:/var/www/FreshRSS/data
|
||||
- freshrss-extensions:/var/www/FreshRSS/extensions
|
||||
|
|
|
|||
113
templates/compose/hatchet.yaml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# documentation: https://docs.hatchet.run/self-hosting/docker-compose
|
||||
# slogan: Hatchet is a high-throughput, low-latency computing service. It's built on an open-source, fault-tolerant queue, allowing work to be delivered as fast as your system can handle
|
||||
# tags: ai-agents,background-tasks,data-pipelines,scheduling
|
||||
# logo: svgs/hatchet.svg
|
||||
# port: 80
|
||||
|
||||
services:
|
||||
hatchet-dashboard:
|
||||
image: ghcr.io/hatchet-dev/hatchet/hatchet-dashboard:latest
|
||||
command: sh ./entrypoint.sh --config /hatchet/config
|
||||
environment:
|
||||
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgres:5432/$POSTGRES_DB
|
||||
- SERVICE_URL_HATCHET_80
|
||||
# Default credentials are "admin@example.com" and "Admin123!!"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
setup-config:
|
||||
condition: service_completed_successfully
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- hatchet-certs:/hatchet/certs
|
||||
- hatchet-config:/hatchet/config
|
||||
|
||||
hatchet-engine:
|
||||
image: ghcr.io/hatchet-dev/hatchet/hatchet-engine:latest
|
||||
command: /hatchet/hatchet-engine --config /hatchet/config
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
setup-config:
|
||||
condition: service_completed_successfully
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgres:5432/$POSTGRES_DB
|
||||
- SERVER_GRPC_BIND_ADDRESS=${SERVER_GRPC_BIND_ADDRESS:-0.0.0.0}
|
||||
- SERVER_GRPC_INSECURE=${SERVER_GRPC_INSECURE:-t}
|
||||
volumes:
|
||||
- hatchet-certs:/hatchet/certs
|
||||
- hatchet-config:/hatchet/config
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
volumes:
|
||||
- postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=$SERVICE_USER_POSTGRES
|
||||
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||
- POSTGRES_DB=${POSTGRES_DB:-hatchet}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-management
|
||||
hostname: "rabbitmq"
|
||||
environment:
|
||||
- SERVICE_URL_RABBITMQ_15672
|
||||
- RABBITMQ_DEFAULT_USER=$SERVICE_USER_RABBITMQ
|
||||
- RABBITMQ_DEFAULT_PASS=$SERVICE_PASSWORD_RABBITMQ
|
||||
- PORT=${RABBITMQ_PORT:-5672}
|
||||
healthcheck:
|
||||
test: rabbitmq-diagnostics -q ping
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 10
|
||||
volumes:
|
||||
- rabbitmq-data:/var/lib/rabbitmq/
|
||||
|
||||
migration:
|
||||
image: ghcr.io/hatchet-dev/hatchet/hatchet-migrate:latest
|
||||
command: /hatchet/hatchet-migrate
|
||||
restart: no
|
||||
exclude_from_hc: true
|
||||
environment:
|
||||
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgres:5432/$POSTGRES_DB
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
setup-config:
|
||||
image: ghcr.io/hatchet-dev/hatchet/hatchet-admin:latest
|
||||
command: /hatchet/hatchet-admin quickstart --skip certs --generated-config-dir /hatchet/config --overwrite=false
|
||||
restart: no
|
||||
environment:
|
||||
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgres:5432/$POSTGRES_DB
|
||||
- SERVER_TASKQUEUE_RABBITMQ_URL=amqp://$SERVICE_USER_RABBITMQ:$SERVICE_PASSWORD_RABBITMQ@rabbitmq:5672/
|
||||
- SERVER_AUTH_COOKIE_DOMAIN=${SERVER_AUTH_COOKIE_DOMAIN:-localhost:8080}
|
||||
- SERVER_AUTH_COOKIE_INSECURE=${SERVER_AUTH_COOKIE_INSECURE:-t}
|
||||
- SERVER_GRPC_BIND_ADDRESS=${SERVER_GRPC_BIND_ADDRESS:-0.0.0.0}
|
||||
- SERVER_GRPC_INSECURE=${SERVER_GRPC_INSECURE:-t}
|
||||
- SERVER_GRPC_BROADCAST_ADDRESS=${SERVER_GRPC_BROADCAST_ADDRESS:-localhost:7077}
|
||||
- SERVER_DEFAULT_ENGINE_VERSION=V1
|
||||
- SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS=${SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS:-hatchet-engine:7077}
|
||||
volumes:
|
||||
- hatchet_certs:/hatchet/certs
|
||||
- hatchet_config:/hatchet/config
|
||||
depends_on:
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
|
@ -12,46 +12,14 @@ services:
|
|||
- SERVICE_URL_HOPPSCOTCH_80
|
||||
- VITE_ALLOWED_AUTH_PROVIDERS=${VITE_ALLOWED_AUTH_PROVIDERS:-GOOGLE,GITHUB,MICROSOFT,EMAIL}
|
||||
- DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@hoppscotch-db:5432/${POSTGRES_DB}
|
||||
- JWT_SECRET=${SERVICE_PASSWORD_JWT}
|
||||
- TOKEN_SALT_COMPLEXITY=${TOKEN_SALT_COMPLEXITY:-10}
|
||||
- MAGIC_LINK_TOKEN_VALIDITY=${MAGIC_LINK_TOKEN_VALIDITY:-3}
|
||||
- REFRESH_TOKEN_VALIDITY=${REFRESH_TOKEN_VALIDITY:-604800000}
|
||||
- ACCESS_TOKEN_VALIDITY=${ACCESS_TOKEN_VALIDITY:-86400000}
|
||||
- SESSION_SECRET=${SERVICE_PASSWORD_SECRET}
|
||||
- ALLOW_SECURE_COOKIES=${ALLOW_SECURE_COOKIES:-true}
|
||||
- DATA_ENCRYPTION_KEY=${DATA_ENCRYPTION_KEY:-mustbeexactry32characterlikethat}
|
||||
- REDIRECT_URL=${SERVICE_URL_HOPPSCOTCH}
|
||||
- DATA_ENCRYPTION_KEY=${SERVICE_BASE64_DATAENCRYPTIONKEY}
|
||||
- WHITELISTED_ORIGINS=${SERVICE_URL_HOPPSCOTCH}/backend,${SERVICE_URL_HOPPSCOTCH},${SERVICE_URL_HOPPSCOTCH}/admin
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-*****}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-*****}
|
||||
- GOOGLE_CALLBACK_URL=${SERVICE_URL_HOPPSCOTCH}/backend/v1/auth/google/callback
|
||||
- GOOGLE_SCOPE=email,profile
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-*****}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-*****}
|
||||
- GITHUB_CALLBACK_URL=${SERVICE_URL_HOPPSCOTCH}/backend/v1/auth/github/callback
|
||||
- GITHUB_SCOPE=user:email
|
||||
- MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID:-*****}
|
||||
- MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET:-*****}
|
||||
- MICROSOFT_CALLBACK_URL=${SERVICE_URL_HOPPSCOTCH}/backend/v1/auth/microsoft/callback
|
||||
- MICROSOFT_SCOPE=user.read
|
||||
- MICROSOFT_TENANT=common
|
||||
- MAILER_SMTP_ENABLE=${MAILER_SMTP_ENABLE:-false}
|
||||
- MAILER_USE_CUSTOM_CONFIGS=${MAILER_USE_CUSTOM_CONFIGS:-true}
|
||||
- MAILER_ADDRESS_FROM=${MAILER_ADDRESS_FROM:-user@example.com}
|
||||
- MAILER_SMTP_URL=${MAILER_SMTP_URL:-smtps_url}
|
||||
- MAILER_SMTP_HOST=${MAILER_SMTP_HOST:-smtp.example.com}
|
||||
- MAILER_SMTP_PORT=${MAILER_SMTP_PORT:-465}
|
||||
- MAILER_SMTP_SECURE=${MAILER_SMTP_SECURE:-true}
|
||||
- MAILER_SMTP_USER=${MAILER_SMTP_USER:-user@example.com}
|
||||
- MAILER_SMTP_PASSWORD=${MAILER_SMTP_PASSWORD:-mailpass}
|
||||
- MAILER_TLS_REJECT_UNAUTHORIZED=${MAILER_TLS_REJECT_UNAUTHORIZED:-false}
|
||||
- RATE_LIMIT_TTL=${RATE_LIMIT_TTL:-60}
|
||||
- RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100}
|
||||
- VITE_BASE_URL=${SERVICE_URL_HOPPSCOTCH}
|
||||
- VITE_SHORTCODE_BASE_URL=${SERVICE_URL_HOPPSCOTCH}
|
||||
- VITE_ADMIN_URL=${SERVICE_URL_HOPPSCOTCH}/admin
|
||||
- VITE_BACKEND_GQL_URL=${SERVICE_URL_HOPPSCOTCH}/backend/graphql
|
||||
- VITE_BACKEND_WS_URL=wss://${SERVICE_URL_HOPPSCOTCH}/backend/graphql
|
||||
- VITE_BACKEND_WS_URL=wss://${SERVICE_FQDN_HOPPSCOTCH}/backend/graphql
|
||||
- VITE_BACKEND_API_URL=${SERVICE_URL_HOPPSCOTCH}/backend/v1
|
||||
- VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms
|
||||
- VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy
|
||||
|
|
|
|||