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 .= "[37mCapturing screenshots ...[0m\n";
- $deployment->setAttribute('buildLogs', $logs);
+
+ if ($resource->getCollection() === 'sites') {
+ $date = \date('H:i:s');
+ $logs .= "[90m[$date] [90m[[0mappwrite[90m][37m Screenshot capturing started. [0m\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 .= "[90m[$date] [90m[[0mappwrite[90m][37m Screenshot capturing finished. [0m\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 .= "[90m[$date] [90m[[0mappwrite[90m][33m Screenshot capturing failed. Deployment will continue. [0m\n";
+
+ $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
}
}
+ $logs = $deployment->getAttribute('buildLogs', '');
+ $date = \date('H:i:s');
+ $logs .= "[90m[$date] [90m[[0mappwrite[90m][32m Deployment finished. [0m\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 = '
';
+ $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 = '
';
- 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 = '
';
+
+ 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);
+ }
}