Merge branch 'next' into feat-forgejo
This commit is contained in:
16
app/Actions/Application/LoadComposeFile.php
Normal file
16
app/Actions/Application/LoadComposeFile.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class LoadComposeFile
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$application->loadComposeFile();
|
||||||
|
}
|
||||||
|
}
|
29
app/Actions/Database/RestartDatabase.php
Normal file
29
app/Actions/Database/RestartDatabase.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
StopDatabase::run($database);
|
||||||
|
|
||||||
|
return StartDatabase::run($database);
|
||||||
|
}
|
||||||
|
}
|
57
app/Actions/Database/StartDatabase.php
Normal file
57
app/Actions/Database/StartDatabase.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
switch ($database->getMorphClass()) {
|
||||||
|
case 'App\Models\StandalonePostgresql':
|
||||||
|
$activity = StartPostgresql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneRedis':
|
||||||
|
$activity = StartRedis::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMongodb':
|
||||||
|
$activity = StartMongodb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMysql':
|
||||||
|
$activity = StartMysql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMariadb':
|
||||||
|
$activity = StartMariadb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneKeydb':
|
||||||
|
$activity = StartKeydb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneDragonfly':
|
||||||
|
$activity = StartDragonfly::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneClickhouse':
|
||||||
|
$activity = StartClickhouse::run($database);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($database->is_public && $database->public_port) {
|
||||||
|
StartDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
@@ -29,7 +29,5 @@ class StopDatabase
|
|||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::run($database);
|
StopDatabaseProxy::run($database);
|
||||||
}
|
}
|
||||||
// TODO: make notification for services
|
|
||||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,6 @@ class StopDatabaseProxy
|
|||||||
$server = data_get($database, 'service.server');
|
$server = data_get($database, 'service.server');
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
|
||||||
$database->save();
|
$database->save();
|
||||||
DatabaseStatusChanged::dispatch();
|
DatabaseStatusChanged::dispatch();
|
||||||
}
|
}
|
||||||
|
@@ -11,11 +11,11 @@ class StartProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
|
18
app/Actions/Service/RestartService.php
Normal file
18
app/Actions/Service/RestartService.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
StopService::run($service);
|
||||||
|
|
||||||
|
return StartService::run($service);
|
||||||
|
}
|
||||||
|
}
|
@@ -5,7 +5,7 @@ namespace App\Console\Commands;
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class CloudCleanupSubs extends Command
|
class CloudCleanupSubscriptions extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cloud:cleanup-subs';
|
protected $signature = 'cloud:cleanup-subs';
|
||||||
|
|
||||||
|
@@ -9,13 +9,41 @@ use Illuminate\Support\Facades\Process;
|
|||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'dev:init';
|
protected $signature = 'dev {--init} {--generate-openapi}';
|
||||||
|
|
||||||
protected $description = 'Init the app in dev mode';
|
protected $description = 'Helper commands for development.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('init')) {
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->option('generate-openapi')) {
|
||||||
|
$this->generateOpenApi();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateOpenApi()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
{
|
{
|
||||||
// Generate APP_KEY if not exists
|
// Generate APP_KEY if not exists
|
||||||
|
|
||||||
if (empty(env('APP_KEY'))) {
|
if (empty(env('APP_KEY'))) {
|
||||||
echo "Generating APP_KEY.\n";
|
echo "Generating APP_KEY.\n";
|
||||||
Artisan::call('key:generate');
|
Artisan::call('key:generate');
|
||||||
|
11
app/Enums/BuildPackTypes.php
Normal file
11
app/Enums/BuildPackTypes.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum BuildPackTypes: string
|
||||||
|
{
|
||||||
|
case NIXPACKS = 'nixpacks';
|
||||||
|
case STATIC = 'static';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
}
|
15
app/Enums/NewDatabaseTypes.php
Normal file
15
app/Enums/NewDatabaseTypes.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewDatabaseTypes: string
|
||||||
|
{
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
22
app/Enums/NewResourceTypes.php
Normal file
22
app/Enums/NewResourceTypes.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewResourceTypes: string
|
||||||
|
{
|
||||||
|
case PUBLIC = 'public';
|
||||||
|
case PRIVATE_GH_APP = 'private-gh-app';
|
||||||
|
case PRIVATE_DEPLOY_KEY = 'private-deploy-key';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
case DOCKER_IMAGE = 'docker-image';
|
||||||
|
case SERVICE = 'service';
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
@@ -12,7 +12,7 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $userId;
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
@@ -20,15 +20,19 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
|||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception('User id is null');
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
|
if ($this->userId) {
|
||||||
return [
|
return [
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $userId;
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
@@ -20,15 +20,19 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception('User id is null');
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
|
if ($this->userId) {
|
||||||
return [
|
return [
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,671 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
|
||||||
use App\Enums\RedirectTypes;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Jobs\DeleteResourceJob;
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\EnvironmentVariable;
|
|
||||||
use App\Models\Project;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Applications extends Controller
|
|
||||||
{
|
|
||||||
public function applications(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
|
||||||
$applications = collect();
|
|
||||||
$applications->push($projects->pluck('applications')->flatten());
|
|
||||||
$applications = $applications->flatten();
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($applications));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function application_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($application));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
$cleanup = $request->query->get('cleanup') ?? false;
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->collect()->count() == 0) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Invalid request.',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
DeleteResourceJob::dispatch($application, $cleanup);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Application deletion request queued.',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->collect()->count() == 0) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Invalid request.',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$server = $application->destination->server;
|
|
||||||
$allowedFields = ['name', 'description', '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', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'redirect'];
|
|
||||||
|
|
||||||
$validator = customApiValidator($request->all(), [
|
|
||||||
'name' => 'string|max:255',
|
|
||||||
'description' => 'string|nullable',
|
|
||||||
'domains' => 'string',
|
|
||||||
'git_repository' => 'string',
|
|
||||||
'git_branch' => 'string',
|
|
||||||
'git_commit_sha' => 'string',
|
|
||||||
'docker_registry_image_name' => 'string|nullable',
|
|
||||||
'docker_registry_image_tag' => 'string|nullable',
|
|
||||||
'build_pack' => 'string',
|
|
||||||
'static_image' => 'string',
|
|
||||||
'install_command' => 'string|nullable',
|
|
||||||
'build_command' => 'string|nullable',
|
|
||||||
'start_command' => 'string|nullable',
|
|
||||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/',
|
|
||||||
'ports_mappings' => 'string|regex:/^(\d+:\d+)(,\d+:\d+)*$/|nullable',
|
|
||||||
'base_directory' => 'string|nullable',
|
|
||||||
'publish_directory' => 'string|nullable',
|
|
||||||
'health_check_enabled' => 'boolean',
|
|
||||||
'health_check_path' => 'string',
|
|
||||||
'health_check_port' => 'string|nullable',
|
|
||||||
'health_check_host' => 'string',
|
|
||||||
'health_check_method' => 'string',
|
|
||||||
'health_check_return_code' => 'numeric',
|
|
||||||
'health_check_scheme' => 'string',
|
|
||||||
'health_check_response_text' => 'string|nullable',
|
|
||||||
'health_check_interval' => 'numeric',
|
|
||||||
'health_check_timeout' => 'numeric',
|
|
||||||
'health_check_retries' => 'numeric',
|
|
||||||
'health_check_start_period' => 'numeric',
|
|
||||||
'limits_memory' => 'string',
|
|
||||||
'limits_memory_swap' => 'string',
|
|
||||||
'limits_memory_swappiness' => 'numeric',
|
|
||||||
'limits_memory_reservation' => 'string',
|
|
||||||
'limits_cpus' => 'string',
|
|
||||||
'limits_cpuset' => 'string|nullable',
|
|
||||||
'limits_cpu_shares' => 'numeric',
|
|
||||||
'custom_labels' => 'string|nullable',
|
|
||||||
'custom_docker_run_options' => 'string|nullable',
|
|
||||||
'post_deployment_command' => 'string|nullable',
|
|
||||||
'post_deployment_command_container' => 'string',
|
|
||||||
'pre_deployment_command' => 'string|nullable',
|
|
||||||
'pre_deployment_command_container' => 'string',
|
|
||||||
'watch_paths' => 'string|nullable',
|
|
||||||
'manual_webhook_secret_github' => 'string|nullable',
|
|
||||||
'manual_webhook_secret_gitlab' => 'string|nullable',
|
|
||||||
'manual_webhook_secret_bitbucket' => 'string|nullable',
|
|
||||||
'manual_webhook_secret_gitea' => 'string|nullable',
|
|
||||||
'docker_compose_location' => 'string',
|
|
||||||
'docker_compose' => 'string|nullable',
|
|
||||||
'docker_compose_raw' => 'string|nullable',
|
|
||||||
// 'docker_compose_domains' => 'string|nullable', // must be like: "{\"api\":{\"domain\":\"http:\\/\\/b8sos8k.127.0.0.1.sslip.io\"}}"
|
|
||||||
'docker_compose_custom_start_command' => 'string|nullable',
|
|
||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
|
||||||
'redirect' => Rule::enum(RedirectTypes::class),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Validate ports_exposes
|
|
||||||
if ($request->has('ports_exposes')) {
|
|
||||||
$ports = explode(',', $request->ports_exposes);
|
|
||||||
foreach ($ports as $port) {
|
|
||||||
if (! is_numeric($port)) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => [
|
|
||||||
'ports_exposes' => 'The ports_exposes should be a comma separated list of numbers.',
|
|
||||||
],
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Validate ports_mappings
|
|
||||||
if ($request->has('ports_mappings')) {
|
|
||||||
$ports = [];
|
|
||||||
foreach (explode(',', $request->ports_mappings) as $portMapping) {
|
|
||||||
$port = explode(':', $portMapping);
|
|
||||||
if (in_array($port[0], $ports)) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => [
|
|
||||||
'ports_mappings' => 'The first number before : should be unique between mappings.',
|
|
||||||
],
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
$ports[] = $port[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Validate custom_labels
|
|
||||||
if ($request->has('custom_labels')) {
|
|
||||||
if (! isBase64Encoded($request->custom_labels)) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => [
|
|
||||||
'custom_labels' => 'The custom_labels should be base64 encoded.',
|
|
||||||
],
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
$customLabels = base64_decode($request->custom_labels);
|
|
||||||
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => [
|
|
||||||
'custom_labels' => 'The custom_labels should be base64 encoded.',
|
|
||||||
],
|
|
||||||
], 422);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
|
||||||
if ($validator->fails() || ! empty($extraFields)) {
|
|
||||||
$errors = $validator->errors();
|
|
||||||
if (! empty($extraFields)) {
|
|
||||||
foreach ($extraFields as $field) {
|
|
||||||
$errors->add($field, 'This field is not allowed.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => $errors,
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
|
||||||
$fqdn = $request->domains;
|
|
||||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
|
||||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
|
||||||
$errors = [];
|
|
||||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
|
||||||
if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
|
||||||
$errors[] = 'Invalid domain: '.$domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str($domain)->trim()->lower();
|
|
||||||
});
|
|
||||||
if (count($errors) > 0) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => $errors,
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
$fqdn = $fqdn->unique()->implode(',');
|
|
||||||
$application->fqdn = $fqdn;
|
|
||||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
|
||||||
$application->custom_labels = base64_encode($customLabels);
|
|
||||||
$request->offsetUnset('domains');
|
|
||||||
}
|
|
||||||
$application->fill($request->all());
|
|
||||||
$application->save();
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($application));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function envs_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$envs = $application->environment_variables->sortBy('id')->merge($application->environment_variables_preview->sortBy('id'));
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($envs));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update_env_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$validator = customApiValidator($request->all(), [
|
|
||||||
'key' => 'string|required',
|
|
||||||
'value' => 'string|nullable',
|
|
||||||
'is_preview' => 'boolean',
|
|
||||||
'is_build_time' => 'boolean',
|
|
||||||
'is_literal' => 'boolean',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
|
||||||
if ($validator->fails() || ! empty($extraFields)) {
|
|
||||||
$errors = $validator->errors();
|
|
||||||
if (! empty($extraFields)) {
|
|
||||||
foreach ($extraFields as $field) {
|
|
||||||
$errors->add($field, 'This field is not allowed.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => $errors,
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
$is_preview = $request->is_preview ?? false;
|
|
||||||
$is_build_time = $request->is_build_time ?? false;
|
|
||||||
$is_literal = $request->is_literal ?? false;
|
|
||||||
if ($is_preview) {
|
|
||||||
$env = $application->environment_variables_preview->where('key', $request->key)->first();
|
|
||||||
if ($env) {
|
|
||||||
$env->value = $request->value;
|
|
||||||
if ($env->is_build_time != $is_build_time) {
|
|
||||||
$env->is_build_time = $is_build_time;
|
|
||||||
}
|
|
||||||
if ($env->is_literal != $is_literal) {
|
|
||||||
$env->is_literal = $is_literal;
|
|
||||||
}
|
|
||||||
if ($env->is_preview != $is_preview) {
|
|
||||||
$env->is_preview = $is_preview;
|
|
||||||
}
|
|
||||||
$env->save();
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($env));
|
|
||||||
} else {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable not found.',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables->where('key', $request->key)->first();
|
|
||||||
if ($env) {
|
|
||||||
$env->value = $request->value;
|
|
||||||
if ($env->is_build_time != $is_build_time) {
|
|
||||||
$env->is_build_time = $is_build_time;
|
|
||||||
}
|
|
||||||
if ($env->is_literal != $is_literal) {
|
|
||||||
$env->is_literal = $is_literal;
|
|
||||||
}
|
|
||||||
if ($env->is_preview != $is_preview) {
|
|
||||||
$env->is_preview = $is_preview;
|
|
||||||
}
|
|
||||||
$env->save();
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($env));
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable not found.',
|
|
||||||
], 404);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Something went wrong.',
|
|
||||||
], 500);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create_bulk_envs(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$bulk_data = $request->get('data');
|
|
||||||
if (! $bulk_data) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Bulk data is required.',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
$bulk_data = collect($bulk_data)->map(function ($item) {
|
|
||||||
return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
|
|
||||||
});
|
|
||||||
foreach ($bulk_data as $item) {
|
|
||||||
$validator = customApiValidator($item, [
|
|
||||||
'key' => 'string|required',
|
|
||||||
'value' => 'string|nullable',
|
|
||||||
'is_preview' => 'boolean',
|
|
||||||
'is_build_time' => 'boolean',
|
|
||||||
'is_literal' => 'boolean',
|
|
||||||
]);
|
|
||||||
if ($validator->fails()) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => $validator->errors(),
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
$is_preview = $item->get('is_preview') ?? false;
|
|
||||||
$is_build_time = $item->get('is_build_time') ?? false;
|
|
||||||
$is_literal = $item->get('is_literal') ?? false;
|
|
||||||
if ($is_preview) {
|
|
||||||
$env = $application->environment_variables_preview->where('key', $item->get('key'))->first();
|
|
||||||
if ($env) {
|
|
||||||
$env->value = $item->get('value');
|
|
||||||
if ($env->is_build_time != $is_build_time) {
|
|
||||||
$env->is_build_time = $is_build_time;
|
|
||||||
}
|
|
||||||
if ($env->is_literal != $is_literal) {
|
|
||||||
$env->is_literal = $is_literal;
|
|
||||||
}
|
|
||||||
$env->save();
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables()->create([
|
|
||||||
'key' => $item->get('key'),
|
|
||||||
'value' => $item->get('value'),
|
|
||||||
'is_preview' => $is_preview,
|
|
||||||
'is_build_time' => $is_build_time,
|
|
||||||
'is_literal' => $is_literal,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables->where('key', $item->get('key'))->first();
|
|
||||||
if ($env) {
|
|
||||||
$env->value = $item->get('value');
|
|
||||||
if ($env->is_build_time != $is_build_time) {
|
|
||||||
$env->is_build_time = $is_build_time;
|
|
||||||
}
|
|
||||||
if ($env->is_literal != $is_literal) {
|
|
||||||
$env->is_literal = $is_literal;
|
|
||||||
}
|
|
||||||
$env->save();
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables()->create([
|
|
||||||
'key' => $item->get('key'),
|
|
||||||
'value' => $item->get('value'),
|
|
||||||
'is_preview' => $is_preview,
|
|
||||||
'is_build_time' => $is_build_time,
|
|
||||||
'is_literal' => $is_literal,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environments updated.',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create_env(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$validator = customApiValidator($request->all(), [
|
|
||||||
'key' => 'string|required',
|
|
||||||
'value' => 'string|nullable',
|
|
||||||
'is_preview' => 'boolean',
|
|
||||||
'is_build_time' => 'boolean',
|
|
||||||
'is_literal' => 'boolean',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
|
||||||
if ($validator->fails() || ! empty($extraFields)) {
|
|
||||||
$errors = $validator->errors();
|
|
||||||
if (! empty($extraFields)) {
|
|
||||||
foreach ($extraFields as $field) {
|
|
||||||
$errors->add($field, 'This field is not allowed.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Validation failed.',
|
|
||||||
'errors' => $errors,
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
$is_preview = $request->is_preview ?? false;
|
|
||||||
if ($is_preview) {
|
|
||||||
$env = $application->environment_variables_preview->where('key', $request->key)->first();
|
|
||||||
if ($env) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
|
||||||
], 409);
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables()->create([
|
|
||||||
'key' => $request->key,
|
|
||||||
'value' => $request->value,
|
|
||||||
'is_preview' => $request->is_preview ?? false,
|
|
||||||
'is_build_time' => $request->is_build_time ?? false,
|
|
||||||
'is_literal' => $request->is_literal ?? false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($env))->setStatusCode(201);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables->where('key', $request->key)->first();
|
|
||||||
if ($env) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
|
||||||
], 409);
|
|
||||||
} else {
|
|
||||||
$env = $application->environment_variables()->create([
|
|
||||||
'key' => $request->key,
|
|
||||||
'value' => $request->value,
|
|
||||||
'is_preview' => $request->is_preview ?? false,
|
|
||||||
'is_build_time' => $request->is_build_time ?? false,
|
|
||||||
'is_literal' => $request->is_literal ?? false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($env))->setStatusCode(201);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Something went wrong.',
|
|
||||||
], 500);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete_env_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
ray()->clearAll();
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found.',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first();
|
|
||||||
if (! $found_env) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Environment variable not found.',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
$found_env->delete();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Environment variable deleted.',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action_deploy(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$force = $request->query->get('force') ?? false;
|
|
||||||
$instant_deploy = $request->query->get('instant_deploy') ?? false;
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $application,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
force_rebuild: $force,
|
|
||||||
is_api: true,
|
|
||||||
no_questions_asked: $instant_deploy
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json(
|
|
||||||
[
|
|
||||||
'message' => 'Deployment request queued.',
|
|
||||||
'deployment_uuid' => $deployment_uuid->toString(),
|
|
||||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
|
||||||
],
|
|
||||||
200
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action_stop(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
$sync = $request->query->get('sync') ?? false;
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
if ($sync) {
|
|
||||||
StopApplication::run($application);
|
|
||||||
|
|
||||||
return response()->json(['message' => 'Stopped the application.'], 200);
|
|
||||||
} else {
|
|
||||||
StopApplication::dispatch($application);
|
|
||||||
|
|
||||||
return response()->json(['message' => 'Stopping request queued.'], 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action_restart(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $application,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
restart_only: true,
|
|
||||||
is_api: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json(
|
|
||||||
[
|
|
||||||
'message' => 'Restart request queued.',
|
|
||||||
'deployment_uuid' => $deployment_uuid->toString(),
|
|
||||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
|
||||||
],
|
|
||||||
200
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
2539
app/Http/Controllers/Api/ApplicationsController.php
Normal file
2539
app/Http/Controllers/Api/ApplicationsController.php
Normal file
File diff suppressed because it is too large
Load Diff
1804
app/Http/Controllers/Api/DatabasesController.php
Normal file
1804
app/Http/Controllers/Api/DatabasesController.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,233 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Actions\Database\StartClickhouse;
|
|
||||||
use App\Actions\Database\StartDragonfly;
|
|
||||||
use App\Actions\Database\StartKeydb;
|
|
||||||
use App\Actions\Database\StartMariadb;
|
|
||||||
use App\Actions\Database\StartMongodb;
|
|
||||||
use App\Actions\Database\StartMysql;
|
|
||||||
use App\Actions\Database\StartPostgresql;
|
|
||||||
use App\Actions\Database\StartRedis;
|
|
||||||
use App\Actions\Service\StartService;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Deploy extends Controller
|
|
||||||
{
|
|
||||||
public function deployments(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$servers = Server::whereTeamId($teamId)->get();
|
|
||||||
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
|
|
||||||
'id',
|
|
||||||
'application_id',
|
|
||||||
'application_name',
|
|
||||||
'deployment_url',
|
|
||||||
'pull_request_id',
|
|
||||||
'server_name',
|
|
||||||
'server_id',
|
|
||||||
'status',
|
|
||||||
])->sortBy('id')->toArray();
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($deployments_per_server), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deployment_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['message' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
|
|
||||||
if (! $deployment) {
|
|
||||||
return response()->json(['message' => 'Deployment not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($deployment->makeHidden('logs')), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deploy(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
$uuids = $request->query->get('uuid');
|
|
||||||
$tags = $request->query->get('tag');
|
|
||||||
$force = $request->query->get('force') ?? false;
|
|
||||||
|
|
||||||
if ($uuids && $tags) {
|
|
||||||
return response()->json(['message' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
if ($tags) {
|
|
||||||
return $this->by_tags($tags, $teamId, $force);
|
|
||||||
} elseif ($uuids) {
|
|
||||||
return $this->by_uuids($uuids, $teamId, $force);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['message' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
|
||||||
{
|
|
||||||
$uuids = explode(',', $uuid);
|
|
||||||
$uuids = collect(array_filter($uuids));
|
|
||||||
|
|
||||||
if (count($uuids) === 0) {
|
|
||||||
return response()->json(['message' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$deployments = collect();
|
|
||||||
$payload = collect();
|
|
||||||
foreach ($uuids as $uuid) {
|
|
||||||
$resource = getResourceByUuid($uuid, $teamId);
|
|
||||||
if ($resource) {
|
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
|
||||||
if ($deployment_uuid) {
|
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
|
||||||
} else {
|
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($deployments->count() > 0) {
|
|
||||||
$payload->put('deployments', $deployments->toArray());
|
|
||||||
|
|
||||||
return response()->json($payload->toArray(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['message' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function by_tags(string $tags, int $team_id, bool $force = false)
|
|
||||||
{
|
|
||||||
$tags = explode(',', $tags);
|
|
||||||
$tags = collect(array_filter($tags));
|
|
||||||
|
|
||||||
if (count($tags) === 0) {
|
|
||||||
return response()->json(['message' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$message = collect([]);
|
|
||||||
$deployments = collect();
|
|
||||||
$payload = collect();
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
|
||||||
if (! $found_tag) {
|
|
||||||
// $message->push("Tag {$tag} not found.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$applications = $found_tag->applications()->get();
|
|
||||||
$services = $found_tag->services()->get();
|
|
||||||
if ($applications->count() === 0 && $services->count() === 0) {
|
|
||||||
$message->push("No resources found for tag {$tag}.");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($applications as $resource) {
|
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
|
||||||
if ($deployment_uuid) {
|
|
||||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
|
||||||
}
|
|
||||||
$message = $message->merge($return_message);
|
|
||||||
}
|
|
||||||
foreach ($services as $resource) {
|
|
||||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
|
||||||
$message = $message->merge($return_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($message->count() > 0) {
|
|
||||||
$payload->put('message', $message->toArray());
|
|
||||||
if ($deployments->count() > 0) {
|
|
||||||
$payload->put('details', $deployments->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($payload->toArray(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['message' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deploy_resource($resource, bool $force = false): array
|
|
||||||
{
|
|
||||||
$message = null;
|
|
||||||
$deployment_uuid = null;
|
|
||||||
if (gettype($resource) !== 'object') {
|
|
||||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
|
||||||
}
|
|
||||||
$type = $resource?->getMorphClass();
|
|
||||||
if ($type === 'App\Models\Application') {
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $resource,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
force_rebuild: $force,
|
|
||||||
);
|
|
||||||
$message = "Application {$resource->name} deployment queued.";
|
|
||||||
} elseif ($type === 'App\Models\StandalonePostgresql') {
|
|
||||||
StartPostgresql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneRedis') {
|
|
||||||
StartRedis::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneKeydb') {
|
|
||||||
StartKeydb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneDragonfly') {
|
|
||||||
StartDragonfly::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneClickhouse') {
|
|
||||||
StartClickhouse::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMongodb') {
|
|
||||||
StartMongodb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMysql') {
|
|
||||||
StartMysql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
|
||||||
StartMariadb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\Service') {
|
|
||||||
StartService::run($resource);
|
|
||||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
|
||||||
}
|
|
||||||
}
|
|
317
app/Http/Controllers/Api/DeployController.php
Normal file
317
app/Http/Controllers/Api/DeployController.php
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class DeployController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($deployment)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployment->makeHidden([
|
||||||
|
'logs',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List currently running deployments',
|
||||||
|
path: '/deployments',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all currently running deployments.',
|
||||||
|
content: [
|
||||||
|
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployments(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = Server::whereTeamId($teamId)->get();
|
||||||
|
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
|
||||||
|
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
|
||||||
|
return $this->removeSensitiveData($deployment);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($deployments_per_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
path: '/deployments/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/ApplicationDeploymentQueue',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployment_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
|
||||||
|
if (! $deployment) {
|
||||||
|
return response()->json(['message' => 'Deployment not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($deployment));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Deploy',
|
||||||
|
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||||
|
path: '/deploy',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||||
|
],
|
||||||
|
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment(s) Uuid\'s',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'deployments' => new OA\Property(
|
||||||
|
property: 'deployments',
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string'],
|
||||||
|
'resource_uuid' => ['type' => 'string'],
|
||||||
|
'deployment_uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} elseif ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
|
{
|
||||||
|
$uuids = explode(',', $uuid);
|
||||||
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
|
if (count($uuids) === 0) {
|
||||||
|
return response()->json(['message' => 'No UUIDs provided.'], 400);
|
||||||
|
}
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
} else {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('deployments', $deployments->toArray());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
|
{
|
||||||
|
$tags = explode(',', $tags);
|
||||||
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
|
if (count($tags) === 0) {
|
||||||
|
return response()->json(['message' => 'No TAGs provided.'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
|
if (! $found_tag) {
|
||||||
|
// $message->push("Tag {$tag} not found.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$applications = $found_tag->applications()->get();
|
||||||
|
$services = $found_tag->services()->get();
|
||||||
|
if ($applications->count() === 0 && $services->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($applications as $resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
}
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
$payload->put('message', $message->toArray());
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('details', $deployments->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deploy_resource($resource, bool $force = false): array
|
||||||
|
{
|
||||||
|
$message = null;
|
||||||
|
$deployment_uuid = null;
|
||||||
|
if (gettype($resource) !== 'object') {
|
||||||
|
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
switch ($resource?->getMorphClass()) {
|
||||||
|
case 'App\Models\Application':
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
|
break;
|
||||||
|
case 'App\Models\Service':
|
||||||
|
StartService::run($resource);
|
||||||
|
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Database resource
|
||||||
|
StartDatabase::dispatch($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message = "Database {$resource->name} started.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
}
|
@@ -6,33 +6,29 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class EnvironmentVariables extends Controller
|
class EnvironmentVariablesController extends Controller
|
||||||
{
|
{
|
||||||
public function delete_env_by_uuid(Request $request)
|
public function delete_env_by_uuid(Request $request)
|
||||||
{
|
{
|
||||||
ray()->clearAll();
|
$teamId = getTeamIdFromToken();
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalid_token();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
||||||
if (! $env) {
|
if (! $env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
|
||||||
'message' => 'Environment variable not found.',
|
'message' => 'Environment variable not found.',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
||||||
if (! $found_app) {
|
if (! $found_app) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
|
||||||
'message' => 'Environment variable not found.',
|
'message' => 'Environment variable not found.',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$env->delete();
|
$env->delete();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
|
||||||
'message' => 'Environment variable deleted.',
|
'message' => 'Environment variable deleted.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
51
app/Http/Controllers/Api/OpenApi.php
Normal file
51
app/Http/Controllers/Api/OpenApi.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Info(title: 'Coolify', version: '0.1')]
|
||||||
|
#[OA\Server(url: 'https://app.coolify.io/api/v1')]
|
||||||
|
#[OA\SecurityScheme(
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
securityScheme: 'bearerAuth',
|
||||||
|
description: 'Go to `Keys & Tokens` / `API tokens` and create a new token. Use the token as the bearer token.')]
|
||||||
|
#[OA\Components(
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
description: 'Invalid token.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Invalid token.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
description: 'Unauthenticated.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Unauthenticated.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Resource not found.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Resource not found.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
class OpenApi
|
||||||
|
{
|
||||||
|
// This class is used to generate OpenAPI documentation
|
||||||
|
// for the Coolify API. It is not a controller and does
|
||||||
|
// not contain any routes. It is used to define the
|
||||||
|
// OpenAPI metadata and security scheme for the API.
|
||||||
|
}
|
184
app/Http/Controllers/Api/OtherController.php
Normal file
184
app/Http/Controllers/Api/OtherController.php
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class OtherController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Version',
|
||||||
|
description: 'Get Coolify version.',
|
||||||
|
path: '/version',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Returns the version of the application',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'v4.0.0',
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function version(Request $request)
|
||||||
|
{
|
||||||
|
return response(config('version'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Enable API',
|
||||||
|
description: 'Enable API (only with root permissions).',
|
||||||
|
path: '/enable',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Enable API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'API enabled.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'You are not allowed to enable the API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to enable the API.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function enable_api(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($teamId !== '0') {
|
||||||
|
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$settings->update(['is_api_enabled' => true]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'API enabled.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Disable API',
|
||||||
|
description: 'Disable API (only with root permissions).',
|
||||||
|
path: '/disable',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Disable API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'API disabled.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'You are not allowed to disable the API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to disable the API.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function disable_api(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($teamId !== '0') {
|
||||||
|
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$settings->update(['is_api_enabled' => false]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'API disabled.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feedback(Request $request)
|
||||||
|
{
|
||||||
|
$content = $request->input('content');
|
||||||
|
$webhook_url = config('coolify.feedback_discord_webhook');
|
||||||
|
if ($webhook_url) {
|
||||||
|
Http::post($webhook_url, [
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Feedback sent.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Healthcheck',
|
||||||
|
description: 'Healthcheck endpoint.',
|
||||||
|
path: '/healthcheck',
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Healthcheck endpoint.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'OK',
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function healthcheck(Request $request)
|
||||||
|
{
|
||||||
|
return 'OK';
|
||||||
|
}
|
||||||
|
}
|
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Project as ModelsProject;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Project extends Controller
|
|
||||||
{
|
|
||||||
public function projects(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
|
||||||
|
|
||||||
return response()->json($projects);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function project_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
|
||||||
|
|
||||||
return response()->json($project);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function environment_details(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
|
||||||
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
|
||||||
|
|
||||||
return response()->json($environment);
|
|
||||||
}
|
|
||||||
}
|
|
147
app/Http/Controllers/Api/ProjectController.php
Normal file
147
app/Http/Controllers/Api/ProjectController.php
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Project;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ProjectController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'list projects.',
|
||||||
|
path: '/projects',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all projects.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Project')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function projects(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($projects),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get project by Uuid.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project details',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Project')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Project not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function project_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($project),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Environment',
|
||||||
|
description: 'Get environment by name.',
|
||||||
|
path: '/projects/{uuid}/{environment_name}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project details',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function environment_details(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
$environment = $project->environments()->whereName(request()->environment_name)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($environment));
|
||||||
|
}
|
||||||
|
}
|
@@ -5,14 +5,42 @@ namespace App\Http\Controllers\Api;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
class Resources extends Controller
|
class ResourcesController extends Controller
|
||||||
{
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'Get all resources.',
|
||||||
|
path: '/resources',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Resources'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all resources',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'Content is very complex. Will be implemented later.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function resources(Request $request)
|
public function resources(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = get_team_id_from_token();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalid_token();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
$resources = collect();
|
$resources = collect();
|
||||||
@@ -34,6 +62,6 @@ class Resources extends Controller
|
|||||||
return $payload;
|
return $payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json($resources);
|
return response()->json(serializeApiResponse($resources));
|
||||||
}
|
}
|
||||||
}
|
}
|
372
app/Http/Controllers/Api/SecurityController.php
Normal file
372
app/Http/Controllers/Api/SecurityController.php
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class SecurityController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($team)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
$team->makeHidden([
|
||||||
|
'private_key',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all private keys.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all private keys.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function keys(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$keys = PrivateKey::where('team_id', $teamId)->get();
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get key by UUID.',
|
||||||
|
path: '/security/keys/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all private keys.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Private Key not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function key_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
|
||||||
|
if (is_null($key)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create a new private key.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: [
|
||||||
|
'application/json' => new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['private_key'],
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'The created private key\'s UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_key(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|max:255',
|
||||||
|
'private_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (! $request->name) {
|
||||||
|
$request->offsetSet('name', generate_random_name());
|
||||||
|
}
|
||||||
|
if (! $request->description) {
|
||||||
|
$request->offsetSet('description', 'Created by Coolify via API');
|
||||||
|
}
|
||||||
|
$key = PrivateKey::create([
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'private_key' => $request->private_key,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse([
|
||||||
|
'uuid' => $key->uuid,
|
||||||
|
]))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update a private key.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: [
|
||||||
|
'application/json' => new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['private_key'],
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'The updated private key\'s UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_key(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'private_key'];
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|max:255',
|
||||||
|
'private_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (is_null($foundKey)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$foundKey->update($request->all());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse([
|
||||||
|
'uuid' => $foundKey->uuid,
|
||||||
|
]))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete a private key.',
|
||||||
|
path: '/security/keys/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Private Key deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Private Key deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Private Key not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_key(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (is_null($key)) {
|
||||||
|
return response()->json(['message' => 'Private Key not found.'], 404);
|
||||||
|
}
|
||||||
|
$key->forceDelete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key deleted.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,167 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Project;
|
|
||||||
use App\Models\Server as ModelsServer;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Server extends Controller
|
|
||||||
{
|
|
||||||
public function servers(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
|
||||||
$server['is_reachable'] = $server->settings->is_reachable;
|
|
||||||
$server['is_usable'] = $server->settings->is_usable;
|
|
||||||
|
|
||||||
return $server;
|
|
||||||
});
|
|
||||||
|
|
||||||
return response()->json($servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function server_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$with_resources = $request->query('resources');
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
|
||||||
if (is_null($server)) {
|
|
||||||
return response()->json(['message' => 'Server not found.'], 404);
|
|
||||||
}
|
|
||||||
if ($with_resources) {
|
|
||||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
|
||||||
$payload = [
|
|
||||||
'id' => $resource->id,
|
|
||||||
'uuid' => $resource->uuid,
|
|
||||||
'name' => $resource->name,
|
|
||||||
'type' => $resource->type(),
|
|
||||||
'created_at' => $resource->created_at,
|
|
||||||
'updated_at' => $resource->updated_at,
|
|
||||||
];
|
|
||||||
if ($resource->type() === 'service') {
|
|
||||||
$payload['status'] = $resource->status();
|
|
||||||
} else {
|
|
||||||
$payload['status'] = $resource->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $payload;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$server->load(['settings']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($server);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get_domains_by_server(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->query->get('uuid');
|
|
||||||
if ($uuid) {
|
|
||||||
$domains = Application::getDomainsByUuid($uuid);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'uuid' => $uuid,
|
|
||||||
'domains' => $domains,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
|
||||||
$domains = collect();
|
|
||||||
$applications = $projects->pluck('applications')->flatten();
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if ($applications->count() > 0) {
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
$ip = $application->destination->server->ip;
|
|
||||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
|
||||||
});
|
|
||||||
if ($ip === 'host.docker.internal') {
|
|
||||||
if ($settings->public_ipv4) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv6,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$services = $projects->pluck('services')->flatten();
|
|
||||||
if ($services->count() > 0) {
|
|
||||||
foreach ($services as $service) {
|
|
||||||
$service_applications = $service->applications;
|
|
||||||
if ($service_applications->count() > 0) {
|
|
||||||
foreach ($service_applications as $application) {
|
|
||||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
|
||||||
});
|
|
||||||
if ($ip === 'host.docker.internal') {
|
|
||||||
if ($settings->public_ipv4) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv6,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
|
||||||
return $domain->pluck('domain')->flatten();
|
|
||||||
})->map(function ($domain, $ip) {
|
|
||||||
return [
|
|
||||||
'ip' => $ip,
|
|
||||||
'domains' => $domain,
|
|
||||||
];
|
|
||||||
})->values();
|
|
||||||
|
|
||||||
return response()->json($domains);
|
|
||||||
}
|
|
||||||
}
|
|
396
app/Http/Controllers/Api/ServersController.php
Normal file
396
app/Http/Controllers/Api/ServersController.php
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server as ModelsServer;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
class ServersController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveDataFromSettings($settings)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($settings);
|
||||||
|
}
|
||||||
|
$settings = $settings->makeHidden([
|
||||||
|
'metrics_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeSensitiveData($server)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$server->makeHidden([
|
||||||
|
'id',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializeApiResponse($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all servers.',
|
||||||
|
path: '/servers',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all servers.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function servers(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||||
|
$server['is_reachable'] = $server->settings->is_reachable;
|
||||||
|
$server['is_usable'] = $server->settings->is_usable;
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
});
|
||||||
|
$servers = $servers->map(function ($server) {
|
||||||
|
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
data_set($server, 'settings', $settings);
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get server by UUID.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get server by UUID',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/Server'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function server_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$with_resources = $request->query('resources');
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($with_resources) {
|
||||||
|
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||||
|
$payload = [
|
||||||
|
'id' => $resource->id,
|
||||||
|
'uuid' => $resource->uuid,
|
||||||
|
'name' => $resource->name,
|
||||||
|
'type' => $resource->type(),
|
||||||
|
'created_at' => $resource->created_at,
|
||||||
|
'updated_at' => $resource->updated_at,
|
||||||
|
];
|
||||||
|
if ($resource->type() === 'service') {
|
||||||
|
$payload['status'] = $resource->status();
|
||||||
|
} else {
|
||||||
|
$payload['status'] = $resource->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$server->load(['settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
data_set($server, 'settings', $settings);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($server));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Resources',
|
||||||
|
description: 'Get resources by server.',
|
||||||
|
path: '/servers/{uuid}/resources',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get resources by server',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'type' => ['type' => 'string'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
'status' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function resources_by_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||||
|
$payload = [
|
||||||
|
'id' => $resource->id,
|
||||||
|
'uuid' => $resource->uuid,
|
||||||
|
'name' => $resource->name,
|
||||||
|
'type' => $resource->type(),
|
||||||
|
'created_at' => $resource->created_at,
|
||||||
|
'updated_at' => $resource->updated_at,
|
||||||
|
];
|
||||||
|
if ($resource->type() === 'service') {
|
||||||
|
$payload['status'] = $resource->status();
|
||||||
|
} else {
|
||||||
|
$payload['status'] = $resource->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
});
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
ray($server);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse(data_get($server, 'resources')));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Domains',
|
||||||
|
description: 'Get domains by server.',
|
||||||
|
path: '/servers/{uuid}/domains',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get domains by server',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'ip' => ['type' => 'string'],
|
||||||
|
'domains' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function domains_by_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->get('uuid');
|
||||||
|
if ($uuid) {
|
||||||
|
$domains = Application::getDomainsByUuid($uuid);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($domains));
|
||||||
|
}
|
||||||
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
|
$domains = collect();
|
||||||
|
$applications = $projects->pluck('applications')->flatten();
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($applications->count() > 0) {
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$ip = $application->destination->server->ip;
|
||||||
|
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||||
|
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||||
|
|
||||||
|
return str(str($f[0])->explode(':')[0]);
|
||||||
|
})->filter(function (Stringable $fqdn) {
|
||||||
|
return $fqdn->isNotEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($ip === 'host.docker.internal') {
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv6,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$services = $projects->pluck('services')->flatten();
|
||||||
|
if ($services->count() > 0) {
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service_applications = $service->applications;
|
||||||
|
if ($service_applications->count() > 0) {
|
||||||
|
foreach ($service_applications as $application) {
|
||||||
|
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||||
|
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||||
|
|
||||||
|
return str(str($f[0])->explode(':')[0]);
|
||||||
|
})->filter(function (Stringable $fqdn) {
|
||||||
|
return $fqdn->isNotEmpty();
|
||||||
|
});
|
||||||
|
if ($ip === 'host.docker.internal') {
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv6,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||||
|
return $domain->pluck('domain')->flatten();
|
||||||
|
})->map(function ($domain, $ip) {
|
||||||
|
return [
|
||||||
|
'ip' => $ip,
|
||||||
|
'domains' => $domain,
|
||||||
|
];
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($domains));
|
||||||
|
}
|
||||||
|
}
|
702
app/Http/Controllers/Api/ServicesController.php
Normal file
702
app/Http/Controllers/Api/ServicesController.php
Normal file
@@ -0,0 +1,702 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Service\RestartService;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\DeleteResourceJob;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ServicesController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($service)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$service->makeHidden([
|
||||||
|
'id',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->makeHidden([
|
||||||
|
'docker_compose_raw',
|
||||||
|
'docker_compose',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all services.',
|
||||||
|
path: '/services',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all services',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Service')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function services(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
|
$services = collect();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
$services->push($project->services()->get());
|
||||||
|
}
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service = $this->removeSensitiveData($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($services->flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create a one-click service',
|
||||||
|
path: '/services',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'type'],
|
||||||
|
properties: [
|
||||||
|
'type' => [
|
||||||
|
'description' => 'The one-click service type',
|
||||||
|
'type' => 'string',
|
||||||
|
'enum' => [
|
||||||
|
'activepieces',
|
||||||
|
'appsmith',
|
||||||
|
'appwrite',
|
||||||
|
'authentik',
|
||||||
|
'babybuddy',
|
||||||
|
'budge',
|
||||||
|
'changedetection',
|
||||||
|
'chatwoot',
|
||||||
|
'classicpress-with-mariadb',
|
||||||
|
'classicpress-with-mysql',
|
||||||
|
'classicpress-without-database',
|
||||||
|
'cloudflared',
|
||||||
|
'code-server',
|
||||||
|
'dashboard',
|
||||||
|
'directus',
|
||||||
|
'directus-with-postgresql',
|
||||||
|
'docker-registry',
|
||||||
|
'docuseal',
|
||||||
|
'docuseal-with-postgres',
|
||||||
|
'dokuwiki',
|
||||||
|
'duplicati',
|
||||||
|
'emby',
|
||||||
|
'embystat',
|
||||||
|
'fider',
|
||||||
|
'filebrowser',
|
||||||
|
'firefly',
|
||||||
|
'formbricks',
|
||||||
|
'ghost',
|
||||||
|
'gitea',
|
||||||
|
'gitea-with-mariadb',
|
||||||
|
'gitea-with-mysql',
|
||||||
|
'gitea-with-postgresql',
|
||||||
|
'glance',
|
||||||
|
'glances',
|
||||||
|
'glitchtip',
|
||||||
|
'grafana',
|
||||||
|
'grafana-with-postgresql',
|
||||||
|
'grocy',
|
||||||
|
'heimdall',
|
||||||
|
'homepage',
|
||||||
|
'jellyfin',
|
||||||
|
'kuzzle',
|
||||||
|
'listmonk',
|
||||||
|
'logto',
|
||||||
|
'mediawiki',
|
||||||
|
'meilisearch',
|
||||||
|
'metabase',
|
||||||
|
'metube',
|
||||||
|
'minio',
|
||||||
|
'moodle',
|
||||||
|
'n8n',
|
||||||
|
'n8n-with-postgresql',
|
||||||
|
'next-image-transformation',
|
||||||
|
'nextcloud',
|
||||||
|
'nocodb',
|
||||||
|
'odoo',
|
||||||
|
'openblocks',
|
||||||
|
'pairdrop',
|
||||||
|
'penpot',
|
||||||
|
'phpmyadmin',
|
||||||
|
'pocketbase',
|
||||||
|
'posthog',
|
||||||
|
'reactive-resume',
|
||||||
|
'rocketchat',
|
||||||
|
'shlink',
|
||||||
|
'slash',
|
||||||
|
'snapdrop',
|
||||||
|
'statusnook',
|
||||||
|
'stirling-pdf',
|
||||||
|
'supabase',
|
||||||
|
'syncthing',
|
||||||
|
'tolgee',
|
||||||
|
'trigger',
|
||||||
|
'trigger-with-external-database',
|
||||||
|
'twenty',
|
||||||
|
'umami',
|
||||||
|
'unleash-with-postgresql',
|
||||||
|
'unleash-without-database',
|
||||||
|
'uptime-kuma',
|
||||||
|
'vaultwarden',
|
||||||
|
'vikunja',
|
||||||
|
'weblate',
|
||||||
|
'whoogle',
|
||||||
|
'wordpress-with-mariadb',
|
||||||
|
'wordpress-with-mysql',
|
||||||
|
'wordpress-without-database',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
|
||||||
|
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
|
||||||
|
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
|
||||||
|
'environment_name' => ['type' => 'string', 'description' => 'Environment name.'],
|
||||||
|
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
|
||||||
|
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
|
||||||
|
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Create a service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'Service UUID.'],
|
||||||
|
'domains' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Service domains.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_service(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'type' => 'string|required',
|
||||||
|
'project_uuid' => 'string|required',
|
||||||
|
'environment_name' => 'string|required',
|
||||||
|
'server_uuid' => 'string|required',
|
||||||
|
'destination_uuid' => 'string',
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'instant_deploy' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$serverUuid = $request->server_uuid;
|
||||||
|
$instantDeploy = $request->instant_deploy ?? false;
|
||||||
|
if ($request->is_public && ! $request->public_port) {
|
||||||
|
$request->offsetSet('is_public', false);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
|
}
|
||||||
|
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$destinations = $server->destinations();
|
||||||
|
if ($destinations->count() == 0) {
|
||||||
|
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||||
|
}
|
||||||
|
if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
|
||||||
|
return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
|
||||||
|
}
|
||||||
|
$destination = $destinations->first();
|
||||||
|
$services = get_service_templates();
|
||||||
|
$serviceKeys = $services->keys();
|
||||||
|
if ($serviceKeys->contains($request->type)) {
|
||||||
|
$oneClickServiceName = $request->type;
|
||||||
|
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||||
|
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||||
|
if ($oneClickDotEnvs) {
|
||||||
|
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
|
||||||
|
return ! empty($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($oneClickService) {
|
||||||
|
$service_payload = [
|
||||||
|
'name' => "$oneClickServiceName-".str()->random(10),
|
||||||
|
'docker_compose_raw' => base64_decode($oneClickService),
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'service_type' => $oneClickServiceName,
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
];
|
||||||
|
if ($oneClickServiceName === 'cloudflared') {
|
||||||
|
data_set($service_payload, 'connect_to_docker_network', true);
|
||||||
|
}
|
||||||
|
$service = Service::create($service_payload);
|
||||||
|
$service->name = "$oneClickServiceName-".$service->uuid;
|
||||||
|
$service->save();
|
||||||
|
if ($oneClickDotEnvs?->count() > 0) {
|
||||||
|
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||||
|
$key = str()->before($value, '=');
|
||||||
|
$value = str(str()->after($value, '='));
|
||||||
|
$generatedValue = $value;
|
||||||
|
if ($value->contains('SERVICE_')) {
|
||||||
|
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||||
|
$generatedValue = generateEnvValue($command->value(), $service);
|
||||||
|
}
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $generatedValue,
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$service->parse(isNew: true);
|
||||||
|
if ($instantDeploy) {
|
||||||
|
StartService::dispatch($service);
|
||||||
|
}
|
||||||
|
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||||
|
$domains = $domains->map(function ($domain) {
|
||||||
|
return str($domain)->beforeLast(':')->value();
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $service->uuid,
|
||||||
|
'domains' => $domains,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
} else {
|
||||||
|
return response()->json(['message' => 'Invalid service type.', 'valid_service_types' => $serviceKeys], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Invalid service type.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get service by UUID.',
|
||||||
|
path: '/services/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get a service by Uuid.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/Service'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function service_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($service));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete service by UUID.',
|
||||||
|
path: '/services/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Delete a service by Uuid',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service deletion request queued.'],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
DeleteResourceJob::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Service deletion request queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Start',
|
||||||
|
description: 'Start service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/start',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Start service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
if (str($service->status())->contains('running')) {
|
||||||
|
return response()->json(['message' => 'Service is already running.'], 400);
|
||||||
|
}
|
||||||
|
StartService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service starting request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Stop',
|
||||||
|
description: 'Stop service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/stop',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Stop service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_stop(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||||
|
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||||
|
}
|
||||||
|
StopService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service stopping request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Restart',
|
||||||
|
description: 'Restart service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/restart',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Restart service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_restart(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
RestartService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service restarting request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Team extends Controller
|
|
||||||
{
|
|
||||||
public function teams(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
|
|
||||||
return response()->json($teams);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function team_by_id(Request $request)
|
|
||||||
{
|
|
||||||
$id = $request->id;
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
$team = $teams->where('id', $id)->first();
|
|
||||||
if (is_null($team)) {
|
|
||||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($team);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function members_by_id(Request $request)
|
|
||||||
{
|
|
||||||
$id = $request->id;
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
$team = $teams->where('id', $id)->first();
|
|
||||||
if (is_null($team)) {
|
|
||||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($team->members);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function current_team(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$team = auth()->user()->currentTeam();
|
|
||||||
|
|
||||||
return response()->json($team);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function current_team_members(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$team = auth()->user()->currentTeam();
|
|
||||||
|
|
||||||
return response()->json($team->members);
|
|
||||||
}
|
|
||||||
}
|
|
270
app/Http/Controllers/Api/TeamController.php
Normal file
270
app/Http/Controllers/Api/TeamController.php
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class TeamController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($team)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$team->makeHidden([
|
||||||
|
'custom_server_limit',
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
$team->makeHidden([
|
||||||
|
'smtp_username',
|
||||||
|
'smtp_password',
|
||||||
|
'resend_api_key',
|
||||||
|
'telegram_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'Get all teams.',
|
||||||
|
path: '/teams',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of teams.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Team')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function teams(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams->sortBy('id');
|
||||||
|
$teams = $teams->map(function ($team) {
|
||||||
|
return $this->removeSensitiveData($team);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$teams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get team by TeamId.',
|
||||||
|
path: '/teams/{id}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of teams.',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Team')
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function team_by_id(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->id;
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams;
|
||||||
|
$team = $teams->where('id', $id)->first();
|
||||||
|
if (is_null($team)) {
|
||||||
|
return response()->json(['message' => 'Team not found.'], 404);
|
||||||
|
}
|
||||||
|
$team = $this->removeSensitiveData($team);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($team),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Members',
|
||||||
|
description: 'Get members by TeamId.',
|
||||||
|
path: '/teams/{id}/members',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of members.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function members_by_id(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->id;
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams;
|
||||||
|
$team = $teams->where('id', $id)->first();
|
||||||
|
if (is_null($team)) {
|
||||||
|
return response()->json(['message' => 'Team not found.'], 404);
|
||||||
|
}
|
||||||
|
$members = $team->members;
|
||||||
|
$members->makeHidden([
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($members),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Authenticated Team',
|
||||||
|
description: 'Get currently authenticated team.',
|
||||||
|
path: '/teams/current',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Current Team.',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Team')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function current_team(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$team = auth()->user()->currentTeam();
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$this->removeSensitiveData($team),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Authenticated Team Members',
|
||||||
|
description: 'Get currently authenticated team members.',
|
||||||
|
path: '/teams/current/members',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Currently authenticated team members.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function current_team_members(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$team = auth()->user()->currentTeam();
|
||||||
|
$team->members->makeHidden([
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($team->members),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -54,6 +54,34 @@ class Stripe extends Controller
|
|||||||
$type = data_get($event, 'type');
|
$type = data_get($event, 'type');
|
||||||
$data = data_get($event, 'data.object');
|
$data = data_get($event, 'data.object');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
case 'radar.early_fraud_warning.created':
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$id = data_get($data, 'id');
|
||||||
|
$charge = data_get($data, 'charge');
|
||||||
|
if ($charge) {
|
||||||
|
$stripe->refunds->create(['charge' => $charge]);
|
||||||
|
}
|
||||||
|
$pi = data_get($data, 'payment_intent');
|
||||||
|
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||||
|
$customerId = data_get($piData, 'customer');
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if (! $subscription) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
}
|
||||||
|
if (! $subscription) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
}
|
||||||
|
if ($subscription) {
|
||||||
|
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
|
||||||
|
$stripe->subscriptions->cancel($subscriptionId, []);
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||||
|
break;
|
||||||
case 'checkout.session.completed':
|
case 'checkout.session.completed':
|
||||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||||
if (is_null($clientReferenceId)) {
|
if (is_null($clientReferenceId)) {
|
||||||
|
@@ -67,5 +67,7 @@ class Kernel extends HttpKernel
|
|||||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||||
|
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
34
app/Http/Middleware/ApiAllowed.php
Normal file
34
app/Http/Middleware/ApiAllowed.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class ApiAllowed
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
ray()->clearAll();
|
||||||
|
if (isCloud()) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($settings->is_api_enabled === false) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isDev()) {
|
||||||
|
if ($settings->allowed_ips) {
|
||||||
|
$allowedIps = explode(',', $settings->allowed_ips);
|
||||||
|
if (! in_array($request->ip(), $allowedIps)) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'You are not allowed to access the API.'], 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IgnoreReadOnlyApiToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('*')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
if ($token->can('read-only')) {
|
||||||
|
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class OnlyRootApiToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('*')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||||
|
}
|
||||||
|
}
|
@@ -127,7 +127,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private string $dockerfile_location = '/Dockerfile';
|
private string $dockerfile_location = '/Dockerfile';
|
||||||
|
|
||||||
private string $docker_compose_location = '/docker-compose.yml';
|
private string $docker_compose_location = '/docker-compose.yaml';
|
||||||
|
|
||||||
private ?string $docker_compose_custom_start_command = null;
|
private ?string $docker_compose_custom_start_command = null;
|
||||||
|
|
||||||
@@ -194,6 +194,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
|
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
$this->container_name = $this->application->settings->custom_internal_name;
|
||||||
|
}
|
||||||
ray('New container name: ', $this->container_name);
|
ray('New container name: ', $this->container_name);
|
||||||
|
|
||||||
savePrivateKeyToFs($this->server);
|
savePrivateKeyToFs($this->server);
|
||||||
@@ -608,10 +611,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
$composeFileName = "$this->configuration_dir/docker-compose.yaml";
|
||||||
} else {
|
} else {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
|
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -1570,23 +1573,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
|
||||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
|
|
||||||
}
|
|
||||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
|
||||||
// } else {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if ($this->env_filename) {
|
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
|
|
||||||
// } else {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (! is_null($this->env_filename)) {
|
if (! is_null($this->env_filename)) {
|
||||||
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
||||||
}
|
}
|
||||||
@@ -1697,16 +1683,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
// if ($this->build_pack === 'dockerfile') {
|
|
||||||
// $docker_compose['services'][$this->container_name]['build'] = [
|
|
||||||
// 'context' => $this->workdir,
|
|
||||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
|
||||||
// ];
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
if (! $this->application->settings->custom_internal_name) {
|
||||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||||
if (count($custom_compose) > 0) {
|
if (count($custom_compose) > 0) {
|
||||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||||
@@ -1724,6 +1705,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (count($custom_compose) > 0) {
|
if (count($custom_compose) > 0) {
|
||||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||||
@@ -1746,7 +1728,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yaml > /dev/null"), 'hidden' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
|
@@ -332,8 +332,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->database->toArray());
|
$url = $this->database->internal_db_url;
|
||||||
$url = $this->database->get_db_url(useInternal: true);
|
|
||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
||||||
|
@@ -35,9 +35,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if ($isInprogress) {
|
// if ($isInprogress) {
|
||||||
throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||||
}
|
// }
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -46,10 +46,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +85,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -44,10 +44,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,13 +100,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -2,14 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Actions\Database\StartClickhouse;
|
use App\Actions\Database\RestartDatabase;
|
||||||
use App\Actions\Database\StartDragonfly;
|
use App\Actions\Database\StartDatabase;
|
||||||
use App\Actions\Database\StartKeydb;
|
|
||||||
use App\Actions\Database\StartMariadb;
|
|
||||||
use App\Actions\Database\StartMongodb;
|
|
||||||
use App\Actions\Database\StartMysql;
|
|
||||||
use App\Actions\Database\StartPostgresql;
|
|
||||||
use App\Actions\Database\StartRedis;
|
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Actions\Docker\GetContainersStatus;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -47,7 +41,6 @@ class Heading extends Component
|
|||||||
public function check_status($showNotification = false)
|
public function check_status($showNotification = false)
|
||||||
{
|
{
|
||||||
GetContainersStatus::run($this->database->destination->server);
|
GetContainersStatus::run($this->database->destination->server);
|
||||||
// dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
|
||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
if ($showNotification) {
|
if ($showNotification) {
|
||||||
$this->dispatch('success', 'Database status updated.');
|
$this->dispatch('success', 'Database status updated.');
|
||||||
@@ -67,32 +60,15 @@ class Heading extends Component
|
|||||||
$this->check_status();
|
$this->check_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function restart()
|
||||||
|
{
|
||||||
|
$activity = RestartDatabase::run($this->database);
|
||||||
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
|
}
|
||||||
|
|
||||||
public function start()
|
public function start()
|
||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
$activity = StartDatabase::run($this->database);
|
||||||
$activity = StartPostgresql::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} elseif ($this->database->type() === 'standalone-redis') {
|
|
||||||
$activity = StartRedis::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-mongodb') {
|
|
||||||
$activity = StartMongodb::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-mysql') {
|
|
||||||
$activity = StartMysql::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-mariadb') {
|
|
||||||
$activity = StartMariadb::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-keydb') {
|
|
||||||
$activity = StartKeydb::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-dragonfly') {
|
|
||||||
$activity = StartDragonfly::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-clickhouse') {
|
|
||||||
$activity = StartClickhouse::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,10 +46,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -108,13 +106,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -52,10 +52,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -114,13 +112,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -50,10 +50,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -115,13 +113,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -52,10 +52,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +111,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -27,10 +27,7 @@ class General extends Component
|
|||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$userId = auth()->user()->id;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped',
|
|
||||||
'refresh',
|
'refresh',
|
||||||
'save_init_script',
|
'save_init_script',
|
||||||
'delete_init_script',
|
'delete_init_script',
|
||||||
@@ -72,18 +69,11 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function database_stopped()
|
|
||||||
{
|
|
||||||
$this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -118,13 +108,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -46,10 +46,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -102,13 +100,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
@@ -10,6 +10,12 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public $tokens = [];
|
public $tokens = [];
|
||||||
|
|
||||||
|
public bool $viewSensitiveData = false;
|
||||||
|
|
||||||
|
public bool $readOnly = true;
|
||||||
|
|
||||||
|
public array $permissions = ['read-only'];
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.security.api-tokens');
|
return view('livewire.security.api-tokens');
|
||||||
@@ -17,7 +23,33 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->tokens = auth()->user()->tokens;
|
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedViewSensitiveData()
|
||||||
|
{
|
||||||
|
if ($this->viewSensitiveData) {
|
||||||
|
$this->permissions[] = 'view:sensitive';
|
||||||
|
$this->permissions = array_diff($this->permissions, ['*']);
|
||||||
|
} else {
|
||||||
|
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
|
||||||
|
}
|
||||||
|
if (count($this->permissions) == 0) {
|
||||||
|
$this->permissions = ['*'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedReadOnly()
|
||||||
|
{
|
||||||
|
if ($this->readOnly) {
|
||||||
|
$this->permissions[] = 'read-only';
|
||||||
|
$this->permissions = array_diff($this->permissions, ['*']);
|
||||||
|
} else {
|
||||||
|
$this->permissions = array_diff($this->permissions, ['read-only']);
|
||||||
|
}
|
||||||
|
if (count($this->permissions) == 0) {
|
||||||
|
$this->permissions = ['*'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addNewToken()
|
public function addNewToken()
|
||||||
@@ -26,7 +58,13 @@ class ApiTokens extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'description' => 'required|min:3|max:255',
|
'description' => 'required|min:3|max:255',
|
||||||
]);
|
]);
|
||||||
$token = auth()->user()->createToken($this->description);
|
// if ($this->viewSensitiveData) {
|
||||||
|
// $this->permissions[] = 'view:sensitive';
|
||||||
|
// }
|
||||||
|
// if ($this->readOnly) {
|
||||||
|
// $this->permissions[] = 'read-only';
|
||||||
|
// }
|
||||||
|
$token = auth()->user()->createToken($this->description, $this->permissions);
|
||||||
$this->tokens = auth()->user()->tokens;
|
$this->tokens = auth()->user()->tokens;
|
||||||
session()->flash('token', $token->plainTextToken);
|
session()->flash('token', $token->plainTextToken);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@@ -84,7 +84,7 @@ class Deploy extends Component
|
|||||||
try {
|
try {
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$activity = StartProxy::run($this->server);
|
$activity = StartProxy::run($this->server, force: true);
|
||||||
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -18,7 +18,8 @@ class Configuration extends Component
|
|||||||
|
|
||||||
public bool $is_dns_validation_enabled;
|
public bool $is_dns_validation_enabled;
|
||||||
|
|
||||||
// public bool $next_channel;
|
public bool $is_api_enabled;
|
||||||
|
|
||||||
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
||||||
|
|
||||||
protected Server $server;
|
protected Server $server;
|
||||||
@@ -30,6 +31,7 @@ class Configuration extends Component
|
|||||||
'settings.public_port_max' => 'required',
|
'settings.public_port_max' => 'required',
|
||||||
'settings.custom_dns_servers' => 'nullable',
|
'settings.custom_dns_servers' => 'nullable',
|
||||||
'settings.instance_name' => 'nullable',
|
'settings.instance_name' => 'nullable',
|
||||||
|
'settings.allowed_ips' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -38,6 +40,7 @@ class Configuration extends Component
|
|||||||
'settings.public_port_min' => 'Public port min',
|
'settings.public_port_min' => 'Public port min',
|
||||||
'settings.public_port_max' => 'Public port max',
|
'settings.public_port_max' => 'Public port max',
|
||||||
'settings.custom_dns_servers' => 'Custom DNS servers',
|
'settings.custom_dns_servers' => 'Custom DNS servers',
|
||||||
|
'settings.allowed_ips' => 'Allowed IPs',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -45,8 +48,8 @@ class Configuration extends Component
|
|||||||
$this->do_not_track = $this->settings->do_not_track;
|
$this->do_not_track = $this->settings->do_not_track;
|
||||||
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
||||||
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
||||||
// $this->next_channel = $this->settings->next_channel;
|
|
||||||
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
|
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
|
||||||
|
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
@@ -55,12 +58,7 @@ class Configuration extends Component
|
|||||||
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
||||||
$this->settings->is_registration_enabled = $this->is_registration_enabled;
|
$this->settings->is_registration_enabled = $this->is_registration_enabled;
|
||||||
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
|
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
|
||||||
// if ($this->next_channel) {
|
$this->settings->is_api_enabled = $this->is_api_enabled;
|
||||||
// $this->settings->next_channel = false;
|
|
||||||
// $this->next_channel = false;
|
|
||||||
// } else {
|
|
||||||
// $this->settings->next_channel = $this->next_channel;
|
|
||||||
// }
|
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->dispatch('success', 'Settings updated!');
|
$this->dispatch('success', 'Settings updated!');
|
||||||
}
|
}
|
||||||
@@ -94,6 +92,13 @@ class Configuration extends Component
|
|||||||
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
|
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
|
||||||
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
|
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
|
||||||
|
|
||||||
|
$this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
|
||||||
|
$this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
|
||||||
|
return str($ip)->trim();
|
||||||
|
});
|
||||||
|
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
|
||||||
|
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
|
||||||
|
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->server->setupDynamicProxyConfiguration();
|
$this->server->setupDynamicProxyConfiguration();
|
||||||
if (! $error_show) {
|
if (! $error_show) {
|
||||||
|
@@ -8,12 +8,95 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Application model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer', 'description' => 'The application identifier in the database.'],
|
||||||
|
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'The application description.'],
|
||||||
|
'repository_project_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'The repository project identifier.'],
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'The application UUID.'],
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||||
|
'fqdn' => ['type' => 'string', 'nullable' => true, 'description' => 'The application domains.'],
|
||||||
|
'config_hash' => ['type' => 'string', 'description' => 'Configuration hash.'],
|
||||||
|
'git_repository' => ['type' => 'string', 'description' => 'Git repository URL.'],
|
||||||
|
'git_branch' => ['type' => 'string', 'description' => 'Git branch.'],
|
||||||
|
'git_commit_sha' => ['type' => 'string', 'description' => 'Git commit SHA.'],
|
||||||
|
'git_full_url' => ['type' => 'string', 'nullable' => true, 'description' => 'Git full URL.'],
|
||||||
|
'docker_registry_image_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image name.'],
|
||||||
|
'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image tag.'],
|
||||||
|
'build_pack' => ['type' => 'string', 'description' => 'Build pack.', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose']],
|
||||||
|
'static_image' => ['type' => 'string', 'description' => 'Static image used when static site is deployed.'],
|
||||||
|
'install_command' => ['type' => 'string', 'description' => 'Install command.'],
|
||||||
|
'build_command' => ['type' => 'string', 'description' => 'Build command.'],
|
||||||
|
'start_command' => ['type' => 'string', 'description' => 'Start command.'],
|
||||||
|
'ports_exposes' => ['type' => 'string', 'description' => 'Ports exposes.'],
|
||||||
|
'ports_mappings' => ['type' => 'string', 'nullable' => true, 'description' => 'Ports mappings.'],
|
||||||
|
'base_directory' => ['type' => 'string', 'description' => 'Base directory for all commands.'],
|
||||||
|
'publish_directory' => ['type' => 'string', 'description' => 'Publish directory.'],
|
||||||
|
'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
|
||||||
|
'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
|
||||||
|
'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
|
||||||
|
'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
|
||||||
|
'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
|
||||||
|
'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
|
||||||
|
'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
|
||||||
|
'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
|
||||||
|
'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
|
||||||
|
'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
|
||||||
|
'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
|
||||||
|
'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
|
||||||
|
'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
|
||||||
|
'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
|
||||||
|
'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
|
||||||
|
'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
|
||||||
|
'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
|
||||||
|
'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
|
||||||
|
'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
|
||||||
|
'status' => ['type' => 'string', 'description' => 'Application status.'],
|
||||||
|
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
|
||||||
|
'destination_type' => ['type' => 'string', 'description' => 'Destination type.'],
|
||||||
|
'destination_id' => ['type' => 'integer', 'description' => 'Destination identifier.'],
|
||||||
|
'source_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'Source identifier.'],
|
||||||
|
'private_key_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'Private key identifier.'],
|
||||||
|
'environment_id' => ['type' => 'integer', 'description' => 'Environment identifier.'],
|
||||||
|
'dockerfile' => ['type' => 'string', 'nullable' => true, 'description' => 'Dockerfile content. Used for dockerfile build pack.'],
|
||||||
|
'dockerfile_location' => ['type' => 'string', 'description' => 'Dockerfile location.'],
|
||||||
|
'custom_labels' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom labels.'],
|
||||||
|
'dockerfile_target_build' => ['type' => 'string', 'nullable' => true, 'description' => 'Dockerfile target build.'],
|
||||||
|
'manual_webhook_secret_github' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for GitHub.'],
|
||||||
|
'manual_webhook_secret_gitlab' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for GitLab.'],
|
||||||
|
'manual_webhook_secret_bitbucket' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for Bitbucket.'],
|
||||||
|
'manual_webhook_secret_gitea' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for Gitea.'],
|
||||||
|
'docker_compose_location' => ['type' => 'string', 'description' => 'Docker compose location.'],
|
||||||
|
'docker_compose' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose content. Used for docker compose build pack.'],
|
||||||
|
'docker_compose_raw' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose raw content.'],
|
||||||
|
'docker_compose_domains' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose domains.'],
|
||||||
|
'docker_compose_custom_start_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose custom start command.'],
|
||||||
|
'docker_compose_custom_build_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose custom build command.'],
|
||||||
|
'swarm_replicas' => ['type' => 'integer', 'nullable' => true, 'description' => 'Swarm replicas. Only used for swarm deployments.'],
|
||||||
|
'swarm_placement_constraints' => ['type' => 'string', 'nullable' => true, 'description' => 'Swarm placement constraints. Only used for swarm deployments.'],
|
||||||
|
'custom_docker_run_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom docker run options.'],
|
||||||
|
'post_deployment_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Post deployment command.'],
|
||||||
|
'post_deployment_command_container' => ['type' => 'string', 'nullable' => true, 'description' => 'Post deployment command container.'],
|
||||||
|
'pre_deployment_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Pre deployment command.'],
|
||||||
|
'pre_deployment_command_container' => ['type' => 'string', 'nullable' => true, 'description' => 'Pre deployment command container.'],
|
||||||
|
'watch_paths' => ['type' => 'string', 'nullable' => true, 'description' => 'Watch paths.'],
|
||||||
|
'custom_healthcheck_found' => ['type' => 'boolean', 'description' => 'Custom healthcheck found.'],
|
||||||
|
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||||
|
'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was created.'],
|
||||||
|
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
|
||||||
|
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
|
||||||
class Application extends BaseModel
|
class Application extends BaseModel
|
||||||
{
|
{
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
@@ -4,7 +4,37 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Project model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'application_id' => ['type' => 'string'],
|
||||||
|
'deployment_uuid' => ['type' => 'string'],
|
||||||
|
'pull_request_id' => ['type' => 'integer'],
|
||||||
|
'force_rebuild' => ['type' => 'boolean'],
|
||||||
|
'commit' => ['type' => 'string'],
|
||||||
|
'status' => ['type' => 'string'],
|
||||||
|
'is_webhook' => ['type' => 'boolean'],
|
||||||
|
'is_api' => ['type' => 'boolean'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
'logs' => ['type' => 'string'],
|
||||||
|
'current_process_id' => ['type' => 'string'],
|
||||||
|
'restart_only' => ['type' => 'boolean'],
|
||||||
|
'git_type' => ['type' => 'string'],
|
||||||
|
'server_id' => ['type' => 'integer'],
|
||||||
|
'application_name' => ['type' => 'string'],
|
||||||
|
'server_name' => ['type' => 'string'],
|
||||||
|
'deployment_url' => ['type' => 'string'],
|
||||||
|
'destination_id' => ['type' => 'string'],
|
||||||
|
'only_this_server' => ['type' => 'boolean'],
|
||||||
|
'rollback' => ['type' => 'boolean'],
|
||||||
|
'commit_message' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
)]
|
||||||
class ApplicationDeploymentQueue extends Model
|
class ApplicationDeploymentQueue extends Model
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
@@ -4,7 +4,20 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Environment model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'project_id' => ['type' => 'integer'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)]
|
||||||
class Environment extends Model
|
class Environment extends Model
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
@@ -27,6 +40,9 @@ class Environment extends Model
|
|||||||
$this->redis()->count() == 0 &&
|
$this->redis()->count() == 0 &&
|
||||||
$this->postgresqls()->count() == 0 &&
|
$this->postgresqls()->count() == 0 &&
|
||||||
$this->mysqls()->count() == 0 &&
|
$this->mysqls()->count() == 0 &&
|
||||||
|
$this->keydbs()->count() == 0 &&
|
||||||
|
$this->dragonflies()->count() == 0 &&
|
||||||
|
$this->clickhouses()->count() == 0 &&
|
||||||
$this->mariadbs()->count() == 0 &&
|
$this->mariadbs()->count() == 0 &&
|
||||||
$this->mongodbs()->count() == 0 &&
|
$this->mongodbs()->count() == 0 &&
|
||||||
$this->services()->count() == 0;
|
$this->services()->count() == 0;
|
||||||
|
@@ -6,9 +6,33 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Environment Variable model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'application_id' => ['type' => 'integer'],
|
||||||
|
'service_id' => ['type' => 'integer'],
|
||||||
|
'database_id' => ['type' => 'integer'],
|
||||||
|
'is_build_time' => ['type' => 'boolean'],
|
||||||
|
'is_literal' => ['type' => 'boolean'],
|
||||||
|
'is_multiline' => ['type' => 'boolean'],
|
||||||
|
'is_preview' => ['type' => 'boolean'],
|
||||||
|
'is_shared' => ['type' => 'boolean'],
|
||||||
|
'is_shown_once' => ['type' => 'boolean'],
|
||||||
|
'key' => ['type' => 'string'],
|
||||||
|
'value' => ['type' => 'string'],
|
||||||
|
'real_value' => ['type' => 'string'],
|
||||||
|
'version' => ['type' => 'string'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)]
|
||||||
class EnvironmentVariable extends Model
|
class EnvironmentVariable extends Model
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
@@ -20,6 +20,17 @@ class GithubApp extends BaseModel
|
|||||||
'webhook_secret',
|
'webhook_secret',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::deleting(function (GithubApp $github_app) {
|
||||||
|
$applications_count = Application::where('source_id', $github_app->id)->count();
|
||||||
|
if ($applications_count > 0) {
|
||||||
|
throw new \Exception('You cannot delete this GitHub App because it is in use by '.$applications_count.' application(s). Delete them first.');
|
||||||
|
}
|
||||||
|
$github_app->privateKey()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static function public()
|
public static function public()
|
||||||
{
|
{
|
||||||
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
|
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
|
||||||
@@ -30,15 +41,9 @@ class GithubApp extends BaseModel
|
|||||||
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(false)->whereNotNull('app_id')->get();
|
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(false)->whereNotNull('app_id')->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function booted(): void
|
public function team()
|
||||||
{
|
{
|
||||||
static::deleting(function (GithubApp $github_app) {
|
return $this->belongsTo(Team::class);
|
||||||
$applications_count = Application::where('source_id', $github_app->id)->count();
|
|
||||||
if ($applications_count > 0) {
|
|
||||||
throw new \Exception('You cannot delete this GitHub App because it is in use by '.$applications_count.' application(s). Delete them first.');
|
|
||||||
}
|
|
||||||
$github_app->privateKey()->delete();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applications()
|
public function applications()
|
||||||
|
@@ -17,6 +17,7 @@ class InstanceSettings extends Model implements SendsEmail
|
|||||||
protected $casts = [
|
protected $casts = [
|
||||||
'resale_license' => 'encrypted',
|
'resale_license' => 'encrypted',
|
||||||
'smtp_password' => 'encrypted',
|
'smtp_password' => 'encrypted',
|
||||||
|
'allowed_ip_ranges' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function fqdn(): Attribute
|
public function fqdn(): Attribute
|
||||||
|
@@ -2,8 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
use phpseclib3\Crypt\PublicKeyLoader;
|
use phpseclib3\Crypt\PublicKeyLoader;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Private Key model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string', 'format' => 'private-key'],
|
||||||
|
'is_git_related' => ['type' => 'boolean'],
|
||||||
|
'team_id' => ['type' => 'integer'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
)]
|
||||||
class PrivateKey extends BaseModel
|
class PrivateKey extends BaseModel
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -14,6 +30,17 @@ class PrivateKey extends BaseModel
|
|||||||
'team_id',
|
'team_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::saving(function ($key) {
|
||||||
|
$privateKey = data_get($key, 'private_key');
|
||||||
|
if (substr($privateKey, -1) !== "\n") {
|
||||||
|
$key->private_key = $privateKey."\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static function ownedByCurrentTeam(array $select = ['*'])
|
public static function ownedByCurrentTeam(array $select = ['*'])
|
||||||
{
|
{
|
||||||
$selectArray = collect($select)->concat(['id']);
|
$selectArray = collect($select)->concat(['id']);
|
||||||
|
@@ -2,6 +2,23 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Project model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'name' => ['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
|
class Project extends BaseModel
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
@@ -12,11 +12,95 @@ use Illuminate\Support\Facades\DB;
|
|||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Stringable;
|
use Illuminate\Support\Stringable;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Application model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'repository_project_id' => ['type' => 'integer', 'nullable' => true],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'fqdn' => ['type' => 'string'],
|
||||||
|
'config_hash' => ['type' => 'string'],
|
||||||
|
'git_repository' => ['type' => 'string'],
|
||||||
|
'git_branch' => ['type' => 'string'],
|
||||||
|
'git_commit_sha' => ['type' => 'string'],
|
||||||
|
'git_full_url' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'docker_registry_image_name' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'build_pack' => ['type' => 'string'],
|
||||||
|
'static_image' => ['type' => 'string'],
|
||||||
|
'install_command' => ['type' => 'string'],
|
||||||
|
'build_command' => ['type' => 'string'],
|
||||||
|
'start_command' => ['type' => 'string'],
|
||||||
|
'ports_exposes' => ['type' => 'string'],
|
||||||
|
'ports_mappings' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'base_directory' => ['type' => 'string'],
|
||||||
|
'publish_directory' => ['type' => 'string'],
|
||||||
|
'health_check_path' => ['type' => 'string'],
|
||||||
|
'health_check_port' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'health_check_host' => ['type' => 'string'],
|
||||||
|
'health_check_method' => ['type' => 'string'],
|
||||||
|
'health_check_return_code' => ['type' => 'integer'],
|
||||||
|
'health_check_scheme' => ['type' => 'string'],
|
||||||
|
'health_check_response_text' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'health_check_interval' => ['type' => 'integer'],
|
||||||
|
'health_check_timeout' => ['type' => 'integer'],
|
||||||
|
'health_check_retries' => ['type' => 'integer'],
|
||||||
|
'health_check_start_period' => ['type' => 'integer'],
|
||||||
|
'limits_memory' => ['type' => 'string'],
|
||||||
|
'limits_memory_swap' => ['type' => 'string'],
|
||||||
|
'limits_memory_swappiness' => ['type' => 'integer'],
|
||||||
|
'limits_memory_reservation' => ['type' => 'string'],
|
||||||
|
'limits_cpus' => ['type' => 'string'],
|
||||||
|
'limits_cpuset' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'limits_cpu_shares' => ['type' => 'integer'],
|
||||||
|
'status' => ['type' => 'string'],
|
||||||
|
'preview_url_template' => ['type' => 'string'],
|
||||||
|
'destination_type' => ['type' => 'string'],
|
||||||
|
'destination_id' => ['type' => 'integer'],
|
||||||
|
'source_type' => ['type' => 'string'],
|
||||||
|
'source_id' => ['type' => 'integer'],
|
||||||
|
'private_key_id' => ['type' => 'integer', 'nullable' => true],
|
||||||
|
'environment_id' => ['type' => 'integer'],
|
||||||
|
'created_at' => ['type' => 'string', 'format' => 'date-time'],
|
||||||
|
'updated_at' => ['type' => 'string', 'format' => 'date-time'],
|
||||||
|
'description' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'dockerfile' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'health_check_enabled' => ['type' => 'boolean'],
|
||||||
|
'dockerfile_location' => ['type' => 'string'],
|
||||||
|
'custom_labels' => ['type' => 'string'],
|
||||||
|
'dockerfile_target_build' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'manual_webhook_secret_github' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'manual_webhook_secret_gitlab' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'docker_compose_location' => ['type' => 'string'],
|
||||||
|
'docker_compose' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'docker_compose_raw' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'docker_compose_domains' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true],
|
||||||
|
'docker_compose_custom_start_command' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'docker_compose_custom_build_command' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'swarm_replicas' => ['type' => 'integer'],
|
||||||
|
'swarm_placement_constraints' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'manual_webhook_secret_bitbucket' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'custom_docker_run_options' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'post_deployment_command' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'post_deployment_command_container' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'pre_deployment_command' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'pre_deployment_command_container' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'watch_paths' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'custom_healthcheck_found' => ['type' => 'boolean'],
|
||||||
|
'manual_webhook_secret_gitea' => ['type' => 'string', 'nullable' => true],
|
||||||
|
'redirect' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
|
||||||
class Server extends BaseModel
|
class Server extends BaseModel
|
||||||
{
|
{
|
||||||
use SchemalessAttributesTrait;
|
use SchemalessAttributesTrait;
|
||||||
@@ -496,16 +580,16 @@ $schema://$host {
|
|||||||
|
|
||||||
public function checkSentinel()
|
public function checkSentinel()
|
||||||
{
|
{
|
||||||
ray("Checking sentinel on server: {$this->name}");
|
// ray("Checking sentinel on server: {$this->name}");
|
||||||
if ($this->isSentinelEnabled()) {
|
if ($this->isSentinelEnabled()) {
|
||||||
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
|
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
|
||||||
$sentinel_found = json_decode($sentinel_found, true);
|
$sentinel_found = json_decode($sentinel_found, true);
|
||||||
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||||
if ($status !== 'running') {
|
if ($status !== 'running') {
|
||||||
ray('Sentinel is not running, starting it...');
|
// ray('Sentinel is not running, starting it...');
|
||||||
PullSentinelImageJob::dispatch($this);
|
PullSentinelImageJob::dispatch($this);
|
||||||
} else {
|
} else {
|
||||||
ray('Sentinel is running');
|
// ray('Sentinel is running');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,46 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Server Settings model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'cleanup_after_percentage' => ['type' => 'integer'],
|
||||||
|
'concurrent_builds' => ['type' => 'integer'],
|
||||||
|
'dynamic_timeout' => ['type' => 'integer'],
|
||||||
|
'force_disabled' => ['type' => 'boolean'],
|
||||||
|
'is_build_server' => ['type' => 'boolean'],
|
||||||
|
'is_cloudflare_tunnel' => ['type' => 'boolean'],
|
||||||
|
'is_jump_server' => ['type' => 'boolean'],
|
||||||
|
'is_logdrain_axiom_enabled' => ['type' => 'boolean'],
|
||||||
|
'is_logdrain_custom_enabled' => ['type' => 'boolean'],
|
||||||
|
'is_logdrain_highlight_enabled' => ['type' => 'boolean'],
|
||||||
|
'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
|
||||||
|
'is_metrics_enabled' => ['type' => 'boolean'],
|
||||||
|
'is_reachable' => ['type' => 'boolean'],
|
||||||
|
'is_server_api_enabled' => ['type' => 'boolean'],
|
||||||
|
'is_swarm_manager' => ['type' => 'boolean'],
|
||||||
|
'is_swarm_worker' => ['type' => 'boolean'],
|
||||||
|
'is_usable' => ['type' => 'boolean'],
|
||||||
|
'logdrain_axiom_api_key' => ['type' => 'string'],
|
||||||
|
'logdrain_axiom_dataset_name' => ['type' => 'string'],
|
||||||
|
'logdrain_custom_config' => ['type' => 'string'],
|
||||||
|
'logdrain_custom_config_parser' => ['type' => 'string'],
|
||||||
|
'logdrain_highlight_project_id' => ['type' => 'string'],
|
||||||
|
'logdrain_newrelic_base_uri' => ['type' => 'string'],
|
||||||
|
'logdrain_newrelic_license_key' => ['type' => 'string'],
|
||||||
|
'metrics_history_days' => ['type' => 'integer'],
|
||||||
|
'metrics_refresh_rate_seconds' => ['type' => 'integer'],
|
||||||
|
'metrics_token' => ['type' => 'string'],
|
||||||
|
'server_id' => ['type' => 'integer'],
|
||||||
|
'wildcard_domain' => ['type' => 'string'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)]
|
||||||
class ServerSetting extends Model
|
class ServerSetting extends Model
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
@@ -6,8 +6,31 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Service model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer', 'description' => 'The unique identifier of the service. Only used for database identification.'],
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'The unique identifier of the service.'],
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the service.'],
|
||||||
|
'environment_id' => ['type' => 'integer', 'description' => 'The unique identifier of the environment where the service is attached to.'],
|
||||||
|
'server_id' => ['type' => 'integer', 'description' => 'The unique identifier of the server where the service is running.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the service.'],
|
||||||
|
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
|
||||||
|
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
|
||||||
|
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
||||||
|
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||||
|
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
|
||||||
|
'config_hash' => ['type' => 'string', 'description' => 'The hash of the service configuration.'],
|
||||||
|
'service_type' => ['type' => 'string', 'description' => 'The type of the service.'],
|
||||||
|
'created_at' => ['type' => 'string', 'description' => 'The date and time when the service was created.'],
|
||||||
|
'updated_at' => ['type' => 'string', 'description' => 'The date and time when the service was last updated.'],
|
||||||
|
'deleted_at' => ['type' => 'string', 'description' => 'The date and time when the service was deleted.'],
|
||||||
|
],
|
||||||
|
)]
|
||||||
class Service extends BaseModel
|
class Service extends BaseModel
|
||||||
{
|
{
|
||||||
use HasFactory, SoftDeletes;
|
use HasFactory, SoftDeletes;
|
||||||
|
@@ -27,6 +27,11 @@ class ServiceApplication extends BaseModel
|
|||||||
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
|
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function ownedByCurrentTeamAPI(int $teamId)
|
||||||
|
{
|
||||||
|
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
||||||
|
}
|
||||||
|
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
return data_get($this, 'is_log_drain_enabled', false);
|
return data_get($this, 'is_log_drain_enabled', false);
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneClickhouse extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'clickhouse_password' => 'encrypted',
|
'clickhouse_password' => 'encrypted',
|
||||||
];
|
];
|
||||||
@@ -178,18 +180,36 @@ class StandaloneClickhouse extends BaseModel
|
|||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-clickhouse';
|
return 'standalone-clickhouse';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
|
get: fn () => "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->uuid}:9000/{$this->clickhouse_db}",
|
||||||
} else {
|
);
|
||||||
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneDragonfly extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'dragonfly_password' => 'encrypted',
|
'dragonfly_password' => 'encrypted',
|
||||||
];
|
];
|
||||||
@@ -178,18 +180,36 @@ class StandaloneDragonfly extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-dragonfly';
|
return 'standalone-dragonfly';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
get: fn () => "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0",
|
||||||
} else {
|
);
|
||||||
return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneKeydb extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'keydb_password' => 'encrypted',
|
'keydb_password' => 'encrypted',
|
||||||
];
|
];
|
||||||
@@ -178,18 +180,36 @@ class StandaloneKeydb extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-keydb';
|
return 'standalone-keydb';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
get: fn () => "redis://{$this->keydb_password}@{$this->uuid}:6379/0",
|
||||||
} else {
|
);
|
||||||
return "redis://{$this->keydb_password}@{$this->uuid}:6379/0";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneMariadb extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'mariadb_password' => 'encrypted',
|
'mariadb_password' => 'encrypted',
|
||||||
];
|
];
|
||||||
@@ -161,6 +163,13 @@ class StandaloneMariadb extends BaseModel
|
|||||||
return data_get($this, 'is_log_drain_enabled', false);
|
return data_get($this, 'is_log_drain_enabled', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-mariadb';
|
return 'standalone-mariadb';
|
||||||
@@ -183,13 +192,24 @@ class StandaloneMariadb extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
|
get: fn () => "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}",
|
||||||
} else {
|
);
|
||||||
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneMongodb extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($database) {
|
static::created(function ($database) {
|
||||||
@@ -198,18 +200,36 @@ class StandaloneMongodb extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-mongodb';
|
return 'standalone-mongodb';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false)
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
|
get: fn () => "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true",
|
||||||
} else {
|
);
|
||||||
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneMysql extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'mysql_password' => 'encrypted',
|
'mysql_password' => 'encrypted',
|
||||||
'mysql_root_password' => 'encrypted',
|
'mysql_root_password' => 'encrypted',
|
||||||
@@ -157,6 +159,13 @@ class StandaloneMysql extends BaseModel
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-mysql';
|
return 'standalone-mysql';
|
||||||
@@ -184,13 +193,24 @@ class StandaloneMysql extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
|
get: fn () => "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}",
|
||||||
} else {
|
);
|
||||||
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandalonePostgresql extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'init_scripts' => 'array',
|
'init_scripts' => 'array',
|
||||||
'postgres_password' => 'encrypted',
|
'postgres_password' => 'encrypted',
|
||||||
@@ -179,18 +181,36 @@ class StandalonePostgresql extends BaseModel
|
|||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databaseType(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => $this->type(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return 'standalone-postgresql';
|
return 'standalone-postgresql';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
|
get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}",
|
||||||
} else {
|
);
|
||||||
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -13,6 +13,8 @@ class StandaloneRedis extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($database) {
|
static::created(function ($database) {
|
||||||
@@ -179,13 +181,31 @@ class StandaloneRedis extends BaseModel
|
|||||||
return 'standalone-redis';
|
return 'standalone-redis';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_db_url(bool $useInternal = false): string
|
public function databaseType(): Attribute
|
||||||
{
|
{
|
||||||
if ($this->is_public && ! $useInternal) {
|
return new Attribute(
|
||||||
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
get: fn () => $this->type(),
|
||||||
} else {
|
);
|
||||||
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function internalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function externalDbUrl(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
if ($this->is_public && $this->public_port) {
|
||||||
|
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
|
@@ -7,7 +7,66 @@ use App\Notifications\Channels\SendsEmail;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'Team model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer', 'description' => 'The unique identifier of the team.'],
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the team.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the team.'],
|
||||||
|
'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'],
|
||||||
|
'created_at' => ['type' => 'string', 'description' => 'The date and time the team was created.'],
|
||||||
|
'updated_at' => ['type' => 'string', 'description' => 'The date and time the team was last updated.'],
|
||||||
|
'smtp_enabled' => ['type' => 'boolean', 'description' => 'Whether SMTP is enabled or not.'],
|
||||||
|
'smtp_from_address' => ['type' => 'string', 'description' => 'The email address to send emails from.'],
|
||||||
|
'smtp_from_name' => ['type' => 'string', 'description' => 'The name to send emails from.'],
|
||||||
|
'smtp_recipients' => ['type' => 'string', 'description' => 'The email addresses to send emails to.'],
|
||||||
|
'smtp_host' => ['type' => 'string', 'description' => 'The SMTP host.'],
|
||||||
|
'smtp_port' => ['type' => 'string', 'description' => 'The SMTP port.'],
|
||||||
|
'smtp_encryption' => ['type' => 'string', 'description' => 'The SMTP encryption.'],
|
||||||
|
'smtp_username' => ['type' => 'string', 'description' => 'The SMTP username.'],
|
||||||
|
'smtp_password' => ['type' => 'string', 'description' => 'The SMTP password.'],
|
||||||
|
'smtp_timeout' => ['type' => 'string', 'description' => 'The SMTP timeout.'],
|
||||||
|
'smtp_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via SMTP.'],
|
||||||
|
'smtp_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via SMTP.'],
|
||||||
|
'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'],
|
||||||
|
'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'],
|
||||||
|
'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'],
|
||||||
|
'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'],
|
||||||
|
'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'],
|
||||||
|
'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'],
|
||||||
|
'discord_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Discord.'],
|
||||||
|
'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'],
|
||||||
|
'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'],
|
||||||
|
'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'],
|
||||||
|
'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'],
|
||||||
|
'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'],
|
||||||
|
'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'],
|
||||||
|
'use_instance_email_settings' => ['type' => 'boolean', 'description' => 'Whether to use instance email settings or not.'],
|
||||||
|
'telegram_enabled' => ['type' => 'boolean', 'description' => 'Whether Telegram is enabled or not.'],
|
||||||
|
'telegram_token' => ['type' => 'string', 'description' => 'The Telegram token.'],
|
||||||
|
'telegram_chat_id' => ['type' => 'string', 'description' => 'The Telegram chat ID.'],
|
||||||
|
'telegram_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Telegram.'],
|
||||||
|
'telegram_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Telegram.'],
|
||||||
|
'telegram_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Telegram.'],
|
||||||
|
'telegram_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Telegram.'],
|
||||||
|
'telegram_notifications_test_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram test message thread ID.'],
|
||||||
|
'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'],
|
||||||
|
'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'],
|
||||||
|
'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'],
|
||||||
|
'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'],
|
||||||
|
'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'],
|
||||||
|
'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'],
|
||||||
|
'members' => new OA\Property(
|
||||||
|
property: 'members',
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User'),
|
||||||
|
description: 'The members of the team.'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class Team extends Model implements SendsDiscord, SendsEmail
|
class Team extends Model implements SendsDiscord, SendsEmail
|
||||||
{
|
{
|
||||||
use Notifiable;
|
use Notifiable;
|
||||||
|
@@ -17,7 +17,23 @@ use Illuminate\Support\Str;
|
|||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Laravel\Sanctum\NewAccessToken;
|
use Laravel\Sanctum\NewAccessToken;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
description: 'User model',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer', 'description' => 'The user identifier in the database.'],
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The user name.'],
|
||||||
|
'email' => ['type' => 'string', 'description' => 'The user email.'],
|
||||||
|
'email_verified_at' => ['type' => 'string', 'description' => 'The date when the user email was verified.'],
|
||||||
|
'created_at' => ['type' => 'string', 'description' => 'The date when the user was created.'],
|
||||||
|
'updated_at' => ['type' => 'string', 'description' => 'The date when the user was updated.'],
|
||||||
|
'two_factor_confirmed_at' => ['type' => 'string', 'description' => 'The date when the user two factor was confirmed.'],
|
||||||
|
'force_password_reset' => ['type' => 'boolean', 'description' => 'The flag to force the user to reset the password.'],
|
||||||
|
'marketing_emails' => ['type' => 'boolean', 'description' => 'The flag to receive marketing emails.'],
|
||||||
|
],
|
||||||
|
)]
|
||||||
class User extends Authenticatable implements SendsEmail
|
class User extends Authenticatable implements SendsEmail
|
||||||
{
|
{
|
||||||
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
|
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
|
||||||
|
@@ -1,38 +1,178 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\BuildPackTypes;
|
||||||
|
use App\Enums\RedirectTypes;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
function get_team_id_from_token()
|
function getTeamIdFromToken()
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
|
||||||
return data_get($token, 'team_id');
|
return data_get($token, 'team_id');
|
||||||
}
|
}
|
||||||
function invalid_token()
|
function invalidTokenResponse()
|
||||||
{
|
{
|
||||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
|
return response()->json(['message' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
function serialize_api_response($data)
|
function serializeApiResponse($data)
|
||||||
{
|
{
|
||||||
if (! $data instanceof Collection) {
|
if ($data instanceof Collection) {
|
||||||
$data = collect($data);
|
$data = $data->map(function ($d) {
|
||||||
}
|
$d = collect($d)->sortKeys();
|
||||||
$data = $data->sortKeys();
|
$created_at = data_get($d, 'created_at');
|
||||||
$created_at = data_get($data, 'created_at');
|
$updated_at = data_get($d, 'updated_at');
|
||||||
$updated_at = data_get($data, 'updated_at');
|
|
||||||
if ($created_at) {
|
if ($created_at) {
|
||||||
unset($data['created_at']);
|
unset($d['created_at']);
|
||||||
$data['created_at'] = $created_at;
|
$d['created_at'] = $created_at;
|
||||||
|
|
||||||
}
|
}
|
||||||
if ($updated_at) {
|
if ($updated_at) {
|
||||||
unset($data['updated_at']);
|
unset($d['updated_at']);
|
||||||
$data['updated_at'] = $updated_at;
|
$d['updated_at'] = $updated_at;
|
||||||
}
|
}
|
||||||
if (data_get($data, 'id')) {
|
if (data_get($d, 'name')) {
|
||||||
$data = $data->prepend($data['id'], 'id');
|
$d = $d->prepend($d['name'], 'name');
|
||||||
|
}
|
||||||
|
if (data_get($d, 'description')) {
|
||||||
|
$d = $d->prepend($d['description'], 'description');
|
||||||
|
}
|
||||||
|
if (data_get($d, 'uuid')) {
|
||||||
|
$d = $d->prepend($d['uuid'], 'uuid');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! is_null(data_get($d, 'id'))) {
|
||||||
|
$d = $d->prepend($d['id'], 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $d;
|
||||||
|
});
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
} else {
|
||||||
|
$d = collect($data)->sortKeys();
|
||||||
|
$created_at = data_get($d, 'created_at');
|
||||||
|
$updated_at = data_get($d, 'updated_at');
|
||||||
|
if ($created_at) {
|
||||||
|
unset($d['created_at']);
|
||||||
|
$d['created_at'] = $created_at;
|
||||||
|
|
||||||
|
}
|
||||||
|
if ($updated_at) {
|
||||||
|
unset($d['updated_at']);
|
||||||
|
$d['updated_at'] = $updated_at;
|
||||||
|
}
|
||||||
|
if (data_get($d, 'name')) {
|
||||||
|
$d = $d->prepend($d['name'], 'name');
|
||||||
|
}
|
||||||
|
if (data_get($d, 'description')) {
|
||||||
|
$d = $d->prepend($d['description'], 'description');
|
||||||
|
}
|
||||||
|
if (data_get($d, 'uuid')) {
|
||||||
|
$d = $d->prepend($d['uuid'], 'uuid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_null(data_get($d, 'id'))) {
|
||||||
|
$d = $d->prepend($d['id'], 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sharedDataApplications()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'git_repository' => 'string',
|
||||||
|
'git_branch' => 'string',
|
||||||
|
'build_pack' => Rule::enum(BuildPackTypes::class),
|
||||||
|
'is_static' => 'boolean',
|
||||||
|
'domains' => 'string',
|
||||||
|
'redirect' => Rule::enum(RedirectTypes::class),
|
||||||
|
'git_commit_sha' => 'string',
|
||||||
|
'docker_registry_image_name' => 'string|nullable',
|
||||||
|
'docker_registry_image_tag' => 'string|nullable',
|
||||||
|
'install_command' => 'string|nullable',
|
||||||
|
'build_command' => 'string|nullable',
|
||||||
|
'start_command' => 'string|nullable',
|
||||||
|
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/',
|
||||||
|
'ports_mappings' => 'string|regex:/^(\d+:\d+)(,\d+:\d+)*$/|nullable',
|
||||||
|
'base_directory' => 'string|nullable',
|
||||||
|
'publish_directory' => 'string|nullable',
|
||||||
|
'health_check_enabled' => 'boolean',
|
||||||
|
'health_check_path' => 'string',
|
||||||
|
'health_check_port' => 'string|nullable',
|
||||||
|
'health_check_host' => 'string',
|
||||||
|
'health_check_method' => 'string',
|
||||||
|
'health_check_return_code' => 'numeric',
|
||||||
|
'health_check_scheme' => 'string',
|
||||||
|
'health_check_response_text' => 'string|nullable',
|
||||||
|
'health_check_interval' => 'numeric',
|
||||||
|
'health_check_timeout' => 'numeric',
|
||||||
|
'health_check_retries' => 'numeric',
|
||||||
|
'health_check_start_period' => 'numeric',
|
||||||
|
'limits_memory' => 'string',
|
||||||
|
'limits_memory_swap' => 'string',
|
||||||
|
'limits_memory_swappiness' => 'numeric',
|
||||||
|
'limits_memory_reservation' => 'string',
|
||||||
|
'limits_cpus' => 'string',
|
||||||
|
'limits_cpuset' => 'string|nullable',
|
||||||
|
'limits_cpu_shares' => 'numeric',
|
||||||
|
'custom_labels' => 'string|nullable',
|
||||||
|
'custom_docker_run_options' => 'string|nullable',
|
||||||
|
'post_deployment_command' => 'string|nullable',
|
||||||
|
'post_deployment_command_container' => 'string',
|
||||||
|
'pre_deployment_command' => 'string|nullable',
|
||||||
|
'pre_deployment_command_container' => 'string',
|
||||||
|
'manual_webhook_secret_github' => 'string|nullable',
|
||||||
|
'manual_webhook_secret_gitlab' => 'string|nullable',
|
||||||
|
'manual_webhook_secret_bitbucket' => 'string|nullable',
|
||||||
|
'manual_webhook_secret_gitea' => 'string|nullable',
|
||||||
|
'docker_compose_location' => 'string',
|
||||||
|
'docker_compose' => 'string|nullable',
|
||||||
|
'docker_compose_raw' => 'string|nullable',
|
||||||
|
'docker_compose_domains' => 'array|nullable',
|
||||||
|
'docker_compose_custom_start_command' => 'string|nullable',
|
||||||
|
'docker_compose_custom_build_command' => 'string|nullable',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateIncomingRequest(Request $request)
|
||||||
|
{
|
||||||
|
// check if request is json
|
||||||
|
if (! $request->isJson()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid request.',
|
||||||
|
'error' => 'Content-Type must be application/json.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
// check if request is valid json
|
||||||
|
if (! json_decode($request->getContent())) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid request.',
|
||||||
|
'error' => 'Invalid JSON.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
// check if valid json is empty
|
||||||
|
if (empty($request->json()->all())) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid request.',
|
||||||
|
'error' => 'Empty JSON.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUnnecessaryFieldsFromRequest(Request $request)
|
||||||
|
{
|
||||||
|
$request->offsetUnset('project_uuid');
|
||||||
|
$request->offsetUnset('environment_name');
|
||||||
|
$request->offsetUnset('destination_uuid');
|
||||||
|
$request->offsetUnset('server_uuid');
|
||||||
|
$request->offsetUnset('type');
|
||||||
|
$request->offsetUnset('domains');
|
||||||
|
$request->offsetUnset('instant_deploy');
|
||||||
|
$request->offsetUnset('github_app_uuid');
|
||||||
|
$request->offsetUnset('private_key_uuid');
|
||||||
}
|
}
|
||||||
|
@@ -19,136 +19,165 @@ function generate_database_name(string $type): string
|
|||||||
return $type.'-database-'.$cuid;
|
return $type.'-database-'.$cuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_standalone_postgresql($environment_id, $destination_uuid): StandalonePostgresql
|
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql
|
||||||
{
|
{
|
||||||
// TODO: If another type of destination is added, this will need to be updated.
|
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandalonePostgresql();
|
||||||
|
$database->name = generate_database_name('postgresql');
|
||||||
|
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environmentId;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandalonePostgresql::create([
|
return $database;
|
||||||
'name' => generate_database_name('postgresql'),
|
|
||||||
'postgres_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_standalone_redis($environment_id, $destination_uuid): StandaloneRedis
|
function create_standalone_redis($environment_id, $destination_uuid, ?array $otherData = null): StandaloneRedis
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneRedis();
|
||||||
|
$database->name = generate_database_name('redis');
|
||||||
|
$database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandaloneRedis::create([
|
return $database;
|
||||||
'name' => generate_database_name('redis'),
|
|
||||||
'redis_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_standalone_mongodb($environment_id, $destination_uuid): StandaloneMongodb
|
function create_standalone_mongodb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMongodb
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneMongodb();
|
||||||
|
$database->name = generate_database_name('mongodb');
|
||||||
|
$database->mongo_initdb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandaloneMongodb::create([
|
return $database;
|
||||||
'name' => generate_database_name('mongodb'),
|
|
||||||
'mongo_initdb_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
function create_standalone_mysql($environment_id, $destination_uuid): StandaloneMysql
|
function create_standalone_mysql($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMysql
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneMysql();
|
||||||
|
$database->name = generate_database_name('mysql');
|
||||||
|
$database->mysql_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->mysql_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandaloneMysql::create([
|
return $database;
|
||||||
'name' => generate_database_name('mysql'),
|
|
||||||
'mysql_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'mysql_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
function create_standalone_mariadb($environment_id, $destination_uuid): StandaloneMariadb
|
function create_standalone_mariadb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMariadb
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneMariadb();
|
||||||
|
$database->name = generate_database_name('mariadb');
|
||||||
|
$database->mariadb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->mariadb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
|
||||||
return StandaloneMariadb::create([
|
if ($otherData) {
|
||||||
'name' => generate_database_name('mariadb'),
|
$database->fill($otherData);
|
||||||
'mariadb_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
}
|
||||||
'mariadb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
$database->save();
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
return $database;
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
function create_standalone_keydb($environment_id, $destination_uuid): StandaloneKeydb
|
function create_standalone_keydb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneKeydb
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneKeydb();
|
||||||
|
$database->name = generate_database_name('keydb');
|
||||||
|
$database->keydb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandaloneKeydb::create([
|
return $database;
|
||||||
'name' => generate_database_name('keydb'),
|
|
||||||
'keydb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_standalone_dragonfly($environment_id, $destination_uuid): StandaloneDragonfly
|
function create_standalone_dragonfly($environment_id, $destination_uuid, ?array $otherData = null): StandaloneDragonfly
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneDragonfly();
|
||||||
|
$database->name = generate_database_name('dragonfly');
|
||||||
|
$database->dragonfly_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandaloneDragonfly::create([
|
return $database;
|
||||||
'name' => generate_database_name('dragonfly'),
|
|
||||||
'dragonfly_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
function create_standalone_clickhouse($environment_id, $destination_uuid): StandaloneClickhouse
|
function create_standalone_clickhouse($environment_id, $destination_uuid, ?array $otherData = null): StandaloneClickhouse
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
throw new Exception('Destination not found');
|
throw new Exception('Destination not found');
|
||||||
}
|
}
|
||||||
|
$database = new StandaloneClickhouse();
|
||||||
|
$database->name = generate_database_name('clickhouse');
|
||||||
|
$database->clickhouse_admin_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
|
$database->environment_id = $environment_id;
|
||||||
|
$database->destination_id = $destination->id;
|
||||||
|
$database->destination_type = $destination->getMorphClass();
|
||||||
|
if ($otherData) {
|
||||||
|
$database->fill($otherData);
|
||||||
|
}
|
||||||
|
$database->save();
|
||||||
|
|
||||||
return StandaloneClickhouse::create([
|
return $database;
|
||||||
'name' => generate_database_name('clickhouse'),
|
|
||||||
'clickhouse_admin_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
|
||||||
'environment_id' => $environment_id,
|
|
||||||
'destination_id' => $destination->id,
|
|
||||||
'destination_type' => $destination->getMorphClass(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete file locally on the filesystem.
|
|
||||||
*/
|
|
||||||
function delete_backup_locally(?string $filename, Server $server): void
|
function delete_backup_locally(?string $filename, Server $server): void
|
||||||
{
|
{
|
||||||
if (empty($filename)) {
|
if (empty($filename)) {
|
||||||
@@ -156,3 +185,17 @@ function delete_backup_locally(?string $filename, Server $server): void
|
|||||||
}
|
}
|
||||||
instant_remote_process(["rm -f \"{$filename}\""], $server, throwError: false);
|
instant_remote_process(["rm -f \"{$filename}\""], $server, throwError: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPublicPortAlreadyUsed(Server $server, int $port, ?string $id = null): bool
|
||||||
|
{
|
||||||
|
if ($id) {
|
||||||
|
$foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->where('id', '!=', $id)->first();
|
||||||
|
} else {
|
||||||
|
$foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->first();
|
||||||
|
}
|
||||||
|
if ($foundDatabase) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@@ -199,3 +199,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
|||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function serviceKeys()
|
||||||
|
{
|
||||||
|
$services = get_service_templates();
|
||||||
|
$serviceKeys = $services->keys();
|
||||||
|
|
||||||
|
return $serviceKeys;
|
||||||
|
}
|
||||||
|
@@ -536,6 +536,43 @@ function getResourceByUuid(string $uuid, ?int $teamId = null)
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function queryDatabaseByUuidWithinTeam(string $uuid, string $teamId)
|
||||||
|
{
|
||||||
|
$postgresql = StandalonePostgresql::whereUuid($uuid)->first();
|
||||||
|
if ($postgresql && $postgresql->team()->id == $teamId) {
|
||||||
|
return $postgresql->unsetRelation('environment')->unsetRelation('destination');
|
||||||
|
}
|
||||||
|
$redis = StandaloneRedis::whereUuid($uuid)->first();
|
||||||
|
if ($redis && $redis->team()->id == $teamId) {
|
||||||
|
return $redis->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
$mongodb = StandaloneMongodb::whereUuid($uuid)->first();
|
||||||
|
if ($mongodb && $mongodb->team()->id == $teamId) {
|
||||||
|
return $mongodb->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
$mysql = StandaloneMysql::whereUuid($uuid)->first();
|
||||||
|
if ($mysql && $mysql->team()->id == $teamId) {
|
||||||
|
return $mysql->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
$mariadb = StandaloneMariadb::whereUuid($uuid)->first();
|
||||||
|
if ($mariadb && $mariadb->team()->id == $teamId) {
|
||||||
|
return $mariadb->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
$keydb = StandaloneKeydb::whereUuid($uuid)->first();
|
||||||
|
if ($keydb && $keydb->team()->id == $teamId) {
|
||||||
|
return $keydb->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
$dragonfly = StandaloneDragonfly::whereUuid($uuid)->first();
|
||||||
|
if ($dragonfly && $dragonfly->team()->id == $teamId) {
|
||||||
|
return $dragonfly->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
$clickhouse = StandaloneClickhouse::whereUuid($uuid)->first();
|
||||||
|
if ($clickhouse && $clickhouse->team()->id == $teamId) {
|
||||||
|
return $clickhouse->unsetRelation('environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function queryResourcesByUuid(string $uuid)
|
function queryResourcesByUuid(string $uuid)
|
||||||
{
|
{
|
||||||
$resource = null;
|
$resource = null;
|
||||||
@@ -1347,10 +1384,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
}
|
}
|
||||||
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
|
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
|
||||||
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
|
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
|
||||||
|
if (! str($value)->startsWith('$')) {
|
||||||
$found_env = $envs_from_coolify->where('key', $key)->first();
|
$found_env = $envs_from_coolify->where('key', $key)->first();
|
||||||
if ($found_env) {
|
if ($found_env) {
|
||||||
return $found_env->value;
|
return $found_env->value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
});
|
});
|
||||||
@@ -2129,6 +2168,75 @@ function ip_match($ip, $cidrs, &$match = null)
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId, string $uuid)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return response()->json(['error' => 'Team ID is required.'], 400);
|
||||||
|
}
|
||||||
|
if (is_array($domains)) {
|
||||||
|
$domains = collect($domains);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domains = $domains->map(function ($domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return str($domain);
|
||||||
|
});
|
||||||
|
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
|
||||||
|
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
|
||||||
|
$domainFound = false;
|
||||||
|
foreach ($applications as $app) {
|
||||||
|
if (is_null($app->fqdn)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
|
foreach ($list_of_domains as $domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
$domainFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($domainFound) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach ($serviceApplications as $app) {
|
||||||
|
if (str($app->fqdn)->isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
|
foreach ($list_of_domains as $domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
$domainFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($domainFound) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (data_get($settings, 'fqdn')) {
|
||||||
|
$domain = data_get($settings, 'fqdn');
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
||||||
{
|
{
|
||||||
if ($resource) {
|
if ($resource) {
|
||||||
|
@@ -13,10 +13,10 @@
|
|||||||
"doctrine/dbal": "^3.6",
|
"doctrine/dbal": "^3.6",
|
||||||
"guzzlehttp/guzzle": "^7.5.0",
|
"guzzlehttp/guzzle": "^7.5.0",
|
||||||
"laravel/fortify": "^v1.16.0",
|
"laravel/fortify": "^v1.16.0",
|
||||||
"laravel/framework": "^v10.7.1",
|
"laravel/framework": "^v11",
|
||||||
"laravel/horizon": "^5.23.1",
|
"laravel/horizon": "^5.23.1",
|
||||||
"laravel/prompts": "^0.1.6",
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v3.2.1",
|
"laravel/sanctum": "^v4.0",
|
||||||
"laravel/socialite": "^v5.14.0",
|
"laravel/socialite": "^v5.14.0",
|
||||||
"laravel/tinker": "^v2.8.1",
|
"laravel/tinker": "^v2.8.1",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
@@ -32,8 +32,7 @@
|
|||||||
"poliander/cron": "^3.0",
|
"poliander/cron": "^3.0",
|
||||||
"purplepixie/phpdns": "^2.1",
|
"purplepixie/phpdns": "^2.1",
|
||||||
"pusher/pusher-php-server": "^7.2",
|
"pusher/pusher-php-server": "^7.2",
|
||||||
"resend/resend-laravel": "^0.5.0",
|
"sentry/sentry-laravel": "^4.6",
|
||||||
"sentry/sentry-laravel": "^3.4",
|
|
||||||
"socialiteproviders/microsoft-azure": "^5.1",
|
"socialiteproviders/microsoft-azure": "^5.1",
|
||||||
"spatie/laravel-activitylog": "^4.7.3",
|
"spatie/laravel-activitylog": "^4.7.3",
|
||||||
"spatie/laravel-data": "^3.4.3",
|
"spatie/laravel-data": "^3.4.3",
|
||||||
@@ -43,14 +42,15 @@
|
|||||||
"stripe/stripe-php": "^12.0",
|
"stripe/stripe-php": "^12.0",
|
||||||
"symfony/yaml": "^6.2",
|
"symfony/yaml": "^6.2",
|
||||||
"visus/cuid2": "^2.0.0",
|
"visus/cuid2": "^2.0.0",
|
||||||
"yosymfony/toml": "^1.0"
|
"yosymfony/toml": "^1.0",
|
||||||
|
"zircote/swagger-php": "^4.10"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^v1.21.0",
|
"fakerphp/faker": "^v1.21.0",
|
||||||
"laravel/dusk": "^v7.7.0",
|
"laravel/dusk": "^v8.0",
|
||||||
"laravel/pint": "^1.16",
|
"laravel/pint": "^1.16",
|
||||||
"mockery/mockery": "^1.5.1",
|
"mockery/mockery": "^1.5.1",
|
||||||
"nunomaduro/collision": "^v7.4.0",
|
"nunomaduro/collision": "^v8.1",
|
||||||
"pestphp/pest": "^2.16",
|
"pestphp/pest": "^2.16",
|
||||||
"phpstan/phpstan": "^1.10",
|
"phpstan/phpstan": "^1.10",
|
||||||
"phpunit/phpunit": "^10.0.19",
|
"phpunit/phpunit": "^10.0.19",
|
||||||
|
2710
composer.lock
generated
2710
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -60,8 +60,9 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
|
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||||
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
|
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||||
|
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.307',
|
'release' => '4.0.0-beta.308',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.307';
|
return '4.0.0-beta.308';
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_api_enabled')->default(true);
|
||||||
|
$table->text('allowed_ips')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_api_enabled');
|
||||||
|
$table->dropColumn('allowed_ips');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('tags', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['name']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('tags', function (Blueprint $table) {
|
||||||
|
$table->unique(['name']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -1,5 +1,5 @@
|
|||||||
#!/command/execlineb -P
|
#!/command/execlineb -P
|
||||||
foreground { composer -d /var/www/html/ install }
|
foreground { composer -d /var/www/html/ install }
|
||||||
foreground { php /var/www/html/artisan migrate --step }
|
foreground { php /var/www/html/artisan migrate --step }
|
||||||
foreground { php /var/www/html/artisan dev:init }
|
foreground { php /var/www/html/artisan dev --init }
|
||||||
|
|
||||||
|
30
lang/zh-tw.json
Normal file
30
lang/zh-tw.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"auth.login": "登入",
|
||||||
|
"auth.login.azure": "使用 Microsoft 登入",
|
||||||
|
"auth.login.bitbucket": "使用 Bitbucket 登入",
|
||||||
|
"auth.login.github": "使用 GitHub 登入",
|
||||||
|
"auth.login.gitlab": "使用 Gitlab 登入",
|
||||||
|
"auth.login.google": "使用 Google 登入",
|
||||||
|
"auth.already_registered": "已經註冊?",
|
||||||
|
"auth.confirm_password": "確認密碼",
|
||||||
|
"auth.forgot_password": "忘記密碼",
|
||||||
|
"auth.forgot_password_send_email": "發送重設密碼電郵",
|
||||||
|
"auth.register_now": "註冊",
|
||||||
|
"auth.logout": "登出",
|
||||||
|
"auth.register": "註冊",
|
||||||
|
"auth.registration_disabled": "註冊已停用,請聯絡管理員。",
|
||||||
|
"auth.reset_password": "重設密碼",
|
||||||
|
"auth.failed": "這些憑證與我們的記錄不符。",
|
||||||
|
"auth.failed.callback": "無法處理來自登入提供者的回呼。",
|
||||||
|
"auth.failed.password": "密碼錯誤。",
|
||||||
|
"auth.failed.email": "找不到該電子郵件地址的使用者。",
|
||||||
|
"auth.throttle": "登入嘗試次數太多。請在 :seconds 秒後重試。",
|
||||||
|
"input.name": "名稱",
|
||||||
|
"input.email": "電子郵件",
|
||||||
|
"input.password": "密碼",
|
||||||
|
"input.password.again": "再次輸入密碼",
|
||||||
|
"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> 分支將被選擇。"
|
||||||
|
}
|
4721
openapi.yaml
Normal file
4721
openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
Notifications | Coolify
|
Notifications | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-notification.navbar />
|
<x-notification.navbar />
|
||||||
<form wire:submit='submit' class="flex flex-col gap-4">
|
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Email</h2>
|
<h2>Email</h2>
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
label="Use Hosted Email Service" />
|
label="Use Hosted Email Service" />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="pb-4 w-96">
|
<div class="w-96 pb-4">
|
||||||
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
||||||
label="Use Hosted Email Service (Pro+ subscription required)" />
|
label="Use Hosted Email Service (Pro+ subscription required)" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if (!$team->use_instance_email_settings)
|
@if (!$team->use_instance_email_settings)
|
||||||
<form class="flex flex-col items-end gap-2 pb-4 xl:flex-row" wire:submit='submitFromFields'>
|
<form class="flex flex-col items-end gap-2 pt-4 pb-4 xl:flex-row" wire:submit='submitFromFields'>
|
||||||
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
|
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
|
||||||
<x-forms.input required id="team.smtp_from_address" helper="Email address used in emails."
|
<x-forms.input required id="team.smtp_from_address" helper="Email address used in emails."
|
||||||
label="From Address" />
|
label="From Address" />
|
||||||
|
@@ -31,12 +31,14 @@
|
|||||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
instantSave id="application.settings.is_consistent_container_name_enabled"
|
instantSave id="application.settings.is_consistent_container_name_enabled"
|
||||||
label="Consistent Container Names" />
|
label="Consistent Container Names" />
|
||||||
|
@if (!$application->settings->is_consistent_container_name_enabled)
|
||||||
<form class="flex items-end gap-2 pl-2" wire:submit.prevent='saveCustomName'>
|
<form class="flex items-end gap-2 pl-2" wire:submit.prevent='saveCustomName'>
|
||||||
<x-forms.input
|
<x-forms.input
|
||||||
helper="You can add a custom internal name for your container. This name will be used in the internal network. <br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
instantSave id="application.settings.custom_internal_name" label="Add Custom Internal Name" />
|
instantSave id="application.settings.custom_internal_name" label="Custom Container Name" />
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
|
@endif
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
<h3>Network</h3>
|
<h3>Network</h3>
|
||||||
<x-forms.checkbox instantSave id="application.settings.connect_to_docker_network"
|
<x-forms.checkbox instantSave id="application.settings.connect_to_docker_network"
|
||||||
|
@@ -7,7 +7,8 @@
|
|||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex items-center flex-shrink-0 gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
<nav
|
||||||
|
class="flex items-center flex-shrink-0 gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
||||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.database.configuration', $parameters) }}">
|
href="{{ route('project.database.configuration', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
@@ -33,6 +34,19 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
@if (!str($database->status)->startsWith('exited'))
|
@if (!str($database->status)->startsWith('exited'))
|
||||||
|
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Restart
|
||||||
|
</x-slot:button-title>
|
||||||
|
This database will be restarted. <br>Please think again.
|
||||||
|
</x-modal-confirmation>
|
||||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||||
<x-slot:button-title>
|
<x-slot:button-title>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
@@ -69,6 +83,10 @@
|
|||||||
$wire.$dispatch('info', 'Stopping database.');
|
$wire.$dispatch('info', 'Stopping database.');
|
||||||
$wire.$call('stop');
|
$wire.$call('stop');
|
||||||
});
|
});
|
||||||
|
$wire.$on('restartEvent', () => {
|
||||||
|
$wire.$dispatch('info', 'Restarting database.');
|
||||||
|
$wire.$call('restart');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@endscript
|
@endscript
|
||||||
</div>
|
</div>
|
||||||
|
@@ -40,9 +40,6 @@
|
|||||||
<livewire:project.resource.environment-select :environments="$project->environments" />
|
<livewire:project.resource.environment-select :environments="$project->environments" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,29 +3,59 @@
|
|||||||
API Tokens | Coolify
|
API Tokens | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-security.navbar />
|
<x-security.navbar />
|
||||||
<div class="flex gap-2">
|
<div class="pb-4 ">
|
||||||
<h2 class="pb-4">API Tokens</h2>
|
<h2>API Tokens</h2>
|
||||||
<x-helper
|
<div>Tokens are created with the current team as scope. You will only have access to this team's resources.
|
||||||
helper="Tokens are created with the current team as scope. You will only have access to this team's resources." />
|
|
||||||
</div>
|
</div>
|
||||||
<h4>Create New Token</h4>
|
</div>
|
||||||
<form class="flex items-end gap-2 pt-4" wire:submit='addNewToken'>
|
<h3>New Token</h3>
|
||||||
|
<form class="flex flex-col gap-2 pt-4" wire:submit='addNewToken'>
|
||||||
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input required id="description" label="Description" />
|
<x-forms.input required id="description" label="Description" />
|
||||||
<x-forms.button type="submit">Create New Token</x-forms.button>
|
<x-forms.button type="submit">Create New Token</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
Permissions <x-helper class="px-1" helper="These permissions will be granted to the token." /><span
|
||||||
|
class="pr-1">:</span>
|
||||||
|
<div class="flex gap-1 font-bold dark:text-white">
|
||||||
|
@if ($permissions)
|
||||||
|
@foreach ($permissions as $permission)
|
||||||
|
@if ($permission === '*')
|
||||||
|
<div>All (root/admin access), be careful!</div>
|
||||||
|
@else
|
||||||
|
<div>{{ $permission }}</div>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4>Token Permissions</h4>
|
||||||
|
<div class="w-64">
|
||||||
|
<x-forms.checkbox label="Read-only" wire:model.live="readOnly"></x-forms.checkbox>
|
||||||
|
<x-forms.checkbox label="View Sensitive Data" wire:model.live="viewSensitiveData"></x-forms.checkbox>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@if (session()->has('token'))
|
@if (session()->has('token'))
|
||||||
<div class="py-4 font-bold dark:text-warning">Please copy this token now. For your security, it won't be shown again.
|
<div class="py-4 font-bold dark:text-warning">Please copy this token now. For your security, it won't be shown
|
||||||
|
again.
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-4 font-bold dark:text-white"> {{ session('token') }}</div>
|
<div class="pb-4 font-bold dark:text-white"> {{ session('token') }}</div>
|
||||||
@endif
|
@endif
|
||||||
<h4 class="py-4">Issued Tokens</h4>
|
<h3 class="py-4">Issued Tokens</h3>
|
||||||
<div class="grid gap-2 lg:grid-cols-1">
|
<div class="grid gap-2 lg:grid-cols-1">
|
||||||
@forelse ($tokens as $token)
|
@forelse ($tokens as $token)
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex flex-col gap-1 p-2 border dark:border-coolgray-200 hover:no-underline">
|
||||||
<div
|
<div>Description: {{ $token->name }}</div>
|
||||||
class="flex items-center gap-2 group-hover:dark:text-white p-2 border border-coolgray-200 hover:dark:text-white hover:no-underline min-w-[24rem] cursor-default">
|
<div>Last used: {{ $token->last_used_at ? $token->last_used_at->diffForHumans() : 'Never' }}</div>
|
||||||
<div>{{ $token->name }}</div>
|
<div class="flex gap-1">
|
||||||
|
@if ($token->abilities)
|
||||||
|
Abilities:
|
||||||
|
@foreach ($token->abilities as $ability)
|
||||||
|
<div class="font-bold dark:text-white">{{ $ability }}</div>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-modal-confirmation isErrorButton action="revoke({{ data_get($token, 'id') }})">
|
<x-modal-confirmation isErrorButton action="revoke({{ data_get($token, 'id') }})">
|
||||||
<x-slot:button-title>
|
<x-slot:button-title>
|
||||||
Revoke token
|
Revoke token
|
||||||
|
@@ -135,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<h3 class="py-4">Settings</h3>
|
<h3 class="pt-4">Settings</h3>
|
||||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
||||||
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
|
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
|
||||||
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
|
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
|
||||||
@@ -144,8 +144,8 @@
|
|||||||
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
|
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
|
||||||
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2 pt-4 pb-2">
|
||||||
<h3 class="py-4">Sentinel</h3>
|
<h3>Sentinel</h3>
|
||||||
@if ($server->isSentinelEnabled())
|
@if ($server->isSentinelEnabled())
|
||||||
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
|
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -21,9 +21,9 @@
|
|||||||
href="https://coolify.io/docs/knowledge-base/server/proxies#switch-between-proxies">this</a>.
|
href="https://coolify.io/docs/knowledge-base/server/proxies#switch-between-proxies">this</a>.
|
||||||
</div>
|
</div>
|
||||||
@if ($server->proxyType() === 'TRAEFIK_V2')
|
@if ($server->proxyType() === 'TRAEFIK_V2')
|
||||||
<h4 class="pb-4">Traefik</h4>
|
<h4>Traefik</h4>
|
||||||
@elseif ($server->proxyType() === 'CADDY')
|
@elseif ($server->proxyType() === 'CADDY')
|
||||||
<h4 class="pb-4 ">Caddy</h4>
|
<h4>Caddy</h4>
|
||||||
@endif
|
@endif
|
||||||
@if (
|
@if (
|
||||||
$server->proxy->last_applied_settings &&
|
$server->proxy->last_applied_settings &&
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
@if (data_get($server, 'proxy.status') === 'running')
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-2">
|
||||||
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
|
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
|
||||||
<button>
|
<button>
|
||||||
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
|
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
|
||||||
@else
|
@else
|
||||||
To configure automatic backup for your Coolify instance, you first need to add as a database resource
|
To configure automatic backup for your Coolify instance, you first need to add a database resource
|
||||||
into Coolify.
|
into Coolify.
|
||||||
<x-forms.button class="mt-2" wire:click="add_coolify_database">Add Database</x-forms.button>
|
<x-forms.button class="mt-2" wire:click="add_coolify_database">Add Database</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -25,7 +25,16 @@
|
|||||||
<x-forms.input type="number" id="settings.public_port_max" label="Public Port Max" />
|
<x-forms.input type="number" id="settings.public_port_max" label="Public Port Max" />
|
||||||
</div> --}}
|
</div> --}}
|
||||||
</div>
|
</div>
|
||||||
|
<h2 class="pt-6">API</h2>
|
||||||
|
|
||||||
|
<div class="md:w-96">
|
||||||
|
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
||||||
|
</div>
|
||||||
|
<x-forms.input id="settings.allowed_ips" label="Allowed IPs"
|
||||||
|
helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere."
|
||||||
|
placeholder="1.1.1.1,8.8.8.8" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2 class="pt-6">Advanced</h2>
|
<h2 class="pt-6">Advanced</h2>
|
||||||
<div class="text-right md:w-96">
|
<div class="text-right md:w-96">
|
||||||
@if (!is_null(env('AUTOUPDATE', null)))
|
@if (!is_null(env('AUTOUPDATE', null)))
|
||||||
@@ -36,13 +45,5 @@
|
|||||||
@endif
|
@endif
|
||||||
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
||||||
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
||||||
{{-- @if ($next_channel)
|
|
||||||
<x-forms.checkbox instantSave helper="Not recommended. Only if you like to live on the edge."
|
|
||||||
id="next_channel" label="Enable pre-release (early) updates" />
|
|
||||||
@else
|
|
||||||
<x-forms.checkbox disabled instantSave
|
|
||||||
helper="Currently disabled. Not recommended. Only if you like to live on the edge." id="next_channel"
|
|
||||||
label="Enable pre-release (early) updates" />
|
|
||||||
@endif --}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -219,6 +219,10 @@
|
|||||||
uuid,
|
uuid,
|
||||||
html_url
|
html_url
|
||||||
} = @json($github_app);
|
} = @json($github_app);
|
||||||
|
if (!webhook_endpoint) {
|
||||||
|
alert('Please select a webhook endpoint.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let baseUrl = webhook_endpoint;
|
let baseUrl = webhook_endpoint;
|
||||||
const name = @js($name);
|
const name = @js($name);
|
||||||
const isDev = @js(config('app.env')) ===
|
const isDev = @js(config('app.env')) ===
|
||||||
|
153
routes/api.php
153
routes/api.php
@@ -1,78 +1,121 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Api\Applications;
|
use App\Http\Controllers\Api\ApplicationsController;
|
||||||
use App\Http\Controllers\Api\Deploy;
|
use App\Http\Controllers\Api\DatabasesController;
|
||||||
use App\Http\Controllers\Api\EnvironmentVariables;
|
use App\Http\Controllers\Api\DeployController;
|
||||||
use App\Http\Controllers\Api\Resources;
|
use App\Http\Controllers\Api\OtherController;
|
||||||
use App\Http\Controllers\Api\Server;
|
use App\Http\Controllers\Api\ProjectController;
|
||||||
use App\Http\Controllers\Api\Team;
|
use App\Http\Controllers\Api\ResourcesController;
|
||||||
use Illuminate\Http\Request;
|
use App\Http\Controllers\Api\SecurityController;
|
||||||
use Illuminate\Support\Facades\Http;
|
use App\Http\Controllers\Api\ServersController;
|
||||||
|
use App\Http\Controllers\Api\ServicesController;
|
||||||
|
use App\Http\Controllers\Api\TeamController;
|
||||||
|
use App\Http\Middleware\ApiAllowed;
|
||||||
|
use App\Http\Middleware\IgnoreReadOnlyApiToken;
|
||||||
|
use App\Http\Middleware\OnlyRootApiToken;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/health', function () {
|
Route::get('/health', [OtherController::class, 'healthcheck']);
|
||||||
return 'OK';
|
Route::post('/feedback', [OtherController::class, 'feedback']);
|
||||||
});
|
|
||||||
Route::post('/feedback', function (Request $request) {
|
|
||||||
$content = $request->input('content');
|
|
||||||
$webhook_url = config('coolify.feedback_discord_webhook');
|
|
||||||
if ($webhook_url) {
|
|
||||||
Http::post($webhook_url, [
|
|
||||||
'content' => $content,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['message' => 'Feedback sent.'], 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::group([
|
Route::group([
|
||||||
'middleware' => ['auth:sanctum'],
|
'middleware' => ['auth:sanctum', OnlyRootApiToken::class],
|
||||||
'prefix' => 'v1',
|
'prefix' => 'v1',
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('/version', function () {
|
Route::get('/enable', [OtherController::class, 'enable_api']);
|
||||||
return response(config('version'));
|
Route::get('/disable', [OtherController::class, 'disable_api']);
|
||||||
});
|
});
|
||||||
Route::match(['get', 'post'], '/deploy', [Deploy::class, 'deploy']);
|
Route::group([
|
||||||
Route::get('/deployments', [Deploy::class, 'deployments']);
|
'middleware' => ['auth:sanctum', ApiAllowed::class],
|
||||||
Route::get('/deployments/{uuid}', [Deploy::class, 'deployment_by_uuid']);
|
'prefix' => 'v1',
|
||||||
|
], function () {
|
||||||
|
Route::get('/version', [OtherController::class, 'version']);
|
||||||
|
|
||||||
Route::get('/servers', [Server::class, 'servers']);
|
Route::get('/teams', [TeamController::class, 'teams']);
|
||||||
Route::get('/servers/{uuid}', [Server::class, 'server_by_uuid']);
|
Route::get('/teams/current', [TeamController::class, 'current_team']);
|
||||||
Route::get('/servers/domains', [Server::class, 'get_domains_by_server']);
|
Route::get('/teams/current/members', [TeamController::class, 'current_team_members']);
|
||||||
|
Route::get('/teams/{id}', [TeamController::class, 'team_by_id']);
|
||||||
|
Route::get('/teams/{id}/members', [TeamController::class, 'members_by_id']);
|
||||||
|
|
||||||
Route::get('/resources', [Resources::class, 'resources']);
|
Route::get('/projects', [ProjectController::class, 'projects']);
|
||||||
|
Route::get('/projects/{uuid}', [ProjectController::class, 'project_by_uuid']);
|
||||||
|
Route::get('/projects/{uuid}/{environment_name}', [ProjectController::class, 'environment_details']);
|
||||||
|
|
||||||
Route::get('/applications', [Applications::class, 'applications']);
|
Route::get('/security/keys', [SecurityController::class, 'keys']);
|
||||||
|
Route::post('/security/keys', [SecurityController::class, 'create_key'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
Route::get('/applications/{uuid}', [Applications::class, 'application_by_uuid']);
|
Route::get('/security/keys/{uuid}', [SecurityController::class, 'key_by_uuid']);
|
||||||
Route::patch('/applications/{uuid}', [Applications::class, 'update_by_uuid']);
|
Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
Route::delete('/applications/{uuid}', [Applications::class, 'delete_by_uuid']);
|
Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
Route::get('/applications/{uuid}/envs', [Applications::class, 'envs_by_uuid']);
|
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
Route::post('/applications/{uuid}/envs', [Applications::class, 'create_env']);
|
Route::get('/deployments', [DeployController::class, 'deployments']);
|
||||||
Route::post('/applications/{uuid}/envs/bulk', [Applications::class, 'create_bulk_envs']);
|
Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid']);
|
||||||
Route::patch('/applications/{uuid}/envs', [Applications::class, 'update_env_by_uuid']);
|
|
||||||
Route::delete('/applications/{uuid}/envs/{env_uuid}', [Applications::class, 'delete_env_by_uuid']);
|
|
||||||
|
|
||||||
Route::delete('/envs/{env_uuid}', [EnvironmentVariables::class, 'delete_env_by_uuid']);
|
Route::get('/servers', [ServersController::class, 'servers']);
|
||||||
|
Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid']);
|
||||||
|
Route::get('/servers/{uuid}/domains', [ServersController::class, 'domains_by_server']);
|
||||||
|
Route::get('/servers/{uuid}/resources', [ServersController::class, 'resources_by_server']);
|
||||||
|
|
||||||
Route::match(['get', 'post'], '/applications/{uuid}/action/deploy', [Applications::class, 'action_deploy']);
|
Route::get('/resources', [ResourcesController::class, 'resources']);
|
||||||
Route::match(['get', 'post'], '/applications/{uuid}/action/restart', [Applications::class, 'action_restart']);
|
|
||||||
Route::match(['get', 'post'], '/applications/{uuid}/action/stop', [Applications::class, 'action_stop']);
|
|
||||||
|
|
||||||
Route::get('/teams', [Team::class, 'teams']);
|
Route::get('/applications', [ApplicationsController::class, 'applications']);
|
||||||
Route::get('/teams/current', [Team::class, 'current_team']);
|
Route::post('/applications/public', [ApplicationsController::class, 'create_public_application'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
Route::get('/teams/current/members', [Team::class, 'current_team_members']);
|
Route::post('/applications/private-github-app', [ApplicationsController::class, 'create_private_gh_app_application'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
Route::get('/teams/{id}', [Team::class, 'team_by_id']);
|
Route::post('/applications/private-deploy-key', [ApplicationsController::class, 'create_private_deploy_key_application'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
Route::get('/teams/{id}/members', [Team::class, 'members_by_id']);
|
Route::post('/applications/dockerfile', [ApplicationsController::class, 'create_dockerfile_application'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/applications/dockerimage', [ApplicationsController::class, 'create_dockerimage_application'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/applications/dockercompose', [ApplicationsController::class, 'create_dockercompose_application'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid']);
|
||||||
|
Route::patch('/applications/{uuid}', [ApplicationsController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::delete('/applications/{uuid}', [ApplicationsController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::get('/applications/{uuid}/envs', [ApplicationsController::class, 'envs']);
|
||||||
|
Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid']);
|
||||||
|
Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::match(['get', 'post'], '/applications/{uuid}/stop', [ApplicationsController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::get('/databases', [DatabasesController::class, 'databases']);
|
||||||
|
Route::post('/databases/postgresql', [DatabasesController::class, 'create_database_postgresql'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/mysql', [DatabasesController::class, 'create_database_mysql'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/mariadb', [DatabasesController::class, 'create_database_mariadb'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/mongodb', [DatabasesController::class, 'create_database_mongodb'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/redis', [DatabasesController::class, 'create_database_redis'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/clickhouse', [DatabasesController::class, 'create_database_clickhouse'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/dragonfly', [DatabasesController::class, 'create_database_dragonfly'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::post('/databases/keydb', [DatabasesController::class, 'create_database_keydb'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::get('/databases/{uuid}', [DatabasesController::class, 'database_by_uuid']);
|
||||||
|
Route::patch('/databases/{uuid}', [DatabasesController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::delete('/databases/{uuid}', [DatabasesController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::match(['get', 'post'], '/databases/{uuid}/start', [DatabasesController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::match(['get', 'post'], '/databases/{uuid}/restart', [DatabasesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::match(['get', 'post'], '/databases/{uuid}/stop', [DatabasesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::get('/services', [ServicesController::class, 'services']);
|
||||||
|
Route::post('/services', [ServicesController::class, 'create_service'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid']);
|
||||||
|
// Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
Route::match(['get', 'post'], '/services/{uuid}/start', [ServicesController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
|
// Route::delete('/envs/{env_uuid}', [EnvironmentVariablesController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
|
||||||
|
|
||||||
// Route::get('/projects', [Project::class, 'projects']);
|
|
||||||
//Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']);
|
|
||||||
//Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::any('/{any}', function () {
|
Route::any('/{any}', function () {
|
||||||
return response()->json(['error' => 'Not found.'], 404);
|
return response()->json(['message' => 'Not found.', 'docs' => 'https://coolify.io/docs'], 404);
|
||||||
})->where('any', '.*');
|
})->where('any', '.*');
|
||||||
|
|
||||||
// Route::middleware(['throttle:5'])->group(function () {
|
// Route::middleware(['throttle:5'])->group(function () {
|
||||||
|
@@ -20,12 +20,6 @@ function help {
|
|||||||
compgen -A function | cat -n
|
compgen -A function | cat -n
|
||||||
}
|
}
|
||||||
|
|
||||||
# function dev:init {
|
|
||||||
# docker exec coolify bash -c "php artisan migrate --seed"
|
|
||||||
# echo "Need to update privileges on a few files. I need your password for that."
|
|
||||||
# sudo chmod -R o+rwx .
|
|
||||||
# }
|
|
||||||
|
|
||||||
# function sync:v3 {
|
# function sync:v3 {
|
||||||
# if [ -z "$1" ]; then
|
# if [ -z "$1" ]; then
|
||||||
# echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
|
# echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
|
||||||
@@ -53,7 +47,6 @@ function sync:bunny {
|
|||||||
# bash spin exec -u webuser coolify php artisan schedule:run
|
# bash spin exec -u webuser coolify php artisan schedule:run
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
|
||||||
# function db {
|
# function db {
|
||||||
# bash spin exec -u webuser coolify php artisan db
|
# bash spin exec -u webuser coolify php artisan db
|
||||||
# }
|
# }
|
||||||
@@ -100,7 +93,6 @@ function tinker {
|
|||||||
bash spin exec -u webuser coolify php artisan tinker
|
bash spin exec -u webuser coolify php artisan tinker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# function build:helper {
|
# function build:helper {
|
||||||
# act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets
|
# act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets
|
||||||
# }
|
# }
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user