Merge branch 'next' into services

This commit is contained in:
🏔️ Peak
2024-11-25 13:16:43 +01:00
committed by GitHub
19 changed files with 320 additions and 250 deletions

View File

@@ -31,7 +31,7 @@ class UpdateCoolify
}
CleanupDocker::dispatch($this->server);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
$this->currentVersion = config('constants.coolify.version');
if (! $manual_update) {
if (! $settings->is_auto_update_enabled) {
return;

View File

@@ -200,7 +200,7 @@ class Init extends Command
private function restore_coolify_db_backup()
{
if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) {
try {
$database = StandalonePostgresql::withTrashed()->find(0);
if ($database && $database->trashed()) {
@@ -228,7 +228,7 @@ class Init extends Command
private function send_alive_signal()
{
$id = config('app.id');
$version = config('version');
$version = config('constants.coolify.version');
$settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
@@ -264,7 +264,7 @@ class Init extends Command
private function replace_slash_in_environment_name()
{
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) {
$environments = Environment::all();
foreach ($environments as $environment) {
if (str_contains($environment->name, '/')) {

View File

@@ -50,7 +50,7 @@ class Kernel extends ConsoleKernel
$this->instanceTimezone = config('app.timezone');
}
$this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
// $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
if (isDev()) {
// Instance Jobs
@@ -154,7 +154,7 @@ class Kernel extends ConsoleKernel
}
// Cleanup multiplexed connections every hour
$this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
// $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
// Temporary solution until we have better memory management for Sentinel
if ($server->isSentinelEnabled()) {

View File

@@ -37,7 +37,7 @@ class OtherController extends Controller
)]
public function version(Request $request)
{
return response(config('version'));
return response(config('constants.coolify.version'));
}
#[OA\Get(

View File

@@ -27,7 +27,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
$versions = $response->json();
$latest_version = data_get($versions, 'coolify.v4.version');
$current_version = config('version');
$current_version = config('constants.coolify.version');
if (version_compare($latest_version, $current_version, '>')) {
// New version available

View File

@@ -67,6 +67,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function handle(): void
{
try {
$databasesToBackup = null;
$this->team = Team::find($this->backup->team_id);
if (! $this->team) {
$this->backup->delete();
@@ -198,8 +200,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$databaseType = $this->database->type();
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
}
if (filled($databasesToBackup)) {
if (blank($databasesToBackup)) {
if (str($databaseType)->contains('postgres')) {
$databasesToBackup = [$this->database->postgres_db];
} elseif (str($databaseType)->contains('mongodb')) {

View File

@@ -25,218 +25,222 @@ class StripeProcessJob implements ShouldQueue
public function handle(): void
{
$excludedPlans = config('subscription.stripe_excluded_plans');
try {
$excludedPlans = config('subscription.stripe_excluded_plans');
$type = data_get($this->event, 'type');
$this->type = $type;
$data = data_get($this->event, 'data.object');
switch ($type) {
case 'radar.early_fraud_warning.created':
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$id = data_get($data, 'id');
$charge = data_get($data, 'charge');
if ($charge) {
$stripe->refunds->create(['charge' => $charge]);
}
$pi = data_get($data, 'payment_intent');
$piData = $stripe->paymentIntents->retrieve($pi, []);
$customerId = data_get($piData, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if ($subscription) {
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
$stripe->subscriptions->cancel($subscriptionId, []);
$subscription->update([
'stripe_invoice_paid' => false,
]);
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
} else {
send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
throw new \Exception("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
}
break;
case 'checkout.session.completed':
$clientReferenceId = data_get($data, 'client_reference_id');
if (is_null($clientReferenceId)) {
send_internal_notification('Checkout session completed without client reference id.');
break;
}
$userId = Str::before($clientReferenceId, ':');
$teamId = Str::after($clientReferenceId, ':');
$subscriptionId = data_get($data, 'subscription');
$customerId = data_get($data, 'customer');
$team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first();
if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
throw new \Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
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);
Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => true,
]);
}
break;
case 'invoice.paid':
$customerId = data_get($data, 'customer');
$planId = data_get($data, 'lines.data.0.plan.id');
if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.');
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if ($subscription) {
$subscription->update([
'stripe_invoice_paid' => true,
]);
} else {
throw new \Exception("No subscription found for customer: {$customerId}");
}
break;
case 'invoice.payment_failed':
$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);
throw new \Exception("No subscription found for customer: {$customerId}");
}
$team = data_get($subscription, 'team');
if (! $team) {
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
throw new \Exception("No team found in Coolify for customer: {$customerId}");
}
if (! $subscription->stripe_invoice_paid) {
SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: '.$customerId);
} else {
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);
throw new \Exception("No subscription found in Coolify for customer: {$customerId}");
}
if ($subscription->stripe_invoice_paid) {
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
return;
}
send_internal_notification('Subscription payment failed for customer: '.$customerId);
break;
case 'customer.subscription.created':
$customerId = data_get($data, 'customer');
$subscriptionId = data_get($data, 'id');
$teamId = data_get($data, 'metadata.team_id');
$userId = data_get($data, 'metadata.user_id');
if (! $teamId || ! $userId) {
$type = data_get($this->event, 'type');
$this->type = $type;
$data = data_get($this->event, 'data.object');
switch ($type) {
case 'radar.early_fraud_warning.created':
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$id = data_get($data, 'id');
$charge = data_get($data, 'charge');
if ($charge) {
$stripe->refunds->create(['charge' => $charge]);
}
$pi = data_get($data, 'payment_intent');
$piData = $stripe->paymentIntents->retrieve($pi, []);
$customerId = data_get($piData, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if ($subscription) {
throw new \Exception("Subscription already exists for customer: {$customerId}");
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
$stripe->subscriptions->cancel($subscriptionId, []);
$subscription->update([
'stripe_invoice_paid' => false,
]);
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
} else {
send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
throw new \RuntimeException("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}");
}
throw new \Exception('No team id or user id found');
}
$team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first();
if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
throw new \Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
send_internal_notification("Subscription already exists for team: {$teamId}");
throw new \Exception("Subscription already exists for team: {$teamId}");
} else {
Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => false,
]);
}
case 'customer.subscription.updated':
$teamId = data_get($data, 'metadata.team_id');
$userId = data_get($data, 'metadata.user_id');
$customerId = data_get($data, 'customer');
$status = data_get($data, 'status');
$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.');
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) {
if ($status === 'incomplete_expired') {
send_internal_notification('Subscription incomplete expired');
throw new \Exception('Subscription incomplete expired');
case 'checkout.session.completed':
$clientReferenceId = data_get($data, 'client_reference_id');
if (is_null($clientReferenceId)) {
send_internal_notification('Checkout session completed without client reference id.');
break;
}
if ($teamId) {
$subscription = Subscription::create([
$userId = Str::before($clientReferenceId, ':');
$teamId = Str::after($clientReferenceId, ':');
$subscriptionId = data_get($data, 'subscription');
$customerId = data_get($data, 'customer');
$team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first();
if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
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);
Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => true,
]);
}
break;
case 'invoice.paid':
$customerId = data_get($data, 'customer');
$planId = data_get($data, 'lines.data.0.plan.id');
if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.');
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if ($subscription) {
$subscription->update([
'stripe_invoice_paid' => true,
]);
} else {
throw new \RuntimeException("No subscription found for customer: {$customerId}");
}
break;
case 'invoice.payment_failed':
$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);
throw new \RuntimeException("No subscription found for customer: {$customerId}");
}
$team = data_get($subscription, 'team');
if (! $team) {
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
throw new \RuntimeException("No team found in Coolify for customer: {$customerId}");
}
if (! $subscription->stripe_invoice_paid) {
SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: '.$customerId);
} else {
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);
throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}");
}
if ($subscription->stripe_invoice_paid) {
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
return;
}
send_internal_notification('Subscription payment failed for customer: '.$customerId);
break;
case 'customer.subscription.created':
$customerId = data_get($data, 'customer');
$subscriptionId = data_get($data, 'id');
$teamId = data_get($data, 'metadata.team_id');
$userId = data_get($data, 'metadata.user_id');
if (! $teamId || ! $userId) {
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if ($subscription) {
throw new \RuntimeException("Subscription already exists for customer: {$customerId}");
}
throw new \RuntimeException('No team id or user id found');
}
$team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first();
if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.");
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
send_internal_notification("Subscription already exists for team: {$teamId}");
throw new \RuntimeException("Subscription already exists for team: {$teamId}");
} else {
Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => false,
]);
} else {
send_internal_notification('No subscription and team id found');
throw new \Exception('No subscription and team id found');
}
}
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
$feedback = data_get($data, 'cancellation_details.feedback');
$comment = data_get($data, 'cancellation_details.comment');
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
if (str($lookup_key)->contains('dynamic')) {
$quantity = data_get($data, 'items.data.0.quantity', 2);
$team = data_get($subscription, 'team');
if ($team) {
$team->update([
'custom_server_limit' => $quantity,
case 'customer.subscription.updated':
$teamId = data_get($data, 'metadata.team_id');
$userId = data_get($data, 'metadata.user_id');
$customerId = data_get($data, 'customer');
$status = data_get($data, 'status');
$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.');
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) {
if ($status === 'incomplete_expired') {
send_internal_notification('Subscription incomplete expired');
throw new \RuntimeException('Subscription incomplete expired');
}
if ($teamId) {
$subscription = Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => false,
]);
} else {
send_internal_notification('No subscription and team id found');
throw new \RuntimeException('No subscription and team id found');
}
}
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
$feedback = data_get($data, 'cancellation_details.feedback');
$comment = data_get($data, 'cancellation_details.comment');
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
if (str($lookup_key)->contains('dynamic')) {
$quantity = data_get($data, 'items.data.0.quantity', 2);
$team = data_get($subscription, 'team');
if ($team) {
$team->update([
'custom_server_limit' => $quantity,
]);
}
ServerLimitCheckJob::dispatch($team);
}
$subscription->update([
'stripe_feedback' => $feedback,
'stripe_comment' => $comment,
'stripe_plan_id' => $planId,
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
]);
if ($status === 'paused' || $status === 'incomplete_expired') {
$subscription->update([
'stripe_invoice_paid' => false,
]);
}
ServerLimitCheckJob::dispatch($team);
}
$subscription->update([
'stripe_feedback' => $feedback,
'stripe_comment' => $comment,
'stripe_plan_id' => $planId,
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
]);
if ($status === 'paused' || $status === 'incomplete_expired') {
$subscription->update([
'stripe_invoice_paid' => false,
]);
}
if ($feedback) {
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
if ($comment) {
$reason .= ' with comment: \''.$comment."'";
if ($feedback) {
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
if ($comment) {
$reason .= ' with comment: \''.$comment."'";
}
}
}
break;
case 'customer.subscription.deleted':
// End subscription
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
$team?->subscriptionEnded();
break;
default:
// Unhandled event type
break;
case 'customer.subscription.deleted':
// End subscription
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
$team?->subscriptionEnded();
break;
default:
throw new \RuntimeException("Unhandled event type: {$type}");
}
} catch (\Exception $e) {
send_internal_notification('StripeProcessJob error: '.$e->getMessage());
}
}
}

