diff --git a/app/config/templates/site.php b/app/config/templates/site.php index ea84367dbd..d466d23282 100644 --- a/app/config/templates/site.php +++ b/app/config/templates/site.php @@ -22,6 +22,8 @@ class UseCases public const DOCUMENTATION = 'documentation'; public const BLOG = 'blog'; public const AI = 'artificial intelligence'; + public const FORMS = 'forms'; + public const DASHBOARD = 'dashboard'; } const TEMPLATE_FRAMEWORKS = [ @@ -1469,4 +1471,135 @@ return [ ], ] ], + [ + 'key' => 'crm-dashboard-react-admin', + 'name' => 'CRM dashboard with React Admin', + 'tagline' => 'A React-based admin dashboard template with CRM features.', + 'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'useCases' => [UseCases::DASHBOARD], + 'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png', + 'frameworks' => [ + getFramework('REACT', [ + 'providerRootDirectory' => './react/react-admin', + 'installCommand' => 'pnpm install', + 'buildCommand' => 'pnpm build && pnpm db-seed', + 'outputDirectory' => './dist', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates-for-sites', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.7.*', + 'variables' => [ + [ + 'name' => 'VITE_APPWRITE_ENDPOINT', + 'description' => 'Endpoint of Appwrite server', + 'value' => '{apiEndpoint}', + 'placeholder' => '{apiEndpoint}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_ID', + 'description' => 'Your Appwrite project ID', + 'value' => '{projectId}', + 'placeholder' => '{projectId}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'Your Appwrite API key (for seeding only)', + 'value' => '', + 'placeholder' => 'a0b1...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VITE_APPWRITE_DATABASE_ID', + 'description' => 'Database ID (default: admin)', + 'value' => 'admin', + 'placeholder' => 'admin', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_TABLE_REVIEWS', + 'description' => 'Table ID for reviews table', + 'value' => 'reviews', + 'placeholder' => 'reviews', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_TABLE_INVOICES', + 'description' => 'Table ID for invoices table', + 'value' => 'invoices', + 'placeholder' => 'invoices', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_TABLE_ORDERS', + 'description' => 'Table ID for orders table', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_TABLE_PRODUCTS', + 'description' => 'Table ID for products table', + 'value' => 'products', + 'placeholder' => 'products', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_TABLE_CATEGORIES', + 'description' => 'Table ID for categories table', + 'value' => 'categories', + 'placeholder' => 'categories', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_TABLE_CUSTOMERS', + 'description' => 'Table ID for customers table', + 'value' => 'customers', + 'placeholder' => 'customers', + 'required' => false, + 'type' => 'text' + ], + ] + ], + [ + 'key' => 'job-applications-formspree', + 'name' => 'Job applications form with Formspree', + 'tagline' => 'A simple form submission template using Formspree.', + 'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'useCases' => [UseCases::FORMS], + 'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png', + 'frameworks' => [ + getFramework('REACT', [ + 'providerRootDirectory' => './react/formspree', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates-for-sites', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.7.*', + 'variables' => [ + [ + 'name' => 'VITE_FORMSPREE_FORM_ID', + 'description' => 'Your Formspree form ID', + 'value' => '', + 'placeholder' => 'xrgkpqld', + 'required' => true, + 'type' => 'text' + ], + ] + ] ]; diff --git a/public/images/sites/templates/crm-dashboard-react-admin-dark.png b/public/images/sites/templates/crm-dashboard-react-admin-dark.png new file mode 100644 index 0000000000..467f6c4ed2 Binary files /dev/null and b/public/images/sites/templates/crm-dashboard-react-admin-dark.png differ diff --git a/public/images/sites/templates/crm-dashboard-react-admin-light.png b/public/images/sites/templates/crm-dashboard-react-admin-light.png new file mode 100644 index 0000000000..467f6c4ed2 Binary files /dev/null and b/public/images/sites/templates/crm-dashboard-react-admin-light.png differ diff --git a/public/images/sites/templates/job-applications-formspree-dark.png b/public/images/sites/templates/job-applications-formspree-dark.png new file mode 100644 index 0000000000..4f263ff9e5 Binary files /dev/null and b/public/images/sites/templates/job-applications-formspree-dark.png differ diff --git a/public/images/sites/templates/job-applications-formspree-light.png b/public/images/sites/templates/job-applications-formspree-light.png new file mode 100644 index 0000000000..4f263ff9e5 Binary files /dev/null and b/public/images/sites/templates/job-applications-formspree-light.png differ diff --git a/src/Appwrite/Platform/Tasks/Screenshot.php b/src/Appwrite/Platform/Tasks/Screenshot.php index 94609de01e..7ad95c6e72 100644 --- a/src/Appwrite/Platform/Tasks/Screenshot.php +++ b/src/Appwrite/Platform/Tasks/Screenshot.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Tasks; // Example usage: docker compose exec appwrite screenshot --templateId="playground-for-tanstack-start" +// Example of env vars flag: --variables="{\"VITE_FORMSPREE_FORM_ID\":\"xvgkbzll\", \"VITE_FORMSPREE_FORM_SECRET\":\"some_secret\"}" // Expected output: public/images/sites/templates/playground-for-tanstack-start-light.png (and dark.png) use Appwrite\ID; @@ -10,6 +11,7 @@ use Tests\E2E\Client; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Platform\Action; +use Utopia\System\System; use Utopia\Validator\Text; class Screenshot extends Action @@ -24,11 +26,24 @@ class Screenshot extends Action $this ->desc('Create Site template screenshot') ->param('templateId', '', new Text(128), 'Template ID.') + ->param('variables', '', new Text(16384), 'JSON of env variables to use when setting up the site.') ->callback($this->action(...)); } - public function action(string $templateId): void + public function action(string $templateId, string $variables): void { + if (empty($variables)) { + $variables = []; + } else { + $variables = \json_decode($variables, true); + if (!\is_array($variables)) { + throw new \Exception('Invalid JSON in --variables flag'); + } + } + if ($variables === null) { + throw new \Exception('Invalid JSON in --variables flag'); + } + $templates = Config::getParam('templates-site', []); $allowedTemplates = \array_filter($templates, function ($item) use ($templateId) { @@ -133,6 +148,11 @@ class Screenshot extends Action $framework = $template['frameworks'][0]; + // Use best specifications to prevent out-of-memory during build + $specifications = Config::getParam('specifications', []); + $specifications = array_keys($specifications); + $specification = \end($specifications); + // Create site $site = $client->call(Client::METHOD_POST, '/sites', [ 'content-type' => 'application/json', @@ -141,6 +161,7 @@ class Screenshot extends Action 'cookie' => $cookieConsole ], [ 'siteId' => ID::unique(), + 'specification' => $specification, 'name' => $template["name"], 'framework' => $framework['key'], 'adapter' => $framework['adapter'], @@ -162,21 +183,48 @@ class Screenshot extends Action $siteId = $site['body']['$id']; + // Prepare API key, incase it's needed as variable + $response = $client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => $cookieConsole + ], [ + 'name' => 'Screenshot API key', + 'scopes' => \array_keys(Config::getParam('scopes', [])) + ]); + + if ($response['headers']['status-code'] !== 201) { + Console::error(\json_encode($response)); + throw new \Exception("Failed to create API key"); + } + + $apiKey = $response['body']['secret']; + + Console::info("API key created"); + + $variables['APPWRITE_API_KEY'] = $apiKey; + // Create variables if (!empty($template['variables'] ?? [])) { foreach ($template['variables'] as $variable) { - if (empty($variable['value'] ?? '')) { - if (($variable['required'] ?? false) === true) { - throw new \Exception("Missing required variable: {$variable['name']}"); - } - - continue; - } + $name = $variable['name']; $value = $variable['value']; $value = \str_replace('{projectName}', $projectName, $value); $value = \str_replace('{projectId}', $projectId, $value); - $value = \str_replace('{apiEndpoint}', 'http://localhost/v1', $value); + $value = \str_replace('{apiEndpoint}', 'http://' . System::getEnv('_APP_DOMAIN', '') . '/v1', $value); + + if (\array_key_exists($name, $variables)) { + $value = $variables[$name]; + } + + if (empty($value)) { + if (($variable['required'] ?? false) === true) { + throw new \Exception("Missing required variable: {$variable['name']}. Provide it using the --variables flag to resolve this."); + } + + continue; + } $response = $client->call(Client::METHOD_POST, '/sites/' . $siteId . '/variables', [ 'content-type' => 'application/json', @@ -207,7 +255,8 @@ class Screenshot extends Action 'owner' => $template['providerOwner'], 'repository' => $template['providerRepositoryId'], 'rootDirectory' => $framework['providerRootDirectory'], - 'version' => $template['providerVersion'], + 'reference' => $template['providerVersion'], + 'type' => 'tag', 'activate' => true, ]);