diff --git a/app/controllers/general.php b/app/controllers/general.php
index 03f53b9582..079b7f642b 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -313,7 +313,9 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
if ($type === 'function') {
$permissions = $resource->getAttribute('execute');
if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
- throw new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView);
+ $exception = new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView);
+ $exception->addCTA('View settings', $url . '/console/project-' . $project->getId() . '/functions/function-' . $resource->getId() . '/settings');
+ throw $exception;
}
}
@@ -548,6 +550,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
foreach ($executionResponse['headers'] as $key => $value) {
if (\strtolower($key) === 'content-length') {
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
+ } elseif (\strtolower($key) === 'content-type') {
+ $executionResponse['headers'][$key] = 'text/html';
}
}
}
diff --git a/app/views/general/error.phtml b/app/views/general/error.phtml
index 48133087fd..d60b81f36c 100644
--- a/app/views/general/error.phtml
+++ b/app/views/general/error.phtml
@@ -25,6 +25,34 @@ if($exception !== null && method_exists($exception, 'getCTAs')) {
}
switch ($type) {
+ case 'empty_proxy_error':
+ $type = '';
+ $label = 'Error ' . $code;
+
+ $message = $code >= 500 ? 'An unexpected server error occured.' : 'An unexpected client error occured.';
+ switch($code) {
+ case 401:
+ $message = 'You must sign in to access this page.';
+ break;
+ case 403:
+ $message = 'You are not authorized to access this page.';
+ break;
+ case 404:
+ $message = 'The page you are looking for does not exist.';
+ break;
+ case 504:
+ $message = 'The server did not respond in time.';
+ break;
+ case 501:
+ $message = 'This page is not implemented yet.';
+ break;
+ }
+
+ break;
+ case 'function_execute_permission_missing':
+ $label = 'Execution not permitted';
+ $labelClass = 'warning';
+ break;
case 'build_not_ready':
$label = 'Deployment is still building';
$message = 'The page will update after the build completes.';
@@ -40,7 +68,7 @@ switch ($type) {
$message = 'This page is empty, but you can make it yours.';
break;
case 'deployment_not_found':
- $label = 'No deployments available';
+ $label = 'No active deployments';
$message = 'This page is empty, activate a deployment to make it live.';
break;
case 'build_canceled':
@@ -246,7 +274,7 @@ switch ($type) {
}
.back-button {
- margin-bottom: 24px;
+ margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
@@ -258,6 +286,10 @@ switch ($type) {
line-height: 140%;
letter-spacing: -0.45px;
}
+
+ .back-button:hover {
+ text-decoration: underline;
+ }
.trace-grid {
display: grid;
@@ -412,9 +444,11 @@ switch ($type) {
print($label); ?>
print($message); ?>
-
- print($type); ?>
-
+
+
+ print($type); ?>
+
+
diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
index c07b83a7e7..f2de2c7737 100644
--- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
+++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
@@ -2189,7 +2189,8 @@ class FunctionsCustomServerTest extends Scope
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(404, $response['headers']['status-code']);
- $this->assertStringContainsString('This page is empty, but you can make it yours.', $response['body']);
+ $this->assertStringContainsString('Nothing is here yet', $response['body']);
+ $this->assertStringContainsString('Start with this domain', $response['body']);
// failed deployment
$functionId = $this->setupFunction([
@@ -2198,7 +2199,7 @@ class FunctionsCustomServerTest extends Scope
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'timeout' => 15,
- 'commands' => 'cd random',
+ 'commands' => 'cd non-existing-directory',
'execute' => ['any']
]);
@@ -2219,7 +2220,8 @@ class FunctionsCustomServerTest extends Scope
]));
$this->assertEquals(404, $response['headers']['status-code']);
- $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']);
+ $this->assertStringContainsString('No active deployments', $response['body']);
+ $this->assertStringContainsString('View deployments', $response['body']);
// canceled deployment
$deployment = $this->createDeployment($functionId, [
@@ -2241,29 +2243,32 @@ class FunctionsCustomServerTest extends Scope
]));
$this->assertEquals(404, $response['headers']['status-code']);
- $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']);
+ $this->assertStringContainsString('No active deployments', $response['body']);
+ $this->assertStringContainsString('View deployments', $response['body']);
$this->cleanupFunction($functionId);
+ }
- // no execute permission
+ public function testErrorPagesPermissions(): void
+ {
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'execute' => [],
'timeout' => 15,
+ 'commands' => '',
+ 'execute' => ['users']
]);
$domain = $this->setupFunctionDomain($functionId);
+ $proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$deploymentId = $this->setupDeployment($functionId, [
- 'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'activate' => true
]);
-
$this->assertNotEmpty($deploymentId);
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
@@ -2272,7 +2277,79 @@ class FunctionsCustomServerTest extends Scope
]));
$this->assertEquals(401, $response['headers']['status-code']);
- $this->assertStringContainsString('FUNCTION_EXECUTE_PERMISSION_MISSING', $response['body']);
+ $this->assertStringContainsString('Execution not permitted', $response['body']);
+ $this->assertStringContainsString('View settings', $response['body']);
+
+ $this->cleanupFunction($functionId);
+ }
+
+ public function testErrorPagesEmptyBody(): void
+ {
+ $functionId = $this->setupFunction([
+ 'functionId' => ID::unique(),
+ 'name' => 'Test Error Pages',
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
+ 'timeout' => 15,
+ 'commands' => '',
+ 'execute' => ['any']
+ ]);
+
+ $domain = $this->setupFunctionDomain($functionId);
+ $proxyClient = new Client();
+ $proxyClient->setEndpoint('http://' . $domain);
+
+ $deploymentId = $this->setupDeployment($functionId, [
+ 'code' => $this->packageFunction('php'),
+ 'activate' => true
+ ]);
+ $this->assertNotEmpty($deploymentId);
+
+ $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=404', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ]));
+ $this->assertEquals(404, $response['headers']['status-code']);
+ $this->assertStringContainsString('Error 404', $response['body']);
+ $this->assertStringContainsString('does not exist', $response['body']);
+
+ $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=504', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ]));
+ $this->assertEquals(504, $response['headers']['status-code']);
+ $this->assertStringContainsString('Error 504', $response['body']);
+ $this->assertStringContainsString('respond in time', $response['body']);
+
+ $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=400', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ]));
+ $this->assertEquals(400, $response['headers']['status-code']);
+ $this->assertStringContainsString('Error 400', $response['body']);
+ $this->assertStringContainsString('unexpected client error', $response['body']);
+
+ $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=500', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ]));
+ $this->assertEquals(500, $response['headers']['status-code']);
+ $this->assertStringContainsString('Error 500', $response['body']);
+ $this->assertStringContainsString('unexpected server error', $response['body']);
+
+ $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=400&body=CustomError400', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ]));
+ $this->assertEquals(400, $response['headers']['status-code']);
+ $this->assertStringContainsString('CustomError400', $response['body']);
+
+ $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=500&body=CustomError500', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ]));
+ $this->assertEquals(500, $response['headers']['status-code']);
+ $this->assertStringContainsString('CustomError500', $response['body']);
$this->cleanupFunction($functionId);
}
diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php
index df6abfc833..e5f88461b2 100644
--- a/tests/e2e/Services/Sites/SitesCustomServerTest.php
+++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php
@@ -2435,7 +2435,7 @@ class SitesCustomServerTest extends Scope
}, 100000, 500);
$response = $proxyClient->call(Client::METHOD_GET, '/');
- $this->assertStringContainsString('his page is empty, activate a deployment to make it live.', $response['body']);
+ $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']);
$this->cleanupSite($siteId);
}
@@ -2515,16 +2515,16 @@ class SitesCustomServerTest extends Scope
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(404, $response['headers']['status-code']);
- $this->assertStringContainsString("This page is empty, but you can make it yours.", $response['body']);
+ $this->assertStringContainsString('Nothing is here yet', $response['body']);
+ $this->assertStringContainsString('Start with this domain', $response['body']);
$siteId = $this->setupSite([
'siteId' => ID::unique(),
- 'name' => 'Astro SSR site',
- 'framework' => 'astro',
+ 'name' => 'Static site',
+ 'framework' => 'other',
'buildRuntime' => 'node-22',
- 'outputDirectory' => './dist',
- 'buildCommand' => 'cd random',
- 'installCommand' => 'npm install',
+ 'outputDirectory' => './',
+ 'buildCommand' => 'sleep 5 && cd non-existing-directory',
]);
$this->assertNotEmpty($siteId);
@@ -2532,29 +2532,20 @@ class SitesCustomServerTest extends Scope
// test canceled deployment error page
$deployment = $this->createDeployment($siteId, [
- 'code' => $this->packageSite('astro'),
+ 'code' => $this->packageSite('static'),
'activate' => 'true'
]);
-
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
- $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
-
- $deploymentDomain = $this->getDeploymentDomain($deploymentId);
- $this->assertNotEmpty($deploymentDomain);
-
- $this->assertEventually(function () use ($siteId, $deploymentId) {
- $deployment = $this->getDeployment($siteId, $deploymentId);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
- $this->assertEquals('building', $deployment['body']['status']);
- }, 100000, 250);
$deployment = $this->cancelDeployment($siteId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('canceled', $deployment['body']['status']);
+ $deploymentDomain = $this->getDeploymentDomain($deploymentId);
+ $this->assertNotEmpty($deploymentDomain);
+
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $deploymentDomain);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
@@ -2571,12 +2562,14 @@ class SitesCustomServerTest extends Scope
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertStringContainsString("Deployment build canceled", $response['body']);
+ $this->assertStringContainsString("View deployments", $response['body']);
// check site domain for no active deployments
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(404, $response['headers']['status-code']);
- $this->assertStringContainsString("No deployments available", $response['body']);
+ $this->assertStringContainsString('No active deployments', $response['body']);
+ $this->assertStringContainsString('View deployments', $response['body']);
$deployment = $this->createDeployment($siteId, [
'code' => $this->packageSite('astro'),
@@ -2599,6 +2592,8 @@ class SitesCustomServerTest extends Scope
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertStringContainsString("Deployment is still building", $response['body']);
+ $this->assertStringContainsString("View logs", $response['body']);
+ $this->assertStringContainsString("Reload", $response['body']);
$this->assertEventually(function () use ($siteId, $deploymentId) {
$deployment = $this->getDeployment($siteId, $deploymentId);
@@ -2612,6 +2607,7 @@ class SitesCustomServerTest extends Scope
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertStringContainsString("Deployment build failed", $response['body']);
+ $this->assertStringContainsString("View logs", $response['body']);
$this->cleanupSite($siteId);
}
diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php
index 27a9418b3c..6c67f27ee1 100644
--- a/tests/resources/functions/php/index.php
+++ b/tests/resources/functions/php/index.php
@@ -1,6 +1,12 @@
req->path === '/custom-response') {
+ $code = (int) ($context->req->query['code'] ?? '200');
+ $body = $context->req->query['body'] ?? '';
+ return $context->res->send($body, $code);
+ }
+
$context->log('body-is-' . ($context->req->body ?? ''));
$context->log('custom-header-is-' . ($context->req->headers['x-custom-header'] ?? ''));
$context->log('method-is-' . \strtolower($context->req->method ?? ''));
diff --git a/ a.txt b/ a.txt
new file mode 100644
index 0000000000..c62bf8784d
--- /dev/null
+++ b/ a.txt
@@ -0,0 +1,8 @@
+PHPUnit 9.6.22 by Sebastian Bergmann and contributors.
+
+Tests\E2E\Services\Sites\SitesCustomServerTest::testErrorPages ended in 7904.421256 milliseconds
+. 1 / 1 (100%)
+
+Time: 00:07.907, Memory: 30.16 MB
+
+OK (1 test, 67 assertions)