Merge branch 'next' into fix-postgres-init-scripts

This commit is contained in:
🏔️ Peak
2024-12-09 19:50:54 +01:00
committed by GitHub
127 changed files with 1896 additions and 581 deletions

View File

@@ -25,6 +25,9 @@ class Advanced extends Component
#[Validate(['boolean'])]
public bool $isAutoDeployEnabled = true;
#[Validate(['boolean'])]
public bool $disableBuildCache = false;
#[Validate(['boolean'])]
public bool $isLogDrainEnabled = false;
@@ -95,6 +98,7 @@ class Advanced extends Component
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
$this->application->settings->disable_build_cache = $this->disableBuildCache;
$this->application->settings->save();
} else {
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
@@ -116,6 +120,7 @@ class Advanced extends Component
$this->customInternalName = $this->application->settings->custom_internal_name;
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
$this->disableBuildCache = $this->application->settings->disable_build_cache;
}
}

View File

@@ -16,24 +16,26 @@ class Configuration extends Component
public function mount()
{
$this->application = Application::query()
->whereHas('environment.project', function ($query) {
$query->where('team_id', currentTeam()->id)
->where('uuid', request()->route('project_uuid'));
})
->whereHas('environment', function ($query) {
$query->where('name', request()->route('environment_name'));
})
$project = currentTeam()
->projects()
->select('id', 'uuid', 'team_id')
->where('uuid', request()->route('project_uuid'))
->firstOrFail();
$environment = $project->environments()
->select('id', 'name', 'project_id')
->where('name', request()->route('environment_name'))
->firstOrFail();
$application = $environment->applications()
->with(['destination'])
->where('uuid', request()->route('application_uuid'))
->with(['destination' => function ($query) {
$query->select('id', 'server_id');
}])
->firstOrFail();
if ($this->application->destination && $this->application->destination->server_id) {
$this->application = $application;
if ($application->destination && $application->destination->server) {
$mainServer = $application->destination->server;
$this->servers = Server::ownedByCurrentTeam()
->select('id', 'name')
->where('id', '!=', $this->application->destination->server_id)
->where('id', '!=', $mainServer->id)
->get();
} else {
$this->servers = collect();

View File

@@ -88,6 +88,9 @@ class Show extends Component
public function lock()
{
$this->env->is_shown_once = true;
if ($this->isSharedVariable) {
unset($this->env->is_required);
}
$this->serialize();
$this->env->save();
$this->checkEnvs();

View File

@@ -168,18 +168,42 @@ class ExecuteContainerCommand extends Component
return;
}
try {
// Validate container name format
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $this->selected_container)) {
throw new \InvalidArgumentException('Invalid container name format');
}
// Verify container exists in our allowed list
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
if (is_null($container)) {
throw new \RuntimeException('Container not found.');
}
$server = data_get($this->container, 'server');
// Verify server ownership and status
$server = data_get($container, 'server');
if (! $server || ! $server instanceof Server) {
throw new \RuntimeException('Invalid server configuration.');
}
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
// Additional ownership verification based on resource type
$resourceServer = match ($this->type) {
'application' => $this->resource->destination->server,
'database' => $this->resource->destination->server,
'service' => $this->resource->server,
default => throw new \RuntimeException('Invalid resource type.')
};
if ($server->id !== $resourceServer->id && ! $this->resource->additional_servers->contains('id', $server->id)) {
throw new \RuntimeException('Server ownership verification failed.');
}
$this->dispatch(
'send-terminal-command',
isset($container),
true,
data_get($container, 'container.Names'),
data_get($container, 'server.uuid')
);

View File

@@ -24,6 +24,14 @@ class Executions extends Component
#[Locked]
public ?string $serverTimezone = null;
public $currentPage = 1;
public $logsPerPage = 100;
public $selectedExecution = null;
public $isPollingActive = false;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
@@ -54,16 +62,84 @@ class Executions extends Component
public function refreshExecutions(): void
{
$this->executions = $this->task->executions()->take(20)->get();
if ($this->selectedKey) {
$this->selectedExecution = $this->task->executions()->find($this->selectedKey);
if ($this->selectedExecution && $this->selectedExecution->status !== 'running') {
$this->isPollingActive = false;
}
}
}
public function selectTask($key): void
{
if ($key == $this->selectedKey) {
$this->selectedKey = null;
$this->selectedExecution = null;
$this->currentPage = 1;
$this->isPollingActive = false;
return;
}
$this->selectedKey = $key;
$this->selectedExecution = $this->task->executions()->find($key);
$this->currentPage = 1;
// Start polling if task is running
if ($this->selectedExecution && $this->selectedExecution->status === 'running') {
$this->isPollingActive = true;
}
}
public function polling()
{
if ($this->selectedExecution && $this->isPollingActive) {
$this->selectedExecution->refresh();
if ($this->selectedExecution->status !== 'running') {
$this->isPollingActive = false;
}
}
}
public function loadMoreLogs()
{
$this->currentPage++;
}
public function getLogLinesProperty()
{
if (! $this->selectedExecution) {
return collect();
}
if (! $this->selectedExecution->message) {
return collect(['Waiting for task output...']);
}
$lines = collect(explode("\n", $this->selectedExecution->message));
return $lines->take($this->currentPage * $this->logsPerPage);
}
public function downloadLogs(int $executionId)
{
$execution = $this->executions->firstWhere('id', $executionId);
if (! $execution) {
return;
}
return response()->streamDownload(function () use ($execution) {
echo $execution->message;
}, 'task-execution-'.$execution->id.'.log');
}
public function hasMoreLogs()
{
if (! $this->selectedExecution || ! $this->selectedExecution->message) {
return false;
}
$lines = collect(explode("\n", $this->selectedExecution->message));
return $lines->count() > ($this->currentPage * $this->logsPerPage);
}
public function formatDateInServerTimezone($date)

View File

@@ -29,11 +29,20 @@ class Terminal extends Component
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
if ($isContainer) {
// Validate container identifier format (alphanumeric, dashes, and underscores only)
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $identifier)) {
throw new \InvalidArgumentException('Invalid container identifier format');
}
// Verify container exists and belongs to the user's team
$status = getContainerStatus($server, $identifier);
if ($status !== 'running') {
return;
}
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
// Escape the identifier for shell usage
$escapedIdentifier = escapeshellarg($identifier);
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$escapedIdentifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
} else {
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
}