View File

@@ -36,7 +36,11 @@ class Heading extends Component
public function mount()
{
$this->parameters = get_route_parameters();
$this->parameters = [
'project_uuid' => $this->application->project()->uuid,
'environment_name' => $this->application->environment->name,
'application_uuid' => $this->application->uuid,
];
$lastDeployment = $this->application->get_last_successful_deployment();
$this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message');
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));

View File

@@ -71,7 +71,7 @@ class EnvironmentVariable extends Model
}
}
$environment_variable->update([
'version' => config('version'),
'version' => config('constants.coolify.version'),
]);
});
static::saving(function (EnvironmentVariable $environmentVariable) {

View File

@@ -46,7 +46,7 @@ class DiscordMessage
public function toPayload(): array
{
$footerText = 'Coolify v'.config('version');
$footerText = 'Coolify v'.config('constants.coolify.version');
if (isCloud()) {
$footerText = 'Coolify Cloud';
}

View File

@@ -1,4 +1,4 @@
<?php
$version = include 'config/version.php';
echo $version;
$version = include 'config/constants.php';
echo $version['coolify']['version'] ?: 'unknown';

View File

@@ -192,7 +192,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
{
$labels = collect([]);
$labels->push('coolify.managed=true');
$labels->push('coolify.version='.config('version'));
$labels->push('coolify.version='.config('constants.coolify.version'));
$labels->push('coolify.'.$type.'Id='.$id);
$labels->push("coolify.type=$type");
$labels->push('coolify.name='.$name);

View File

@@ -1,3 +0,0 @@
<?php
return '4.0.0-beta.372';

View File

@@ -60,7 +60,22 @@ export function initializeTerminalComponent() {
};
},
resetTerminal() {
if (this.term) {
this.$wire.dispatch('error', 'Terminal websocket connection lost.');
this.term.reset();
this.term.clear();
this.pendingWrites = 0;
this.paused = false;
this.commandBuffer = '';
// Force a refresh
this.$nextTick(() => {
this.resizeTerminal();
this.term.focus();
});
}
},
setupTerminal() {
const terminalElement = document.getElementById('terminal');
if (terminalElement) {
@@ -69,9 +84,15 @@ export function initializeTerminalComponent() {
rows: 30,
fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"',
cursorBlink: true,
rendererType: 'canvas',
convertEol: true,
disableStdin: false
});
this.fitAddon = new FitAddon();
this.term.loadAddon(this.fitAddon);
this.$nextTick(() => {
this.resizeTerminal();
});
}
},
@@ -101,12 +122,19 @@ export function initializeTerminalComponent() {
`${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}`
this.socket = new WebSocket(url);
this.socket.onopen = () => {
console.log('[Terminal] WebSocket connection established. Cool cool cool cool cool cool.');
};
this.socket.onmessage = this.handleSocketMessage.bind(this);
this.socket.onerror = (e) => {
console.error('WebSocket error:', e);
console.error('[Terminal] WebSocket error.');
};
this.socket.onclose = () => {
console.log('WebSocket connection closed');
console.warn('[Terminal] WebSocket connection closed.');
this.resetTerminal();
this.message = '(connection closed)';
this.terminalActive = false;
this.reconnect();
};
}
@@ -117,19 +145,18 @@ export function initializeTerminalComponent() {
clearInterval(this.reconnectInterval);
}
this.reconnectInterval = setInterval(() => {
console.log('Attempting to reconnect...');
console.warn('[Terminal] Attempting to reconnect...');
this.initializeWebSocket();
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
console.log('Reconnected successfully');
console.log('[Terminal] Reconnected successfully');
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
window.location.reload();
}
}, 2000);
},
handleSocketMessage(event) {
this.message = '(connection closed)';
if (event.data === 'pty-ready') {
if (!this.term._initialized) {
this.term.open(document.getElementById('terminal'));
@@ -150,8 +177,17 @@ export function initializeTerminalComponent() {
this.term.reset();
this.commandBuffer = '';
} else {
this.pendingWrites++;
this.term.write(event.data, this.flowControlCallback.bind(this));
try {
this.pendingWrites++;
this.term.write(event.data, (err) => {
if (err) {
console.error('[Terminal] Write error:', err);
}
this.flowControlCallback();
});
} catch (error) {
console.error('[Terminal] Write operation failed:', error);
}
}
},
@@ -173,11 +209,15 @@ export function initializeTerminalComponent() {
if (!this.term) return;
this.term.onData((data) => {
this.socket.send(JSON.stringify({ message: data }));
if (data === '\r') {
this.commandBuffer = '';
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ message: data }));
if (data === '\r') {
this.commandBuffer = '';
} else {
this.commandBuffer += data;
}
} else {
this.commandBuffer += data;
console.warn('[Terminal] WebSocket not ready, data not sent');
}
});

