diff --git a/README.md b/README.md index 2f61468a1..e45b60b83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ +![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest_released_version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge +) + [![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new) [![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open) [![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed) + # About the Project Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. @@ -34,13 +38,17 @@ Thank you so much! Special thanks to our biggest sponsors! cccareers logo -hetzner logo -logto logo +hetzner logo +logto logo bc direct logo -quantcdn logo +quantcdn logo arcjet logo supaguide logo -tigris logo +tigris logo +fractal logo +advin logo +trieve logo +blacksmith logo ## Github Sponsors ($40+) SerpAPI @@ -51,6 +59,7 @@ Special thanks to our biggest sponsors! FlintCompany American Cloud CryptoJobsList +Codext Thompson Edolo UXWizz Younes Barrad diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php index 984225435..1b262c898 100644 --- a/app/Actions/Database/StopDatabaseProxy.php +++ b/app/Actions/Database/StopDatabaseProxy.php @@ -2,6 +2,7 @@ namespace App\Actions\Database; +use App\Events\DatabaseStatusChanged; use App\Models\ServiceDatabase; use App\Models\StandaloneClickhouse; use App\Models\StandaloneDragonfly; @@ -28,5 +29,6 @@ class StopDatabaseProxy instant_remote_process(["docker rm -f {$uuid}-proxy"], $server); $database->is_public = false; $database->save(); + DatabaseStatusChanged::dispatch(); } } diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php index eea429c79..b79bc8f67 100644 --- a/app/Actions/Server/StartSentinel.php +++ b/app/Actions/Server/StartSentinel.php @@ -12,12 +12,15 @@ class StartSentinel public function handle(Server $server, $version = 'latest', bool $restart = false) { if ($restart) { - instant_remote_process(['docker rm -f coolify-sentinel'], $server, false); + StopSentinel::run($server); } + $metrics_history = $server->settings->metrics_history_days; + $refresh_rate = $server->settings->metrics_refresh_rate_seconds; + $token = $server->settings->metrics_token; instant_remote_process([ - "docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", + "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", 'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', 'chmod -R 700 /data/coolify/metrics /data/coolify/logs', - ], $server, false); + ], $server, true); } } diff --git a/app/Actions/Server/StopSentinel.php b/app/Actions/Server/StopSentinel.php new file mode 100644 index 000000000..21ffca3bd --- /dev/null +++ b/app/Actions/Server/StopSentinel.php @@ -0,0 +1,16 @@ +option('full-cleanup'); $cleanup_deployments = $this->option('cleanup-deployments'); + + $this->replace_slash_in_environment_name(); if ($cleanup_deployments) { echo "Running cleanup deployments.\n"; $this->cleanup_in_progress_application_deployments(); @@ -150,4 +153,15 @@ class Init extends Command echo "Error: {$e->getMessage()}\n"; } } + + private function replace_slash_in_environment_name() + { + $environments = Environment::all(); + foreach ($environments as $environment) { + if (str_contains($environment->name, '/')) { + $environment->name = str_replace('/', '-', $environment->name); + $environment->save(); + } + } + } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index e2698d90e..f529f63b9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -61,7 +61,7 @@ class Kernel extends ConsoleKernel { $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); foreach ($servers as $server) { - if (config('coolify.is_sentinel_enabled')) { + if ($server->isSentinelEnabled()) { $schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer(); } $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); diff --git a/app/Data/ServerMetadata.php b/app/Data/ServerMetadata.php index b96efa622..d95944b15 100644 --- a/app/Data/ServerMetadata.php +++ b/app/Data/ServerMetadata.php @@ -11,6 +11,5 @@ class ServerMetadata extends Data public function __construct( public ?ProxyTypes $type, public ?ProxyStatus $status - ) { - } + ) {} } diff --git a/app/Events/ProxyStarted.php b/app/Events/ProxyStarted.php index ed62eccb1..64d562e0a 100644 --- a/app/Events/ProxyStarted.php +++ b/app/Events/ProxyStarted.php @@ -10,8 +10,5 @@ class ProxyStarted { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public $data) - { - - } + public function __construct(public $data) {} } diff --git a/app/Exceptions/ProcessException.php b/app/Exceptions/ProcessException.php index 728a0d81b..47eaa6fd8 100644 --- a/app/Exceptions/ProcessException.php +++ b/app/Exceptions/ProcessException.php @@ -4,6 +4,4 @@ namespace App\Exceptions; use Exception; -class ProcessException extends Exception -{ -} +class ProcessException extends Exception {} diff --git a/app/Http/Controllers/Api/Applications.php b/app/Http/Controllers/Api/Applications.php new file mode 100644 index 000000000..a638adecd --- /dev/null +++ b/app/Http/Controllers/Api/Applications.php @@ -0,0 +1,183 @@ +get(); + $applications = collect(); + $applications->push($projects->pluck('applications')->flatten()); + $applications = $applications->flatten(); + + return response()->json($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::where('uuid', $uuid)->first(); + if (! $application) { + return response()->json(['error' => 'Application not found.'], 404); + } + + return response()->json($application); + } + + public function update_by_uuid(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + + if ($request->collect()->count() == 0) { + return response()->json([ + 'message' => 'No data provided.', + ], 400); + } + $application = Application::where('uuid', $request->uuid)->first(); + + if (! $application) { + return response()->json([ + 'success' => false, + 'message' => 'Application not found', + ], 404); + } + ray($request->collect()); + + // if ($request->has('domains')) { + // $existingDomains = explode(',', $application->fqdn); + // $newDomains = $request->domains; + // $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) { + // return ! in_array($domain, $existingDomains); + // }); + // $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains)); + // $application->fqdn = implode(',', $mergedDomains); + // $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application))); + // $application->save(); + // } + + return response()->json([ + 'message' => 'Application updated successfully.', + 'application' => serialize_api_response($application), + ]); + } + + 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::where('uuid', $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::where('uuid', $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::where('uuid', $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 + ); + + } +} diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php index d2abe2e31..f7a34facc 100644 --- a/app/Http/Controllers/Api/Deploy.php +++ b/app/Http/Controllers/Api/Deploy.php @@ -38,7 +38,25 @@ class Deploy extends Controller 'status', ])->sortBy('id')->toArray(); - return response()->json($deployments_per_server, 200); + 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(['error' => 'UUID is required.'], 400); + } + $deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first()->makeHidden('logs'); + if (! $deployment) { + return response()->json(['error' => 'Deployment not found.'], 404); + } + + return response()->json(serialize_api_response($deployment), 200); } public function deploy(Request $request) diff --git a/app/Http/Controllers/Api/Domains.php b/app/Http/Controllers/Api/Domains.php index c27ddf620..6473b64de 100644 --- a/app/Http/Controllers/Api/Domains.php +++ b/app/Http/Controllers/Api/Domains.php @@ -3,102 +3,52 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; -use App\Models\InstanceSettings; -use App\Models\Project as ModelsProject; +use App\Models\Application; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class Domains extends Controller { - public function domains(Request $request) + public function deleteDomains(Request $request) { $teamId = get_team_id_from_token(); if (is_null($teamId)) { return invalid_token(); } - $projects = ModelsProject::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(); + $validator = Validator::make($request->all(), [ + 'uuid' => 'required|string|exists:applications,uuid', + 'domains' => 'required|array', + 'domains.*' => 'required|string|distinct', + ]); - return response()->json($domains); + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation failed', + 'errors' => $validator->errors(), + ], 422); + } + + $application = Application::where('uuid', $request->uuid)->first(); + + if (! $application) { + return response()->json([ + 'success' => false, + 'message' => 'Application not found', + ], 404); + } + + $existingDomains = explode(',', $application->fqdn); + $domainsToDelete = $request->domains; + $updatedDomains = array_diff($existingDomains, $domainsToDelete); + $application->fqdn = implode(',', $updatedDomains); + $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application))); + $application->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Domains updated successfully', + 'application' => $application, + ]); } } diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/Server.php index 9f88a3b28..7a6090bf8 100644 --- a/app/Http/Controllers/Api/Server.php +++ b/app/Http/Controllers/Api/Server.php @@ -3,6 +3,9 @@ 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; @@ -59,4 +62,106 @@ class Server extends Controller 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); + } } diff --git a/app/Http/Controllers/OauthController.php b/app/Http/Controllers/OauthController.php index 5b17fe926..9569e8cfa 100644 --- a/app/Http/Controllers/OauthController.php +++ b/app/Http/Controllers/OauthController.php @@ -2,8 +2,10 @@ namespace App\Http\Controllers; +use App\Models\InstanceSettings; use App\Models\User; use Illuminate\Support\Facades\Auth; +use Symfony\Component\HttpKernel\Exception\HttpException; class OauthController extends Controller { @@ -20,6 +22,11 @@ class OauthController extends Controller $oauthUser = get_socialite_provider($provider)->user(); $user = User::whereEmail($oauthUser->email)->first(); if (! $user) { + $settings = InstanceSettings::get(); + if (! $settings->is_registration_enabled) { + abort(403, 'Registration is disabled'); + } + $user = User::create([ 'name' => $oauthUser->name, 'email' => $oauthUser->email, @@ -31,7 +38,9 @@ class OauthController extends Controller } catch (\Exception $e) { ray($e->getMessage()); - return redirect()->route('login')->withErrors([__('auth.failed.callback')]); + $errorCode = $e instanceof HttpException ? 'auth.failed' : 'auth.failed.callback'; + + return redirect()->route('login')->withErrors([__($errorCode)]); } } } diff --git a/app/Http/Controllers/Webhook/Stripe.php b/app/Http/Controllers/Webhook/Stripe.php index e404a8ebc..230525c0e 100644 --- a/app/Http/Controllers/Webhook/Stripe.php +++ b/app/Http/Controllers/Webhook/Stripe.php @@ -72,14 +72,14 @@ class Stripe extends Controller } $subscription = Subscription::where('team_id', $teamId)->first(); if ($subscription) { - send_internal_notification('Old subscription activated for team: '.$teamId); + // send_internal_notification('Old subscription activated for team: '.$teamId); $subscription->update([ 'stripe_subscription_id' => $subscriptionId, 'stripe_customer_id' => $customerId, 'stripe_invoice_paid' => true, ]); } else { - send_internal_notification('New subscription for team: '.$teamId); + // send_internal_notification('New subscription for team: '.$teamId); Subscription::create([ 'team_id' => $teamId, 'stripe_subscription_id' => $subscriptionId, @@ -92,7 +92,7 @@ class Stripe extends Controller $customerId = data_get($data, 'customer'); $planId = data_get($data, 'lines.data.0.plan.id'); if (Str::contains($excludedPlans, $planId)) { - send_internal_notification('Subscription excluded.'); + // send_internal_notification('Subscription excluded.'); break; } $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); @@ -108,33 +108,33 @@ class Stripe extends Controller $customerId = data_get($data, 'customer'); $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); if (! $subscription) { - send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); + // send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); return response('No subscription found in Coolify.'); } $team = data_get($subscription, 'team'); if (! $team) { - send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); + // send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); return response('No team found in Coolify.'); } if (! $subscription->stripe_invoice_paid) { SubscriptionInvoiceFailedJob::dispatch($team); - send_internal_notification('Invoice payment failed: '.$customerId); + // send_internal_notification('Invoice payment failed: '.$customerId); } else { - send_internal_notification('Invoice payment failed but already paid: '.$customerId); + // send_internal_notification('Invoice payment failed but already paid: '.$customerId); } break; case 'payment_intent.payment_failed': $customerId = data_get($data, 'customer'); $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); if (! $subscription) { - send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); + // send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); return response('No subscription found in Coolify.'); } if ($subscription->stripe_invoice_paid) { - send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); + // send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); return; } @@ -146,7 +146,7 @@ class Stripe extends Controller $subscriptionId = data_get($data, 'items.data.0.subscription'); $planId = data_get($data, 'items.data.0.plan.id'); if (Str::contains($excludedPlans, $planId)) { - send_internal_notification('Subscription excluded.'); + // send_internal_notification('Subscription excluded.'); break; } $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); @@ -156,11 +156,11 @@ class Stripe extends Controller } if (! $subscription) { if ($status === 'incomplete_expired') { - send_internal_notification('Subscription incomplete expired for customer: '.$customerId); + // send_internal_notification('Subscription incomplete expired for customer: '.$customerId); return response('Subscription incomplete expired', 200); } - send_internal_notification('No subscription found for: '.$customerId); + // send_internal_notification('No subscription found for: '.$customerId); return response('No subscription found', 400); } @@ -194,7 +194,7 @@ class Stripe extends Controller $subscription->update([ 'stripe_invoice_paid' => false, ]); - send_internal_notification('Subscription paused or incomplete for customer: '.$customerId); + // send_internal_notification('Subscription paused or incomplete for customer: '.$customerId); } // Trial ended but subscribed, reactive servers @@ -208,13 +208,13 @@ class Stripe extends Controller if ($comment) { $reason .= ' with comment: \''.$comment."'"; } - send_internal_notification($reason); + // send_internal_notification($reason); } if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) { if ($cancelAtPeriodEnd) { // send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id); } else { - send_internal_notification('customer.subscription.updated for customer: '.$customerId); + // send_internal_notification('customer.subscription.updated for customer: '.$customerId); } } break; @@ -233,7 +233,7 @@ class Stripe extends Controller 'stripe_invoice_paid' => false, 'stripe_trial_already_ended' => true, ]); - send_internal_notification('customer.subscription.deleted for customer: '.$customerId); + // send_internal_notification('customer.subscription.deleted for customer: '.$customerId); break; case 'customer.subscription.trial_will_end': // Not used for now @@ -258,7 +258,7 @@ class Stripe extends Controller 'stripe_invoice_paid' => false, ]); SubscriptionTrialEndedJob::dispatch($team); - send_internal_notification('Subscription paused for customer: '.$customerId); + // send_internal_notification('Subscription paused for customer: '.$customerId); break; default: // Unhandled event type diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index fb577b895..2944b746d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -340,7 +340,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function post_deployment() { if ($this->server->isProxyShouldRun()) { - GetContainersStatus::dispatch($this->server); + GetContainersStatus::dispatch($this->server)->onQueue('high'); // dispatch(new ContainerStatusJob($this->server)); } $this->next(ApplicationDeploymentStatus::FINISHED->value); @@ -828,6 +828,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { $envs->push("COOLIFY_BRANCH={$local_branch}"); } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); + } foreach ($sorted_environment_variables_preview as $env) { $real_value = $env->real_value; if ($env->version === '4.0.0-beta.239') { @@ -869,6 +872,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { $envs->push("COOLIFY_BRANCH={$local_branch}"); } + if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); + } foreach ($sorted_environment_variables as $env) { $real_value = $env->real_value; if ($env->version === '4.0.0-beta.239') { @@ -1863,12 +1869,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); - $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; } else { $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; } $base64_build_command = base64_encode($build_command); @@ -1898,7 +1904,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); ] ); } - $dockerfile = base64_encode("FROM {$this->application->static_image} WORKDIR /usr/share/nginx/html/ LABEL coolify.deploymentId={$this->deployment_uuid} diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index d400642dd..6120d1cba 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -25,8 +25,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue public ApplicationPreview $preview, public ProcessStatus $status, public ?string $deployment_uuid = null - ) { - } + ) {} public function handle() { diff --git a/app/Jobs/CheckLogDrainContainerJob.php b/app/Jobs/CheckLogDrainContainerJob.php index 312200f66..16ef85192 100644 --- a/app/Jobs/CheckLogDrainContainerJob.php +++ b/app/Jobs/CheckLogDrainContainerJob.php @@ -19,9 +19,7 @@ class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { diff --git a/app/Jobs/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php index 8f2039ef2..b55ae9967 100644 --- a/app/Jobs/CheckResaleLicenseJob.php +++ b/app/Jobs/CheckResaleLicenseJob.php @@ -14,9 +14,7 @@ class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/CleanupHelperContainersJob.php b/app/Jobs/CleanupHelperContainersJob.php index 418c7a0f4..7b064a464 100644 --- a/app/Jobs/CleanupHelperContainersJob.php +++ b/app/Jobs/CleanupHelperContainersJob.php @@ -15,9 +15,7 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php index b846ad2bc..d9de3f6fe 100644 --- a/app/Jobs/CleanupInstanceStuffsJob.php +++ b/app/Jobs/CleanupInstanceStuffsJob.php @@ -16,10 +16,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct() - { - - } + public function __construct() {} // public function uniqueId(): string // { diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index c50d17d4c..e919855d5 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -23,9 +23,7 @@ class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue return isDev() ? 1 : 3; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { diff --git a/app/Jobs/CoolifyTask.php b/app/Jobs/CoolifyTask.php index e5f4dfd5e..5418daa22 100755 --- a/app/Jobs/CoolifyTask.php +++ b/app/Jobs/CoolifyTask.php @@ -23,8 +23,7 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue public bool $ignore_errors = false, public $call_event_on_finish = null, public $call_event_data = null - ) { - } + ) {} /** * Execute the job. diff --git a/app/Jobs/DatabaseBackupStatusJob.php b/app/Jobs/DatabaseBackupStatusJob.php index cf240e0d7..d3b0e99cf 100644 --- a/app/Jobs/DatabaseBackupStatusJob.php +++ b/app/Jobs/DatabaseBackupStatusJob.php @@ -18,9 +18,7 @@ class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue public $tries = 1; - public function __construct() - { - } + public function __construct() {} public function handle() { diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 6d4720f6b..8710fda88 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -28,9 +28,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) - { - } + public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {} public function handle() { diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 32c41e99c..e637fb6d4 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -22,9 +22,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public ?int $usageBefore = null; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { @@ -62,7 +60,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue Log::info('No need to clean up '.$this->server->name); } } catch (\Throwable $e) { - send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage()); + // send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage()); ray($e->getMessage()); throw $e; } diff --git a/app/Jobs/GithubAppPermissionJob.php b/app/Jobs/GithubAppPermissionJob.php index bab8f3a25..3188d35d6 100644 --- a/app/Jobs/GithubAppPermissionJob.php +++ b/app/Jobs/GithubAppPermissionJob.php @@ -23,9 +23,7 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue return isDev() ? 1 : 3; } - public function __construct(public GithubApp $github_app) - { - } + public function __construct(public GithubApp $github_app) {} public function middleware(): array { diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php index bce60bbc8..1bbfcf8cb 100644 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ b/app/Jobs/InstanceAutoUpdateJob.php @@ -19,9 +19,7 @@ class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, Should public $tries = 1; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/PullCoolifyImageJob.php b/app/Jobs/PullCoolifyImageJob.php index ccaa785dc..2bcbfc4df 100644 --- a/app/Jobs/PullCoolifyImageJob.php +++ b/app/Jobs/PullCoolifyImageJob.php @@ -19,9 +19,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 1000; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php index d3bda2ea1..30a1b8026 100644 --- a/app/Jobs/PullHelperImageJob.php +++ b/app/Jobs/PullHelperImageJob.php @@ -27,9 +27,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue return $this->server->uuid; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php index 1dd4b1dd3..f8c769382 100644 --- a/app/Jobs/PullSentinelImageJob.php +++ b/app/Jobs/PullSentinelImageJob.php @@ -28,9 +28,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue return $this->server->uuid; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { @@ -52,7 +50,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue } ray('Sentinel image is up to date'); } catch (\Throwable $e) { - send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); + // send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); ray($e->getMessage()); throw $e; } diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php index 948060033..396ff29f4 100644 --- a/app/Jobs/PullTemplatesFromCDN.php +++ b/app/Jobs/PullTemplatesFromCDN.php @@ -17,9 +17,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue public $timeout = 10; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/PullVersionsFromCDN.php b/app/Jobs/PullVersionsFromCDN.php index 1ad4989de..79ebad7a8 100644 --- a/app/Jobs/PullVersionsFromCDN.php +++ b/app/Jobs/PullVersionsFromCDN.php @@ -17,9 +17,7 @@ class PullVersionsFromCDN implements ShouldBeEncrypted, ShouldQueue public $timeout = 10; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php index 4d5618df0..73e8658ee 100755 --- a/app/Jobs/SendConfirmationForWaitlistJob.php +++ b/app/Jobs/SendConfirmationForWaitlistJob.php @@ -14,9 +14,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public string $email, public string $uuid) - { - } + public function __construct(public string $email, public string $uuid) {} public function handle() { diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php index 90f2e0b30..f38cf823c 100644 --- a/app/Jobs/SendMessageToDiscordJob.php +++ b/app/Jobs/SendMessageToDiscordJob.php @@ -31,8 +31,7 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public string $text, public string $webhookUrl - ) { - } + ) {} /** * Execute the job. diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index b81bbc50b..bf52b782f 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -33,8 +33,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue public string $token, public string $chatId, public ?string $topicId = null, - ) { - } + ) {} /** * Execute the job. diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php index 2476c12dd..769dfc004 100644 --- a/app/Jobs/ServerFilesFromServerJob.php +++ b/app/Jobs/ServerFilesFromServerJob.php @@ -16,9 +16,7 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) - { - } + public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {} public function handle() { diff --git a/app/Jobs/ServerLimitCheckJob.php b/app/Jobs/ServerLimitCheckJob.php index 3eaf88ba7..24292025b 100644 --- a/app/Jobs/ServerLimitCheckJob.php +++ b/app/Jobs/ServerLimitCheckJob.php @@ -24,9 +24,7 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue return isDev() ? 1 : 3; } - public function __construct(public Team $team) - { - } + public function __construct(public Team $team) {} public function middleware(): array { diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index aaf8f5784..c7321a74c 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -25,9 +25,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue return isDev() ? 1 : 3; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { @@ -48,7 +46,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue if ($this->server->isFunctional()) { $this->cleanup(notify: false); $this->remove_unnecessary_coolify_yaml(); - if (config('coolify.is_sentinel_enabled')) { + if ($this->server->isSentinelEnabled()) { $this->server->checkSentinel(); } } diff --git a/app/Jobs/ServerStorageSaveJob.php b/app/Jobs/ServerStorageSaveJob.php index c94a3edc5..526cd5375 100644 --- a/app/Jobs/ServerStorageSaveJob.php +++ b/app/Jobs/ServerStorageSaveJob.php @@ -14,9 +14,7 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public LocalFileVolume $localFileVolume) - { - } + public function __construct(public LocalFileVolume $localFileVolume) {} public function handle() { diff --git a/app/Jobs/SubscriptionInvoiceFailedJob.php b/app/Jobs/SubscriptionInvoiceFailedJob.php index e4cd219c8..64a75671f 100755 --- a/app/Jobs/SubscriptionInvoiceFailedJob.php +++ b/app/Jobs/SubscriptionInvoiceFailedJob.php @@ -15,9 +15,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(protected Team $team) - { - } + public function __construct(protected Team $team) {} public function handle() { diff --git a/app/Jobs/SubscriptionTrialEndedJob.php b/app/Jobs/SubscriptionTrialEndedJob.php index ee260d8d9..dd2250dd7 100755 --- a/app/Jobs/SubscriptionTrialEndedJob.php +++ b/app/Jobs/SubscriptionTrialEndedJob.php @@ -17,8 +17,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public Team $team - ) { - } + ) {} public function handle(): void { diff --git a/app/Jobs/SubscriptionTrialEndsSoonJob.php b/app/Jobs/SubscriptionTrialEndsSoonJob.php index fba668108..80e232a3e 100755 --- a/app/Jobs/SubscriptionTrialEndsSoonJob.php +++ b/app/Jobs/SubscriptionTrialEndsSoonJob.php @@ -17,8 +17,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public Team $team - ) { - } + ) {} public function handle(): void { diff --git a/app/Listeners/MaintenanceModeDisabledNotification.php b/app/Listeners/MaintenanceModeDisabledNotification.php index 9f676ca99..ded53ccee 100644 --- a/app/Listeners/MaintenanceModeDisabledNotification.php +++ b/app/Listeners/MaintenanceModeDisabledNotification.php @@ -9,9 +9,7 @@ use Symfony\Component\HttpFoundation\Request as SymfonyRequest; class MaintenanceModeDisabledNotification { - public function __construct() - { - } + public function __construct() {} public function handle(EventsMaintenanceModeDisabled $event): void { diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php index 64271cc52..d0541b162 100644 --- a/app/Listeners/ProxyStartedNotification.php +++ b/app/Listeners/ProxyStartedNotification.php @@ -9,9 +9,7 @@ class ProxyStartedNotification { public Server $server; - public function __construct() - { - } + public function __construct() {} public function handle(ProxyStarted $event): void { diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index b787ed0cc..7acf5ed87 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -12,7 +12,7 @@ use Livewire\Component; class Index extends Component { - protected $listeners = ['serverInstalled' => 'validateServer']; + protected $listeners = ['refreshBoardingIndex' => 'validateServer']; public string $currentState = 'welcome'; diff --git a/app/Livewire/MonacoEditor.php b/app/Livewire/MonacoEditor.php new file mode 100644 index 000000000..156c63d3a --- /dev/null +++ b/app/Livewire/MonacoEditor.php @@ -0,0 +1,51 @@ + '$refresh', + ]; + + public function __construct( + public ?string $id, + public ?string $name, + public ?string $type, + public ?string $monacoContent, + public ?string $value, + public ?string $label, + public ?string $placeholder, + public bool $required, + public bool $disabled, + public bool $readonly, + public bool $allowTab, + public bool $spellcheck, + public ?string $helper, + public bool $realtimeValidation, + public bool $allowToPeak, + public string $defaultClass, + public string $defaultClassInput, + public ?string $language + + ) { + // + } + + public function render() + { + if (is_null($this->id)) { + $this->id = new Cuid2(7); + } + + if (is_null($this->name)) { + $this->name = $this->id; + } + + return view('components.forms.monaco-editor'); + } +} diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 60cdee48e..06ff7b1de 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -347,7 +347,9 @@ class General extends Component public function submit($showToaster = true) { try { - $this->set_redirect(); + if ($this->application->isDirty('redirect')) { + $this->set_redirect(); + } $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index d224f4a9d..feb54c7f0 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -45,7 +45,7 @@ class Heading extends Component public function check_status($showNotification = false) { if ($this->application->destination->server->isFunctional()) { - GetContainersStatus::dispatch($this->application->destination->server); + GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high'); // dispatch(new ContainerStatusJob($this->application->destination->server)); } else { dispatch(new ServerStatusJob($this->application->destination->server)); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index f29cd43ce..df64c3fd3 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -186,7 +186,7 @@ class Previews extends Component instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); } } - GetContainersStatus::dispatchSync($this->application->destination->server); + GetContainersStatus::dispatchSync($this->application->destination->server)->onQueue('high'); $this->dispatch('reloadWindow'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php index 61dafa76f..ae88ac12b 100644 --- a/app/Livewire/Project/Database/Heading.php +++ b/app/Livewire/Project/Database/Heading.php @@ -12,7 +12,6 @@ use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartRedis; use App\Actions\Database\StopDatabase; use App\Actions\Docker\GetContainersStatus; -use App\Jobs\ContainerStatusJob; use Livewire\Component; class Heading extends Component diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 38cac2e5c..1c5d39055 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -25,7 +25,17 @@ class General extends Component public ?string $db_url_public = null; - protected $listeners = ['refresh', 'save_init_script', 'delete_init_script']; + public function getListeners() + { + $userId = auth()->user()->id; + + return [ + "echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped', + 'refresh', + 'save_init_script', + 'delete_init_script', + ]; + } protected $rules = [ 'database.name' => 'required', @@ -69,6 +79,11 @@ class General extends Component $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() { try { diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index b8d186dab..b25290f71 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -176,10 +176,12 @@ class Select extends Component return; } - // if (count($this->servers) === 1) { - // $server = $this->servers->first(); - // $this->setServer($server); - // } + if (count($this->servers) === 1) { + $server = $this->servers->first(); + if ($server instanceof Server) { + $this->setServer($server); + } + } if (! is_null($this->server)) { $foundServer = $this->servers->where('id', $this->server->id)->first(); if ($foundServer) { @@ -195,6 +197,13 @@ class Select extends Component $this->server = $server; $this->standaloneDockers = $server->standaloneDockers; $this->swarmDockers = $server->swarmDockers; + $count = count($this->standaloneDockers) + count($this->swarmDockers); + if ($count === 1) { + $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first(); + if ($docker) { + $this->setDestination($docker->uuid); + } + } $this->current_step = 'destinations'; } diff --git a/app/Livewire/Project/Resource/EnvironmentSelect.php b/app/Livewire/Project/Resource/EnvironmentSelect.php new file mode 100644 index 000000000..efb1b6ca2 --- /dev/null +++ b/app/Livewire/Project/Resource/EnvironmentSelect.php @@ -0,0 +1,35 @@ +selectedEnvironment = request()->route('environment_name'); + $this->project_uuid = request()->route('project_uuid'); + } + + public function updatedSelectedEnvironment($value) + { + if ($value === 'edit') { + return redirect()->route('project.show', [ + 'project_uuid' => $this->project_uuid, + ]); + } else { + return redirect()->route('project.resource.index', [ + 'project_uuid' => $this->project_uuid, + 'environment_name' => $value, + ]); + } + } +} diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index fd4d684b1..c9bdf12fc 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -11,6 +11,8 @@ class EditCompose extends Component public $serviceId; + protected $listeners = ['refreshEnvs' => 'mount']; + protected $rules = [ 'service.docker_compose_raw' => 'required', 'service.docker_compose' => 'required', diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index 05917f895..3f62202c8 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -75,14 +75,12 @@ class StackForm extends Component $this->service->parse(); $this->service->refresh(); $this->service->saveComposeConfigs(); - $this->dispatch('refreshStacks'); $this->dispatch('refreshEnvs'); $this->dispatch('success', 'Service saved.'); } catch (\Throwable $e) { return handleError($e, $this); } finally { if (is_null($this->service->config_hash)) { - ray('asdf'); $this->service->isConfigurationChanged(true); } else { $this->dispatch('configurationChanged'); diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php index 008d743ed..5af0a6a50 100644 --- a/app/Livewire/Project/Shared/Logs.php +++ b/app/Livewire/Project/Shared/Logs.php @@ -59,15 +59,6 @@ class Logs extends Component } } - public function loadMetrics() - { - return; - $server = data_get($this->resource, 'destination.server'); - if ($server->isFunctional()) { - $this->cpu = $server->getMetrics(); - } - } - public function mount() { try { @@ -122,7 +113,6 @@ class Logs extends Component } - $this->loadMetrics(); } catch (\Exception $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Shared/Metrics.php b/app/Livewire/Project/Shared/Metrics.php new file mode 100644 index 000000000..d9d7dd3ef --- /dev/null +++ b/app/Livewire/Project/Shared/Metrics.php @@ -0,0 +1,64 @@ +poll || $this->interval <= 10) { + $this->loadData(); + if ($this->interval > 10) { + $this->poll = false; + } + } + } + + public function loadData() + { + try { + $metrics = $this->resource->getMetrics($this->interval); + $cpuMetrics = collect($metrics)->map(function ($metric) { + return [$metric[0], $metric[1]]; + }); + $memoryMetrics = collect($metrics)->map(function ($metric) { + return [$metric[0], $metric[2]]; + }); + $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ + 'seriesData' => $cpuMetrics, + ]); + $this->dispatch("refreshChartData-{$this->chartId}-memory", [ + 'seriesData' => $memoryMetrics, + ]); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function setInterval() + { + if ($this->interval <= 10) { + $this->poll = true; + } + $this->loadData(); + } + + public function render() + { + return view('livewire.project.shared.metrics'); + } +} diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php index d5d660017..1082f078c 100644 --- a/app/Livewire/Project/Show.php +++ b/app/Livewire/Project/Show.php @@ -9,6 +9,8 @@ class Show extends Component { public Project $project; + public $environments; + public function mount() { $projectUuid = request()->route('project_uuid'); @@ -18,7 +20,8 @@ class Show extends Component if (! $project) { return redirect()->route('dashboard'); } - $project->load(['environments']); + + $this->environments = $project->environments->sortBy('created_at'); $this->project = $project; } diff --git a/app/Livewire/Server/Charts.php b/app/Livewire/Server/Charts.php new file mode 100644 index 000000000..0921c7fa4 --- /dev/null +++ b/app/Livewire/Server/Charts.php @@ -0,0 +1,62 @@ +poll || $this->interval <= 10) { + $this->loadData(); + if ($this->interval > 10) { + $this->poll = false; + } + } + } + + public function loadData() + { + try { + $cpuMetrics = $this->server->getCpuMetrics($this->interval); + $memoryMetrics = $this->server->getMemoryMetrics($this->interval); + $cpuMetrics = collect($cpuMetrics)->map(function ($metric) { + return [$metric[0], $metric[1]]; + }); + $memoryMetrics = collect($memoryMetrics)->map(function ($metric) { + return [$metric[0], $metric[1]]; + }); + $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ + 'seriesData' => $cpuMetrics, + ]); + $this->dispatch("refreshChartData-{$this->chartId}-memory", [ + 'seriesData' => $memoryMetrics, + ]); + + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function setInterval() + { + if ($this->interval <= 10) { + $this->poll = true; + } + $this->loadData(); + } +} diff --git a/app/Livewire/Server/ConfigureCloudflareTunnels.php b/app/Livewire/Server/ConfigureCloudflareTunnels.php index 7d2103e37..f7306a5b5 100644 --- a/app/Livewire/Server/ConfigureCloudflareTunnels.php +++ b/app/Livewire/Server/ConfigureCloudflareTunnels.php @@ -21,7 +21,7 @@ class ConfigureCloudflareTunnels extends Component $server->settings->is_cloudflare_tunnel = true; $server->settings->save(); $this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); - $this->dispatch('serverInstalled'); + $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -37,7 +37,7 @@ class ConfigureCloudflareTunnels extends Component $server->save(); $server->settings->save(); $this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); - $this->dispatch('serverInstalled'); + $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index 263ff6367..5616123a5 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -2,6 +2,9 @@ namespace App\Livewire\Server; +use App\Actions\Server\StartSentinel; +use App\Actions\Server\StopSentinel; +use App\Jobs\PullSentinelImageJob; use App\Models\Server; use Livewire\Component; @@ -36,7 +39,12 @@ class Form extends Component 'server.settings.is_build_server' => 'required|boolean', 'server.settings.concurrent_builds' => 'required|integer|min:1', 'server.settings.dynamic_timeout' => 'required|integer|min:1', + 'server.settings.is_metrics_enabled' => 'required|boolean', + 'server.settings.metrics_token' => 'required', + 'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1', + 'server.settings.metrics_history_days' => 'required|integer|min:1', 'wildcard_domain' => 'nullable|url', + 'server.settings.is_server_api_enabled' => 'required|boolean', ]; protected $validationAttributes = [ @@ -52,7 +60,11 @@ class Form extends Component 'server.settings.is_build_server' => 'Build Server', 'server.settings.concurrent_builds' => 'Concurrent Builds', 'server.settings.dynamic_timeout' => 'Dynamic Timeout', - + 'server.settings.is_metrics_enabled' => 'Metrics', + 'server.settings.metrics_token' => 'Metrics Token', + 'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', + 'server.settings.metrics_history_days' => 'Metrics History', + 'server.settings.is_server_api_enabled' => 'Server API', ]; public function mount() @@ -69,18 +81,59 @@ class Form extends Component public function updatedServerSettingsIsBuildServer() { - $this->dispatch('serverInstalled'); + $this->dispatch('refreshServerShow'); $this->dispatch('serverRefresh'); $this->dispatch('proxyStatusUpdated'); } + public function checkPortForServerApi() + { + try { + if ($this->server->settings->is_server_api_enabled === true) { + $this->server->checkServerApi(); + $this->dispatch('success', 'Server API is reachable.'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function instantSave() { try { refresh_server_connection($this->server->privateKey); $this->validateServer(false); $this->server->settings->save(); + $this->server->save(); $this->dispatch('success', 'Server updated.'); + $this->dispatch('refreshServerShow'); + if ($this->server->isSentinelEnabled()) { + PullSentinelImageJob::dispatchSync($this->server); + ray('Sentinel is enabled'); + if ($this->server->settings->isDirty('is_metrics_enabled')) { + $this->dispatch('reloadWindow'); + } + if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { + ray('Starting sentinel'); + + } + } else { + ray('Sentinel is not enabled'); + StopSentinel::dispatch($this->server); + } + // $this->checkPortForServerApi(); + + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function restartSentinel() + { + try { + $version = get_latest_sentinel_version(); + StartSentinel::run($this->server, $version, true); + $this->dispatch('success', 'Sentinel restarted.'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index 7ebf90115..0751b186e 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -14,7 +14,7 @@ class Show extends Component public $parameters = []; - protected $listeners = ['serverInstalled' => '$refresh']; + protected $listeners = ['refreshServerShow' => '$refresh']; public function mount() { diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index bd33937e0..422cae779 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -143,7 +143,8 @@ class ValidateAndInstall extends Component } else { $this->docker_version = $this->server->validateDockerEngineVersion(); if ($this->docker_version) { - $this->dispatch('serverInstalled'); + $this->dispatch('refreshServerShow'); + $this->dispatch('refreshBoardingIndex'); $this->dispatch('success', 'Server validated.'); } else { $this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: documentation.'; diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php index 4dfa16e30..3b6d7cd72 100644 --- a/app/Livewire/Settings/Configuration.php +++ b/app/Livewire/Settings/Configuration.php @@ -29,6 +29,7 @@ class Configuration extends Component 'settings.public_port_min' => 'required', 'settings.public_port_max' => 'required', 'settings.custom_dns_servers' => 'nullable', + 'settings.instance_name' => 'nullable', ]; protected $validationAttributes = [ diff --git a/app/Models/Application.php b/app/Models/Application.php index f2a7ce51c..50c7760ff 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -228,7 +228,7 @@ class Application extends BaseModel public function gitCommitLink($link): string { - if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { + if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) { if (str($this->source->html_url)->contains('bitbucket')) { return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}"; } @@ -245,8 +245,11 @@ class Application extends BaseModel } if (strpos($this->git_repository, 'git@') === 0) { $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + if (data_get($this, 'source.html_url')) { + return "{$this->source->html_url}/{$git_repository}/commit/{$link}"; + } - return "https://{$git_repository}/commit/{$link}"; + return "{$git_repository}/commit/{$link}"; } return $this->git_repository; @@ -1167,4 +1170,44 @@ class Application extends BaseModel return $preview; } + + public static function getDomainsByUuid(string $uuid): array + { + $application = self::where('uuid', $uuid)->first(); + + if ($application) { + return $application->fqdns; + } + + return []; + } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/Environment.php b/app/Models/Environment.php index e84b6989b..b2bb51092 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -109,7 +109,7 @@ class Environment extends Model protected function name(): Attribute { return Attribute::make( - set: fn (string $value) => strtolower($value), + set: fn (string $value) => str($value)->lower()->trim()->replace('/', '-')->toString(), ); } } diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index 452c5ca22..38f79ce75 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -47,4 +47,14 @@ class InstanceSettings extends Model implements SendsEmail return explode(',', $recipients); } + + public function getTitleDisplayName(): string + { + $instanceName = $this->instance_name; + if (! $instanceName) { + return ''; + } + + return "[{$instanceName}]"; + } } diff --git a/app/Models/Kubernetes.php b/app/Models/Kubernetes.php index 2ad7a2110..174cb5bc8 100644 --- a/app/Models/Kubernetes.php +++ b/app/Models/Kubernetes.php @@ -2,6 +2,4 @@ namespace App\Models; -class Kubernetes extends BaseModel -{ -} +class Kubernetes extends BaseModel {} diff --git a/app/Models/Project.php b/app/Models/Project.php index acc98e341..b7d34e876 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -112,4 +112,18 @@ class Project extends BaseModel { return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get()); } + + public function default_environment() + { + $default = $this->environments()->where('name', 'production')->first(); + if ($default) { + return $default->name; + } + $default = $this->environments()->get(); + if ($default->count() > 0) { + return $default->sortBy('created_at')->first()->name; + } + + return null; + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index b1419dc0e..7a99940fd 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -5,12 +5,11 @@ namespace App\Models; use App\Actions\Server\InstallDocker; use App\Enums\ProxyTypes; use App\Jobs\PullSentinelImageJob; -use App\Notifications\Server\Revived; -use App\Notifications\Server\Unreachable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Illuminate\Support\Stringable; @@ -462,10 +461,44 @@ $schema://$host { Storage::disk('ssh-mux')->delete($this->muxFilename()); } + public function isSentinelEnabled() + { + return $this->isMetricsEnabled() || $this->isServerApiEnabled(); + } + + public function isMetricsEnabled() + { + return $this->settings->is_metrics_enabled; + } + + public function isServerApiEnabled() + { + return $this->settings->is_server_api_enabled; + } + + public function checkServerApi() + { + if ($this->isServerApiEnabled()) { + $server_ip = $this->ip; + if (isDev()) { + if ($this->id === 0) { + $server_ip = 'localhost'; + } + } + $command = "curl -s http://{$server_ip}:12172/api/health"; + $process = Process::timeout(5)->run($command); + if ($process->failed()) { + ray($process->exitCode(), $process->output(), $process->errorOutput()); + throw new \Exception("Server API is not reachable on http://{$server_ip}:12172"); + } + + } + } + public function checkSentinel() { ray("Checking sentinel on server: {$this->name}"); - if ($this->is_metrics_enabled) { + if ($this->isSentinelEnabled()) { $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false); $sentinel_found = json_decode($sentinel_found, true); $status = data_get($sentinel_found, '0.State.Status', 'exited'); @@ -478,21 +511,57 @@ $schema://$host { } } - public function getMetrics() + public function getCpuMetrics(int $mins = 5) { - if ($this->is_metrics_enabled) { - $from = now()->subMinutes(5)->toIso8601ZuluString(); - $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl http://localhost:8888/api/cpu/history?from=$from'"], $this, false); + if ($this->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false); + if (str($cpu)->contains('error')) { + $error = json_decode($cpu, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } $cpu = str($cpu)->explode("\n")->skip(1)->all(); $parsedCollection = collect($cpu)->flatMap(function ($item) { return collect(explode("\n", trim($item)))->map(function ($line) { - [$time, $value] = explode(',', trim($line)); + [$time, $cpu_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 0); - return [(int) $time, (float) $value]; + return [(int) $time, (float) $cpu_usage_percent]; }); - })->toArray(); + }); - return $parsedCollection; + return $parsedCollection->toArray(); + } + } + + public function getMemoryMetrics(int $mins = 5) + { + if ($this->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false); + if (str($memory)->contains('error')) { + $error = json_decode($memory, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $memory = str($memory)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($memory)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $used, $free, $usedPercent] = explode(',', trim($line)); + $usedPercent = number_format($usedPercent, 0); + + return [(int) $time, (float) $usedPercent]; + }); + }); + + return $parsedCollection->toArray(); } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 7851eb58a..05c380a4c 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; +use Symfony\Component\Yaml\Yaml; class Service extends BaseModel { @@ -837,14 +838,38 @@ class Service extends BaseModel $commands[] = "mkdir -p $workdir"; $commands[] = "cd $workdir"; + $json = Yaml::parse($this->docker_compose); + $envs_from_coolify = $this->environment_variables()->get(); + // foreach ($json['services'] as $service => $config) { + // if (data_get($config, 'environment') === null) { + // data_set($json, "services.$service.environment", []); + // $envs = collect([]); + // } else { + // $envs = collect($config['environment']); + // } + // // $envs->put('COOLIFY_CONTAINER_NAME', "$service-{$this->uuid}"); + // foreach ($envs_from_coolify as $env) { + // $envs = $envs->map(function ($value) use ($env) { + // if (str($value)->startsWith($env->key)) { + // return "{$env->key}={$env->real_value}"; + // } + + // return $value; + // }); + // } + // $envs = $envs->unique(); + // data_set($json, "services.$service.environment", $envs->toArray()); + // } + $this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); $docker_compose_base64 = base64_encode($this->docker_compose); + $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; - $envs = $this->environment_variables()->get(); $commands[] = 'rm -f .env || true'; - foreach ($envs as $env) { + + foreach ($envs_from_coolify as $env) { $commands[] = "echo '{$env->key}={$env->real_value}' >> .env"; } - if ($envs->count() === 0) { + if ($envs_from_coolify->count() === 0) { $commands[] = 'touch .env'; } instant_remote_process($commands, $this->server); diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index c5e252c34..e968db18d 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -226,4 +226,33 @@ class StandaloneClickhouse extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 8c739d984..c6718acfe 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -226,4 +226,33 @@ class StandaloneDragonfly extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 5216681c9..142f960aa 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -226,4 +226,33 @@ class StandaloneKeydb extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 33fd2cbc2..7e6d2e0d1 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -226,4 +226,33 @@ class StandaloneMariadb extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 0cc52b3f7..df895bb34 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -246,4 +246,33 @@ class StandaloneMongodb extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 174736f77..bd160f877 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -227,4 +227,33 @@ class StandaloneMysql extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index a5bf4dc2a..114d376e8 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -227,4 +227,33 @@ class StandalonePostgresql extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index ed379750e..022cd8d09 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -222,4 +222,33 @@ class StandaloneRedis extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index a55f16a83..86c1e6e69 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -14,9 +14,7 @@ class ContainerRestarted extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $name, public Server $server, public ?string $url = null) - { - } + public function __construct(public string $name, public Server $server, public ?string $url = null) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index d9dc57b98..75b4872cb 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -14,9 +14,7 @@ class ContainerStopped extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $name, public Server $server, public ?string $url = null) - { - } + public function __construct(public string $name, public Server $server, public ?string $url = null) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Database/DailyBackup.php b/app/Notifications/Database/DailyBackup.php index c74676eb7..90abee8a6 100644 --- a/app/Notifications/Database/DailyBackup.php +++ b/app/Notifications/Database/DailyBackup.php @@ -16,9 +16,7 @@ class DailyBackup extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public $databases) - { - } + public function __construct(public $databases) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index 6acd770f6..1d4d648c8 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -14,9 +14,7 @@ class GeneralNotification extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $message) - { - } + public function __construct(public string $message) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index 0e445f035..f8195ec1d 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -15,9 +15,7 @@ class DockerCleanup extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public string $message) - { - } + public function __construct(public Server $server, public string $message) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index 960a7c79f..9a76558e2 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -17,9 +17,7 @@ class ForceDisabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 6a4b5d74b..a43e30376 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -17,9 +17,7 @@ class ForceEnabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 5f63ef8f1..a6e248170 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -17,9 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) - { - } + public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php index e7d3baf3e..8eaadf359 100644 --- a/app/Notifications/Server/Revived.php +++ b/app/Notifications/Server/Revived.php @@ -24,7 +24,7 @@ class Revived extends Notification implements ShouldQueue if ($this->server->unreachable_notification_sent === false) { return; } - GetContainersStatus::dispatch($server); + GetContainersStatus::dispatch($server)->onQueue('high'); // dispatch(new ContainerStatusJob($server)); } diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 2dcfe28b8..ebbd6af77 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -17,10 +17,7 @@ class Unreachable extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) - { - - } + public function __construct(public Server $server) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 925859aba..f873a95d3 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -13,9 +13,7 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) - { - } + public function __construct(public ?string $emails = null) {} public function via(object $notifiable): array { diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index a251b47ea..49d2ad487 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -22,9 +22,7 @@ class InvitationLink extends Notification implements ShouldQueue return [TransactionalEmailChannel::class]; } - public function __construct(public User $user) - { - } + public function __construct(public User $user) {} public function toMail(): MailMessage { diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index ed30c1883..a417e1ee5 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -14,9 +14,7 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public string $emails) - { - } + public function __construct(public string $emails) {} public function via(): array { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1bce22c12..6822dec13 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,9 +9,7 @@ use Laravel\Sanctum\Sanctum; class AppServiceProvider extends ServiceProvider { - public function register(): void - { - } + public function register(): void {} public function boot(): void { diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index 36c07dae1..35448d5e5 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -22,8 +22,7 @@ class Input extends Component public bool $allowToPeak = true, public bool $isMultiline = false, public string $defaultClass = 'input', - ) { - } + ) {} public function render(): View|Closure|string { diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php index bfdf03a31..7d1860500 100644 --- a/app/View/Components/Forms/Textarea.php +++ b/app/View/Components/Forms/Textarea.php @@ -19,6 +19,8 @@ class Textarea extends Component public ?string $value = null, public ?string $label = null, public ?string $placeholder = null, + public ?string $monacoEditorLanguage = '', + public bool $useMonacoEditor = false, public bool $required = false, public bool $disabled = false, public bool $readonly = false, diff --git a/app/View/Components/ResourceView.php b/app/View/Components/ResourceView.php index 5a11b159d..d1107465b 100644 --- a/app/View/Components/ResourceView.php +++ b/app/View/Components/ResourceView.php @@ -16,9 +16,7 @@ class ResourceView extends Component public ?string $logo = null, public ?string $documentation = null, public bool $upgrade = false, - ) { - - } + ) {} /** * Get the view / contents that represent the component. diff --git a/app/View/Components/Status/Index.php b/app/View/Components/Status/Index.php index f8436a102..ada9eb682 100644 --- a/app/View/Components/Status/Index.php +++ b/app/View/Components/Status/Index.php @@ -14,8 +14,7 @@ class Index extends Component public function __construct( public $resource = null, public bool $showRefreshButton = true, - ) { - } + ) {} /** * Get the view / contents that represent the component. diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index 999de45c2..c278a5045 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -1,5 +1,7 @@ user()->currentAccessToken(); @@ -10,3 +12,27 @@ function invalid_token() { return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400); } + +function serialize_api_response($data) +{ + if (! $data instanceof Collection) { + $data = collect($data); + } + $data = $data->sortKeys(); + $created_at = data_get($data, 'created_at'); + $updated_at = data_get($data, 'updated_at'); + if ($created_at) { + unset($data['created_at']); + $data['created_at'] = $created_at; + + } + if ($updated_at) { + unset($data['updated_at']); + $data['updated_at'] = $updated_at; + } + if (data_get($data, 'id')) { + $data = $data->prepend($data['id'], 'id'); + } + + return $data; +} diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 816a13853..1a08a46eb 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -8,7 +8,7 @@ use App\Models\Server; use App\Models\StandaloneDocker; use Spatie\Url\Url; -function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) +function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $is_api = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) { $application_id = $application->id; $deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}"); @@ -35,6 +35,7 @@ function queue_application_deployment(Application $application, string $deployme 'pull_request_id' => $pull_request_id, 'force_rebuild' => $force_rebuild, 'is_webhook' => $is_webhook, + 'is_api' => $is_api, 'restart_only' => $restart_only, 'commit' => $commit, 'rollback' => $rollback, @@ -45,11 +46,11 @@ function queue_application_deployment(Application $application, string $deployme if ($no_questions_asked) { dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, - )); + ))->onQueue('high'); } elseif (next_queuable($server_id, $application_id)) { dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, - )); + ))->onQueue('high'); } } function force_start_deployment(ApplicationDeploymentQueue $deployment) @@ -60,7 +61,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment) dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, - )); + ))->onQueue('high'); } function queue_next_deployment(Application $application) { @@ -73,7 +74,7 @@ function queue_next_deployment(Application $application) dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $next_found->id, - )); + ))->onQueue('high'); } } @@ -114,7 +115,7 @@ function next_after_cancel(?Server $server = null) dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $next->id, - )); + ))->onQueue('high'); } break; } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 91e553cf6..06416db24 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -458,7 +458,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares = collect([]); if ($is_stripprefix_enabled && ! str($image)->contains('ghost')) { $labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}"); - $middlewares->push("{$https_label}-stripprefix"); + $middlewares->push("{$http_label}-stripprefix"); } if ($is_gzip_enabled) { $middlewares->push('gzip'); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7994c10af..aef362491 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -79,6 +79,10 @@ function backup_dir(): string { return base_configuration_dir().'/backups'; } +function metrics_dir(): string +{ + return base_configuration_dir().'/metrics'; +} function generate_readme_file(string $name, string $updated_at): string { @@ -158,10 +162,10 @@ function get_route_parameters(): array function get_latest_sentinel_version(): string { try { - $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); + $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); $versions = $response->json(); - return data_get($versions, 'coolify.sentinel.version'); + return data_get($versions, 'sentinel.version'); } catch (\Throwable $e) { //throw $e; ray($e->getMessage()); @@ -1250,6 +1254,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ]); } } + $envs_from_coolify = $resource->environment_variables()->get(); + $serviceVariables = $serviceVariables->map(function ($variable) use ($envs_from_coolify) { + $env_variable_key = str($variable)->before('='); + $env_variable_value = str($variable)->after('='); + $found_env = $envs_from_coolify->where('key', $env_variable_key)->first(); + if ($found_env) { + $env_variable_value = $found_env->value; + } + + return "$env_variable_key=$env_variable_value"; + }); + } // Add labels to the service if ($savedService->serviceType()) { @@ -1314,19 +1330,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_forget($service, 'volumes.*.isDirectory'); data_forget($service, 'volumes.*.is_directory'); data_forget($service, 'exclude_from_hc'); - - // Remove unnecessary variables from service.environment - // $withoutServiceEnvs = collect([]); - // collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) { - // ray($key, $value); - // if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) { - // $k = Str::of($value)->before("="); - // $v = Str::of($value)->after("="); - // $withoutServiceEnvs->put($k->value(), $v->value()); - // } - // }); - // ray($withoutServiceEnvs); - // data_set($service, 'environment', $withoutServiceEnvs->toArray()); + data_set($service, 'environment', $serviceVariables->toArray()); updateCompose($savedService); return $service; @@ -2282,3 +2286,10 @@ function isAnyDeploymentInprogress() echo "No deployments in progress.\n"; exit(0); } + +function generateSentinelToken() +{ + $token = Str::random(64); + + return $token; +} diff --git a/config/constants.php b/config/constants.php index 444d144a8..861b645ed 100644 --- a/config/constants.php +++ b/config/constants.php @@ -22,8 +22,8 @@ return [ ], 'services' => [ // Temporary disabled until cache is implemented - 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', - // 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json', + // 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', + 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json', ], 'limits' => [ 'trial_period' => 0, diff --git a/config/coolify.php b/config/coolify.php index c7cfe6101..a6d6d8581 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -14,5 +14,4 @@ return [ 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), 'is_horizon_enabled' => env('HORIZON_ENABLED', true), 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), - 'is_sentinel_enabled' => env('SENTINEL_ENABLED', false), ]; diff --git a/config/horizon.php b/config/horizon.php index ef7df3f1b..939d74883 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -182,7 +182,7 @@ return [ 'defaults' => [ 's6' => [ 'connection' => 'redis', - 'queue' => ['default'], + 'queue' => ['high', 'default'], 'balance' => env('HORIZON_BALANCE', 'auto'), 'maxTime' => 0, 'maxJobs' => 0, diff --git a/config/sentry.php b/config/sentry.php index caa659921..17e51ddb7 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.298', + 'release' => '4.0.0-beta.304', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index ddcd3f2d4..1221a8428 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ dropColumn('is_metrics_enabled'); + }); + Schema::table('server_settings', function (Blueprint $table) { + $table->boolean('is_metrics_enabled')->default(false); + $table->integer('metrics_refresh_rate_seconds')->default(5); + $table->integer('metrics_history_days')->default(30); + $table->string('metrics_token')->default(generateSentinelToken()); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->boolean('is_metrics_enabled')->default(true); + }); + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('is_metrics_enabled'); + $table->dropColumn('metrics_refresh_rate_seconds'); + $table->dropColumn('metrics_history_days'); + $table->dropColumn('metrics_token'); + }); + } +}; diff --git a/database/migrations/2024_06_20_102551_add_server_api_sentinel.php b/database/migrations/2024_06_20_102551_add_server_api_sentinel.php new file mode 100644 index 000000000..b840195af --- /dev/null +++ b/database/migrations/2024_06_20_102551_add_server_api_sentinel.php @@ -0,0 +1,28 @@ +boolean('is_server_api_enabled')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('is_server_api_enabled'); + }); + } +}; diff --git a/database/migrations/2024_06_21_143358_add_api_deployment_type.php b/database/migrations/2024_06_21_143358_add_api_deployment_type.php new file mode 100644 index 000000000..03f4d4796 --- /dev/null +++ b/database/migrations/2024_06_21_143358_add_api_deployment_type.php @@ -0,0 +1,28 @@ +boolean('is_api')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('is_api'); + }); + } +}; diff --git a/database/migrations/2024_06_22_081140_alter_instance_settings_add_instance_name.php b/database/migrations/2024_06_22_081140_alter_instance_settings_add_instance_name.php new file mode 100644 index 000000000..1687e047c --- /dev/null +++ b/database/migrations/2024_06_22_081140_alter_instance_settings_add_instance_name.php @@ -0,0 +1,28 @@ +string('instance_name')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('instance_name'); + }); + } +}; diff --git a/database/seeders/EnvironmentSeeder.php b/database/seeders/EnvironmentSeeder.php index 0e980f22b..1c6d562a9 100644 --- a/database/seeders/EnvironmentSeeder.php +++ b/database/seeders/EnvironmentSeeder.php @@ -9,7 +9,5 @@ class EnvironmentSeeder extends Seeder /** * Run the database seeds. */ - public function run(): void - { - } + public function run(): void {} } diff --git a/lang/ar.json b/lang/ar.json new file mode 100644 index 000000000..c5ec96c8d --- /dev/null +++ b/lang/ar.json @@ -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": "أمثلة
للمستودعات العامة، استخدم https://....
للمستودعات الخاصة، استخدم git@....

سيتم تحديد الفرع main لـ https://github.com/coollabsio/coolify-examples
سيتم تحديد الفرع nodejs-fastify لـ https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify
سيتم تحديد الفرع main لـ https://gitea.com/sedlav/expressjs.git
سيتم تحديد الفرع main لـ https://gitlab.com/andrasbacsai/nodejs-example.git." +} diff --git a/lang/cs.json b/lang/cs.json new file mode 100644 index 000000000..48b47b06a --- /dev/null +++ b/lang/cs.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Přihlásit se", + "auth.login.azure": "Přihlásit se pomocí Microsoftu", + "auth.login.bitbucket": "Přihlásit se pomocí Bitbucketu", + "auth.login.github": "Přihlásit se pomocí GitHubu", + "auth.login.gitlab": "Přihlásit se pomocí Gitlabu", + "auth.login.google": "Přihlásit se pomocí Google", + "auth.already_registered": "Již jste registrováni?", + "auth.confirm_password": "Potvrďte heslo", + "auth.forgot_password": "Zapomněli jste heslo", + "auth.forgot_password_send_email": "Poslat e-mail pro resetování hesla", + "auth.register_now": "Registrovat se", + "auth.logout": "Odhlásit se", + "auth.register": "Registrovat se", + "auth.registration_disabled": "Registrace je zakázána. Kontaktujte prosím administrátora.", + "auth.reset_password": "Obnovit heslo", + "auth.failed": "Tyto údaje neodpovídají našim záznamům.", + "auth.failed.callback": "Nepodařilo se zpracovat zpětné volání od poskytovatele přihlášení.", + "auth.failed.password": "Zadané heslo je nesprávné.", + "auth.failed.email": "Nemůžeme najít uživatele s touto e-mailovou adresou.", + "auth.throttle": "Příliš mnoho pokusů o přihlášení. Zkuste to prosím znovu za :seconds sekund.", + "input.name": "Jméno", + "input.email": "E-mail", + "input.password": "Heslo", + "input.password.again": "Heslo znovu", + "input.code": "Jednorázový kód", + "input.recovery_code": "Obnovovací kód", + "button.save": "Uložit", + "repository.url": "Příklady
Pro veřejné repozitáře, použijte https://....
Pro soukromé repozitáře, použijte git@....

https://github.com/coollabsio/coolify-examples main branch bude zvolena
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch bude vybrána.
https://gitea.com/sedlav/expressjs.git main branch vybrána.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch bude vybrána." +} diff --git a/lang/de.json b/lang/de.json new file mode 100644 index 000000000..29fec629f --- /dev/null +++ b/lang/de.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Anmelden", + "auth.login.azure": "Mit Microsoft anmelden", + "auth.login.bitbucket": "Mit Bitbucket anmelden", + "auth.login.github": "Mit GitHub anmelden", + "auth.login.gitlab": "Mit GitLab anmelden", + "auth.login.google": "Mit Google anmelden", + "auth.already_registered": "Bereits registriert?", + "auth.confirm_password": "Passwort bestätigen", + "auth.forgot_password": "Passwort vergessen", + "auth.forgot_password_send_email": "Passwort zurücksetzen E-Mail senden", + "auth.register_now": "Registrieren", + "auth.logout": "Abmelden", + "auth.register": "Registrieren", + "auth.registration_disabled": "Registrierung ist deaktiviert. Bitte kontaktiere einen Administrator.", + "auth.reset_password": "Passwort zurücksetzen", + "auth.failed": "Diese Anmeldedaten wurden nicht gefunden.", + "auth.failed.callback": "Fehlerhafte Verarbeitung der Antwort des Anmeldeanbieters.", + "auth.failed.password": "Das angegebene Passwort ist inkorrekt.", + "auth.failed.email": "Wir können keinen Benutzer mit dieser E-Mail Adresse finden.", + "auth.throttle": "Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.", + "input.name": "Name", + "input.email": "E-Mail", + "input.password": "Passwort", + "input.password.again": "Passwort wiederholen", + "input.code": "Einmalcode", + "input.recovery_code": "Wiederherstellungscode", + "button.save": "Speichern", + "repository.url": "Beispiele
Für öffentliche Repositories benutze https://....
Für private Repositories benutze git@....

https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt." +} diff --git a/lang/es.json b/lang/es.json new file mode 100644 index 000000000..0d8c0c940 --- /dev/null +++ b/lang/es.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Iniciar Sesión", + "auth.login.azure": "Acceder con Microsoft", + "auth.login.bitbucket": "Acceder con Bitbucket", + "auth.login.github": "Acceder con GitHub", + "auth.login.gitlab": "Acceder con Gitlab", + "auth.login.google": "Acceder con Google", + "auth.already_registered": "¿Ya estás registrado?", + "auth.confirm_password": "Confirmar contraseña", + "auth.forgot_password": "¿Olvidaste tu contraseña?", + "auth.forgot_password_send_email": "Enviar correo de recuperación de contraseña", + "auth.register_now": "Registrar", + "auth.logout": "Cerrar sesión", + "auth.register": "Registrar", + "auth.registration_disabled": "El registro está desactivado. Por favor contacta con el administrador.", + "auth.reset_password": "Cambiar contraseña", + "auth.failed": "Las credenciales no coinciden con nuestro registro..", + "auth.failed.callback": "Falló el proceso de inicio de sesión con el proveedor.", + "auth.failed.password": "La contraseña es incorrecta.", + "auth.failed.email": "No encontramos un usuario con ese correo.", + "auth.throttle": "Demasiados intentos. Por favor inténtalo en :seconds segundos.", + "input.name": "Nombre", + "input.email": "Correo", + "input.password": "Contraseña", + "input.password.again": "Escribe la contraseña otra vez", + "input.code": "Código de único uso", + "input.recovery_code": "Código de recuperación", + "button.save": "Guardar", + "repository.url": "Examples
Para repositorios públicos, usar https://....
Para repositorios privados, usar git@....

https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." +} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json new file mode 100644 index 000000000..ae7fa0a03 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Connexion", + "auth.login.azure": "Connexion avec Microsoft", + "auth.login.bitbucket": "Connexion avec Bitbucket", + "auth.login.github": "Connexion avec GitHub", + "auth.login.gitlab": "Connexion avec Gitlab", + "auth.login.google": "Connexion avec Google", + "auth.already_registered": "Déjà enregistré ?", + "auth.confirm_password": "Confirmer le mot de passe", + "auth.forgot_password": "Mot de passe oublié", + "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe", + "auth.register_now": "S'enregistrer", + "auth.logout": "Déconnexion", + "auth.register": "S'enregistrer", + "auth.registration_disabled": "L'enregistrement est désactivé. Merci de contacter l'administateur.", + "auth.reset_password": "Réinitialiser le mot de passe", + "auth.failed": "Aucune correspondance n'a été trouvé pour les informations d'identification renseignées.", + "auth.failed.callback": "Erreur lors du processus de retour de la plateforme de connexion.", + "auth.failed.password": "Le mot de passe renseigné est incorrect.", + "auth.failed.email": "Aucun utilisateur avec cette adresse email n'a été trouvé.", + "auth.throttle": "Trop de tentatives de connexion. Merci de réessayer dans :seconds secondes.", + "input.name": "Nom", + "input.email": "Email", + "input.password": "Mot de passe", + "input.password.again": "Mot de passe identique", + "input.code": "Code à usage unique", + "input.recovery_code": "Code de récupération", + "button.save": "Sauvegarder", + "repository.url": "Exemples
Pour les dépôts publiques, utilisez https://....
Pour les dépôts privés, utilisez git@....

https://github.com/coollabsio/coolify-examples main sera la branche selectionnée
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify sera la branche selectionnée.
https://gitea.com/sedlav/expressjs.git main sera la branche selectionnée.
https://gitlab.com/andrasbacsai/nodejs-example.git main sera la branche selectionnée." +} diff --git a/lang/it.json b/lang/it.json new file mode 100644 index 000000000..6e4feb9cc --- /dev/null +++ b/lang/it.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Accedi", + "auth.login.azure": "Accedi con Microsoft", + "auth.login.bitbucket": "Accedi con Bitbucket", + "auth.login.github": "Accedi con GitHub", + "auth.login.gitlab": "Accedi con Gitlab", + "auth.login.google": "Accedi con Google", + "auth.already_registered": "Già registrato?", + "auth.confirm_password": "Conferma password", + "auth.forgot_password": "Password dimenticata", + "auth.forgot_password_send_email": "Invia email per reimpostare la password", + "auth.register_now": "Registrati", + "auth.logout": "Esci", + "auth.register": "Registrati", + "auth.registration_disabled": "La registrazione è disabilitata. Si prega di contattare l'amministratore.", + "auth.reset_password": "Reimposta password", + "auth.failed": "Queste credenziali non corrispondono ai nostri record.", + "auth.failed.callback": "Errore durante l'elaborazione del callback dal provider di accesso.", + "auth.failed.password": "La password fornita non è corretta.", + "auth.failed.email": "Non possiamo trovare un utente con questo indirizzo email.", + "auth.throttle": "Troppi tentativi di accesso. Per favore riprova tra :seconds secondi.", + "input.name": "Nome", + "input.email": "Email", + "input.password": "Password", + "input.password.again": "Ripeti password", + "input.code": "Codice monouso", + "input.recovery_code": "Codice di recupero", + "button.save": "Salva", + "repository.url": "Esempi
Per i repository pubblici, utilizza https://....
Per i repository privati, utilizza git@....

https://github.com/coollabsio/coolify-examples verrà selezionato il branch main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify verrà selezionato il branch nodejs-fastify.
https://gitea.com/sedlav/expressjs.git verrà selezionato il branch main.
https://gitlab.com/andrasbacsai/nodejs-example.git verrà selezionato il branch main." +} diff --git a/lang/ja.json b/lang/ja.json new file mode 100644 index 000000000..4652a3b17 --- /dev/null +++ b/lang/ja.json @@ -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": "
公開リポジトリの場合はhttps://...を使用してください。
プライベートリポジトリの場合はgit@...を使用してください。

https://github.com/coollabsio/coolify-examples mainブランチが選択されます
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastifyブランチが選択されます。
https://gitea.com/sedlav/expressjs.git mainブランチが選択されます。
https://gitlab.com/andrasbacsai/nodejs-example.git mainブランチが選択されます。" +} diff --git a/lang/pt.json b/lang/pt.json new file mode 100644 index 000000000..b5dd5c434 --- /dev/null +++ b/lang/pt.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Entrar", + "auth.login.azure": "Entrar com Microsoft", + "auth.login.bitbucket": "Entrar com Bitbucket", + "auth.login.github": "Entrar com GitHub", + "auth.login.gitlab": "Entrar com Gitlab", + "auth.login.google": "Entrar com Google", + "auth.already_registered": "Já tem uma conta?", + "auth.confirm_password": "Confirmar senha", + "auth.forgot_password": "Esqueceu a senha?", + "auth.forgot_password_send_email": "Enviar e-mail de redefinição de senha", + "auth.register_now": "Cadastrar-se", + "auth.logout": "Sair", + "auth.register": "Cadastrar", + "auth.registration_disabled": "Cadastro desativado. Por favor, entre em contato com o administrador.", + "auth.reset_password": "Redefinir senha", + "auth.failed": "Essas credenciais não correspondem aos nossos registros.", + "auth.failed.callback": "Falha ao processar o callback do provedor de login.", + "auth.failed.password": "A senha fornecida está incorreta.", + "auth.failed.email": "Não encontramos um usuário com esse endereço de e-mail.", + "auth.throttle": "Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.", + "input.name": "Nome", + "input.email": "E-mail", + "input.password": "Senha", + "input.password.again": "Repetir senha", + "input.code": "Código único", + "input.recovery_code": "Código de recuperação", + "button.save": "Salvar", + "repository.url": "Exemplos
Para repositórios públicos, use https://....
Para repositórios privados, use git@....

https://github.com/coollabsio/coolify-examples a branch main será selecionada
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify a branch nodejs-fastify será selecionada.
https://gitea.com/sedlav/expressjs.git a branch main será selecionada.
https://gitlab.com/andrasbacsai/nodejs-example.git a branch main será selecionada." +} diff --git a/lang/tr.json b/lang/tr.json new file mode 100644 index 000000000..255b0d15b --- /dev/null +++ b/lang/tr.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Giriş", + "auth.login.azure": "Microsoft ile Giriş Yap", + "auth.login.bitbucket": "Bitbucket ile Giriş Yap", + "auth.login.github": "GitHub ile Giriş Yap", + "auth.login.gitlab": "GitLab ile Giriş Yap", + "auth.login.google": "Google ile Giriş Yap", + "auth.already_registered": "Zaten kayıtlı mısınız?", + "auth.confirm_password": "Şifreyi Onayla", + "auth.forgot_password": "Şifremi Unuttum", + "auth.forgot_password_send_email": "Şifre sıfırlama e-postası gönder", + "auth.register_now": "Kayıt Ol", + "auth.logout": "Çıkış Yap", + "auth.register": "Kayıt Ol", + "auth.registration_disabled": "Kayıt devre dışı bırakıldı. Lütfen yöneticiyle iletişime geçin.", + "auth.reset_password": "Şifreyi Sıfırla", + "auth.failed": "Bu kimlik bilgileri kayıtlarımızla eşleşmiyor.", + "auth.failed.callback": "Giriş sağlayıcıdan gelen istek işlenemedi.", + "auth.failed.password": "Sağlanan şifre yanlış.", + "auth.failed.email": "Bu e-posta adresiyle bir kullanıcı bulamıyoruz.", + "auth.throttle": "Çok fazla giriş denemesi. Lütfen :seconds saniye sonra tekrar deneyin.", + "input.name": "İsim", + "input.email": "E-posta", + "input.password": "Şifre", + "input.password.again": "Şifreyi Tekrar Girin", + "input.code": "Tek Kullanımlık Kod", + "input.recovery_code": "Kurtarma Kodu", + "button.save": "Kaydet", + "repository.url": "Örnekler
Halka açık depolar için https://... kullanın.
Özel depolar için git@... kullanın.

https://github.com/coollabsio/coolify-examples main dalı seçilecek
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek.
https://gitea.com/sedlav/expressjs.git main dalı seçilecek.
https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek." +} diff --git a/lang/vi.json b/lang/vi.json new file mode 100644 index 000000000..548dbe8b7 --- /dev/null +++ b/lang/vi.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Đăng Nhập", + "auth.login.azure": "Đăng Nhập Bằng Microsoft", + "auth.login.bitbucket": "Đăng Nhập Bằng Bitbucket", + "auth.login.github": "Đăng Nhập Bằng GitHub", + "auth.login.gitlab": "Đăng Nhập Bằng Gitlab", + "auth.login.google": "Đăng Nhập Bằng Google", + "auth.already_registered": "Đã đăng ký?", + "auth.confirm_password": "Nhập lại mật khẩu", + "auth.forgot_password": "Quên mật khẩu", + "auth.forgot_password_send_email": "Gửi email đặt lại mật khẩu", + "auth.register_now": "Đăng ký ngay", + "auth.logout": "Đăng xuất", + "auth.register": "Đăng ký", + "auth.registration_disabled": "Đăng ký không khả dụng. Vui lòng liên hệ quản trị viên.", + "auth.reset_password": "Đặt lại mật khẩu", + "auth.failed": "Thông tin đăng nhập không khớp với bất kỳ tài khoản nào.", + "auth.failed.callback": "Xử lý thông tin từ nhà cung cấp đăng nhập thất bại.", + "auth.failed.password": "Mật khẩu bạn cung cấp không chính xác.", + "auth.failed.email": "Không có người dùng nào đã đăng ký với email đó.", + "auth.throttle": "Quá nhiều lần đăng nhập thất bại. Vui lòng thử lại sau :seconds giây.", + "input.name": "Tên", + "input.email": "Email", + "input.password": "Mật khẩu", + "input.password.again": "Mật khẩu lần nữa", + "input.code": "One-time code", + "input.recovery_code": "Mã khôi phục", + "button.save": "Lưu", + "repository.url": "Ví dụ
Với repo công khai, sử dụng https://....
Với repo riêng tư, sử dụng git@....

https://github.com/coollabsio/coolify-examples nhánh chính sẽ được chọn
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nhánh nodejs-fastify sẽ được chọn.
https://gitea.com/sedlav/expressjs.git nhánh chính sẽ được chọn.
https://gitlab.com/andrasbacsai/nodejs-example.git nhánh chính sẽ được chọn." +} diff --git a/other/logos/advin.png b/other/logos/advin.png new file mode 100644 index 000000000..155408b9c Binary files /dev/null and b/other/logos/advin.png differ diff --git a/other/logos/blacksmith.svg b/other/logos/blacksmith.svg new file mode 100644 index 000000000..d8fbf0441 --- /dev/null +++ b/other/logos/blacksmith.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/other/logos/codext.jpg b/other/logos/codext.jpg new file mode 100644 index 000000000..8abf63972 Binary files /dev/null and b/other/logos/codext.jpg differ diff --git a/other/logos/fractal.png b/other/logos/fractal.png new file mode 100644 index 000000000..c4d39c1f1 Binary files /dev/null and b/other/logos/fractal.png differ diff --git a/other/logos/fractal.svg b/other/logos/fractal.svg new file mode 100644 index 000000000..cd2ee4134 --- /dev/null +++ b/other/logos/fractal.svg @@ -0,0 +1,40 @@ + + + + + + Networks + Fractal + + + + + + + \ No newline at end of file diff --git a/other/logos/trieve_bg.png b/other/logos/trieve_bg.png new file mode 100644 index 000000000..5425fbd73 Binary files /dev/null and b/other/logos/trieve_bg.png differ diff --git a/package-lock.json b/package-lock.json index 0010d87fa..bec5a7f66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,9 @@ "laravel-vite-plugin": "0.8.1", "postcss": "8.4.38", "pusher-js": "8.4.0-rc2", - "tailwindcss": "3.4.3", + "tailwindcss": "3.4.4", "vite": "4.5.3", - "vue": "3.4.27" + "vue": "3.4.29" } }, "node_modules/@alloc/quick-lru": { @@ -36,9 +36,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -535,51 +535,51 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", - "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", + "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", "dev": true, "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.27", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.29", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-core/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/compiler-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", - "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", + "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-core": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", - "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", + "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.27", - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27", + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.29", + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -587,25 +587,25 @@ } }, "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", - "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", + "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/reactivity": { @@ -617,64 +617,74 @@ } }, "node_modules/@vue/runtime-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", - "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz", + "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==", "dev": true, "dependencies": { - "@vue/reactivity": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/reactivity": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", - "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", + "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", "dev": true, "dependencies": { - "@vue/shared": "3.4.27" + "@vue/shared": "3.4.29" } }, "node_modules/@vue/runtime-core/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/runtime-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", - "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz", + "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.4.27", - "@vue/shared": "3.4.27", + "@vue/reactivity": "3.4.29", + "@vue/runtime-core": "3.4.29", + "@vue/shared": "3.4.29", "csstype": "^3.1.3" } }, + "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": { + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", + "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", + "dev": true, + "dependencies": { + "@vue/shared": "3.4.29" + } + }, "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/server-renderer": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", - "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz", + "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29" }, "peerDependencies": { - "vue": "3.4.27" + "vue": "3.4.29" } }, "node_modules/@vue/server-renderer/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/@vue/shared": { @@ -789,11 +799,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1094,9 +1104,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1897,9 +1907,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -2082,16 +2092,16 @@ } }, "node_modules/vue": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", - "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz", + "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-sfc": "3.4.27", - "@vue/runtime-dom": "3.4.27", - "@vue/server-renderer": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-sfc": "3.4.29", + "@vue/runtime-dom": "3.4.29", + "@vue/server-renderer": "3.4.29", + "@vue/shared": "3.4.29" }, "peerDependencies": { "typescript": "*" @@ -2103,9 +2113,9 @@ } }, "node_modules/vue/node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/wrappy": { diff --git a/package.json b/package.json index 4d6b321c8..b4609a025 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "laravel-vite-plugin": "0.8.1", "postcss": "8.4.38", "pusher-js": "8.4.0-rc2", - "tailwindcss": "3.4.3", + "tailwindcss": "3.4.4", "vite": "4.5.3", - "vue": "3.4.27" + "vue": "3.4.29" }, "dependencies": { "@tailwindcss/forms": "0.5.7", diff --git a/public/svgs/statusnook.svg b/public/svgs/statusnook.svg new file mode 100644 index 000000000..74fd69651 --- /dev/null +++ b/public/svgs/statusnook.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/css/app.css b/resources/css/app.css index 42496cffe..a037def51 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -4,7 +4,6 @@ html, body { - zoom: 0.95; @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400; } @@ -12,6 +11,18 @@ body { @apply text-sm antialiased scrollbar; } +.apexcharts-tooltip { + @apply dark:text-white dark:border-coolgray-300 dark:bg-coolgray-200 shadow-none !important; +} + +.apexcharts-tooltip-title { + @apply hidden !important; +} + +.apexcharts-xaxistooltip { + @apply hidden !important; +} + .input, .select { @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300; @@ -116,15 +127,19 @@ tr td:first-child { .alert-error { @apply flex items-center gap-2 text-error; } + .tag { @apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200; } + .add-tag { @apply flex items-center px-2 text-xs cursor-pointer dark:text-neutral-500/20 text-neutral-500 group-hover:text-neutral-700 group-hover:dark:text-white dark:hover:bg-coolgray-300 hover:bg-neutral-200; } + .dropdown-item { @apply relative flex cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs items-center pr-4 pl-2 py-1 text-xs justify-start outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2 w-full; } + .dropdown-item-no-padding { @apply relative flex cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs items-center py-1 text-xs justify-start outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2 w-full; } @@ -200,12 +215,15 @@ tr td:first-child { .box { @apply relative flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline; } + .box-boarding { @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black; } + .box-without-bg { @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black; } + .box-without-bg-without-border { @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem]; } diff --git a/resources/views/components/forms/monaco-editor.blade.php b/resources/views/components/forms/monaco-editor.blade.php new file mode 100644 index 000000000..d3793785b --- /dev/null +++ b/resources/views/components/forms/monaco-editor.blade.php @@ -0,0 +1,437 @@ +
+
+
+
+
+
+
+
+
+
diff --git a/resources/views/components/forms/textarea.blade.php b/resources/views/components/forms/textarea.blade.php index 2c32c585e..24226ecdb 100644 --- a/resources/views/components/forms/textarea.blade.php +++ b/resources/views/components/forms/textarea.blade.php @@ -24,47 +24,54 @@ @endif @endif - @if ($type === 'password') -
- @if ($allowToPeak) -
- - - - - -
- @endif - merge(['class' => $defaultClassInput]) }} @required($required) - @if ($id !== 'null') wire:model={{ $id }} @endif - wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300' - wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" - type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}" - name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" - aria-placeholder="{{ $attributes->get('placeholder') }}"> - + @disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" + name="{{ $name }}" name={{ $id }}> -
- @else - + @disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" + name="{{ $name }}" name={{ $id }}> + @endif @endif - @error($id)