diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 5b2cabdab2..29a186f9f2 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -134,12 +134,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', ''); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); } else { $comment = new Comment(); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', ''); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); $latestCommentId = \strval($github->createComment($owner, $repositoryName, $providerPullRequestId, $comment->generateComment())); if (!empty($latestCommentId)) { @@ -176,7 +176,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = $comment->getAttribute('providerCommentId', ''); $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', ''); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); } @@ -219,7 +219,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $commands[] = $resource->getAttribute('commands', ''); } - $deployment = $dbForProject->createDocument('deployments', new Document([ + $deployment = Authorization::skip(fn () => $dbForProject->createDocument('deployments', new Document([ '$id' => $deploymentId, '$permissions' => [ Permission::read(Role::any()), @@ -253,14 +253,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId 'providerBranch' => $providerBranch, 'search' => implode(' ', [$deploymentId, $resource->getAttribute('entrypoint', '')]), 'activate' => $activate, - ])); + ]))); $resource = $resource ->setAttribute('latestDeploymentId', $deployment->getId()) ->setAttribute('latestDeploymentInternalId', $deployment->getInternalId()) ->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt()) ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); - $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource); + Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource)); if ($resource->getCollection() === 'sites') { $projectId = $project->getId(); diff --git a/app/controllers/general.php b/app/controllers/general.php index 5ce0a03471..0c2cdbb60a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -478,24 +478,28 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw ); // Branded 404 override + $isResponseBranded = false; if ($executionResponse['statusCode'] === 404 && $deployment->getAttribute('adapter', '') === 'static') { $layout = new View(__DIR__ . '/../views/general/404.phtml'); $executionResponse['body'] = $layout->render(); $executionResponse['headers']['content-length'] = \strlen($executionResponse['body']); + $isResponseBranded = true; } // Branded banner for previews - if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) { - $transformation = new Transformation(); - $transformation->addAdapter(new Preview()); - $transformation->setInput($executionResponse['body']); - $transformation->setTraits($executionResponse['headers']); - if ($isPreview && $transformation->transform()) { - $executionResponse['body'] = $transformation->getOutput(); + if (!$isResponseBranded) { + if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) { + $transformation = new Transformation(); + $transformation->addAdapter(new Preview()); + $transformation->setInput($executionResponse['body']); + $transformation->setTraits($executionResponse['headers']); + if ($isPreview && $transformation->transform()) { + $executionResponse['body'] = $transformation->getOutput(); - foreach ($executionResponse['headers'] as $key => $value) { - if (\strtolower($key) === 'content-length') { - $executionResponse['headers'][$key] = \strlen($executionResponse['body']); + foreach ($executionResponse['headers'] as $key => $value) { + if (\strtolower($key) === 'content-length') { + $executionResponse['headers'][$key] = \strlen($executionResponse['body']); + } } } } diff --git a/composer.lock b/composer.lock index 33550a105c..a9585532f6 100644 --- a/composer.lock +++ b/composer.lock @@ -4205,16 +4205,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.1", + "version": "0.8.4", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab" + "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab", - "reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/845fd04ccf5e0edb03c184b864e0596080a432b8", + "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8", "shasum": "" }, "require": { @@ -4222,7 +4222,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.61.*", + "utopia-php/database": "0.*.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4255,9 +4255,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.1" + "source": "https://github.com/utopia-php/migration/tree/0.8.4" }, - "time": "2025-03-18T07:48:08+00:00" + "time": "2025-03-28T02:08:22+00:00" }, { "name": "utopia-php/mongo", @@ -7527,16 +7527,16 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "e51498ea18570c062e7df29d05a7003585b19b88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", + "reference": "e51498ea18570c062e7df29d05a7003585b19b88", "shasum": "" }, "require": { @@ -7600,7 +7600,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.5" }, "funding": [ { @@ -7616,7 +7616,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-03-12T08:11:12+00:00" }, { "name": "symfony/filesystem", @@ -8131,16 +8131,16 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -8172,7 +8172,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -8188,7 +8188,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/string", @@ -8507,7 +8507,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8531,5 +8531,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 4bd32189dc..f36ced63f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -975,7 +975,7 @@ services: # It's not possible to share mount file between 2 containers without host mount (copying is too slow) - /tmp:/tmp:rw environment: - - OPR_EXECUTOR_IMAGE_PULL=enabled + - OPR_EXECUTOR_IMAGE_PULL=disabled - OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_COMPUTE_INACTIVE_THRESHOLD - OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_COMPUTE_MAINTENANCE_INTERVAL - OPR_EXECUTOR_NETWORK=$_APP_COMPUTE_RUNTIMES_NETWORK diff --git a/public/images/vcs/qr-dark.svg b/public/images/vcs/qr-dark.svg new file mode 100644 index 0000000000..bbbc681f15 --- /dev/null +++ b/public/images/vcs/qr-dark.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/images/vcs/qr-light.svg b/public/images/vcs/qr-light.svg new file mode 100644 index 0000000000..5026db975d --- /dev/null +++ b/public/images/vcs/qr-light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/images/vcs/status-building-dark.gif b/public/images/vcs/status-building-dark.gif new file mode 100644 index 0000000000..6d4afe72a1 Binary files /dev/null and b/public/images/vcs/status-building-dark.gif differ diff --git a/public/images/vcs/status-building-light.gif b/public/images/vcs/status-building-light.gif new file mode 100644 index 0000000000..c64a7643c1 Binary files /dev/null and b/public/images/vcs/status-building-light.gif differ diff --git a/public/images/vcs/status-failed-dark.png b/public/images/vcs/status-failed-dark.png new file mode 100644 index 0000000000..c6508e93bc Binary files /dev/null and b/public/images/vcs/status-failed-dark.png differ diff --git a/public/images/vcs/status-failed-light.png b/public/images/vcs/status-failed-light.png new file mode 100644 index 0000000000..0e4d4533d0 Binary files /dev/null and b/public/images/vcs/status-failed-light.png differ diff --git a/public/images/vcs/status-ready-dark.png b/public/images/vcs/status-ready-dark.png new file mode 100644 index 0000000000..7408fb0cba Binary files /dev/null and b/public/images/vcs/status-ready-dark.png differ diff --git a/public/images/vcs/status-ready-light.png b/public/images/vcs/status-ready-light.png new file mode 100644 index 0000000000..87e141fcfe Binary files /dev/null and b/public/images/vcs/status-ready-light.png differ diff --git a/public/images/vcs/status-waiting-dark.png b/public/images/vcs/status-waiting-dark.png new file mode 100644 index 0000000000..29a4ee540c Binary files /dev/null and b/public/images/vcs/status-waiting-dark.png differ diff --git a/public/images/vcs/status-waiting-light.png b/public/images/vcs/status-waiting-light.png new file mode 100644 index 0000000000..676d530cf4 Binary files /dev/null and b/public/images/vcs/status-waiting-light.png differ diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Runtimes/XList.php b/src/Appwrite/Platform/Modules/Functions/Http/Runtimes/XList.php index f5d33e5632..f00c4a0cd5 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Runtimes/XList.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Runtimes/XList.php @@ -27,9 +27,9 @@ class XList extends Base $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/functions/runtimes') - ->groups(['api', 'functions']) + ->groups(['api']) ->desc('List runtimes') - ->label('scope', 'functions.read') + ->label('scope', 'public') ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk', new Method( namespace: 'functions', diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php b/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php index b495235d0c..cfd1f6cdb2 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php @@ -30,7 +30,7 @@ class XList extends Base ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/functions/templates') ->desc('List templates') - ->groups(['api', 'functions']) + ->groups(['api']) ->label('scope', 'public') ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk', new Method( diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 18e52e7e16..b96c1887e4 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -648,7 +648,11 @@ class Builds extends Action Co::join([ Co\go(function () use ($executor, &$response, $project, $deployment, $source, $resource, $runtime, $vars, $command, $cpus, $memory, $timeout, &$err, $version) { try { - $command = $version === 'v2' ? 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh' : 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh "' . \trim(\escapeshellarg($command), "\'") . '"'; + if ($version === 'v2') { + $command = 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh'; + } else { + $command = 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh ' . \trim(\escapeshellarg($command)); + } $response = $executor->createRuntime( deploymentId: $deployment->getId(), @@ -793,8 +797,12 @@ class Builds extends Action foreach ($response['output'] as $log) { $logs .= $log['content']; } - $logs .= "Capturing screenshots ...\n"; - $deployment->setAttribute('buildLogs', $logs); + + if ($resource->getCollection() === 'sites') { + $date = \date('H:i:s'); + $logs .= "[$date] [appwrite] Screenshot capturing started. \n"; + $deployment->setAttribute('buildLogs', $logs); + } $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); @@ -844,8 +852,8 @@ class Builds extends Action ]); $screenshotError = null; - $screenshots = batch(\array_map(function ($key) use ($configs, $deviceForFiles, $apiKey, $resource, $client, $bucket, $project, $dbForPlatform, &$screenshotError) { - return function () use ($key, $configs, $deviceForFiles, $apiKey, $resource, $client, $bucket, $project, $dbForPlatform, &$screenshotError) { + $screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $resource, $client, &$screenshotError) { + return function () use ($key, $configs, $apiKey, $resource, $client, &$screenshotError) { try { $config = $configs[$key]; @@ -872,44 +880,7 @@ class Builds extends Action $screenshot = $fetchResponse->getBody(); - $fileId = ID::unique(); - $fileName = $fileId . '.png'; - $path = $deviceForFiles->getPath($fileName); - $path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root - $success = $deviceForFiles->write($path, $screenshot, "image/png"); - - if (!$success) { - throw new \Exception("Screenshot failed to save"); - } - - $teamId = $project->getAttribute('teamId', ''); - $file = new Document([ - '$id' => $fileId, - '$permissions' => [ - Permission::read(Role::team(ID::custom($teamId))), - ], - 'bucketId' => $bucket->getId(), - 'bucketInternalId' => $bucket->getInternalId(), - 'name' => $fileName, - 'path' => $path, - 'signature' => $deviceForFiles->getFileHash($path), - 'mimeType' => $deviceForFiles->getFileMimeType($path), - 'sizeOriginal' => \strlen($screenshot), - 'sizeActual' => $deviceForFiles->getFileSize($path), - 'algorithm' => Compression::GZIP, - 'comment' => '', - 'chunksTotal' => 1, - 'chunksUploaded' => 1, - 'openSSLVersion' => null, - 'openSSLCipher' => null, - 'openSSLTag' => null, - 'openSSLIV' => null, - 'search' => implode(' ', [$fileId, $fileName]), - 'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)], - ]); - $file = Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file)); - - return [ 'key' => $key, 'fileId' => $fileId ]; + return ['key' => $key, 'screenshot' => $screenshot]; } catch (\Throwable $th) { $screenshotError = $th->getMessage(); return; @@ -921,11 +892,56 @@ class Builds extends Action throw new \Exception($screenshotError); } - foreach ($screenshots as $screenshot) { - $deployment->setAttribute($screenshot['key'], $screenshot['fileId']); + foreach ($screenshots as $data) { + $key = $data['key']; + $screenshot = $data['screenshot']; + + $fileId = ID::unique(); + $fileName = $fileId . '.png'; + $path = $deviceForFiles->getPath($fileName); + $path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root + $success = $deviceForFiles->write($path, $screenshot, "image/png"); + + if (!$success) { + throw new \Exception("Screenshot failed to save"); + } + + $teamId = $project->getAttribute('teamId', ''); + $file = new Document([ + '$id' => $fileId, + '$permissions' => [ + Permission::read(Role::team(ID::custom($teamId))), + ], + 'bucketId' => $bucket->getId(), + 'bucketInternalId' => $bucket->getInternalId(), + 'name' => $fileName, + 'path' => $path, + 'signature' => $deviceForFiles->getFileHash($path), + 'mimeType' => $deviceForFiles->getFileMimeType($path), + 'sizeOriginal' => \strlen($screenshot), + 'sizeActual' => $deviceForFiles->getFileSize($path), + 'algorithm' => Compression::GZIP, + 'comment' => '', + 'chunksTotal' => 1, + 'chunksUploaded' => 1, + 'openSSLVersion' => null, + 'openSSLCipher' => null, + 'openSSLTag' => null, + 'openSSLIV' => null, + 'search' => implode(' ', [$fileId, $fileName]), + 'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)], + ]); + + Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file)); + + $deployment->setAttribute($key, $fileId); } - $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); + $logs = $deployment->getAttribute('buildLogs', ''); + $date = \date('H:i:s'); + $logs .= "[$date] [appwrite] Screenshot capturing finished. \n"; + + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); $queueForRealtime ->setPayload($deployment->getArrayCopy()) @@ -934,9 +950,20 @@ class Builds extends Action Console::warning("Screenshot failed to generate:"); Console::warning($th->getMessage()); Console::warning($th->getTraceAsString()); + + $logs = $deployment->getAttribute('buildLogs', ''); + $date = \date('H:i:s'); + $logs .= "[$date] [appwrite] Screenshot capturing failed. Deployment will continue. \n"; + + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); } } + $logs = $deployment->getAttribute('buildLogs', ''); + $date = \date('H:i:s'); + $logs .= "[$date] [appwrite] Deployment finished. \n"; + $deployment->setAttribute('buildLogs', $logs); + /** Update the status */ $deployment->setAttribute('status', 'ready'); $deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment); @@ -1372,15 +1399,9 @@ class Builds extends Action default => throw new \Exception('Invalid resource type') }; - $previweQrCode = match($resource->getCollection()) { - 'functions' => '', - 'sites' => 'https://cloud.appwrite.io/v1/avatars/qr?text=' . $previewUrl, - default => throw new \Exception('Invalid resource type') - }; - $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $commentId)); - $comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl, $previweQrCode); + $comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl); $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); } finally { $dbForPlatform->deleteDocument('vcsCommentLocks', $commentId); diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Frameworks/XList.php b/src/Appwrite/Platform/Modules/Sites/Http/Frameworks/XList.php index b96390fae8..6317ef7514 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Frameworks/XList.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Frameworks/XList.php @@ -27,8 +27,8 @@ class XList extends Base ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/sites/frameworks') ->desc('List frameworks') - ->groups(['api', 'sites']) - ->label('scope', 'sites.read') + ->groups(['api']) + ->label('scope', 'public') ->label('resourceType', RESOURCE_TYPE_SITES) ->label('sdk', new Method( namespace: 'sites', diff --git a/src/Appwrite/Transformation/Adapter/Preview.php b/src/Appwrite/Transformation/Adapter/Preview.php index 352dc97204..70af19a188 100644 --- a/src/Appwrite/Transformation/Adapter/Preview.php +++ b/src/Appwrite/Transformation/Adapter/Preview.php @@ -36,6 +36,12 @@ class Preview extends Adapter @import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400); #appwrite-preview { + min-width: auto; + min-height: auto; + max-width: none; + max-height: none; + width: auto; + height: auto; padding: 0; margin: 0; position: fixed; diff --git a/src/Appwrite/Vcs/Comment.php b/src/Appwrite/Vcs/Comment.php index b89d3bff1d..62f6ef61d0 100644 --- a/src/Appwrite/Vcs/Comment.php +++ b/src/Appwrite/Vcs/Comment.php @@ -9,10 +9,11 @@ use Utopia\System\System; class Comment { + // TODO: Add more tips protected array $tips = [ - 'Appwrite has a Discord community with over 16 000 members. [Come join us!](https://appwrite.io/discord)', - 'You can use [Avatars API](https://appwrite.io/docs/client/avatars?sdk=web-default#avatarsGetQR) to generate QR code for any text or URLs', - '[Cursor pagination](https://appwrite.io/docs/pagination#cursor-pagination) performs better than offset pagination when loading further pages', + 'Appwrite has a Discord community with over 16 000 members.', + 'You can use Avatars API to generate QR code for any text or URLs.', + 'Cursor pagination performs better than offset pagination when loading further pages.', ]; protected string $statePrefix = '[appwrite]: #'; @@ -27,7 +28,7 @@ class Comment return \count($this->builds) === 0; } - public function addBuild(Document $project, Document $resource, string $resourceType, string $buildStatus, string $deploymentId, array $action, string $previewUrl, string $previewQrCode): void + public function addBuild(Document $project, Document $resource, string $resourceType, string $buildStatus, string $deploymentId, array $action, string $previewUrl): void { // Unique index $id = $project->getId() . '_' . $resource->getId(); @@ -41,7 +42,6 @@ class Comment 'buildStatus' => $buildStatus, 'deploymentId' => $deploymentId, 'action' => $action, - 'previewQrCode' => $previewQrCode, 'previewUrl' => $previewUrl, ]; } @@ -70,7 +70,6 @@ class Comment 'deploymentId' => $build['deploymentId'], 'action' => $build['action'], 'previewUrl' => $build['previewUrl'], - 'previewQrCode' => $build['previewQrCode'] ]; } elseif ($build['resourceType'] === 'function') { $projects[$build['projectId']]['function'][$build['resourceId']] = [ @@ -82,32 +81,36 @@ class Comment } } + $i = 0; foreach ($projects as $projectId => $project) { $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; $hostname = System::getEnv('_APP_DOMAIN'); - $text .= "Project name: **{$project['name']}** \nProject ID: `{$projectId}`\n\n"; + $text .= "## {$project['name']}\n\n"; + $text .= "Project ID: `{$projectId}`\n\n"; + + $isOpen = $i === 0; if (\count($project['site']) > 0) { + $text .= "\n"; + $text .= "Sites (" . \count($project['site']) . ")\n\n"; + $text .= "
\n\n"; - $text .= "| Site | ID | Status | Previews | Action |\n"; + $text .= "| Site | Status | Logs | Preview | QR\n"; $text .= "| :- | :- | :- | :- | :- |\n"; foreach ($project['site'] as $siteId => $site) { - $generateImage = function (string $status) use ($protocol, $hostname) { - $extention = $status === 'building' ? 'gif' : 'png'; - $imagesUrl = $protocol . '://' . $hostname . '/console/images/vcs/'; - $imageUrl = '' . $status . ''; + $extension = $site['status'] === 'building' ? 'gif' : 'png'; - return $imageUrl; - }; + $pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension; + $pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension; $status = match ($site['status']) { - 'waiting' => $generateImage('waiting') . ' Waiting to build', - 'processing' => $generateImage('processing') . ' Processing', - 'building' => $generateImage('building') . ' Building', - 'ready' => $generateImage('ready') . ' Ready', - 'failed' => $generateImage('failed') . ' Failed', + 'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_', + 'processing' => $this->generatImage($pathLight, $pathDark, 'Processing', 85) . ' _Processing_', + 'building' => $this->generatImage($pathLight, $pathDark, 'Building', 85) . ' _Building_', + 'ready' => $this->generatImage($pathLight, $pathDark, 'Ready', 85) . ' _Ready_', + 'failed' => $this->generatImage($pathLight, $pathDark, 'Failed', 85) . ' _Failed_', }; if ($site['action']['type'] === 'logs') { @@ -116,33 +119,44 @@ class Comment $action = '[Authorize](' . $site['action']['url'] . ')'; } - $previews = '[Preview URL](' . $site['previewUrl'] . ') [QR Code](' . $site['previewQrCode'] . ')'; + $qrImagePathLight = '/images/vcs/qr-light.svg'; + $qrImagePathDark = '/images/vcs/qr-dark.svg'; - $text .= "| {$site['name']} | `{$siteId}` | {$status} | {$previews} | {$action} |\n"; + $consoleUrl = $protocol . '://' . $hostname . '/v1/avatars/qr?text=' . \urlencode($site['previewUrl']); + $qr = '[' . $this->generatImage($qrImagePathLight, $qrImagePathDark, 'QR Code', 28) . '](' . $consoleUrl . ')'; + + $preview = '[Preview URL](' . $site['previewUrl'] . ')'; + + $text .= "|  **{$site['name']}**
`$siteId`"; + $text .= "| {$status}"; + $text .= "| {$action}"; + $text .= "| {$preview}"; + $text .= "| {$qr}"; + $text .= "|\n"; } - $text .= "\n\n"; + $text .= "\n\n\n"; } if (\count($project['function']) > 0) { - - $text .= "| Function | ID | Status | Action |\n"; + $text .= "\n"; + $text .= "Functions (" . \count($project['function']) . ")\n\n"; + $text .= "
\n\n"; + $text .= "| Function | ID | Status | Logs |\n"; $text .= "| :- | :- | :- | :- |\n"; foreach ($project['function'] as $functionId => $function) { - $generateImage = function (string $status) use ($protocol, $hostname) { - $extention = $status === 'building' ? 'gif' : 'png'; - $imagesUrl = $protocol . '://' . $hostname . '/console/images/vcs/'; - $imageUrl = '' . $status . ''; - return $imageUrl; - }; + $extension = $site['status'] === 'building' ? 'gif' : 'png'; - $status = match ($function['status']) { - 'waiting' => $generateImage('waiting') . ' Waiting to build', - 'processing' => $generateImage('processing') . ' Processing', - 'building' => $generateImage('building') . ' Building', - 'ready' => $generateImage('ready') . ' Ready', - 'failed' => $generateImage('failed') . ' Failed', + $pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension; + $pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension; + + $status = match ($site['status']) { + 'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_', + 'processing' => $this->generatImage($pathLight, $pathDark, 'Processing', 85) . ' _Processing_', + 'building' => $this->generatImage($pathLight, $pathDark, 'Building', 85) . ' _Building_', + 'ready' => $this->generatImage($pathLight, $pathDark, 'Ready', 85) . ' _Ready_', + 'failed' => $this->generatImage($pathLight, $pathDark, 'Failed', 85) . ' _Failed_', }; if ($function['action']['type'] === 'logs') { @@ -151,22 +165,45 @@ class Comment $action = '[Authorize](' . $function['action']['url'] . ')'; } - $text .= "| {$function['name']} | `{$functionId}` | {$status} | {$action} |\n"; + $text .= "|  **{$function['name']}**
`$functionId`"; + $text .= "| {$status}"; + $text .= "| {$action}"; + $text .= "|\n"; } - $text .= "\n\n"; + $text .= "\n\n"; } + $text .= "\n\n"; + + $isLast = $i === \count($projects) - 1; + + if (\count($projects) > 1 && $isLast) { + $text .= "---\n\n"; + } + + $i++; } - $text .= "Only deployments on the production branch are activated automatically. Learn more about Appwrite [Functions](https://appwrite.io/docs/functions) and [Sites](https://appwrite.io/docs/sites).\n\n"; - $tip = $this->tips[array_rand($this->tips)]; - $text .= "> **💡 Did you know?** \n " . $tip . "\n\n"; + $text .= "\n
\n\n> [!NOTE]\n> $tip\n\n"; return $text; } + public function generatImage(string $pathLight, string $pathDark, string $alt, int $width): string + { + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; + $hostname = System::getEnv('_APP_DOMAIN'); + + $imageLight = $protocol . '://' . $hostname . $pathLight; + $imageDark = $protocol . '://' . $hostname . $pathDark; + + $imageUrl = '' . $alt . ''; + + return $imageUrl; + } + public function parseComment(string $comment): self { $state = \explode("\n", $comment)[0] ?? ''; diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 249e4d6edb..4cc9c75483 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -1736,6 +1736,14 @@ class SitesCustomServerTest extends Scope $this->assertStringContainsString("Preview by", $response['body']); $this->assertGreaterThan($contentLength, $response['headers']['content-length']); + $response = $proxyClient->call(Client::METHOD_GET, '/non-existing-path', followRedirects: false, headers: [ + 'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey, + ]); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString("Page not found", $response['body']); + $this->assertStringNotContainsString("Preview by", $response['body']); + $this->assertGreaterThan($contentLength, $response['headers']['content-length']); + $this->cleanupSite($siteId); } @@ -2464,4 +2472,35 @@ class SitesCustomServerTest extends Scope $this->assertStringContainsString('Sub-directory project1', $response2['body']); $this->cleanupSite($siteId); } + + public function testDeploymentCommandEscaping(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'A site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => "echo 'Hello two'", + 'installCommand' => 'echo "Hello one"', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertStringContainsString('Hello one', $deployment['body']['buildLogs']); + $this->assertStringContainsString('Hello two', $deployment['body']['buildLogs']); + + $this->cleanupSite($siteId); + } }