View File

@@ -11,16 +11,20 @@
toast(this.title, { description: this.description, type: this.type, position: this.position, html: html })
}
}" x-init="window.toast = function(message, options = {}) {
let description = '';
let type = 'default';
let position = 'top-center';
let html = '';
if (typeof options.description != 'undefined') description = options.description;
if (typeof options.type != 'undefined') type = options.type;
if (typeof options.position != 'undefined') position = options.position;
if (typeof options.html != 'undefined') html = options.html;
try {
let description = '';
let type = 'default';
let position = 'top-center';
let html = '';
if (typeof options.description != 'undefined') description = options.description;
if (typeof options.type != 'undefined') type = options.type;
if (typeof options.position != 'undefined') position = options.position;
if (typeof options.html != 'undefined') html = options.html;
window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } }));
window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } }));
} catch (error) {
console.error('Error showing toast:', error);
}
}" class="relative space-y-5">
<template x-teleport="body">
<ul x-data="{

View File

@@ -1,4 +1,4 @@
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-90 hover:opacity-100 dark:hover:text-white hover:text-black']) }}
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}" target="_blank">
v{{ config('version') }}
</a>
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('constants.coolify.version') }}" target="_blank">
v{{ config('constants.coolify.version') }}
</a>

View File

@@ -96,6 +96,20 @@
enableStats: false,
enableLogging: true,
enabledTransports: ['ws', 'wss'],
disableStats: true,
// Add auto reconnection settings
enabledTransports: ['ws', 'wss'],
disabledTransports: ['sockjs', 'xhr_streaming', 'xhr_polling'],
// Attempt to reconnect on connection lost
autoReconnect: true,
// Wait 1 second before first reconnect attempt
reconnectionDelay: 1000,
// Maximum delay between reconnection attempts
maxReconnectionDelay: 1000,
// Multiply delay by this number for each reconnection attempt
reconnectionDelayGrowth: 1,
// Maximum number of reconnection attempts
maxAttempts: 15
});
@endauth
let checkHealthInterval = null;

View File

@@ -11,16 +11,22 @@
let checkNumber = 1;
let checkPusherInterval = null;
let checkReconnectInterval = null;
if (!this.popups.realtime) {
checkPusherInterval = setInterval(() => {
if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') {
checkNumber++;
if (checkNumber > 5) {
this.popups.realtime = true;
console.error(
'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
);
clearInterval(checkPusherInterval);
if (window.Echo) {
if (window.Echo.connector.pusher.connection.state === 'connected') {
this.popups.realtime = false;
} else {
checkNumber++;
if (checkNumber > 5) {
this.popups.realtime = true;
console.error(
'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
);
}
}
}
}, 2000);

View File

@@ -26,8 +26,8 @@
@else
@if (count($containers) > 0)
@if (count($containers) === 1)
<form class="w-full pt-4"
wire:submit="$dispatchSelf('connectToContainer')" wire:init="$dispatchSelf('connectToContainer')">
<form class="w-full pt-4" wire:submit="$dispatchSelf('connectToContainer')"
wire:init="$dispatchSelf('connectToContainer')">
<x-forms.button class="w-full" type="submit">Reconnect</x-forms.button>
</form>
@else