From e9828f8ccf7e94ee65b4aa0f3a0745c843361308 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Tue, 17 Feb 2026 10:10:45 +0100 Subject: [PATCH 01/34] Datetime format settings --- migrations/Version20260217093259.php | 94 +++++++++++++++++++ src/Core/Enum/SettingEnum.php | 3 + .../Repository/SettingOptionRepository.php | 2 +- .../Resources/translations/messages.en.yaml | 3 + src/Core/Service/DateFormatterService.php | 57 +++++++++++ .../Email/EmailContextBuilderService.php | 2 +- src/Core/Twig/AppExtension.php | 8 ++ .../default/email/purchased_product.html.twig | 2 +- themes/default/email/renew_product.html.twig | 2 +- .../default/email/server_suspended.html.twig | 4 +- .../panel/admin/field/role_users.html.twig | 2 +- .../admin/widgets/recent_payments.html.twig | 2 +- .../admin/widgets/recent_users.html.twig | 2 +- themes/default/panel/cart/renew.html.twig | 2 +- .../panel/crud/plugin/detail.html.twig | 6 +- .../dashboard/components/activity.html.twig | 2 +- themes/default/panel/server/server.html.twig | 2 +- .../panel/server/server_suspended.html.twig | 2 +- .../server/tabs/activity/activity.html.twig | 2 +- .../server/tabs/backups/backups.html.twig | 2 +- .../server/tabs/schedules/schedules.html.twig | 4 +- .../components/extend_server.html.twig | 2 +- .../panel/server/tabs/users/users.html.twig | 2 +- .../servers/components/server-list.html.twig | 2 +- .../panel/servers/components/server.html.twig | 2 +- 25 files changed, 189 insertions(+), 24 deletions(-) create mode 100644 migrations/Version20260217093259.php create mode 100644 src/Core/Service/DateFormatterService.php diff --git a/migrations/Version20260217093259.php b/migrations/Version20260217093259.php new file mode 100644 index 00000000..90546559 --- /dev/null +++ b/migrations/Version20260217093259.php @@ -0,0 +1,94 @@ +addSql(" + INSERT INTO setting (name, value, type, context, hierarchy, nullable) + SELECT 'date_format', 'Y-m-d H:i', 'select', 'general_settings', 16, 0 + WHERE NOT EXISTS (SELECT 1 FROM setting WHERE name = 'date_format') + "); + + // 2. Options for date_format (8 formats) + $this->addSql(" + INSERT INTO setting_option (setting_name, option_key, option_value, sort_order, created_at, updated_at) VALUES + ('date_format', '2026-02-17 14:30', 'Y-m-d H:i', 0, NOW(), NOW()), + ('date_format', '2026-02-17 14:30:45', 'Y-m-d H:i:s', 1, NOW(), NOW()), + ('date_format', '17.02.2026 14:30', 'd.m.Y H:i', 2, NOW(), NOW()), + ('date_format', '17.02.2026 14:30:45', 'd.m.Y H:i:s', 3, NOW(), NOW()), + ('date_format', '17/02/2026 14:30', 'd/m/Y H:i', 4, NOW(), NOW()), + ('date_format', '02/17/2026 2:30 PM', 'm/d/Y g:i A', 5, NOW(), NOW()), + ('date_format', 'Feb 17, 2026 2:30 PM', 'M d, Y g:i A', 6, NOW(), NOW()), + ('date_format', 'Feb 17, 2026 14:30', 'M d, Y H:i', 7, NOW(), NOW()) + ON DUPLICATE KEY UPDATE updated_at = NOW() + "); + + // 3. Setting: date_timezone (SELECT) + $this->addSql(" + INSERT INTO setting (name, value, type, context, hierarchy, nullable) + SELECT 'date_timezone', 'UTC', 'select', 'general_settings', 17, 0 + WHERE NOT EXISTS (SELECT 1 FROM setting WHERE name = 'date_timezone') + "); + + // 4. Options for date_timezone (~25 zones) + $this->addSql(" + INSERT INTO setting_option (setting_name, option_key, option_value, sort_order, created_at, updated_at) VALUES + ('date_timezone', 'UTC (GMT+0:00)', 'UTC', 0, NOW(), NOW()), + ('date_timezone', 'Europe/Warsaw (GMT+1:00/+2:00)', 'Europe/Warsaw', 1, NOW(), NOW()), + ('date_timezone', 'Europe/London (GMT+0:00/+1:00)', 'Europe/London', 2, NOW(), NOW()), + ('date_timezone', 'Europe/Berlin (GMT+1:00/+2:00)', 'Europe/Berlin', 3, NOW(), NOW()), + ('date_timezone', 'Europe/Paris (GMT+1:00/+2:00)', 'Europe/Paris', 4, NOW(), NOW()), + ('date_timezone', 'America/New_York (GMT-5:00/-4:00)', 'America/New_York', 5, NOW(), NOW()), + ('date_timezone', 'America/Chicago (GMT-6:00/-5:00)', 'America/Chicago', 6, NOW(), NOW()), + ('date_timezone', 'America/Los_Angeles (GMT-8:00/-7:00)', 'America/Los_Angeles', 7, NOW(), NOW()), + ('date_timezone', 'America/Toronto (GMT-5:00/-4:00)', 'America/Toronto', 8, NOW(), NOW()), + ('date_timezone', 'America/Sao_Paulo (GMT-3:00)', 'America/Sao_Paulo', 9, NOW(), NOW()), + ('date_timezone', 'Asia/Tokyo (GMT+9:00)', 'Asia/Tokyo', 10, NOW(), NOW()), + ('date_timezone', 'Asia/Shanghai (GMT+8:00)', 'Asia/Shanghai', 11, NOW(), NOW()), + ('date_timezone', 'Asia/Hong_Kong (GMT+8:00)', 'Asia/Hong_Kong', 12, NOW(), NOW()), + ('date_timezone', 'Asia/Singapore (GMT+8:00)', 'Asia/Singapore', 13, NOW(), NOW()), + ('date_timezone', 'Asia/Dubai (GMT+4:00)', 'Asia/Dubai', 14, NOW(), NOW()), + ('date_timezone', 'Asia/Kolkata (GMT+5:30)', 'Asia/Kolkata', 15, NOW(), NOW()), + ('date_timezone', 'Australia/Sydney (GMT+10:00/+11:00)', 'Australia/Sydney', 16, NOW(), NOW()), + ('date_timezone', 'Australia/Melbourne (GMT+10:00/+11:00)', 'Australia/Melbourne', 17, NOW(), NOW()), + ('date_timezone', 'Pacific/Auckland (GMT+12:00/+13:00)', 'Pacific/Auckland', 18, NOW(), NOW()), + ('date_timezone', 'Europe/Amsterdam (GMT+1:00/+2:00)', 'Europe/Amsterdam', 19, NOW(), NOW()), + ('date_timezone', 'Europe/Moscow (GMT+3:00)', 'Europe/Moscow', 20, NOW(), NOW()), + ('date_timezone', 'Africa/Cairo (GMT+2:00)', 'Africa/Cairo', 21, NOW(), NOW()), + ('date_timezone', 'America/Mexico_City (GMT-6:00/-5:00)', 'America/Mexico_City', 22, NOW(), NOW()), + ('date_timezone', 'America/Denver (GMT-7:00/-6:00)', 'America/Denver', 23, NOW(), NOW()), + ('date_timezone', 'Pacific/Honolulu (GMT-10:00)', 'Pacific/Honolulu', 24, NOW(), NOW()) + ON DUPLICATE KEY UPDATE updated_at = NOW() + "); + + // 5. Setting: date_show_timezone (BOOLEAN) + $this->addSql(" + INSERT INTO setting (name, value, type, context, hierarchy, nullable) + SELECT 'date_show_timezone', '0', 'boolean', 'general_settings', 18, 0 + WHERE NOT EXISTS (SELECT 1 FROM setting WHERE name = 'date_show_timezone') + "); + } + + public function down(Schema $schema): void + { + $this->addSql("DELETE FROM setting_option WHERE setting_name IN ('date_format', 'date_timezone')"); + $this->addSql("DELETE FROM setting WHERE name IN ('date_format', 'date_timezone', 'date_show_timezone')"); + } +} diff --git a/src/Core/Enum/SettingEnum.php b/src/Core/Enum/SettingEnum.php index d6b58796..4af2e43e 100644 --- a/src/Core/Enum/SettingEnum.php +++ b/src/Core/Enum/SettingEnum.php @@ -51,4 +51,7 @@ enum SettingEnum: string case LOG_CLEANUP_ENABLED = 'log_cleanup_enabled'; case LOG_CLEANUP_DAYS_AFTER = 'log_cleanup_days_after'; case TELEMETRY_CONSENT = 'telemetry_consent'; + case DATE_FORMAT = 'date_format'; + case DATE_TIMEZONE = 'date_timezone'; + case DATE_SHOW_TIMEZONE = 'date_show_timezone'; } diff --git a/src/Core/Repository/SettingOptionRepository.php b/src/Core/Repository/SettingOptionRepository.php index fc1964c5..3fe1d3c7 100644 --- a/src/Core/Repository/SettingOptionRepository.php +++ b/src/Core/Repository/SettingOptionRepository.php @@ -50,7 +50,7 @@ public function getOptionsForSetting(string $settingName): array $options = []; foreach ($results as $result) { - $options[$result['optionValue']] = $result['optionKey']; + $options[$result['optionKey']] = $result['optionValue']; } return $options; diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index c6c0e6af..7863ee78 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -1114,6 +1114,9 @@ pteroca: site_url: 'Base URL of your panel (e.g., https://panel.example.com). Used in emails, SSO, and redirects.' site_title: 'Panel name displayed in browser tabs, headers, and emails. Your brand visible to all users.' site_locale: 'Default language for the interface. Affects all menu items, labels, and system messages.' + date_format: 'Choose how dates and times are displayed throughout the panel. Affects all date displays for all users.' + date_timezone: 'Select the timezone for displaying dates. All dates will be converted from UTC to the selected timezone.' + date_show_timezone: 'When enabled, timezone abbreviation (e.g., "UTC", "EST") will be shown next to all displayed dates.' site_favicon: 'Icon in browser tabs and bookmarks. Upload ICO, PNG, or SVG (16x16 or 32x32px recommended).' pterodactyl_url: 'URL of your Pterodactyl panel (e.g., https://ptero.example.com). Required for all server operations.' pterodactyl_api_key: 'Application API key from Pterodactyl panel. Required for user & server creation, suspension, and deletion.' diff --git a/src/Core/Service/DateFormatterService.php b/src/Core/Service/DateFormatterService.php new file mode 100644 index 00000000..0929d4c3 --- /dev/null +++ b/src/Core/Service/DateFormatterService.php @@ -0,0 +1,57 @@ +settingService->getSetting('date_format') ?? 'Y-m-d H:i'; + $timezone = $this->settingService->getSetting('date_timezone') ?? 'UTC'; + $showTimezone = $this->settingService->getSetting('date_show_timezone') === '1'; + + // Clone date to avoid mutating original + $dateTime = DateTime::createFromInterface($date); + + // Apply timezone conversion + $dateTime->setTimezone(new DateTimeZone($timezone)); + + // Format date + $formatted = $dateTime->format($format); + + // Optionally append timezone abbreviation + if ($showTimezone) { + $formatted .= ' (' . $dateTime->format('T') . ')'; + } + + return $formatted; + } + + /** + * Get current date format setting + */ + public function getDateFormat(): string + { + return $this->settingService->getSetting('date_format') ?? 'Y-m-d H:i'; + } + + /** + * Get current timezone setting + */ + public function getTimezone(): string + { + return $this->settingService->getSetting('date_timezone') ?? 'UTC'; + } +} diff --git a/src/Core/Service/Email/EmailContextBuilderService.php b/src/Core/Service/Email/EmailContextBuilderService.php index 23189f90..683d3f79 100644 --- a/src/Core/Service/Email/EmailContextBuilderService.php +++ b/src/Core/Service/Email/EmailContextBuilderService.php @@ -98,7 +98,7 @@ private function buildBaseContext( $serverData = [ 'ip' => $serverDetails->ip, - 'expiresAt' => $server->getExpiresAt()->format('Y-m-d H:i'), + 'expiresAt' => $server->getExpiresAt(), ]; $panelData = [ diff --git a/src/Core/Twig/AppExtension.php b/src/Core/Twig/AppExtension.php index 8491edd3..8910a312 100644 --- a/src/Core/Twig/AppExtension.php +++ b/src/Core/Twig/AppExtension.php @@ -8,6 +8,7 @@ use App\Core\Enum\SettingEnum; use App\Core\DTO\TemplateOptionsDTO; use App\Core\Service\SettingService; +use App\Core\Service\DateFormatterService; use App\Core\Trait\FormatBytesTrait; use Symfony\Component\Asset\Packages; use Twig\Extension\AbstractExtension; @@ -32,6 +33,7 @@ public function __construct( private readonly RouterInterface $router, private readonly PterodactylRedirectService $pterodactylRedirectService, private readonly PluginAssetManager $pluginAssetManager, + private readonly DateFormatterService $dateFormatterService, ) {} public function getFunctions(): array @@ -59,6 +61,7 @@ public function getFilters(): array { return [ new TwigFilter('format_bytes', [$this, 'formatBytes']), + new TwigFilter('app_date', [$this, 'formatDate']), ]; } @@ -174,4 +177,9 @@ public function pluginAsset(string $pluginName, string $path): string { return $this->pluginAssetManager->getAssetUrl($pluginName, $path); } + + public function formatDate(\DateTimeInterface $date): string + { + return $this->dateFormatterService->formatDateTime($date); + } } diff --git a/themes/default/email/purchased_product.html.twig b/themes/default/email/purchased_product.html.twig index 974bed7e..93fc3b34 100644 --- a/themes/default/email/purchased_product.html.twig +++ b/themes/default/email/purchased_product.html.twig @@ -13,7 +13,7 @@ {% include 'email/components/alert.html.twig' with { type: 'success', - message: 'pteroca.email.store.server_purchased'|trans ~ ' ' ~ 'pteroca.email.store.expiration_date'|trans ~ ': ' ~ server.expiresAt|date('Y-m-d H:i') + message: 'pteroca.email.store.server_purchased'|trans ~ ' ' ~ 'pteroca.email.store.expiration_date'|trans ~ ': ' ~ server.expiresAt|app_date } %} {% include 'email/components/divider.html.twig' %} diff --git a/themes/default/email/renew_product.html.twig b/themes/default/email/renew_product.html.twig index 6c2d9be8..d02ae28e 100644 --- a/themes/default/email/renew_product.html.twig +++ b/themes/default/email/renew_product.html.twig @@ -13,7 +13,7 @@ {% include 'email/components/alert.html.twig' with { type: 'success', - message: 'pteroca.email.renew.server_renewed'|trans ~ ' ' ~ 'pteroca.email.renew.new_expiration'|trans ~ ' ' ~ server.expiresAt|date('Y-m-d H:i') + message: 'pteroca.email.renew.server_renewed'|trans ~ ' ' ~ 'pteroca.email.renew.new_expiration'|trans ~ ' ' ~ server.expiresAt|app_date } %} {% include 'email/components/divider.html.twig' %} diff --git a/themes/default/email/server_suspended.html.twig b/themes/default/email/server_suspended.html.twig index 4acef5fa..3b56e356 100644 --- a/themes/default/email/server_suspended.html.twig +++ b/themes/default/email/server_suspended.html.twig @@ -19,7 +19,7 @@ {% include 'email/components/table.html.twig' with { rows: [ {label: 'pteroca.email.suspended.server_details'|trans, value: '' ~ serverName ~ ''}, - {label: 'pteroca.email.suspended.suspension_date_label'|trans, value: suspensionDate|date('Y-m-d H:i')} + {label: 'pteroca.email.suspended.suspension_date_label'|trans, value: suspensionDate|app_date} ] } %} @@ -29,7 +29,7 @@ title: 'pteroca.email.suspended.auto_delete_title'|trans, message: 'pteroca.email.suspended.auto_delete_warning'|trans({ '%days%': deleteAfterDays, - '%deleteDate%': deleteDate|date('Y-m-d H:i') + '%deleteDate%': deleteDate|app_date }) } %} {% endif %} diff --git a/themes/default/panel/admin/field/role_users.html.twig b/themes/default/panel/admin/field/role_users.html.twig index 08aaea12..2c8be3f9 100644 --- a/themes/default/panel/admin/field/role_users.html.twig +++ b/themes/default/panel/admin/field/role_users.html.twig @@ -52,7 +52,7 @@ - {{ user.createdAt|date('Y-m-d H:i') }} + {{ user.createdAt|app_date }} diff --git a/themes/default/panel/admin/widgets/recent_payments.html.twig b/themes/default/panel/admin/widgets/recent_payments.html.twig index 10d0cb50..51e5bbfe 100644 --- a/themes/default/panel/admin/widgets/recent_payments.html.twig +++ b/themes/default/panel/admin/widgets/recent_payments.html.twig @@ -47,7 +47,7 @@ {% endif %} - {{ payment.createdAt|date('Y-m-d H:i') }} + {{ payment.createdAt|app_date }} {% endfor %} diff --git a/themes/default/panel/admin/widgets/recent_users.html.twig b/themes/default/panel/admin/widgets/recent_users.html.twig index ce7edb51..9dd0edf6 100644 --- a/themes/default/panel/admin/widgets/recent_users.html.twig +++ b/themes/default/panel/admin/widgets/recent_users.html.twig @@ -39,7 +39,7 @@ {% endif %} - {{ user.createdAt|date('Y-m-d H:i') }} + {{ user.createdAt|app_date }} {% endfor %} diff --git a/themes/default/panel/cart/renew.html.twig b/themes/default/panel/cart/renew.html.twig index 467b21b4..228d3806 100644 --- a/themes/default/panel/cart/renew.html.twig +++ b/themes/default/panel/cart/renew.html.twig @@ -53,7 +53,7 @@ diff --git a/themes/default/panel/server/tabs/startup/startup.html.twig b/themes/default/panel/server/tabs/startup/startup.html.twig index 256e0ca2..ac30708b 100644 --- a/themes/default/panel/server/tabs/startup/startup.html.twig +++ b/themes/default/panel/server/tabs/startup/startup.html.twig @@ -32,10 +32,17 @@ if (!toggleDisabledElement(element)) { element.focus(); - } else if (!saveSetting(settingName, isStartupOption)) { - toggleDisabledElement(element); - element.focus(); - return; + } else { + if (element.classList.contains('is-invalid')) { + toggleDisabledElement(element); + element.focus(); + return; + } + if (!saveSetting(settingName, isStartupOption)) { + toggleDisabledElement(element); + element.focus(); + return; + } } icon.classList.toggle('fa-edit'); @@ -156,5 +163,21 @@ throw new Error(message); } + {% include 'components/egg_variable_validator.html.twig' %} + {% endif %} {% endblock %} \ No newline at end of file From 533f28541c5ab42d52a1e233a49a1bb19e8cba32 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Mon, 23 Feb 2026 20:08:47 +0100 Subject: [PATCH 18/34] Manager server button on server management page --- migrations/Version20260223000001.php | 33 +++++++++++++++++++ src/Core/Enum/SettingEnum.php | 1 + .../Resources/translations/messages.cn.yaml | 3 ++ .../Resources/translations/messages.de.yaml | 3 ++ .../translations/messages.de_CH.yaml | 3 ++ .../Resources/translations/messages.en.yaml | 3 ++ .../Resources/translations/messages.es.yaml | 3 ++ .../Resources/translations/messages.fr.yaml | 3 ++ .../Resources/translations/messages.hi.yaml | 3 ++ .../Resources/translations/messages.id.yaml | 3 ++ .../Resources/translations/messages.it.yaml | 3 ++ .../Resources/translations/messages.nl.yaml | 3 ++ .../Resources/translations/messages.pl.yaml | 3 ++ .../Resources/translations/messages.pt.yaml | 3 ++ .../Resources/translations/messages.ru.yaml | 3 ++ .../Resources/translations/messages.ua.yaml | 3 ++ .../PterodactylRedirectService.php | 11 +++++++ src/Core/Twig/AppExtension.php | 6 ++++ themes/default/panel/server/server.html.twig | 11 +++++++ 19 files changed, 104 insertions(+) create mode 100644 migrations/Version20260223000001.php diff --git a/migrations/Version20260223000001.php b/migrations/Version20260223000001.php new file mode 100644 index 00000000..c662242c --- /dev/null +++ b/migrations/Version20260223000001.php @@ -0,0 +1,33 @@ +addSql(" + INSERT INTO setting (name, value, type, context, hierarchy, nullable) + SELECT 'pterodactyl_manage_in_panel_button_enabled', '0', 'boolean', 'pterodactyl_settings', 105, 0 + WHERE NOT EXISTS (SELECT 1 FROM setting WHERE name = 'pterodactyl_manage_in_panel_button_enabled') + "); + } + + public function down(Schema $schema): void + { + $this->addSql("DELETE FROM setting WHERE name = 'pterodactyl_manage_in_panel_button_enabled'"); + } +} diff --git a/src/Core/Enum/SettingEnum.php b/src/Core/Enum/SettingEnum.php index 54ed1bda..fb93092e 100644 --- a/src/Core/Enum/SettingEnum.php +++ b/src/Core/Enum/SettingEnum.php @@ -19,6 +19,7 @@ enum SettingEnum: string case PTERODACTYL_API_KEY = 'pterodactyl_api_key'; case PTERODACTYL_SSO_ENABLED = 'pterodactyl_sso_enabled'; case PTERODACTYL_SSO_SECRET = 'pterodactyl_sso_secret'; + case PTERODACTYL_MANAGE_IN_PANEL_BUTTON_ENABLED = 'pterodactyl_manage_in_panel_button_enabled'; case SHOW_PTERODACTYL_LOGS_IN_SERVER_ACTIVITY = 'show_pterodactyl_logs_in_server_activity'; case STRIPE_SECRET_KEY = 'stripe_secret_key'; case STRIPE_PAYMENT_METHODS = 'stripe_payment_methods'; diff --git a/src/Core/Resources/translations/messages.cn.yaml b/src/Core/Resources/translations/messages.cn.yaml index 66720447..a625b2ac 100644 --- a/src/Core/Resources/translations/messages.cn.yaml +++ b/src/Core/Resources/translations/messages.cn.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: 重新安装服务器将停止它,然后重新运行最初配置它的安装脚本。在此过程中某些文件可能会被删除或修改,请在继续之前备份您的数据。 select_software: 选择软件 admin_view_warning: 您当前以管理员身份查看此服务器。请谨慎进行更改,因为它们可能会影响服务器及其用户。 + back_to_servers: '返回服务器' + manage_in_pterodactyl: '在 Pterodactyl 中管理' extend_server: 延长服务器 extend_server_expires: '您的服务器{{ productName }}当前设置为在{{ expiresAt }}到期。' extend_server_hint: 您可以通过点击下面的按钮来延长服务器。 @@ -1163,6 +1165,7 @@ pteroca: delete_suspended_servers_days_after: 从服务器暂停日期起,经过多少天后自动删除已暂停的服务器 pterodactyl_sso_enabled: '启用使用 Pterodactyl 面板的单点登录 (SSO)(必须设置 SSO 密钥)' pterodactyl_sso_secret: 'Pterodactyl SSO 登录密钥' + pterodactyl_manage_in_panel_button_enabled: '在服务器管理页面启用"在 Pterodactyl 中管理"按钮' show_pterodactyl_logs_in_server_activity: '在服务器活动中显示 Pterodactyl 日志' current_theme: 当前面板的主题 renewal_notification_enabled: 服务器续订时发送邮件确认。所有续订通知的主开关。 diff --git a/src/Core/Resources/translations/messages.de.yaml b/src/Core/Resources/translations/messages.de.yaml index bc497cce..3dd6d709 100644 --- a/src/Core/Resources/translations/messages.de.yaml +++ b/src/Core/Resources/translations/messages.de.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: 'Das Neuinstallieren Ihres Servers wird ihn stoppen und dann das Installationsskript erneut ausführen, das ihn ursprünglich eingerichtet hat. Einige Dateien können während dieses Vorgangs gelöscht oder geändert werden, bitte sichern Sie Ihre Daten, bevor Sie fortfahren.' select_software: 'Software auswählen' admin_view_warning: 'Sie sehen diesen Server derzeit als Administrator. Seien Sie vorsichtig mit den Änderungen, die Sie vornehmen, da sie den Server und seine Benutzer beeinflussen können.' + back_to_servers: 'Zurück zu Servern' + manage_in_pterodactyl: 'In Pterodactyl verwalten' extend_server: 'Server verlängern' extend_server_expires: 'Ihr {{ productName }}-Server läuft derzeit am {{ expiresAt }} ab.' extend_server_hint: 'Sie können den Server verlängern, indem Sie auf die Schaltfläche unten klicken.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Anzahl der Tage nach dem Sperrdatum, nach denen gesperrte Server dauerhaft gelöscht werden' pterodactyl_sso_enabled: 'Aktiviere Single-Sign-On-Anmeldung mit dem Pterodactyl-Panel (SSO-Schlüssel muss festgelegt sein)' pterodactyl_sso_secret: 'Pterodactyl SSO-Anmeldeschlüssel' + pterodactyl_manage_in_panel_button_enabled: 'Schaltfläche "In Pterodactyl verwalten" auf der Serververwaltungsseite aktivieren' show_pterodactyl_logs_in_server_activity: 'Zeige Pterodactyl-Logs in der Serveraktivität an' renewal_notification_enabled: 'E-Mail-Bestätigungen bei Server-Verlängerungen senden. Hauptschalter für alle Verlängerungsbenachrichtigungen.' renewal_notification_min_period_hours: 'Mindest-Stunden zwischen Verlängerungs-E-Mails pro Server. Verhindert Spam. Z.B. 24 = max einmal täglich.' diff --git a/src/Core/Resources/translations/messages.de_CH.yaml b/src/Core/Resources/translations/messages.de_CH.yaml index 2930c622..ee12d82b 100644 --- a/src/Core/Resources/translations/messages.de_CH.yaml +++ b/src/Core/Resources/translations/messages.de_CH.yaml @@ -390,6 +390,8 @@ pteroca: reinstall_server_hint: 'Wenn Sie Ihren Server neu installieren, wird er angehalten und das Installationsskript, mit dem er ursprünglich eingerichtet wurde, erneut ausgeführt. Während dieses Vorgangs können einige Dateien gelöscht oder geändert werden. Bitte sichern Sie Ihre Daten, bevor Sie fortfahren.' select_software: 'Software auswählen' admin_view_warning: 'Sie sehen diesen Server derzeit als Administrator. Seien Sie vorsichtig mit den Änderungen, die Sie vornehmen, da sie sich auf den Server und seine Benutzer auswirken können.' + back_to_servers: 'Zurück zu Servern' + manage_in_pterodactyl: 'In Pterodactyl verwalten' extend_server: 'Server erweitern' extend_server_expires: 'Ihr {{ productName }} Server läuft derzeit am {{ expiresAt }} ab.' extend_server_hint: 'Sie können den Server verlängern, indem Sie auf den Button unten klicken.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Anzahl der Tage nach dem Datum der Suspendierung des Servers, bevor die suspendierten Server endgültig gelöscht werden' pterodactyl_sso_enabled: 'Single Sign-On Login mit Pterodactyl Panel aktivieren (SSO Secret muss gesetzt sein)' pterodactyl_sso_secret: 'Pterodactyl SSO Login Geheimschlüssel' + pterodactyl_manage_in_panel_button_enabled: 'Schaltfläche "In Pterodactyl verwalten" auf der Serververwaltungsseite aktivieren' show_pterodactyl_logs_in_server_activity: 'Pterodactyl-Logs in der Serveraktivität anzeigen' current_theme: 'Aktuelles Theme des Panels' renewal_notification_enabled: 'E-Mail-Bestätigungen bei Server-Verlängerungen senden. Hauptschalter für alle Verlängerungsbenachrichtigungen.' diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index 6ada6a27..a14d657f 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -407,6 +407,8 @@ pteroca: reinstall_server_hint: 'Reinstalling your server will stop it, and then re-run the installation script that initially set it up. Some files may be deleted or modified during this process, please back up your data before continuing.' select_software: 'Select software' admin_view_warning: 'You are currently viewing this server as an administrator. Be careful with the changes you make, as they may affect the server and its users.' + back_to_servers: 'Back to Servers' + manage_in_pterodactyl: 'Manage in Pterodactyl' extend_server: 'Extend Server' extend_server_expires: 'Your {{ productName }} server is currently set to expire on {{ expiresAt }}.' extend_server_hint: 'You can extend the server by clicking the button below.' @@ -1171,6 +1173,7 @@ pteroca: delete_suspended_servers_days_after: 'Number of days after the server suspension date before suspended servers are permanently deleted' pterodactyl_sso_enabled: 'Enable Single Sign-On Login with Pterodactyl Panel (SSO Secret must be set)' pterodactyl_sso_secret: 'Pterodactyl SSO Login Secret Key' + pterodactyl_manage_in_panel_button_enabled: 'Enable "Manage in Pterodactyl" button on server management page' show_pterodactyl_logs_in_server_activity: 'Show Pterodactyl logs in server activity' current_theme: 'Current theme of the panel (LEGACY - replaced by context-based themes)' panel_theme: 'Theme for user panel (dashboard, servers, store). Controls the look of all admin and user-facing pages.' diff --git a/src/Core/Resources/translations/messages.es.yaml b/src/Core/Resources/translations/messages.es.yaml index 00281a2c..cc177d81 100644 --- a/src/Core/Resources/translations/messages.es.yaml +++ b/src/Core/Resources/translations/messages.es.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: 'Reinstalar tu servidor lo detendrá y luego volverá a ejecutar el script de instalación que lo configuró inicialmente. Algunos archivos pueden ser eliminados o modificados durante este proceso, por favor haz una copia de seguridad de tus datos antes de continuar.' select_software: 'Seleccionar software' admin_view_warning: 'Actualmente estás viendo este servidor como administrador. Ten cuidado con los cambios que realices, ya que pueden afectar al servidor y a sus usuarios.' + back_to_servers: 'Volver a servidores' + manage_in_pterodactyl: 'Administrar en Pterodactyl' extend_server: 'Extender servidor' extend_server_expires: 'Tu servidor {{ productName }} está actualmente configurado para expirar el {{ expiresAt }}.' extend_server_hint: 'Puedes extender el servidor haciendo clic en el botón de abajo.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Número de días después de la fecha de suspensión del servidor antes de eliminar permanentemente los servidores suspendidos' pterodactyl_sso_enabled: 'Habilitar inicio de sesión de Single Sign-On con el panel de Pterodactyl (Se debe configurar la clave SSO)' pterodactyl_sso_secret: 'Clave secreta de inicio de sesión SSO de Pterodactyl' + pterodactyl_manage_in_panel_button_enabled: 'Habilitar botón "Administrar en Pterodactyl" en la página de administración del servidor' show_pterodactyl_logs_in_server_activity: 'Mostrar registros de Pterodactyl en la actividad del servidor' current_theme: 'Tema actual del panel' renewal_notification_enabled: 'Enviar confirmaciones email cuando se renuevan servidores. Interruptor principal para todas notificaciones renovación.' diff --git a/src/Core/Resources/translations/messages.fr.yaml b/src/Core/Resources/translations/messages.fr.yaml index 332a9862..e0ab76a9 100644 --- a/src/Core/Resources/translations/messages.fr.yaml +++ b/src/Core/Resources/translations/messages.fr.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: "La réinstallation de votre serveur l'arrêtera, puis relancera le script d'installation initial. Certains fichiers peuvent être supprimés ou modifiés pendant ce processus, veuillez sauvegarder vos données avant de continuer." select_software: 'Sélectionner le logiciel' admin_view_warning: "Vous consultez actuellement ce serveur en tant qu'administrateur. Soyez prudent avec les modifications que vous apportez, car elles peuvent affecter le serveur et ses utilisateurs." + back_to_servers: 'Retour aux serveurs' + manage_in_pterodactyl: 'Gérer dans Pterodactyl' extend_server: 'Prolonger le serveur' extend_server_expires: 'Votre serveur {{ productName }} est actuellement configuré pour expirer le {{ expiresAt }}.' extend_server_hint: 'Vous pouvez prolonger le serveur en cliquant sur le bouton ci-dessous.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Nombre de jours après la date de suspension du serveur avant que les serveurs suspendus ne soient supprimés définitivement' pterodactyl_sso_enabled: 'Activer la connexion Single Sign-On avec le panneau Pterodactyl (Le secret SSO doit être défini)' pterodactyl_sso_secret: 'Clé secrète de connexion SSO de Pterodactyl' + pterodactyl_manage_in_panel_button_enabled: 'Activer le bouton "Gérer dans Pterodactyl" sur la page de gestion du serveur' show_pterodactyl_logs_in_server_activity: "Afficher les journaux de Pterodactyl dans l'activité du serveur" current_theme: 'Thème actuel du panneau' renewal_notification_enabled: 'Envoyer confirmations email lors renouvellement serveurs. Interrupteur principal pour toutes notifications renouvellement.' diff --git a/src/Core/Resources/translations/messages.hi.yaml b/src/Core/Resources/translations/messages.hi.yaml index ba074647..43adaa9a 100644 --- a/src/Core/Resources/translations/messages.hi.yaml +++ b/src/Core/Resources/translations/messages.hi.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: 'सर्वर को पुनः स्थापित करने से यह बंद हो जाएगा, और फिर प्रारंभिक सेटअप स्क्रिप्ट फिर से चलेगी। कुछ फ़ाइलें हटाई या संशोधित की जा सकती हैं, कृपया आगे बढ़ने से पहले अपने डेटा का बैकअप लें।' select_software: 'सॉफ़्टवेयर चुनें' admin_view_warning: 'आप वर्तमान में इस सर्वर को व्यवस्थापक के रूप में देख रहे हैं। कृपया सावधान रहें, क्योंकि आपके द्वारा किए गए परिवर्तन सर्वर और इसके उपयोगकर्ताओं को प्रभावित कर सकते हैं।' + back_to_servers: 'सर्वर पर वापस जाएं' + manage_in_pterodactyl: 'Pterodactyl में प्रबंधित करें' extend_server: 'सर्वर का विस्तार करें' extend_server_expires: 'आपका {{ productName }} सर्वर वर्तमान में {{ expiresAt }} को समाप्त होने के लिए सेट है।' extend_server_hint: 'आप नीचे दिए गए बटन पर क्लिक करके सर्वर का विस्तार कर सकते हैं।' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'निलंबित सर्वरों को स्थायी रूप से हटाने से पहले सर्वर निलंबन तिथि के बाद के दिनों की संख्या' pterodactyl_sso_enabled: 'Pterodactyl पैनल के साथ सिंगल साइन-ऑन लॉगिन सक्षम करें (SSO गुप्त सेट किया जाना चाहिए)' pterodactyl_sso_secret: 'Pterodactyl SSO लॉगिन गुप्त कुंजी' + pterodactyl_manage_in_panel_button_enabled: 'सर्वर प्रबंधन पृष्ठ पर "Pterodactyl में प्रबंधित करें" बटन सक्षम करें' show_pterodactyl_logs_in_server_activity: 'सर्वर गतिविधि में Pterodactyl लॉग्स दिखाएं' current_theme: 'पैनल की वर्तमान थीम' renewal_notification_enabled: 'सर्वर नवीनीकृत होने पर ईमेल पुष्टि भेजें। सभी नवीकरण सूचनाओं के लिए मास्टर टॉगल।' diff --git a/src/Core/Resources/translations/messages.id.yaml b/src/Core/Resources/translations/messages.id.yaml index 9dbc1825..b5d86be6 100644 --- a/src/Core/Resources/translations/messages.id.yaml +++ b/src/Core/Resources/translations/messages.id.yaml @@ -419,6 +419,8 @@ pteroca: reinstall_server_hint: 'Menginstal ulang server Anda akan menghentikannya, dan kemudian menjalankan ulang skrip instalasi yang awalnya mengaturnya. Beberapa file mungkin dihapus atau dimodifikasi selama proses ini, harap cadangkan data Anda sebelum melanjutkan.' select_software: 'Pilih perangkat lunak' admin_view_warning: 'Anda sedang melihat server ini sebagai administrator. Berhati-hatilah dengan perubahan yang Anda buat, karena mungkin mempengaruhi server dan penggunanya.' + back_to_servers: 'Kembali ke server' + manage_in_pterodactyl: 'Kelola di Pterodactyl' extend_server: 'Perpanjang Server' extend_server_expires: 'Server {{ productName }} Anda saat ini disetel untuk kedaluwarsa pada {{ expiresAt }}.' extend_server_hint: 'Anda dapat memperpanjang server dengan mengklik tombol di bawah.' @@ -1213,6 +1215,7 @@ pteroca: delete_suspended_servers_days_after: 'Jumlah hari setelah tanggal penangguhan server sebelum server yang ditangguhkan dihapus secara permanen' pterodactyl_sso_enabled: 'Aktifkan Login Single Sign-On dengan Panel Pterodactyl (Rahasia SSO harus disetel)' pterodactyl_sso_secret: 'Kunci Rahasia Login SSO Pterodactyl' + pterodactyl_manage_in_panel_button_enabled: 'Aktifkan tombol "Kelola di Pterodactyl" di halaman manajemen server' show_pterodactyl_logs_in_server_activity: 'Tampilkan log Pterodactyl di aktivitas server' current_theme: 'Tema saat ini dari panel' smtp_from: "Email pengirim di bidang 'From' (mis. noreply@example.com). Harus cocok dengan pengirim yang diizinkan SMTP." diff --git a/src/Core/Resources/translations/messages.it.yaml b/src/Core/Resources/translations/messages.it.yaml index 89e1bb2c..ed9ada87 100644 --- a/src/Core/Resources/translations/messages.it.yaml +++ b/src/Core/Resources/translations/messages.it.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: 'Reinstallare il server lo fermerà e poi eseguirà nuovamente lo script di installazione iniziale. Alcuni file potrebbero essere eliminati o modificati durante questo processo, si prega di eseguire il backup dei dati prima di continuare.' select_software: 'Seleziona software' admin_view_warning: 'Stai visualizzando questo server come amministratore. Fai attenzione alle modifiche che apporti, poiché potrebbero influire sul server e sui suoi utenti.' + back_to_servers: 'Torna ai server' + manage_in_pterodactyl: 'Gestisci in Pterodactyl' extend_server: 'Estendi server' extend_server_expires: 'Il tuo server {{ productName }} è attualmente impostato per scadere il {{ expiresAt }}.' extend_server_hint: 'Puoi estendere il server facendo clic sul pulsante qui sotto.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Numero di giorni dopo la data di sospensione del server prima che i server sospesi vengano eliminati definitivamente' pterodactyl_sso_enabled: "Abilita l'accesso Single Sign-On con il pannello Pterodactyl (Il segreto SSO deve essere impostato)" pterodactyl_sso_secret: 'Chiave segreta di accesso SSO di Pterodactyl' + pterodactyl_manage_in_panel_button_enabled: 'Abilita il pulsante "Gestisci in Pterodactyl" sulla pagina di gestione del server' show_pterodactyl_logs_in_server_activity: "Mostra i log di Pterodactyl nell'attività del server" current_theme: 'Tema attuale del pannello' renewal_notification_enabled: 'Invia conferme email quando i server vengono rinnovati. Interruttore principale per tutte le notifiche di rinnovo.' diff --git a/src/Core/Resources/translations/messages.nl.yaml b/src/Core/Resources/translations/messages.nl.yaml index 23522f8f..f33f299b 100644 --- a/src/Core/Resources/translations/messages.nl.yaml +++ b/src/Core/Resources/translations/messages.nl.yaml @@ -365,6 +365,8 @@ pteroca: reinstall_server_hint: 'Het opnieuw installeren van uw server zal het stoppen, en voer het installatiescript dat het oorspronkelijk heeft ingesteld dan opnieuw uit. Sommige bestanden kunnen verwijderd of gewijzigd worden tijdens dit proces, maak een back-up van uw gegevens voordat u doorgaat.' select_software: 'Selecteer software' admin_view_warning: 'U bekijkt momenteel deze server als beheerder. Wees voorzichtig met de wijzigingen die u aanbrengt, aangezien deze van invloed kunnen zijn op de server en de gebruikers.' + back_to_servers: 'Terug naar servers' + manage_in_pterodactyl: 'Beheren in Pterodactyl' extend_server: 'Server verlengen' extend_server_expires: 'Uw {{ productName }} server verloopt op {{ expiresAt }}.' extend_server_hint: 'Je kunt de server verlengen door op de knop hieronder te klikken.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Aantal dagen na de datum van opschorting van de server voordat opgeschorte servers permanent worden verwijderd' pterodactyl_sso_enabled: 'Single Sign-On login inschakelen met Pterodactyl Panel (SSO Secret moet zijn ingesteld)' pterodactyl_sso_secret: 'Pterodactyl SSO login geheime sleutel' + pterodactyl_manage_in_panel_button_enabled: 'Schakel de knop "Beheren in Pterodactyl" in op de servermanagementpagina' show_pterodactyl_logs_in_server_activity: 'Toon Pterodactyl logs in server activiteit' current_theme: 'Huidig thema van het paneel' renewal_notification_enabled: 'E-mailbevestigingen verzenden bij verlenging servers. Hoofdschakelaar voor alle verlengingsmeldingen.' diff --git a/src/Core/Resources/translations/messages.pl.yaml b/src/Core/Resources/translations/messages.pl.yaml index cfc15e7c..42056b61 100644 --- a/src/Core/Resources/translations/messages.pl.yaml +++ b/src/Core/Resources/translations/messages.pl.yaml @@ -392,6 +392,8 @@ pteroca: reinstall_server_hint: 'Przeinstalowanie serwera zatrzyma go, a następnie ponownie uruchomi skrypt instalacyjny, który go początkowo skonfigurował. Niektóre pliki mogą zostać usunięte lub zmodyfikowane podczas tego procesu, proszę wykonać kopię zapasową danych przed kontynuowaniem.' select_software: 'Wybierz oprogramowanie' admin_view_warning: 'Obecnie przeglądasz ten serwer jako administrator. Uważaj na wprowadzane zmiany, ponieważ mogą one wpłynąć na serwer i jego użytkowników.' + back_to_servers: 'Wróć do serwerów' + manage_in_pterodactyl: 'Zarządzaj w Pterodactyl' extend_server: 'Przedłuż serwer' extend_server_expires: 'Twój serwer {{ productName }} jest obecnie ustawiony na wygaśnięcie {{ expiresAt }}.' extend_server_hint: 'Możesz przedłużyć serwer, klikając poniższy przycisk.' @@ -1160,6 +1162,7 @@ pteroca: delete_suspended_servers_days_after: 'Liczba dni od daty zawieszenia serwera, po których zawieszone serwery zostaną trwale usunięte' pterodactyl_sso_enabled: 'Włącz logowanie Single Sign-On z panelem Pterodactyl (Sekret SSO musi być ustawiony)' pterodactyl_sso_secret: 'Sekretny klucz logowania SSO Pterodactyl' + pterodactyl_manage_in_panel_button_enabled: 'Włącz przycisk "Zarządzaj w Pterodactyl" na stronie zarządzania serwerem' show_pterodactyl_logs_in_server_activity: 'Wyświetlaj logi z Pterodactyla w aktywności serwera' current_theme: 'Aktualny motyw panelu' renewal_notification_enabled: 'Wysyłaj emaile potwierdzające odnowienie serwerów. Główny przełącznik dla wszystkich powiadomień o odnowieniu.' diff --git a/src/Core/Resources/translations/messages.pt.yaml b/src/Core/Resources/translations/messages.pt.yaml index 90bba221..adc9425e 100644 --- a/src/Core/Resources/translations/messages.pt.yaml +++ b/src/Core/Resources/translations/messages.pt.yaml @@ -394,6 +394,8 @@ pteroca: reinstall_server_hint: 'Reinstalar o servidor irá pará-lo e, em seguida, executar novamente o script de instalação que o configurou inicialmente. Alguns arquivos podem ser excluídos ou modificados durante este processo, faça backup dos seus dados antes de continuar.' select_software: 'Selecionar software' admin_view_warning: 'Você está visualizando este servidor como administrador. Tenha cuidado com as alterações que fizer, pois elas podem afetar o servidor e seus usuários.' + back_to_servers: 'Voltar para servidores' + manage_in_pterodactyl: 'Gerenciar no Pterodactyl' extend_server: 'Estender servidor' extend_server_expires: 'Seu servidor {{ productName }} está atualmente configurado para expirar em {{ expiresAt }}.' extend_server_hint: 'Você pode estender o servidor clicando no botão abaixo.' @@ -1162,6 +1164,7 @@ pteroca: delete_suspended_servers_days_after: 'Número de dias após a data de suspensão do servidor antes que os servidores suspensos sejam excluídos permanentemente' pterodactyl_sso_enabled: 'Habilitar login Single Sign-On com o painel Pterodactyl (A chave secreta SSO deve ser definida)' pterodactyl_sso_secret: 'Chave secreta de login SSO do Pterodactyl' + pterodactyl_manage_in_panel_button_enabled: 'Habilitar botão "Gerenciar no Pterodactyl" na página de gerenciamento do servidor' current_theme: 'Tema atual do painel' renewal_notification_enabled: 'Enviar confirmações email quando servidores são renovados. Interruptor principal para todas notificações renovação.' renewal_notification_min_period_hours: 'Horas mínimas entre emails renovação por servidor. Previne spam. Ex. 24 = máx uma vez/dia.' diff --git a/src/Core/Resources/translations/messages.ru.yaml b/src/Core/Resources/translations/messages.ru.yaml index 6fc33a4d..f433e57e 100644 --- a/src/Core/Resources/translations/messages.ru.yaml +++ b/src/Core/Resources/translations/messages.ru.yaml @@ -416,6 +416,8 @@ pteroca: reinstall_server_hint: 'При переустановке сервера он будет остановлен, а затем заново будет выполнен сценарий установки, который изначально его устанавливал. В ходе этого процесса некоторые файлы могут быть удалены или изменены, поэтому перед тем, как продолжить, создайте резервную копию данных.' select_software: 'Выберите серверное ПО' admin_view_warning: 'В настоящее время вы просматриваете этот сервер как администратор. Будьте осторожны с изменениями, которые вы вносите, так как они могут повлиять на сервер и его пользователей.' + back_to_servers: 'Вернуться к серверам' + manage_in_pterodactyl: 'Управлять в Pterodactyl' extend_server: 'Продлить сервер' extend_server_expires: 'Ваш сервер {{ productName }} в настоящее время истекает {{ expiresAt }}.' extend_server_hint: 'Вы можете продлить срок действия сервера, нажав кнопку ниже.' @@ -1188,6 +1190,7 @@ pteroca: delete_suspended_servers_days_after: 'Количество дней после даты приостановки сервера, через которые приостановленные серверы будут окончательно удалены' pterodactyl_sso_enabled: 'Включить вход через Single Sign-On в панели Pterodactyl (необходимо установить секрет SSO)' pterodactyl_sso_secret: 'Секретный ключ для входа в Pterodactyl SSO' + pterodactyl_manage_in_panel_button_enabled: 'Включить кнопку "Управлять в Pterodactyl" на странице управления сервером' current_theme: 'Текущая тема панели' renewal_notification_enabled: 'Отправлять email подтверждения при продлении серверов. Главный переключатель для всех уведомлений о продлении.' renewal_notification_min_period_hours: 'Минимальные часы между письмами о продлении на сервер. Предотвращает спам. Напр. 24 = макс раз в день.' diff --git a/src/Core/Resources/translations/messages.ua.yaml b/src/Core/Resources/translations/messages.ua.yaml index efecaa2f..28186678 100644 --- a/src/Core/Resources/translations/messages.ua.yaml +++ b/src/Core/Resources/translations/messages.ua.yaml @@ -416,6 +416,8 @@ pteroca: reinstall_server_hint: 'Перевстановлення сервера зупинить його, а потім знову запустить скрипт встановлення, який спочатку його налаштував. Деякі файли можуть бути видалені або змінені під час цього процесу, будь ласка, зробіть резервну копію даних перед продовженням.' select_software: 'Вибрати програмне забезпечення' admin_view_warning: 'Ви зараз переглядаєте цей сервер як адміністратор. Будьте обережні з внесеними змінами, оскільки вони можуть вплинути на сервер та його користувачів.' + back_to_servers: 'Повернутися до серверів' + manage_in_pterodactyl: 'Керувати в Pterodactyl' extend_server: 'Продовжити сервер' extend_server_expires: 'Ваш сервер {{ productName }} наразі налаштований на завершення {{ expiresAt }}.' extend_server_hint: 'Ви можете продовжити сервер, натиснувши кнопку нижче.' @@ -1188,6 +1190,7 @@ pteroca: delete_suspended_servers_days_after: 'Кількість днів після дати призупинення сервера, через які призупинені сервери будуть остаточно видалені' pterodactyl_sso_enabled: 'Увімкнути вхід Single Sign-On у панелі Pterodactyl (необхідно встановити секрет SSO)' pterodactyl_sso_secret: 'Секретний ключ для входу в Pterodactyl SSO' + pterodactyl_manage_in_panel_button_enabled: 'Включити кнопку "Керувати в Pterodactyl" на сторінці управління сервером' current_theme: 'Поточна тема панелі' renewal_notification_enabled: 'Надсилати email підтвердження при поновленні серверів. Головний перемикач для всіх сповіщень про поновлення.' renewal_notification_min_period_hours: 'Мінімальні години між листами про поновлення на сервер. Запобігає спаму. Напр. 24 = макс раз на день.' diff --git a/src/Core/Service/Pterodactyl/PterodactylRedirectService.php b/src/Core/Service/Pterodactyl/PterodactylRedirectService.php index d73b7b4e..1651a79d 100644 --- a/src/Core/Service/Pterodactyl/PterodactylRedirectService.php +++ b/src/Core/Service/Pterodactyl/PterodactylRedirectService.php @@ -34,6 +34,17 @@ public function isSSOEnabled(): bool )); } + /** + * Check if the "Manage in Pterodactyl" button should be shown + * Requires both SSO and the button setting to be enabled + */ + public function isManageInPterodactylButtonEnabled(): bool + { + return !empty($this->settingsService->getSetting( + SettingEnum::PTERODACTYL_MANAGE_IN_PANEL_BUTTON_ENABLED->value + )); + } + /** * Get redirect URL for a server (without redirecting) * diff --git a/src/Core/Twig/AppExtension.php b/src/Core/Twig/AppExtension.php index 960e186a..9d99ec22 100644 --- a/src/Core/Twig/AppExtension.php +++ b/src/Core/Twig/AppExtension.php @@ -53,6 +53,7 @@ public function getFunctions(): array new TwigFunction('use_pterodactyl_panel_as_client_panel', [$this, 'usePterodactylPanelAsClientPanel']), new TwigFunction('get_pterodactyl_panel_url', [$this, 'getPterodactylPanelUrl']), new TwigFunction('is_pterodactyl_sso_enabled', [$this, 'isPterodactylSSOEnabled']), + new TwigFunction('is_manage_in_pterodactyl_button_enabled', [$this, 'isManageInPterodactylButtonEnabled']), new TwigFunction('template_asset', [$this, 'templateAsset']), new TwigFunction('get_current_template_options', [$this, 'getCurrentTemplateOptions']), new TwigFunction('plugin_asset', [$this, 'pluginAsset']), @@ -146,6 +147,11 @@ public function isPterodactylSSOEnabled(): bool return $this->pterodactylRedirectService->isSSOEnabled(); } + public function isManageInPterodactylButtonEnabled(): bool + { + return $this->pterodactylRedirectService->isManageInPterodactylButtonEnabled(); + } + public function getPterodactylPanelUrl(string $path = ''): string { return $this->pterodactylRedirectService->getPterodactylUrl($path); diff --git a/themes/default/panel/server/server.html.twig b/themes/default/panel/server/server.html.twig index 428ebbad..708c44b1 100644 --- a/themes/default/panel/server/server.html.twig +++ b/themes/default/panel/server/server.html.twig @@ -33,6 +33,17 @@

{{ serverData.pterodactylServer.description }}

{% endif %} +
+ + {{ 'pteroca.server.back_to_servers'|trans }} + + {% if is_manage_in_pterodactyl_button_enabled() %} + + {{ 'pteroca.server.manage_in_pterodactyl'|trans }} + + {% endif %} +
From 45ede82644aa86c2305a1e8edae20f27121a6991 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Mon, 23 Feb 2026 20:25:49 +0100 Subject: [PATCH 19/34] Set unknown in placeholders if couldnt check server state & fix doubled server id in name after migration from pterodactyl --- src/Core/Controller/API/ServerController.php | 23 ++++++++-- src/Core/Handler/MigrateServersHandler.php | 2 +- .../Resources/translations/messages.en.yaml | 2 + src/Core/Service/Server/ServerService.php | 14 +++++-- .../server/tabs/console/console.html.twig | 24 +++++++++++ .../default/panel/servers/servers.html.twig | 42 ++++++++++++++----- 6 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/Core/Controller/API/ServerController.php b/src/Core/Controller/API/ServerController.php index d7d95989..a0ee0166 100644 --- a/src/Core/Controller/API/ServerController.php +++ b/src/Core/Controller/API/ServerController.php @@ -53,14 +53,31 @@ public function serverDetails( ); $this->eventDispatcher->dispatch($requestedEvent); - $serverDetailsDTO = $this->serverService->getServerStateByClient($user, $server); - $serverDetails = $serverDetailsDTO?->toArray(); + try { + $serverDetailsDTO = $this->serverService->getServerStateByClient($user, $server); + } catch (\Exception) { + $serverDetailsDTO = null; + } + + if ($serverDetailsDTO === null) { + $this->eventDispatcher->dispatch(new ServerDetailsLoadedEvent( + $this->getUserId(), + $server->getId(), + $server->getPterodactylServerIdentifier(), + null, + $server->getIsSuspended(), + $context + )); + return new JsonResponse(['state' => 'unknown']); + } + + $serverDetails = $serverDetailsDTO->toArray(); unset($serverDetails['egg']); $loadedEvent = new ServerDetailsLoadedEvent( $this->getUserId(), $server->getId(), $server->getPterodactylServerIdentifier(), - $serverDetailsDTO?->state?->value, + $serverDetailsDTO->state?->value, $server->getIsSuspended(), $context ); diff --git a/src/Core/Handler/MigrateServersHandler.php b/src/Core/Handler/MigrateServersHandler.php index 84716f57..8566f677 100644 --- a/src/Core/Handler/MigrateServersHandler.php +++ b/src/Core/Handler/MigrateServersHandler.php @@ -398,7 +398,7 @@ private function migrateServerProductEntity(Server $serverEntity, array $pteroda $serverProductEntity = (new ServerProduct()) ->setServer($serverEntity) ->setOriginalProduct(null) - ->setName(sprintf('%s #%s', $pterodactylServer['name'], $pterodactylServer['identifier'])) + ->setName($pterodactylServer['name']) ->setDiskSpace($pterodactylServer['limits']['disk']) ->setMemory($pterodactylServer['limits']['memory']) ->setIo($pterodactylServer['limits']['io']) diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index 0eaf7047..3a3af8fd 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -361,6 +361,7 @@ pteroca: starting: 'Starting' stopping: 'Stopping' offline: 'Offline' + unknown: 'Unknown' server: title: 'Server management' @@ -433,6 +434,7 @@ pteroca: console_token_expired: 'The console token has expired. Please refresh the page.' console_connection_closed: 'The console connection has been closed.' console_connection_error: 'An error occurred while connecting to the console.' + console_stats_unknown: 'Unknown' server_installing: 'Server is being installed' server_installing_message: 'Your server is currently being installed and will be available in a few minutes. Please wait while the installation process completes.' server_installing_description: 'The installation process may take several minutes depending on the selected software and server specifications.' diff --git a/src/Core/Service/Server/ServerService.php b/src/Core/Service/Server/ServerService.php index a7ee30cb..48d97218 100644 --- a/src/Core/Service/Server/ServerService.php +++ b/src/Core/Service/Server/ServerService.php @@ -27,14 +27,22 @@ public function getServerStateByClient( ->getClientApi($user); if (false === $server->getIsSuspended()) { - $serverState = $clientApi->servers() - ->getServerResources($server->getPterodactylServerIdentifier())['current_state'] ?? null; + try { + $serverState = $clientApi->servers() + ->getServerResources($server->getPterodactylServerIdentifier())['current_state'] ?? null; + } catch (\Exception) { + $serverState = null; + } } else { $serverState = ServerStateEnum::SUSPENDED->value; } - + $serverDetails = $this->getServerDetails($server); + if ($serverDetails === null) { + return null; + } + return new ServerDetailsDTO( identifier: $server->getPterodactylServerIdentifier(), name: $serverDetails->name, diff --git a/themes/default/panel/server/tabs/console/console.html.twig b/themes/default/panel/server/tabs/console/console.html.twig index 7a5eb49f..59d1e26e 100644 --- a/themes/default/panel/server/tabs/console/console.html.twig +++ b/themes/default/panel/server/tabs/console/console.html.twig @@ -46,6 +46,9 @@ terminal.getWebsocketToken() .then(data => { terminal.initializeWebsocket(data.token, data.socket); + }) + .catch(() => { + terminal.setUnknownStatistics(); }); {% else %} terminal.showSuspendedServerMessage(); @@ -58,6 +61,7 @@ terminal: null, ws: null, eulaModalShown: false, + statsReceived: false, eulaPatterns: [ /You need to agree to the EULA in order to run the server/i, /Go to eula.txt for more info/i, @@ -135,10 +139,16 @@ this.ws.onclose = function() { terminal.terminal.write(terminal.colors.red + '{{ 'pteroca.server.console_connection_closed'|trans }}' + terminal.colors.default + '\r\n'); terminal.toggleButtons(true); + if (!terminal.statsReceived) { + terminal.setUnknownStatistics(); + } }; this.ws.onerror = function(event) { terminal.terminal.write(terminal.colors.red + '{{ 'pteroca.server.console_connection_error'|trans }}' + terminal.colors.default + '\r\n'); terminal.toggleButtons(true); + if (!terminal.statsReceived) { + terminal.setUnknownStatistics(); + } }; }, sendCommand: function(command) { @@ -149,6 +159,7 @@ this.ws.send(JSON.stringify({ event: 'set state', args: [state] })); }, loadStatisticsFromWebsocket: function(stats) { + this.statsReceived = true; const isOnline = stats.state !== 'offline', uptimeIcon = document.querySelector('[data-uptime-icon]'); @@ -167,6 +178,19 @@ const networkRate = this.calculateNetworkRate(stats); addChartData(networkChart, new Date().toLocaleTimeString(), networkRate); }, + setUnknownStatistics: function() { + const unknownText = '{{ 'pteroca.server.console_stats_unknown'|trans }}'; + const uptimeIcon = document.querySelector('[data-uptime-icon]'); + if (uptimeIcon) { + uptimeIcon.classList.remove('text-success', 'text-danger'); + } + this.updateStatisticValue('[data-uptime]', unknownText); + this.updateStatisticValue('[data-cpu-load]', unknownText); + this.updateStatisticValue('[data-memory]', unknownText); + this.updateStatisticValue('[data-disk]', unknownText); + this.updateStatisticValue('[data-network-inbound]', unknownText); + this.updateStatisticValue('[data-network-outbound]', unknownText); + }, updateStatisticValue(selector, value) { const element = document.querySelector(selector); if (element) { diff --git a/themes/default/panel/servers/servers.html.twig b/themes/default/panel/servers/servers.html.twig index 7f612cc2..423f31de 100644 --- a/themes/default/panel/servers/servers.html.twig +++ b/themes/default/panel/servers/servers.html.twig @@ -123,17 +123,31 @@ const endpointUrl = '{{ path('panel') }}/api/server/' + serverId + '/details'; fetch(endpointUrl) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error('Server details unavailable'); + } + return response.json(); + }) .then(data => { const serverElements = document.querySelectorAll(`[data-server-id="${serverId}"]`); serverElements.forEach(element => { updateServerElements(element, data); + clearRemainingPlaceholders(element); }); - document.querySelectorAll('.placeholder-glow').forEach(el => el.classList.remove('placeholder-glow')); document.querySelectorAll('.placeholder-wave').forEach(el => el.classList.remove('placeholder-wave')); }) - .catch(error => console.error('Error fetching server details:', error)); + .catch(error => { + console.error('Error fetching server details:', error); + const serverElements = document.querySelectorAll(`[data-server-id="${serverId}"]`); + serverElements.forEach(element => { + updateServerElements(element, { state: 'unknown' }); + clearRemainingPlaceholders(element); + }); + document.querySelectorAll('.placeholder-glow').forEach(el => el.classList.remove('placeholder-glow')); + document.querySelectorAll('.placeholder-wave').forEach(el => el.classList.remove('placeholder-wave')); + }); }); function updateServerElements(element, data, prefix = '') { @@ -167,17 +181,25 @@ } } + function clearRemainingPlaceholders(element) { + element.querySelectorAll('.placeholder').forEach(el => { + el.textContent = '-'; + el.classList.remove('placeholder'); + }); + } + function updateServerStateBadge(element, state) { const stateConfig = { - 'running': { class: 'badge-success', text: '{{ 'pteroca.servers.state.running'|trans }}' }, - 'stopped': { class: 'badge-secondary', text: '{{ 'pteroca.servers.state.stopped'|trans }}' }, - 'starting': { class: 'badge-warning', text: '{{ 'pteroca.servers.state.starting'|trans }}' }, - 'stopping': { class: 'badge-warning', text: '{{ 'pteroca.servers.state.stopping'|trans }}' }, - 'offline': { class: 'badge-danger', text: '{{ 'pteroca.servers.state.offline'|trans }}' } + 'running': { class: 'badge-success', text: '{{ 'pteroca.servers.state.running'|trans }}' }, + 'stopped': { class: 'badge-secondary', text: '{{ 'pteroca.servers.state.stopped'|trans }}' }, + 'starting': { class: 'badge-warning', text: '{{ 'pteroca.servers.state.starting'|trans }}' }, + 'stopping': { class: 'badge-warning', text: '{{ 'pteroca.servers.state.stopping'|trans }}' }, + 'offline': { class: 'badge-danger', text: '{{ 'pteroca.servers.state.offline'|trans }}' }, + 'unknown': { class: 'badge-secondary', text: '{{ 'pteroca.servers.state.unknown'|trans }}' }, }; - const config = stateConfig[state] || { class: 'badge-secondary', text: state }; - + const config = stateConfig[state] || { class: 'badge-secondary', text: '{{ 'pteroca.servers.state.unknown'|trans }}' }; + element.className = `badge ${config.class}`; element.textContent = config.text; } From 6bbad2656b09c835a6b04f4db7f5cd070a069553 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Mon, 23 Feb 2026 20:34:53 +0100 Subject: [PATCH 20/34] Added missing translations --- src/Core/Resources/translations/messages.cn.yaml | 2 ++ src/Core/Resources/translations/messages.de.yaml | 2 ++ src/Core/Resources/translations/messages.de_CH.yaml | 2 ++ src/Core/Resources/translations/messages.es.yaml | 2 ++ src/Core/Resources/translations/messages.fr.yaml | 2 ++ src/Core/Resources/translations/messages.hi.yaml | 2 ++ src/Core/Resources/translations/messages.id.yaml | 2 ++ src/Core/Resources/translations/messages.it.yaml | 2 ++ src/Core/Resources/translations/messages.nl.yaml | 2 ++ src/Core/Resources/translations/messages.pl.yaml | 6 ++++-- src/Core/Resources/translations/messages.pt.yaml | 2 ++ src/Core/Resources/translations/messages.ru.yaml | 2 ++ src/Core/Resources/translations/messages.ua.yaml | 2 ++ 13 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Core/Resources/translations/messages.cn.yaml b/src/Core/Resources/translations/messages.cn.yaml index d05a81c7..7d0b0397 100644 --- a/src/Core/Resources/translations/messages.cn.yaml +++ b/src/Core/Resources/translations/messages.cn.yaml @@ -350,6 +350,7 @@ pteroca: starting: 启动中 stopping: 停止中 offline: 离线 + unknown: 未知 server: title: 服务器管理 console: 控制台 @@ -417,6 +418,7 @@ pteroca: console_token_expired: 控制台令牌已过期。请刷新页面。 console_connection_closed: 控制台连接已关闭。 console_connection_error: 连接控制台时发生错误。 + console_stats_unknown: 未知 server_installing: 服务器正在安装 server_installing_message: 您的服务器正在安装,几分钟后即可使用。请耐心等待安装过程完成。 server_installing_description: 安装过程可能需要几分钟,具体取决于所选软件和服务器规格。 diff --git a/src/Core/Resources/translations/messages.de.yaml b/src/Core/Resources/translations/messages.de.yaml index acd9c2b4..1c8e3039 100644 --- a/src/Core/Resources/translations/messages.de.yaml +++ b/src/Core/Resources/translations/messages.de.yaml @@ -350,6 +350,7 @@ pteroca: starting: Startet stopping: Stoppt offline: Offline + unknown: Unbekannt server: title: Serververwaltung console: Konsole @@ -417,6 +418,7 @@ pteroca: console_token_expired: 'Das Konsolen-Token ist abgelaufen. Bitte aktualisieren Sie die Seite.' console_connection_closed: 'Die Konsolenverbindung wurde geschlossen.' console_connection_error: 'Beim Verbinden mit der Konsole ist ein Fehler aufgetreten.' + console_stats_unknown: Unbekannt server_installing: 'Server wird installiert' server_installing_message: 'Ihr Server wird derzeit installiert und steht in wenigen Minuten zur Verfügung. Bitte warten Sie, bis der Installationsvorgang abgeschlossen ist.' server_installing_description: 'Der Installationsvorgang kann je nach ausgewählter Software und Serverspezifikation mehrere Minuten dauern.' diff --git a/src/Core/Resources/translations/messages.de_CH.yaml b/src/Core/Resources/translations/messages.de_CH.yaml index 55cad62a..5e1eaaa7 100644 --- a/src/Core/Resources/translations/messages.de_CH.yaml +++ b/src/Core/Resources/translations/messages.de_CH.yaml @@ -350,6 +350,7 @@ pteroca: starting: Startet stopping: Stoppt offline: Offline + unknown: Unbekannt server: title: Serververwaltung console: Konsole @@ -415,6 +416,7 @@ pteroca: console_token_expired: 'Das Konsolen-Token ist abgelaufen. Bitte aktualisieren Sie die Seite.' console_connection_closed: 'Die Konsole Verbindung wurde geschlossen.' console_connection_error: 'Beim Verbinden mit der Konsole ist ein Fehler aufgetreten.' + console_stats_unknown: Unbekannt server_installing: 'Server wird installiert' server_installing_message: 'Ihr Server wird derzeit installiert und steht in wenigen Minuten zur Verfügung. Bitte warten Sie, bis der Installationsvorgang abgeschlossen ist.' server_installing_description: 'Der Installationsvorgang kann je nach ausgewählter Software und Serverspezifikation mehrere Minuten dauern.' diff --git a/src/Core/Resources/translations/messages.es.yaml b/src/Core/Resources/translations/messages.es.yaml index 6441a065..2b721f70 100644 --- a/src/Core/Resources/translations/messages.es.yaml +++ b/src/Core/Resources/translations/messages.es.yaml @@ -350,6 +350,7 @@ pteroca: starting: Iniciando stopping: Deteniendo offline: Desconectado + unknown: Desconocido server: title: 'Gestión del servidor' console: Consola @@ -417,6 +418,7 @@ pteroca: console_token_expired: 'El token de la consola ha expirado. Por favor, actualice la página.' console_connection_closed: 'La conexión de la consola se ha cerrado.' console_connection_error: 'Ocurrió un error al conectar con la consola.' + console_stats_unknown: Desconocido server_installing: 'El servidor se está instalando' server_installing_message: 'Tu servidor se está instalando y estará disponible en unos minutos. Por favor, espera mientras se completa el proceso de instalación.' server_installing_description: 'El proceso de instalación puede tardar varios minutos dependiendo del software seleccionado y las especificaciones del servidor.' diff --git a/src/Core/Resources/translations/messages.fr.yaml b/src/Core/Resources/translations/messages.fr.yaml index f015e20a..d60ae327 100644 --- a/src/Core/Resources/translations/messages.fr.yaml +++ b/src/Core/Resources/translations/messages.fr.yaml @@ -350,6 +350,7 @@ pteroca: starting: Démarrage stopping: 'Arrêt en cours' offline: 'Hors ligne' + unknown: Inconnu server: title: 'Gestion du serveur' console: Console @@ -417,6 +418,7 @@ pteroca: console_token_expired: 'Le jeton de la console a expiré. Veuillez rafraîchir la page.' console_connection_closed: 'La connexion à la console a été fermée.' console_connection_error: "Une erreur s'est produite lors de la connexion à la console." + console_stats_unknown: Inconnu server_installing: "Le serveur est en cours d'installation" server_installing_message: "Votre serveur est en cours d'installation et sera disponible dans quelques minutes. Veuillez patienter pendant la finalisation du processus." server_installing_description: "Le processus d'installation peut prendre plusieurs minutes en fonction du logiciel sélectionné et des spécifications du serveur." diff --git a/src/Core/Resources/translations/messages.hi.yaml b/src/Core/Resources/translations/messages.hi.yaml index 7215e32b..b6e7994b 100644 --- a/src/Core/Resources/translations/messages.hi.yaml +++ b/src/Core/Resources/translations/messages.hi.yaml @@ -350,6 +350,7 @@ pteroca: starting: 'शुरू हो रहा है' stopping: 'रोक रहा है' offline: ऑफलाइन + unknown: अज्ञात server: title: 'सर्वर प्रबंधन' console: कंसोल @@ -417,6 +418,7 @@ pteroca: console_token_expired: 'कंसोल टोकन की अवधि समाप्त हो गई है। कृपया पृष्ठ को रीफ़्रेश करें।' console_connection_closed: 'कंसोल कनेक्शन बंद कर दिया गया।' console_connection_error: 'कंसोल से कनेक्ट करते समय त्रुटि हुई।' + console_stats_unknown: अज्ञात server_installing: 'सर्वर स्थापित किया जा रहा है' server_installing_message: 'आपका सर्वर वर्तमान में स्थापित किया जा रहा है और कुछ ही मिनटों में उपलब्ध होगा। कृपया स्थापना प्रक्रिया पूरी होने तक प्रतीक्षा करें।' server_installing_description: 'स्थापना प्रक्रिया में चयनित सॉफ़्टवेयर और सर्वर विनिर्देशों के आधार पर कई मिनट लग सकते हैं।' diff --git a/src/Core/Resources/translations/messages.id.yaml b/src/Core/Resources/translations/messages.id.yaml index 6ff42899..c60737f6 100644 --- a/src/Core/Resources/translations/messages.id.yaml +++ b/src/Core/Resources/translations/messages.id.yaml @@ -374,6 +374,7 @@ pteroca: starting: Memulai stopping: Menghentikan offline: Offline + unknown: 'Tidak Diketahui' server: title: 'Manajemen server' console: Konsol @@ -445,6 +446,7 @@ pteroca: console_token_expired: 'Token konsol telah kedaluwarsa. Silakan refresh halaman.' console_connection_closed: 'Koneksi konsol telah ditutup.' console_connection_error: 'Terjadi kesalahan saat menghubungkan ke konsol.' + console_stats_unknown: 'Tidak Diketahui' server_installing: 'Server sedang diinstal' server_installing_message: 'Server Anda sedang diinstal dan akan tersedia dalam beberapa menit. Harap tunggu sementara proses instalasi selesai.' server_installing_description: 'Proses instalasi mungkin memakan waktu beberapa menit tergantung pada perangkat lunak yang dipilih dan spesifikasi server.' diff --git a/src/Core/Resources/translations/messages.it.yaml b/src/Core/Resources/translations/messages.it.yaml index d52f2176..6b1fa333 100644 --- a/src/Core/Resources/translations/messages.it.yaml +++ b/src/Core/Resources/translations/messages.it.yaml @@ -350,6 +350,7 @@ pteroca: starting: 'Avvio in corso' stopping: 'Arresto in corso' offline: Offline + unknown: Sconosciuto server: title: 'Gestione del server' console: Console @@ -417,6 +418,7 @@ pteroca: console_token_expired: 'Il token della console è scaduto. Si prega di aggiornare la pagina.' console_connection_closed: 'La connessione alla console è stata chiusa.' console_connection_error: 'Si è verificato un errore durante la connessione alla console.' + console_stats_unknown: Sconosciuto server_installing: 'Il server è in fase di installazione' server_installing_message: 'Il tuo server è attualmente in fase di installazione e sarà disponibile tra pochi minuti. Attendi il completamento del processo di installazione.' server_installing_description: 'Il processo di installazione può richiedere diversi minuti a seconda del software selezionato e delle specifiche del server.' diff --git a/src/Core/Resources/translations/messages.nl.yaml b/src/Core/Resources/translations/messages.nl.yaml index dc59e30d..d5c7d3be 100644 --- a/src/Core/Resources/translations/messages.nl.yaml +++ b/src/Core/Resources/translations/messages.nl.yaml @@ -325,6 +325,7 @@ pteroca: starting: Opstarten stopping: Stoppen offline: Offline + unknown: Onbekend server: title: 'Server beheer' console: Console @@ -390,6 +391,7 @@ pteroca: console_token_expired: 'De console-token is verlopen. Gelieve de pagina te vernieuwen.' console_connection_closed: 'De console verbinding is verbroken.' console_connection_error: 'Er is een fout opgetreden tijdens het verbinden met de console.' + console_stats_unknown: Onbekend server_installing: 'Server wordt geïnstalleerd' server_installing_message: 'Uw server wordt momenteel geïnstalleerd en is over enkele minuten beschikbaar. Wacht alstublieft tot het installatieproces is voltooid.' server_installing_description: 'Het installatieproces kan enkele minuten duren, afhankelijk van de geselecteerde software en serverspecificaties.' diff --git a/src/Core/Resources/translations/messages.pl.yaml b/src/Core/Resources/translations/messages.pl.yaml index 99285778..468ff79a 100644 --- a/src/Core/Resources/translations/messages.pl.yaml +++ b/src/Core/Resources/translations/messages.pl.yaml @@ -350,6 +350,7 @@ pteroca: starting: Uruchamianie stopping: Zatrzymywanie offline: Offline + unknown: Nieznany server: title: 'Zarządzanie serwerem' console: Konsola @@ -417,6 +418,7 @@ pteroca: console_token_expired: 'Token konsoli wygasł. Proszę odświeżyć stronę.' console_connection_closed: 'Połączenie z konsolą zostało zamknięte.' console_connection_error: 'Wystąpił błąd podczas łączenia z konsolą.' + console_stats_unknown: Nieznany server_installing: 'Serwer jest instalowany' server_installing_message: 'Twój serwer jest obecnie instalowany i będzie dostępny za kilka minut. Proszę poczekać na zakończenie procesu instalacji.' server_installing_description: 'Proces instalacji może potrwać kilka minut w zależności od wybranego oprogramowania i specyfikacji serwera.' @@ -822,8 +824,6 @@ pteroca: nodes_hint: 'Wybierz jedną lub więcej fizycznych lub wirtualnych maszyn (węzłów) z konfiguracji Pterodactyl.' nest_hint: 'Nest to kategoria grupująca powiązane Eggs. Wybierz Nest, aby załadować jego Eggs.' eggs_hint: 'Eggs definiują specyficzne konfiguracje serwera (gry lub aplikacje). Wybierz Eggs, które chcesz, aby ten produkt obsługiwał.' - egg_variable_rules: Zasady - egg_variable_validation_error: 'Błąd walidacji w "%name%": wartość nie spełnia wymaganych reguł.' pricing: Cennik price_static_plan: 'Cena stała' price_static_plan_hint: 'Cena produktu na określony czas, który użytkownicy mogą wybrać jako cykl rozliczeniowy.' @@ -859,6 +859,8 @@ pteroca: egg_variable_slot_variable_hint: 'Wybierz zmienną, która ustawia maksymalne sloty (graczy) dla cen opartych na slotach' slot_variable_not_configured_egg: 'Zmienna slotu nie skonfigurowana dla tego egg' slot_variables_unconfigured_eggs: 'Niektóre egg nie mają skonfigurowanych zmiennych slotu' + egg_variable_rules: Reguły + egg_variable_validation_error: 'Błąd weryfikacji w "%name%": wartość nie spełnia wymaganych reguł.' see_product_configuration_guide: 'Kliknij tutaj, aby otworzyć szczegółowy przewodnik, jak skonfigurować produkty.' slot_variable_required_for_slot_prices: 'Wszystkie egg muszą mieć skonfigurowaną zmienną slotu, gdy zdefiniowano ceny za slot.' show_archived: 'Pokaż zarchiwizowane' diff --git a/src/Core/Resources/translations/messages.pt.yaml b/src/Core/Resources/translations/messages.pt.yaml index 1fe90758..b00b71fb 100644 --- a/src/Core/Resources/translations/messages.pt.yaml +++ b/src/Core/Resources/translations/messages.pt.yaml @@ -352,6 +352,7 @@ pteroca: starting: Iniciando stopping: Parando offline: Offline + unknown: Desconhecido server: title: 'Gerenciamento do servidor' console: Console @@ -419,6 +420,7 @@ pteroca: console_token_expired: 'O token do console expirou. Por favor, atualize a página.' console_connection_closed: 'A conexão do console foi fechada.' console_connection_error: 'Ocorreu um erro ao conectar ao console.' + console_stats_unknown: Desconhecido server_installing: 'O servidor está sendo instalado' server_installing_message: 'Seu servidor está sendo instalado e estará disponível em alguns minutos. Por favor, aguarde a conclusão do processo de instalação.' server_installing_description: 'O processo de instalação pode levar vários minutos, dependendo do software selecionado e das especificações do servidor.' diff --git a/src/Core/Resources/translations/messages.ru.yaml b/src/Core/Resources/translations/messages.ru.yaml index 8fa34f3d..64f1a120 100644 --- a/src/Core/Resources/translations/messages.ru.yaml +++ b/src/Core/Resources/translations/messages.ru.yaml @@ -362,6 +362,7 @@ pteroca: starting: Запуск stopping: Остановка offline: Оффлайн + unknown: Неизвестно cpu: ЦПУ databases: 'Базы данных' backups: 'Резервные копии' @@ -441,6 +442,7 @@ pteroca: console_token_expired: 'Срок действия токена консоли истек. Пожалуйста, обновите страницу.' console_connection_closed: 'Соединение с консолью было закрыто.' console_connection_error: 'Произошла ошибка при подключении к консоли.' + console_stats_unknown: Неизвестно server_installing: 'Сервер устанавливается' server_installing_message: 'Ваш сервер сейчас устанавливается и будет доступен через несколько минут. Пожалуйста, дождитесь завершения процесса установки.' server_installing_description: 'Процесс установки может занять несколько минут в зависимости от выбранного программного обеспечения и характеристик сервера.' diff --git a/src/Core/Resources/translations/messages.ua.yaml b/src/Core/Resources/translations/messages.ua.yaml index 34ab15f9..be96f0f1 100644 --- a/src/Core/Resources/translations/messages.ua.yaml +++ b/src/Core/Resources/translations/messages.ua.yaml @@ -374,6 +374,7 @@ pteroca: starting: Запуск stopping: Зупинка offline: Офлайн + unknown: Невідомо server: title: 'Управління сервером' console: Консоль @@ -441,6 +442,7 @@ pteroca: console_token_expired: 'Термін дії токена консолі закінчився. Будь ласка, оновіть сторінку.' console_connection_closed: "З'єднання з консоллю закрито." console_connection_error: 'Сталася помилка під час підключення до консолі.' + console_stats_unknown: 'Невідомо' server_installing: 'Сервер встановлюється' server_installing_message: 'Ваш сервер зараз встановлюється і буде доступний за кілька хвилин. Будь ласка, зачекайте, поки процес встановлення завершиться.' server_installing_description: 'Процес встановлення може тривати кілька хвилин залежно від вибраного програмного забезпечення та специфікацій сервера.' From 640a8c8ecaac90c557744e9d560658b193e49bc0 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Wed, 25 Feb 2026 21:55:02 +0100 Subject: [PATCH 21/34] Required user server variables support --- src/Core/Controller/CartController.php | 32 +++- .../Resources/translations/messages.cn.yaml | 6 + .../Resources/translations/messages.de.yaml | 6 + .../translations/messages.de_CH.yaml | 6 + .../Resources/translations/messages.en.yaml | 6 + .../Resources/translations/messages.es.yaml | 6 + .../Resources/translations/messages.fr.yaml | 6 + .../Resources/translations/messages.hi.yaml | 6 + .../Resources/translations/messages.id.yaml | 6 + .../Resources/translations/messages.it.yaml | 6 + .../Resources/translations/messages.nl.yaml | 6 + .../Resources/translations/messages.pl.yaml | 6 + .../Resources/translations/messages.pt.yaml | 6 + .../Resources/translations/messages.ru.yaml | 6 + .../Resources/translations/messages.ua.yaml | 6 + .../Service/Server/CreateServerService.php | 8 +- .../Service/Server/ServerBuildService.php | 3 +- .../Server/ServerEggEnvironmentService.php | 11 +- src/Core/Service/Server/ServerEggService.php | 17 +- .../Server/ServerUserVariableService.php | 155 ++++++++++++++++++ themes/default/panel/cart/configure.html.twig | 153 ++++++++++++++++- .../panel/components/egg_manager.html.twig | 55 ++++++- 22 files changed, 503 insertions(+), 15 deletions(-) create mode 100644 src/Core/Service/Server/ServerUserVariableService.php diff --git a/src/Core/Controller/CartController.php b/src/Core/Controller/CartController.php index fa5f545c..7a44f28e 100644 --- a/src/Core/Controller/CartController.php +++ b/src/Core/Controller/CartController.php @@ -41,6 +41,7 @@ use App\Core\Event\Payment\PaymentGatewaysCollectedEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use App\Core\Service\Product\LocationService; +use App\Core\Service\Server\ServerUserVariableService; class CartController extends AbstractController { @@ -54,6 +55,7 @@ public function __construct( private readonly UserRepository $userRepository, private readonly EntityManagerInterface $entityManager, private readonly LocationService $locationService, + private readonly ServerUserVariableService $serverUserVariableService, ) {} #[Route('/cart/topup', name: 'cart_topup', methods: ['GET', 'POST'])] @@ -258,6 +260,25 @@ public function configure(Request $request): Response [$product->getId(), $preparedEggs, $hasSlotPrices] ); + $userRequiredVariablesByEgg = []; + try { + $eggsConfig = json_decode($product->getEggsConfiguration() ?? '{}', true, 512, JSON_THROW_ON_ERROR); + foreach ($eggsConfig as $eggId => $eggConfig) { + foreach ($eggConfig['variables'] ?? [] as $varId => $varConfig) { + if (!empty($varConfig['user_required']) && !empty($varConfig['env_variable'])) { + $userRequiredVariablesByEgg[$eggId][] = [ + 'env_variable' => $varConfig['env_variable'], + 'name' => $varConfig['name'] ?? $varConfig['env_variable'], + 'description' => $varConfig['description'] ?? '', + 'rules' => $varConfig['rules'] ?? '', + 'default_value' => $varConfig['value'] ?? '', + ]; + } + } + } + } catch (\JsonException) { + } + $viewData = [ 'product' => $product, 'eggs' => $preparedEggs, @@ -273,6 +294,7 @@ public function configure(Request $request): Response 'allowAutoRenewal' => $product->getAllowAutoRenewal(), 'allowLocationSelection' => $product->getAllowUserSelectLocation(), 'groupedLocations' => $groupedLocations, + 'userRequiredVariablesByEgg' => $userRequiredVariablesByEgg, ]; return $this->renderWithEvent(ViewNameEnum::CART_CONFIGURE, 'panel/cart/configure.html.twig', $viewData, $request); @@ -341,6 +363,11 @@ public function buy( $autoRenewal = $formData['auto-renewal'] ?? false; $slots = $formData['slots'] ?? null; $voucher = $formData['voucher'] ?? ''; + $userVariables = $this->serverUserVariableService->extractAndValidate( + $request->request->all('user_variables'), + $product, + $eggId + ); $selectedNodeId = null; if ($product->getAllowUserSelectLocation() && isset($formData['node'])) { @@ -367,7 +394,7 @@ public function buy( ); $result = null; $this->entityManager->wrapInTransaction(function() use ( - $product, $eggId, $priceId, $serverName, $autoRenewal, $slots, $voucher, $selectedNodeId, $createServerService, &$result + $product, $eggId, $priceId, $serverName, $autoRenewal, $slots, $voucher, $selectedNodeId, $userVariables, $createServerService, &$result ) { $lockedUser = $this->userRepository->findOneByIdWithLock($this->getUser()->getId()); @@ -392,7 +419,8 @@ public function buy( $lockedUser, $voucher, $slots, - $selectedNodeId + $selectedNodeId, + $userVariables ); }); diff --git a/src/Core/Resources/translations/messages.cn.yaml b/src/Core/Resources/translations/messages.cn.yaml index 7d0b0397..a7e5f62d 100644 --- a/src/Core/Resources/translations/messages.cn.yaml +++ b/src/Core/Resources/translations/messages.cn.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 检查位置可用性失败 checking_availability: 正在检查可用性... invalid_node_selection: 无效的节点选择 + user_required_variable_missing: '缺少必需变量 "%name%"。' + user_required_variable_invalid: '必需变量 "%name%" 的值无效。' cart_configuration: title: 配置你的服务器 configuration: 配置 @@ -300,6 +302,8 @@ pteroca: location: 服务器位置 owner_only: 仅限所有者 description: 配置您的服务器并进行支付 + user_required_variables_title: '必需配置' + user_required_variables_description: '请为选定的服务器软件提供必需的值' cart_topup: title: 充值你的账户 order: 订单 @@ -809,6 +813,8 @@ pteroca: egg_variable_value: '值' egg_variable_user_viewable: '用户可见' egg_variable_user_editable: '用户可编辑' + egg_variable_user_required: '用户必需' + egg_variable_user_required_hint: '用户在订购服务器时需要输入此值' egg_allow_change: '允许在重新安装时更改egg' egg_allow_change_hint: '启用后,客户可以通过重新安装其服务器来更改服务器软件(egg)。服务器将被停止并使用新的软件配置重新安装。' allow_auto_renewal: '允许自动续费' diff --git a/src/Core/Resources/translations/messages.de.yaml b/src/Core/Resources/translations/messages.de.yaml index 1c8e3039..4e8a8671 100644 --- a/src/Core/Resources/translations/messages.de.yaml +++ b/src/Core/Resources/translations/messages.de.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Standortverfügbarkeit konnte nicht überprüft werden' checking_availability: 'Verfügbarkeit wird geprüft...' invalid_node_selection: 'Ungültige Knotenauswahl' + user_required_variable_missing: 'Erforderliche Variable "%name%" wurde nicht bereitgestellt.' + user_required_variable_invalid: 'Ungültiger Wert für erforderliche Variable "%name%".' cart_configuration: title: 'Server konfigurieren' configuration: Konfiguration @@ -300,6 +302,8 @@ pteroca: location: Serverstandort owner_only: 'nur Besitzer' description: 'Konfigurieren Sie Ihren Server und fahren Sie mit der Zahlung fort' + user_required_variables_title: 'Erforderliche Konfiguration' + user_required_variables_description: 'Bitte geben Sie die erforderlichen Werte für die ausgewählte Serversoftware an' cart_topup: title: 'Konto aufladen' order: Bestellen @@ -809,6 +813,8 @@ pteroca: egg_variable_value: Wert egg_variable_user_viewable: 'Für Benutzer sichtbar' egg_variable_user_editable: 'Vom Benutzer bearbeitbar' + egg_variable_user_required: 'Vom Benutzer erforderlich' + egg_variable_user_required_hint: 'Der Benutzer muss diesen Wert bei der Bestellung eines Servers eingeben' egg_allow_change: 'Änderung des Eggs bei Neuinstallation zulassen' egg_allow_change_hint: 'Wenn aktiviert, können Kunden das Server-Betriebssystem (Egg) durch Neuinstallation ändern. Der Server wird gestoppt und mit der neuen Softwarekonfiguration neu installiert.' allow_auto_renewal: 'Automatische Verlängerung erlauben' diff --git a/src/Core/Resources/translations/messages.de_CH.yaml b/src/Core/Resources/translations/messages.de_CH.yaml index 5e1eaaa7..178a63d4 100644 --- a/src/Core/Resources/translations/messages.de_CH.yaml +++ b/src/Core/Resources/translations/messages.de_CH.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Standortverfügbarkeit konnte nicht überprüft werden' checking_availability: 'Verfügbarkeit wird geprüft...' invalid_node_selection: 'Ungültige Knotenauswahl' + user_required_variable_missing: 'Erforderliche Variable "%name%" wurde nicht bereitgestellt.' + user_required_variable_invalid: 'Ungültiger Wert für erforderliche Variable "%name%".' cart_configuration: title: 'Konfigurieren Sie Ihren Server' configuration: Konfiguration @@ -300,6 +302,8 @@ pteroca: location: Serverstandort owner_only: 'nur Besitzer' description: 'Konfigurieren Sie Ihren Server und fahren Sie mit der Zahlung fort' + user_required_variables_title: 'Erforderliche Konfiguration' + user_required_variables_description: 'Bitte geben Sie die erforderlichen Werte für die ausgewählte Serversoftware an' cart_topup: title: 'Konto aufladen' order: Bestellung @@ -809,6 +813,8 @@ pteroca: egg_variable_value: Wert egg_variable_user_viewable: Benutzeransicht egg_variable_user_editable: 'Benutzer editierbar' + egg_variable_user_required: 'Benutzer erforderlich' + egg_variable_user_required_hint: 'Der Benutzer muss diesen Wert bei der Bestellung eines Servers eingeben' egg_allow_change: 'Ändern von Egg durch Neuinstallation erlauben' egg_allow_change_hint: 'Wenn aktiviert, können Kunden das Server-Betriebssystem (Egg) durch Neuinstallation ändern. Der Server wird gestoppt und mit der neuen Softwarekonfiguration neu installiert.' allow_auto_renewal: 'Automatische Verlängerung erlauben' diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index 3a3af8fd..94bef8ac 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -290,6 +290,8 @@ pteroca: no_eggs_available: 'No server software options available' product_unavailable_title: 'Product Temporarily Unavailable' auto_renewal_not_allowed: 'Auto-renewal is not available for this product.' + user_required_variable_missing: 'Required variable "%name%" was not provided.' + user_required_variable_invalid: 'Invalid value for required variable "%name%".' insufficient_balance: 'Insufficient balance' please_select_location: 'Please select a server location' location_available: 'This location is available' @@ -309,6 +311,8 @@ pteroca: location: 'Server Location' owner_only: 'owner only' description: 'Configure your server and proceed to payment' + user_required_variables_title: 'Required Configuration' + user_required_variables_description: 'Please provide the required values for the selected server software' cart_topup: title: 'Top up your account' order: 'Order' @@ -824,6 +828,8 @@ pteroca: slot_variables_unconfigured_eggs: 'Some eggs have unconfigured slot variables' egg_variable_rules: 'Rules' egg_variable_validation_error: 'Validation error in "%name%": value does not meet the required rules.' + egg_variable_user_required: 'Required from user' + egg_variable_user_required_hint: 'User will be required to enter this value when ordering a server' see_product_configuration_guide: 'Click here to open the detailed guide on how to set up products.' egg_allow_change: 'Allow to change egg with reinstallation' egg_allow_change_hint: 'When enabled, customers can change server software (egg) by reinstalling their server. The server will be stopped and reinstalled with the new software configuration.' diff --git a/src/Core/Resources/translations/messages.es.yaml b/src/Core/Resources/translations/messages.es.yaml index 2b721f70..7c17b49f 100644 --- a/src/Core/Resources/translations/messages.es.yaml +++ b/src/Core/Resources/translations/messages.es.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Error al verificar la disponibilidad de la ubicación' checking_availability: 'Verificando disponibilidad...' invalid_node_selection: 'Selección de nodo inválida' + user_required_variable_missing: 'Variable requerida "%name%" no fue proporcionada.' + user_required_variable_invalid: 'Valor inválido para la variable requerida "%name%".' cart_configuration: title: 'Configura tu servidor' configuration: Configuración @@ -300,6 +302,8 @@ pteroca: location: 'Ubicación del servidor' owner_only: 'solo propietario' description: 'Configura tu servidor y procede con el pago' + user_required_variables_title: 'Configuración Requerida' + user_required_variables_description: 'Proporcione los valores requeridos para el software de servidor seleccionado' cart_topup: title: 'Recarga tu cuenta' order: Ordenar @@ -809,6 +813,8 @@ pteroca: egg_variable_value: Valor egg_variable_user_viewable: 'Visible para el usuario' egg_variable_user_editable: 'Editable por el usuario' + egg_variable_user_required: 'Requerido del usuario' + egg_variable_user_required_hint: 'Se requerirá que el usuario ingrese este valor al solicitar un servidor' egg_allow_change: 'Permitir cambiar el egg con la reinstalación' egg_allow_change_hint: 'Cuando está habilitado, los clientes pueden cambiar el software del servidor (egg) reinstalando el servidor. El servidor se detendrá y se reinstalará con la nueva configuración de software.' allow_auto_renewal: 'Permitir renovación automática' diff --git a/src/Core/Resources/translations/messages.fr.yaml b/src/Core/Resources/translations/messages.fr.yaml index d60ae327..94f0c9c4 100644 --- a/src/Core/Resources/translations/messages.fr.yaml +++ b/src/Core/Resources/translations/messages.fr.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: "Échec de la vérification de la disponibilité de l'emplacement" checking_availability: 'Vérification de la disponibilité...' invalid_node_selection: 'Sélection de nœud invalide' + user_required_variable_missing: 'Variable requise "%name%" non fournie.' + user_required_variable_invalid: 'Valeur invalide pour la variable requise "%name%".' cart_configuration: title: 'Configurer votre serveur' configuration: Configuration @@ -300,6 +302,8 @@ pteroca: location: 'Emplacement du serveur' owner_only: 'propriétaire uniquement' description: 'Configurez votre serveur et procédez au paiement' + user_required_variables_title: 'Configuration Requise' + user_required_variables_description: 'Veuillez fournir les valeurs requises pour le logiciel serveur sélectionné' cart_topup: title: 'Recharger votre compte' order: Commander @@ -807,6 +811,8 @@ pteroca: egg_variable_value: 'Valeur' egg_variable_user_viewable: 'Visible par l''utilisateur' egg_variable_user_editable: 'Modifiable par l''utilisateur' + egg_variable_user_required: 'Requis de l''utilisateur' + egg_variable_user_required_hint: 'L''utilisateur devra entrer cette valeur lors de la commande d''un serveur' egg_allow_change: 'Permettre de changer l''egg lors de la réinstallation' egg_allow_change_hint: 'Si activé, les clients peuvent changer le logiciel serveur (egg) en réinstallant le serveur. Le serveur sera arrêté et réinstallé avec la nouvelle configuration logicielle.' allow_auto_renewal: 'Autoriser le renouvellement automatique' diff --git a/src/Core/Resources/translations/messages.hi.yaml b/src/Core/Resources/translations/messages.hi.yaml index b6e7994b..d33b6291 100644 --- a/src/Core/Resources/translations/messages.hi.yaml +++ b/src/Core/Resources/translations/messages.hi.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'स्थान उपलब्धता की जाँच विफल' checking_availability: 'उपलब्धता जाँच रहे हैं...' invalid_node_selection: 'अमान्य नोड चयन' + user_required_variable_missing: 'आवश्यक चर "%name%" प्रदान नहीं किया गया।' + user_required_variable_invalid: 'आवश्यक चर "%name%" के लिए अमान्य मान।' cart_configuration: title: 'अपने सर्वर को कॉन्फ़िगर करें' configuration: कॉन्फ़िगरेशन @@ -300,6 +302,8 @@ pteroca: location: 'सर्वर स्थान' owner_only: 'केवल मालिक' description: 'अपने सर्वर को कॉन्फ़िगर करें और भुगतान के लिए आगे बढ़ें' + user_required_variables_title: 'आवश्यक कॉन्फ़िगरेशन' + user_required_variables_description: 'कृपया चयनित सर्वर सॉफ़्टवेयर के लिए आवश्यक मान प्रदान करें' cart_topup: title: 'अपना खाता रिचार्ज करें' order: ऑर्डर @@ -809,6 +813,8 @@ pteroca: egg_variable_value: मूल्य egg_variable_user_viewable: 'उपयोगकर्ता द्वारा देखा जा सकता है' egg_variable_user_editable: 'उपयोगकर्ता द्वारा संपादित किया जा सकता है' + egg_variable_user_required: 'उपयोगकर्ता से आवश्यक' + egg_variable_user_required_hint: 'सर्वर ऑर्डर करते समय उपयोगकर्ता को इस मान को दर्ज करना होगा' egg_allow_change: 'इंस्टॉलेशन के साथ सॉफ़्टवेयर बदलने की अनुमति दें' egg_allow_change_hint: 'जब सक्षम किया जाता है, तो ग्राहक अपने सर्वर को पुनः स्थापित करके सर्वर सॉफ़्टवेयर (egg) बदल सकते हैं। सर्वर बंद कर दिया जाएगा और नई सॉफ़्टवेयर कॉन्फ़िगरेशन के साथ पुनः स्थापित किया जाएगा।' allow_auto_renewal: 'ऑटो-रिन्यूअल की अनुमति दें' diff --git a/src/Core/Resources/translations/messages.id.yaml b/src/Core/Resources/translations/messages.id.yaml index c60737f6..c040a54c 100644 --- a/src/Core/Resources/translations/messages.id.yaml +++ b/src/Core/Resources/translations/messages.id.yaml @@ -312,6 +312,8 @@ pteroca: location_check_failed: 'Gagal memeriksa ketersediaan lokasi' checking_availability: 'Memeriksa ketersediaan...' invalid_node_selection: 'Pemilihan node tidak valid' + user_required_variable_missing: 'Variabel yang diperlukan "%name%" tidak disediakan.' + user_required_variable_invalid: 'Nilai tidak valid untuk variabel wajib "%name%".' cart_configuration: title: 'Konfigurasi server Anda' configuration: Konfigurasi @@ -324,6 +326,8 @@ pteroca: location: 'Lokasi Server' owner_only: 'hanya pemilik' description: 'Konfigurasi server Anda dan lanjutkan ke pembayaran' + user_required_variables_title: 'Konfigurasi yang Diperlukan' + user_required_variables_description: 'Silakan berikan nilai yang diperlukan untuk perangkat lunak server yang dipilih' cart_topup: title: 'Isi ulang akun Anda' order: Pesan @@ -825,6 +829,8 @@ pteroca: egg_variable_value: Nilai egg_variable_user_viewable: 'Dapat dilihat pengguna' egg_variable_user_editable: 'Dapat diedit pengguna' + egg_variable_user_required: 'Diperlukan dari pengguna' + egg_variable_user_required_hint: 'Pengguna akan diminta memasukkan nilai ini saat memesan server' egg_variable_slot_variable: 'Variabel slot' egg_variable_slot_variable_hint: 'Pilih variabel yang menetapkan slot maks (pemain) untuk harga berdasarkan slot' slot_variable_not_configured_egg: 'Variabel slot tidak dikonfigurasi untuk egg ini' diff --git a/src/Core/Resources/translations/messages.it.yaml b/src/Core/Resources/translations/messages.it.yaml index 6b1fa333..9a70c357 100644 --- a/src/Core/Resources/translations/messages.it.yaml +++ b/src/Core/Resources/translations/messages.it.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Impossibile verificare la disponibilità della posizione' checking_availability: 'Verifica disponibilità...' invalid_node_selection: 'Selezione del nodo non valida' + user_required_variable_missing: 'Variabile richiesta "%name%" non fornita.' + user_required_variable_invalid: 'Valore non valido per la variabile richiesta "%name%".' cart_configuration: title: 'Configura il tuo server' configuration: Configurazione @@ -300,6 +302,8 @@ pteroca: location: 'Posizione del server' owner_only: 'solo proprietario' description: 'Configura il tuo server e procedi al pagamento' + user_required_variables_title: 'Configurazione Richiesta' + user_required_variables_description: 'Fornire i valori richiesti per il software server selezionato' cart_topup: title: 'Ricarica il tuo conto' order: Ordina @@ -809,6 +813,8 @@ pteroca: egg_variable_value: 'Valore' egg_variable_user_viewable: 'Visibile dall''utente' egg_variable_user_editable: 'Modificabile dall''utente' + egg_variable_user_required: 'Richiesto dall''utente' + egg_variable_user_required_hint: 'L''utente dovrà inserire questo valore quando ordina un server' egg_allow_change: 'Permettere di cambiare l''egg con la reinstallazione' egg_allow_change_hint: 'Se abilitato, i clienti possono cambiare il software del server (egg) reinstallando il server. Il server sarà fermato e reinstallato con la nuova configurazione software.' allow_auto_renewal: 'Consenti rinnovo automatico' diff --git a/src/Core/Resources/translations/messages.nl.yaml b/src/Core/Resources/translations/messages.nl.yaml index d5c7d3be..f8f5986f 100644 --- a/src/Core/Resources/translations/messages.nl.yaml +++ b/src/Core/Resources/translations/messages.nl.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Kon locatiebeschikbaarheid niet controleren' checking_availability: 'Beschikbaarheid controleren...' invalid_node_selection: 'Ongeldige knoopselectie' + user_required_variable_missing: 'Vereiste variabele "%name%" niet aangeleverd.' + user_required_variable_invalid: 'Ongeldige waarde voor vereiste variabele "%name%".' renew: title: 'Server verlengen' renew: 'Server verlengen' @@ -703,6 +705,8 @@ pteroca: location: Serverlocatie owner_only: 'alleen eigenaar' description: 'Configureer uw server en ga door naar betaling' + user_required_variables_title: 'Vereiste Configuratie' + user_required_variables_description: 'Geef de vereiste waarden voor de geselecteerde serversoftware' cart_topup: title: 'Vul je account aan' order: Bestellen @@ -808,6 +812,8 @@ pteroca: egg_variable_value: Waarde egg_variable_user_viewable: 'Gebruiker zichtbaar' egg_variable_user_editable: 'Gebruiker aanpasbaar' + egg_variable_user_required: 'Vereist van gebruiker' + egg_variable_user_required_hint: 'Gebruiker moet deze waarde invoeren bij het bestellen van een server' egg_allow_change: 'Sta het veranderen van egg toe bij herinstallatie' egg_allow_change_hint: 'Wanneer ingeschakeld, kunnen klanten de serversoftware (egg) wijzigen door hun server opnieuw te installeren. De server wordt gestopt en opnieuw geïnstalleerd met de nieuwe softwareconfiguratie.' allow_auto_renewal: 'Automatische verlenging toestaan' diff --git a/src/Core/Resources/translations/messages.pl.yaml b/src/Core/Resources/translations/messages.pl.yaml index 468ff79a..c98cd59d 100644 --- a/src/Core/Resources/translations/messages.pl.yaml +++ b/src/Core/Resources/translations/messages.pl.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Nie udało się sprawdzić dostępności lokalizacji' checking_availability: 'Sprawdzanie dostępności...' invalid_node_selection: 'Nieprawidłowy wybór węzła' + user_required_variable_missing: 'Wymagana zmienna "%name%" nie została podana.' + user_required_variable_invalid: 'Nieprawidłowa wartość dla wymaganej zmiennej "%name%".' cart_configuration: title: 'Skonfiguruj swój serwer' configuration: Konfiguracja @@ -300,6 +302,8 @@ pteroca: location: 'Lokalizacja serwera' owner_only: 'tylko właściciel' description: 'Skonfiguruj swój serwer i przejdź do płatności' + user_required_variables_title: 'Wymagana Konfiguracja' + user_required_variables_description: 'Podaj wymagane wartości dla wybranego oprogramowania serwera' cart_topup: title: 'Doładuj swoje konto' order: Zamów @@ -807,6 +811,8 @@ pteroca: egg_variable_value: Wartość egg_variable_user_viewable: 'Widoczne dla użytkownika' egg_variable_user_editable: 'Edytowalne przez użytkownika' + egg_variable_user_required: 'Wymagane od użytkownika' + egg_variable_user_required_hint: 'Użytkownik będzie musiał wprowadzić tę wartość podczas zamawiania serwera' egg_allow_change: 'Zezwól na zmianę egga przy reinstalacji' egg_allow_change_hint: 'Gdy włączone, klienci mogą zmienić oprogramowanie serwera (egg) poprzez przeinstalowanie serwera. Serwer zostanie zatrzymany i przeinstalowany z nową konfiguracją oprogramowania.' allow_auto_renewal: 'Zezwól na automatyczne odnawianie' diff --git a/src/Core/Resources/translations/messages.pt.yaml b/src/Core/Resources/translations/messages.pt.yaml index b00b71fb..2a9d881e 100644 --- a/src/Core/Resources/translations/messages.pt.yaml +++ b/src/Core/Resources/translations/messages.pt.yaml @@ -288,6 +288,8 @@ pteroca: location_check_failed: 'Falha ao verificar disponibilidade da localização' checking_availability: 'Verificando disponibilidade...' invalid_node_selection: 'Seleção de nó inválida' + user_required_variable_missing: 'Variável obrigatória "%name%" não foi fornecida.' + user_required_variable_invalid: 'Valor inválido para a variável obrigatória "%name%".' cart_configuration: title: 'Configure seu servidor' configuration: Configuração @@ -300,6 +302,8 @@ pteroca: location: 'Localização do servidor' owner_only: 'apenas proprietário' description: 'Configure seu servidor e prossiga para o pagamento' + user_required_variables_title: 'Configuração Obrigatória' + user_required_variables_description: 'Forneça os valores obrigatórios para o software do servidor selecionado' cart_topup: title: 'Recarregar sua conta' order: Pedir @@ -810,6 +814,8 @@ pteroca: egg_variable_value: Valor egg_variable_user_viewable: 'Visível para o usuário' egg_variable_user_editable: 'Editável pelo usuário' + egg_variable_user_required: 'Obrigatório do usuário' + egg_variable_user_required_hint: 'O usuário será obrigado a inserir este valor ao solicitar um servidor' egg_allow_change: 'Permitir alterar o egg com a reinstalação' egg_allow_change_hint: 'Quando ativado, os clientes podem alterar o software do servidor (egg) reinstalando o servidor. O servidor será parado e reinstalado com a nova configuração de software.' allow_auto_renewal: 'Permitir renovação automática' diff --git a/src/Core/Resources/translations/messages.ru.yaml b/src/Core/Resources/translations/messages.ru.yaml index 64f1a120..b3ce3156 100644 --- a/src/Core/Resources/translations/messages.ru.yaml +++ b/src/Core/Resources/translations/messages.ru.yaml @@ -312,6 +312,8 @@ pteroca: location_check_failed: 'Не удалось проверить доступность расположения' checking_availability: 'Проверка доступности...' invalid_node_selection: 'Неверный выбор узла' + user_required_variable_missing: 'Обязательная переменная "%name%" не предоставлена.' + user_required_variable_invalid: 'Недействительное значение для обязательной переменной "%name%".' cart_configuration: title: 'Настроить сервер' configuration: Конфигурация @@ -324,6 +326,8 @@ pteroca: location: 'Расположение сервера' owner_only: 'только владелец' description: 'Настройте ваш сервер и произведите оплату' + user_required_variables_title: 'Требуемая Конфигурация' + user_required_variables_description: 'Пожалуйста, укажите требуемые значения для выбранного серверного ПО' cart_topup: title: 'Пополнить аккаунт' order: Заказать @@ -833,6 +837,8 @@ pteroca: egg_variable_value: Значение egg_variable_user_viewable: 'Доступно пользователю' egg_variable_user_editable: 'Редактируемо пользователем' + egg_variable_user_required: 'Требуется от пользователя' + egg_variable_user_required_hint: 'Пользователь должен будет ввести это значение при заказе сервера' egg_allow_change: 'Разрешить замену яиц с переустановкой' egg_allow_change_hint: 'Если параметр включен, клиенты могут изменить программное обеспечение сервера (egg) путем переустановки своего сервера. Сервер будет остановлен и переустановлен с новой конфигурацией программного обеспечения.' allow_auto_renewal: 'Разрешить автоматическое продление' diff --git a/src/Core/Resources/translations/messages.ua.yaml b/src/Core/Resources/translations/messages.ua.yaml index be96f0f1..9f5ba8a2 100644 --- a/src/Core/Resources/translations/messages.ua.yaml +++ b/src/Core/Resources/translations/messages.ua.yaml @@ -312,6 +312,8 @@ pteroca: location_check_failed: 'Не вдалося перевірити доступність розташування' checking_availability: 'Перевірка доступності...' invalid_node_selection: 'Невірний вибір вузла' + user_required_variable_missing: 'Обов''язкова змінна "%name%" не надана.' + user_required_variable_invalid: 'Невірне значення для обов''язкової змінної "%name%".' cart_configuration: title: 'Налаштувати сервер' configuration: Конфігурація @@ -324,6 +326,8 @@ pteroca: location: 'Розташування сервера' owner_only: 'тільки власник' description: 'Налаштуйте ваш сервер та здійсніть оплату' + user_required_variables_title: 'Необхідна Конфігурація' + user_required_variables_description: 'Будь ласка, надайте необхідні значення для вибраного серверного ПО' cart_topup: title: 'Поповнити свій рахунок' order: Замовити @@ -833,6 +837,8 @@ pteroca: egg_variable_value: Значення egg_variable_user_viewable: 'Видиме для користувача' egg_variable_user_editable: 'Редаговане користувачем' + egg_variable_user_required: 'Потрібно від користувача' + egg_variable_user_required_hint: 'Користувач повинен буде ввести це значення при замовленні сервера' egg_allow_change: 'Дозволити змінювати egg при перевстановленні' egg_allow_change_hint: 'Якщо ввімкнено, клієнти можуть змінити програмне забезпечення сервера (egg) шляхом переустановлення свого сервера. Сервер буде зупинений та переустановлений з новою конфігурацією програмного забезпечення.' allow_auto_renewal: 'Дозволити автоматичне продовження' diff --git a/src/Core/Service/Server/CreateServerService.php b/src/Core/Service/Server/CreateServerService.php index 9c2296e9..8a0aa074 100644 --- a/src/Core/Service/Server/CreateServerService.php +++ b/src/Core/Service/Server/CreateServerService.php @@ -78,6 +78,7 @@ public function createServer( ?string $voucherCode = null, ?int $slots = null, ?int $selectedNodeId = null, + array $userVariables = [], ): array { if (!empty($voucherCode)) { @@ -110,7 +111,7 @@ public function createServer( throw new Exception($this->translator->trans('pteroca.store.server_creation_blocked')); } - $createdPterodactylServer = $this->createPterodactylServer($product, $eggId, $serverName, $user, $slots, $selectedNodeId); + $createdPterodactylServer = $this->createPterodactylServer($product, $eggId, $serverName, $user, $slots, $selectedNodeId, $userVariables); $createdOnPterodactylEvent = new ServerCreatedOnPterodactylEvent( $user->getId(), @@ -216,12 +217,13 @@ private function createPterodactylServer( string $serverName, UserInterface $user, ?int $slots = null, - ?int $selectedNodeId = null + ?int $selectedNodeId = null, + array $userVariables = [] ): PterodactylServer { try { $preparedServerBuild = $this->serverBuildService - ->prepareServerBuild($product, $user, $eggId, $serverName, $slots, $selectedNodeId); + ->prepareServerBuild($product, $user, $eggId, $serverName, $slots, $selectedNodeId, $userVariables); return $this->pterodactylApplicationService ->getApplicationApi() diff --git a/src/Core/Service/Server/ServerBuildService.php b/src/Core/Service/Server/ServerBuildService.php index 7c619968..617df7b9 100644 --- a/src/Core/Service/Server/ServerBuildService.php +++ b/src/Core/Service/Server/ServerBuildService.php @@ -30,6 +30,7 @@ public function prepareServerBuild( string $serverName = '', ?int $slots = null, ?int $selectedNodeId = null, + array $userVariables = [], ): array { $selectedEgg = $this->getSelectedEgg($eggId, $product); @@ -61,7 +62,7 @@ public function prepareServerBuild( 'egg' => $selectedEgg->get('id'), 'docker_image' => $dockerImage, 'startup' => $startup, - 'environment' => $this->serverEggEnvironmentService->buildEnvironmentVariables($selectedEgg, $productEggConfiguration, $slots), + 'environment' => $this->serverEggEnvironmentService->buildEnvironmentVariables($selectedEgg, $productEggConfiguration, $slots, $userVariables), 'limits' => [ 'memory' => $product->getMemory(), 'swap' => $product->getSwap(), diff --git a/src/Core/Service/Server/ServerEggEnvironmentService.php b/src/Core/Service/Server/ServerEggEnvironmentService.php index b989c825..a611cc39 100644 --- a/src/Core/Service/Server/ServerEggEnvironmentService.php +++ b/src/Core/Service/Server/ServerEggEnvironmentService.php @@ -13,12 +13,14 @@ * @param Resource $egg The egg resource from Pterodactyl API (must include 'variables' relationship) * @param array $productEggConfiguration Product-specific egg configuration (from ServerProduct.eggsConfiguration) * @param int|null $slots Optional slots count for slot-based pricing (overrides slot variables) + * @param array $userVariables User-provided values keyed by env_variable name (highest priority) * @return array Environment variables as key-value pairs */ public function buildEnvironmentVariables( Resource $egg, array $productEggConfiguration, - ?int $slots = null + ?int $slots = null, + array $userVariables = [] ): array { $environmentVariables = []; @@ -36,8 +38,11 @@ public function buildEnvironmentVariables( foreach ($variables->toArray() as $variable) { $variableFromProduct = $productEggConfiguration[$eggId]['variables'][$variable['id']] ?? null; - // Priority: product config > default value from egg - $valueToSet = $variableFromProduct['value'] ?? $variable['default_value'] ?? null; + // Priority: user_variables > product config > default value from egg + $valueToSet = $userVariables[$variable['env_variable']] + ?? $variableFromProduct['value'] + ?? $variable['default_value'] + ?? null; // Special handling: Override with slot count if this is a slot variable if ($slots !== null && !empty($variableFromProduct['slot_variable']) && $variableFromProduct['slot_variable'] === 'on') { diff --git a/src/Core/Service/Server/ServerEggService.php b/src/Core/Service/Server/ServerEggService.php index d1cff284..0219a96f 100644 --- a/src/Core/Service/Server/ServerEggService.php +++ b/src/Core/Service/Server/ServerEggService.php @@ -29,17 +29,30 @@ public function prepareEggsConfiguration(int $pterodactylServerId): string $defaultValue = $variable['attributes']['default_value'] ?? $variable['default_value']; $userViewable = $variable['attributes']['user_viewable'] ?? $variable['user_viewable']; $userEditable = $variable['attributes']['user_editable'] ?? $variable['user_editable']; + $envVariable = $variable['attributes']['env_variable'] ?? $variable['env_variable'] ?? ''; + $name = $variable['attributes']['name'] ?? $variable['name'] ?? ''; + $description = $variable['attributes']['description'] ?? $variable['description'] ?? ''; + $rules = $variable['attributes']['rules'] ?? $variable['rules'] ?? ''; } else { $id = $variable->get('id'); $defaultValue = $variable->get('default_value'); $userViewable = $variable->get('user_viewable'); $userEditable = $variable->get('user_editable'); + $envVariable = $variable->get('env_variable') ?? ''; + $name = $variable->get('name') ?? ''; + $description = $variable->get('description') ?? ''; + $rules = $variable->get('rules') ?? ''; } $preparedVariables[$id] = [ - 'value' => $defaultValue, + 'value' => $defaultValue, 'user_viewable' => $userViewable, 'user_editable' => $userEditable, + 'user_required' => false, + 'env_variable' => $envVariable, + 'name' => $name, + 'description' => $description, + 'rules' => $rules, ]; } @@ -113,6 +126,8 @@ private function getEggsTranslations(): array 'slot_variables_unconfigured_eggs' => $this->translator->trans('pteroca.crud.product.slot_variables_unconfigured_eggs'), 'egg_variable_rules' => $this->translator->trans('pteroca.crud.product.egg_variable_rules'), 'egg_variable_validation_error' => $this->translator->trans('pteroca.crud.product.egg_variable_validation_error'), + 'egg_variable_user_required' => $this->translator->trans('pteroca.crud.product.egg_variable_user_required'), + 'egg_variable_user_required_hint' => $this->translator->trans('pteroca.crud.product.egg_variable_user_required_hint'), 'use_as_starting_egg' => $this->translator->trans('pteroca.admin.server_create.use_as_starting_egg'), 'starting_egg_help' => $this->translator->trans('pteroca.admin.server_create.starting_egg_help'), ]; diff --git a/src/Core/Service/Server/ServerUserVariableService.php b/src/Core/Service/Server/ServerUserVariableService.php new file mode 100644 index 00000000..5e8f061a --- /dev/null +++ b/src/Core/Service/Server/ServerUserVariableService.php @@ -0,0 +1,155 @@ +getEggsConfiguration() ?? '{}', true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException) { + return []; + } + + $eggVariables = $eggsConfiguration[$eggId]['variables'] ?? []; + + foreach ($eggVariables as $varConfig) { + if (empty($varConfig['user_required'])) { + continue; + } + + $envVariable = $varConfig['env_variable'] ?? ''; + if (empty($envVariable)) { + continue; + } + + $value = trim($submittedVars[$envVariable] ?? ''); + $name = $varConfig['name'] ?? $envVariable; + + if ($value === '') { + throw new \Exception($this->translator->trans( + 'pteroca.store.user_required_variable_missing', + ['%name%' => $name] + )); + } + + $rules = $varConfig['rules'] ?? ''; + if (!empty($rules) && !$this->validateRules($value, $rules)) { + throw new \Exception($this->translator->trans( + 'pteroca.store.user_required_variable_invalid', + ['%name%' => $name] + )); + } + } + + $result = []; + foreach ($eggVariables as $varConfig) { + if (empty($varConfig['user_required'])) { + continue; + } + $envVariable = $varConfig['env_variable'] ?? ''; + if (empty($envVariable)) { + continue; + } + $value = trim($submittedVars[$envVariable] ?? ''); + if ($value !== '') { + $result[$envVariable] = $value; + } + } + + return $result; + } + + private function validateRules(string $value, string $rules): bool + { + $ruleList = explode('|', $rules); + $isNumeric = in_array('numeric', $ruleList, true) || in_array('integer', $ruleList, true); + $constraints = []; + + foreach ($ruleList as $rule) { + [$ruleName, $ruleParam] = str_contains($rule, ':') + ? explode(':', $rule, 2) + : [$rule, null]; + + switch ($ruleName) { + case 'required': + $constraints[] = new Assert\NotBlank(); + break; + case 'string': + $constraints[] = new Assert\Type('string'); + break; + case 'numeric': + $constraints[] = new Assert\Type('numeric'); + break; + case 'integer': + $constraints[] = new Assert\Type('integer'); + break; + case 'email': + $constraints[] = new Assert\Email(); + break; + case 'url': + $constraints[] = new Assert\Url(); + break; + case 'ip': + $constraints[] = new Assert\Ip(); + break; + case 'min': + if ($ruleParam !== null) { + $constraints[] = $isNumeric + ? new Assert\GreaterThanOrEqual((float) $ruleParam) + : new Assert\Length(min: (int) $ruleParam); + } + break; + case 'max': + if ($ruleParam !== null) { + $constraints[] = $isNumeric + ? new Assert\LessThanOrEqual((float) $ruleParam) + : new Assert\Length(max: (int) $ruleParam); + } + break; + case 'between': + if ($ruleParam !== null) { + [$min, $max] = array_map('floatval', explode(',', $ruleParam, 2)); + $constraints[] = $isNumeric + ? new Assert\Range(min: $min, max: $max) + : new Assert\Length(min: (int) $min, max: (int) $max); + } + break; + case 'in': + if ($ruleParam !== null) { + $constraints[] = new Assert\Choice(explode(',', $ruleParam)); + } + break; + case 'regex': + if ($ruleParam !== null) { + $constraints[] = new Assert\Regex($ruleParam); + } + break; + } + } + + if (empty($constraints)) { + return true; + } + + $validator = Validation::createValidator(); + return count($validator->validate($value, $constraints)) === 0; + } +} diff --git a/themes/default/panel/cart/configure.html.twig b/themes/default/panel/cart/configure.html.twig index 9c4d6c5d..c435e2ef 100644 --- a/themes/default/panel/cart/configure.html.twig +++ b/themes/default/panel/cart/configure.html.twig @@ -236,6 +236,24 @@ + {% if userRequiredVariablesByEgg is not empty %} + +
+
+
+ +
{{ 'pteroca.cart_configuration.user_required_variables_title'|trans }}
+
+
+
+

+ + {{ 'pteroca.cart_configuration.user_required_variables_description'|trans }} +

+
+
+
+ {% endif %} @@ -329,6 +347,7 @@ {% endblock %} {% block body_javascript %} + {% include 'components/egg_variable_validator.html.twig' %} {% set priceSeps = get_price_separators() %} diff --git a/themes/default/panel/crud/plugin/components/marketplace-sidebar.html.twig b/themes/default/panel/crud/plugin/components/marketplace-sidebar.html.twig new file mode 100644 index 00000000..e9ccfd2f --- /dev/null +++ b/themes/default/panel/crud/plugin/components/marketplace-sidebar.html.twig @@ -0,0 +1,30 @@ +{# Marketplace Plugin Browser Sidebar #} +
+
+ + {{ 'pteroca.marketplace.browser_title'|trans }} + + ... +
+
+ +
+
+
+
+
+
+ +
diff --git a/themes/default/panel/crud/plugin/index.html.twig b/themes/default/panel/crud/plugin/index.html.twig index 6f50b843..7c351fd0 100644 --- a/themes/default/panel/crud/plugin/index.html.twig +++ b/themes/default/panel/crud/plugin/index.html.twig @@ -26,60 +26,73 @@ {% endblock %} {% block main %} -
-
- - - - - - - - - - - - - {% for plugin in plugins %} +
+
+
+
+
{{ 'pteroca.crud.plugin.name'|trans }}{{ 'pteroca.crud.plugin.display_name'|trans }}{{ 'pteroca.crud.plugin.version'|trans }}{{ 'pteroca.crud.plugin.author'|trans }}{{ 'pteroca.crud.plugin.state'|trans }}{{ 'pteroca.crud.plugin.actions'|trans }}
+ - - - - - - - - {% else %} - - + + + + + + - {% endfor %} - -
{{ plugin.name }}{{ plugin.displayName }}{{ plugin.version }}{{ plugin.author }} - - {{ plugin.state.label|trans }} - - - {# Detail button - always visible #} - - - - - {# Dynamic actions from controller #} - {% if plugin_actions[plugin.name] is defined %} - {% for action in plugin_actions[plugin.name] %} - - - - {% endfor %} - {% endif %} -
- {{ 'pteroca.crud.plugin.no_plugins_found'|trans }} - {{ 'pteroca.crud.plugin.name'|trans }}{{ 'pteroca.crud.plugin.display_name'|trans }}{{ 'pteroca.crud.plugin.version'|trans }}{{ 'pteroca.crud.plugin.author'|trans }}{{ 'pteroca.crud.plugin.state'|trans }}{{ 'pteroca.crud.plugin.actions'|trans }}
+ + + {% for plugin in plugins %} + + {{ plugin.name }} + {{ plugin.displayName }} + {{ plugin.version }} + {{ plugin.author }} + + + {{ plugin.state.label|trans }} + + + + {# Detail button - always visible #} + + + + + {# Dynamic actions from controller #} + {% if plugin_actions[plugin.name] is defined %} + {% for action in plugin_actions[plugin.name] %} + + + + {% endfor %} + {% endif %} + + + {% else %} + + + {{ 'pteroca.crud.plugin.no_plugins_found'|trans }} + + + {% endfor %} + + +
+ {% include 'panel/crud/plugin/components/marketplace-sidebar.html.twig' %} + +{% endblock %} + +{% block body_javascript %} + {{ parent() }} + {% include 'panel/crud/plugin/components/marketplace-sidebar-js.html.twig' with { + tags: 'plugin', + placeholder_icon: 'fa-puzzle-piece' + } %} {% endblock %} diff --git a/themes/default/panel/crud/theme/index.html.twig b/themes/default/panel/crud/theme/index.html.twig index f0f6999a..58e3a4ae 100644 --- a/themes/default/panel/crud/theme/index.html.twig +++ b/themes/default/panel/crud/theme/index.html.twig @@ -33,89 +33,102 @@ {% endblock %} {% block main %} -
-
- - - - - - - - - - - - - {% for theme in themes %} +
+
+
+
+
{{ 'pteroca.crud.theme.name'|trans }}{{ 'pteroca.crud.theme.version'|trans }}{{ 'pteroca.crud.theme.author'|trans }}{{ 'pteroca.crud.theme.contexts'|trans }}{{ 'pteroca.crud.theme.status'|trans }}{{ 'pteroca.crud.theme.actions'|trans }}
+ - - - - - + + + + + + + + + {% for theme in themes %} + + + + + - + - - {% else %} - - - - {% endfor %} - -
{{ theme.displayName }}{{ theme.version }}{{ theme.author }} - {% if theme.contexts is defined and theme.contexts|length > 0 %} - {% for context in theme.contexts %} - {{ context|capitalize }} - {% endfor %} - {% else %} - - - {% endif %} - - {% if theme.activeContexts|length > 0 %} -
- {% for activeContext in theme.activeContexts %} - - {{ 'pteroca.crud.theme.active'|trans }}: {{ activeContext|capitalize }} - +
{{ 'pteroca.crud.theme.name'|trans }}{{ 'pteroca.crud.theme.version'|trans }}{{ 'pteroca.crud.theme.author'|trans }}{{ 'pteroca.crud.theme.contexts'|trans }}{{ 'pteroca.crud.theme.status'|trans }}{{ 'pteroca.crud.theme.actions'|trans }}
{{ theme.displayName }}{{ theme.version }}{{ theme.author }} + {% if theme.contexts is defined and theme.contexts|length > 0 %} + {% for context in theme.contexts %} + {{ context|capitalize }} {% endfor %} - - {% else %} - - {{ 'pteroca.crud.theme.inactive'|trans }} - - {% endif %} - - {# Dropdown menu with actions #} - {% if theme_actions[theme.name] is defined and theme_actions[theme.name]|length > 0 %} - + {% if theme.activeContexts|length > 0 %} +
+ {% for activeContext in theme.activeContexts %} + + {{ 'pteroca.crud.theme.active'|trans }}: {{ activeContext|capitalize }} + {% endfor %} - -
- {% endif %} -
- {{ 'pteroca.crud.theme.no_themes_found_unified'|trans }} -
+
+ {% else %} + + {{ 'pteroca.crud.theme.inactive'|trans }} + + {% endif %} + + + {# Dropdown menu with actions #} + {% if theme_actions[theme.name] is defined and theme_actions[theme.name]|length > 0 %} + + {% endif %} + + + {% else %} + + + {{ 'pteroca.crud.theme.no_themes_found_unified'|trans }} + + + {% endfor %} + + +
+ + {# Include modal components #} + {% include 'panel/crud/theme/components/set-default-modal.html.twig' %} + {% include 'panel/crud/theme/components/copy-theme-modal.html.twig' %} + {% include 'panel/crud/theme/components/delete-theme-modal.html.twig' %} + {% include 'panel/crud/plugin/components/marketplace-sidebar.html.twig' %} + +{% endblock %} - {# Include modal components #} - {% include 'panel/crud/theme/components/set-default-modal.html.twig' %} - {% include 'panel/crud/theme/components/copy-theme-modal.html.twig' %} - {% include 'panel/crud/theme/components/delete-theme-modal.html.twig' %} +{% block body_javascript %} + {{ parent() }} + {% include 'panel/crud/plugin/components/marketplace-sidebar-js.html.twig' with { + tags: 'theme', + placeholder_icon: 'fa-brush' + } %} {% endblock %} From 11063fb9d7c5c6c0548fa3c20fbd449ad84aec61 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sat, 28 Feb 2026 22:19:37 +0100 Subject: [PATCH 23/34] Improvements in contribution notes --- .github/ISSUE_TEMPLATE/custom.md | 10 --- .github/PULL_REQUEST_TEMPLATE.md | 19 ++++++ CODE_OF_CONDUCT.md | 3 + CONTRIBUTING.md | 107 +++++++++++++++++++++++++------ README.md | 2 + 5 files changed, 111 insertions(+), 30 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index 48d5f81f..00000000 --- a/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..d75169d8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## Description + + +## Type of change +- [ ] Bug fix +- [ ] New feature +- [ ] Refactoring / code improvement +- [ ] Documentation update +- [ ] Translation update + +## Checklist +- [ ] CI pipeline passes (PHPStan, migrations, translations check) +- [ ] Branch targets `main` +- [ ] Code follows PSR-12 (PHP) / project conventions +- [ ] If adding/editing translations: all language files in `src/Core/Resources/translations/` were updated +- [ ] If introducing behavior changes: CHANGELOG entry added + +## Testing + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b5659f58..2a3f7dda 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -56,6 +56,9 @@ Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. +This includes our GitHub repository, Discord server (discord.gg/Gz5phhuZym), +and any other official community spaces. + ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4f91d75..d0628e4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to PteroCA -Thank you for your interest in contributing to **PteroCA**! We welcome contributions from everyone. Here’s how you can help: +Thank you for your interest in contributing to **PteroCA**! We welcome contributions from everyone. Here's how you can help: ## How to Contribute @@ -23,57 +23,124 @@ We love new ideas! If you have a feature request, follow these steps: ### Submitting Pull Requests 1. **Fork the repository**: Create your own fork of the repository by clicking the "Fork" button in the top right of our [GitHub repository](https://github.com/pteroca-com/panel). -2. **Create a new branch**: Use a descriptive name for your branch. For example: `feature/add-payment-integration` or `bugfix/fix-user-auth`. +2. **Create a new branch**: Use a descriptive name for your branch. See [Branch Naming](#branch-naming) below. 3. **Make changes**: Add your code, following our coding standards: - Follow PSR-12 for PHP. - Write clear, concise code with comments where necessary. - Write tests for your code whenever possible. -4. **Commit your changes**: Write meaningful and descriptive commit messages. +4. **Commit your changes**: Write meaningful and descriptive commit messages. See [Commit Messages](#commit-messages) below. 5. **Push to your fork**: Push the changes from your local repository to your fork on GitHub. -6. **Submit a pull request**: From your forked repository, click the “New Pull Request” button. Ensure that your pull request is well-documented and references any issues it resolves. +6. **Submit a pull request**: From your forked repository, click the "New Pull Request" button. Make sure your PR targets the `main` branch. -### Code Review and Feedback +## Branch Naming -- **Be patient**: Our maintainers will review your pull request as soon as possible. We may ask you to make additional changes. -- **Tests**: Ensure your pull request passes all tests before submission. Use PHPUnit for testing the PHP code. -- **Constructive Feedback**: If your pull request is rejected, don’t be discouraged. We will provide constructive feedback to help you improve. +Use the following naming conventions for your branches: -## Community Guidelines +- `feature/` — new functionality (e.g. `feature/add-payment-integration`) +- `bugfix/` — bug fixes (e.g. `bugfix/fix-user-auth`) +- `hotfix/` — urgent fixes for production issues +- `task--` — tasks tracked in the issue tracker (e.g. `task-42-improve-logging`) -- **Be respectful**: We value a welcoming, respectful, and inclusive community. Disrespectful or inappropriate behavior will not be tolerated. -- **Collaboration**: Help fellow contributors by reviewing pull requests and participating in discussions. -- **Support**: For usage or configuration questions, please refer to our [Documentation](https://pteroca.gitbook.io) and the [Discord Support Server](https://discord.gg/Gz5phhuZym). +## Commit Messages + +Write clear, descriptive commit messages in English. Focus on what the change does and why: + +**Good examples:** +- `Add user balance notification on low funds` +- `Fix server suspension not triggered on expired subscription` +- `Refactor payment gateway to support multiple providers` + +Avoid vague messages like `fix`, `update`, or `WIP`. + +## Pull Request Process + +1. **Target `main`** — all PRs should target the `main` branch. +2. **CI pipeline must pass** — the GitHub Actions CI pipeline runs automatically on every PR. A PR with a failing pipeline will not be reviewed. See [CI Pipeline](#ci-pipeline) for details. +3. **Code review** — a maintainer will review your pull request. Be patient; we will provide feedback as soon as possible. +4. **Address all comments** — all review comments must be addressed before the PR can be merged. +5. **Resolve all threads** — all discussion threads must be marked as resolved before merge. +6. **Merge** — once approved and all checks pass, a maintainer will merge the PR. + +## CI Pipeline + +Every pull request to `main` or `develop` automatically triggers the GitHub Actions CI pipeline (`symfony.yml`). The pipeline must pass before a PR will be reviewed or merged. + +The pipeline checks: + +- **PHPStan** (static analysis, level 1) — ensures no type errors or undefined references +- **Doctrine Migrations** — all migrations must execute without errors +- **Translation completeness** — no translation keys may be missing from any language file + +### Running checks locally + +Before pushing, you can run the checks inside the Docker development container: + +```bash +# Static analysis +docker exec -it pteroca_web_dev vendor/bin/phpstan analyse + +# Database migrations +docker exec -it pteroca_web_dev bin/console doctrine:migrations:migrate --no-interaction + +# Translation check (replace messages.pl.yaml with any non-English file) +docker exec -it pteroca_web_dev bin/console app:show-missing-translations \ + src/Core/Resources/translations/messages.en.yaml \ + src/Core/Resources/translations/messages.pl.yaml +``` + +> **Note:** A PR with a failing CI pipeline will not be reviewed. Fix all issues before requesting a review. ## Setting Up Your Development Environment +PteroCA uses Docker for local development. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed. + 1. **Clone the repository**: ```bash git clone https://github.com/pteroca-com/panel.git cd panel ``` -2. **Install dependencies**: - Make sure you have [Composer](https://getcomposer.org/) installed. Run: +2. **Configure environment**: ```bash - composer install + cp .env.SAMPLE .env + # Edit .env with your local settings ``` -3. **Set up the environment**: - Create a `.env.local` file from `.env.example` and configure your database and environment variables: +3. **Start the containers**: ```bash - cp .env.example .env.local + docker-compose up -d ``` 4. **Run migrations**: ```bash - php bin/console doctrine:migrations:migrate + docker exec -it pteroca_web_dev bin/console doctrine:migrations:migrate ``` 5. **Start developing!** +> All Symfony and Composer commands must be executed inside the `pteroca_web_dev` container: +> ```bash +> docker exec -it pteroca_web_dev bin/console [symfony-command] +> docker exec -it pteroca_web_dev composer [composer-command] +> ``` + +For more details, see our [Documentation](https://docs.pteroca.com). ## Helping with Translations -We want PteroCA to be accessible to users all over the world. If you're interested in helping translate PteroCA into more languages, you can contribute via our [Crowdin page](https://crowdin.com/project/pteroca). No coding skills are required—just your language expertise! + +We want PteroCA to be accessible to users all over the world. If you're interested in helping translate PteroCA into more languages, you can contribute directly via a pull request. + +**Important rules for translations:** + +- When **adding new translation keys or editing existing ones**, update all language files located in `src/Core/Resources/translations/`. +- The English file (`messages.en.yaml`) is the reference — make sure the key exists there first, then add the translated value to each other language file. +- The CI pipeline checks for missing translation keys — your PR will fail if any language file is incomplete. + +## Community Guidelines + +- **Be respectful**: We value a welcoming, respectful, and inclusive community. Disrespectful or inappropriate behavior will not be tolerated. +- **Collaboration**: Help fellow contributors by reviewing pull requests and participating in discussions. +- **Support**: For usage or configuration questions, please refer to our [Documentation](https://docs.pteroca.com) and the [Discord Support Server](https://discord.gg/Gz5phhuZym). ## License diff --git a/README.md b/README.md index 6af9e7e0..8392caf1 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,8 @@ We welcome contributions from developers of all skill levels! [Read the Contributing Guide →](CONTRIBUTING.md) +> **Before submitting a PR:** Make sure the CI pipeline passes (PHPStan, migrations, translations) and that your branch is up to date with `main`. All review threads must be resolved before merge. + --- ## 📄 License From e7ca084c739b4cd10e266cf5368fb0c8f929f9a7 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 00:01:25 +0100 Subject: [PATCH 24/34] Permission precheck during plugin upload --- docker-compose.yml | 13 ++++- .../Panel/Setting/PluginCrudController.php | 42 +++++++++++++++ src/Core/Resources/config/services.yaml | 6 +++ .../Resources/translations/messages.en.yaml | 3 ++ .../Plugin/PluginFilesystemCheckService.php | 52 +++++++++++++++++++ .../panel/crud/plugin/upload.html.twig | 10 ++++ 6 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/Core/Service/Plugin/PluginFilesystemCheckService.php diff --git a/docker-compose.yml b/docker-compose.yml index 2acb6a3d..028944a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,8 +51,17 @@ services: db: condition: service_healthy volumes: - - app_var_dev:/app/var - - app_uploads_dev:/app/public/uploads + # Live source code sync - changes on host immediately visible in container + - ./src:/app/src + - ./themes:/app/themes + - ./config:/app/config + - ./migrations:/app/migrations + - ./plugins:/app/plugins + - ./public:/app/public + - ./tests:/app/tests + # Persistent data volumes + - app_var_dev:/app/var # Persist cache/logs across restarts + - app_uploads_dev:/app/public/uploads # Persist user uploads across restarts - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro working_dir: /app diff --git a/src/Core/Controller/Panel/Setting/PluginCrudController.php b/src/Core/Controller/Panel/Setting/PluginCrudController.php index 5b68ab9d..e0528106 100644 --- a/src/Core/Controller/Panel/Setting/PluginCrudController.php +++ b/src/Core/Controller/Panel/Setting/PluginCrudController.php @@ -28,6 +28,7 @@ use App\Core\Event\Plugin\PluginUploadRequestedEvent; use App\Core\Service\Crud\PanelCrudService; use App\Core\Service\Logs\LogService; +use App\Core\Service\Plugin\PluginFilesystemCheckService; use App\Core\Service\Plugin\PluginManager; use App\Core\Service\Plugin\PluginDependencyResolver; use App\Core\Service\Plugin\PluginHealthCheckService; @@ -69,6 +70,7 @@ public function __construct( private readonly PluginHealthCheckService $healthCheckService, private readonly PluginSecurityValidator $securityValidator, private readonly PluginUploadService $pluginUploadService, + private readonly PluginFilesystemCheckService $pluginFilesystemCheckService, ) { parent::__construct($panelCrudService, $requestStack); } @@ -378,6 +380,11 @@ public function enablePlugin(AdminContext $context): RedirectResponse $request = $context->getRequest(); $pluginName = $request->query->get('pluginName'); + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction(Action::INDEX)->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $this->dispatchDataEvent( PluginEnablementRequestedEvent::class, $request, @@ -438,6 +445,11 @@ public function disablePlugin(AdminContext $context): RedirectResponse $request = $context->getRequest(); $pluginName = $request->query->get('pluginName'); + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction(Action::INDEX)->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $this->dispatchDataEvent( PluginDisablementRequestedEvent::class, $request, @@ -502,6 +514,11 @@ public function resetPlugin(AdminContext $context): RedirectResponse throw $this->createAccessDeniedException('You do not have permission to reset plugins.'); } + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction(Action::INDEX)->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $request = $context->getRequest(); $pluginName = $request->query->get('pluginName'); @@ -576,6 +593,11 @@ public function deletePlugin(AdminContext $context): RedirectResponse throw $this->createAccessDeniedException('You do not have permission to delete plugins.'); } + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction(Action::INDEX)->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $request = $context->getRequest(); $pluginName = $request->request->get('pluginName'); @@ -673,10 +695,12 @@ public function uploadPlugin(): Response $this->dispatchSimpleEvent(PluginUploadPageAccessedEvent::class, $request); $form = $this->createForm(PluginUploadFormType::class); + $filesystemIssues = $this->pluginFilesystemCheckService->getUnwritablePaths(); return $this->render('panel/crud/plugin/upload.html.twig', [ 'form' => $form->createView(), 'page_title' => $this->translator->trans('pteroca.plugin.upload.page_title'), + 'filesystem_issues' => $filesystemIssues, ]); } @@ -686,6 +710,11 @@ public function processUpload(AdminContext $context): Response throw $this->createAccessDeniedException('You do not have permission to upload plugins.'); } + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction(Action::INDEX)->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $request = $context->getRequest(); $form = $this->createForm(PluginUploadFormType::class); $form->handleRequest($request); @@ -800,6 +829,19 @@ public function processUpload(AdminContext $context): Response return new RedirectResponse($url); } + private function checkFilesystemPermissions(string $redirectUrl): ?RedirectResponse + { + $unwritable = $this->pluginFilesystemCheckService->getUnwritablePaths(); + if (!empty($unwritable)) { + $this->addFlash('danger', $this->translator->trans( + 'pteroca.plugin.upload.filesystem_permission_error', + ['%paths%' => implode(', ', $unwritable)] + )); + return new RedirectResponse($redirectUrl); + } + return null; + } + /** * Get visible actions for a plugin based on configured actions * This method is exposed to Twig templates for dynamic action rendering diff --git a/src/Core/Resources/config/services.yaml b/src/Core/Resources/config/services.yaml index 67d25b42..095b6f7f 100644 --- a/src/Core/Resources/config/services.yaml +++ b/src/Core/Resources/config/services.yaml @@ -191,6 +191,12 @@ services: $tempDirectory: '%plugin_upload.temp_directory%' $enablePreScan: '%plugin_upload.enable_pre_scan%' + App\Core\Service\Plugin\PluginFilesystemCheckService: + arguments: + $projectDir: '%kernel.project_dir%' + $pluginsDirectory: '%kernel.project_dir%/plugins' + $tempDirectory: '%plugin_upload.temp_directory%' + App\Core\Service\Plugin\EnabledPluginsCacheManager: arguments: $projectDir: '%kernel.project_dir%' diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index fa224fda..b56b5f40 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -1823,6 +1823,9 @@ pteroca: failed: 'Upload failed: %s' security_warnings_detected: 'Warning: Security issues detected. Review before enabling.' invalid_mime_type: 'Please upload a valid ZIP file' + filesystem_permission_error: 'Plugin operation aborted: the following directories are not writable by the web server: %paths%. Fix with: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Filesystem Permission Warning' + filesystem_warning_body: 'Some directories required for plugin operations are not writable. Plugin upload or activation may fail:' widget: plugin_status: diff --git a/src/Core/Service/Plugin/PluginFilesystemCheckService.php b/src/Core/Service/Plugin/PluginFilesystemCheckService.php new file mode 100644 index 00000000..cc3db980 --- /dev/null +++ b/src/Core/Service/Plugin/PluginFilesystemCheckService.php @@ -0,0 +1,52 @@ +getRequiredPaths() as $absolutePath => $relativePath) { + if (!$this->filesystem->exists($absolutePath)) { + try { + $this->filesystem->mkdir($absolutePath, 0755); + } catch (\Exception) { + $unwritable[] = $relativePath; + continue; + } + } + + if (!is_writable($absolutePath)) { + $unwritable[] = $relativePath; + } + } + + return $unwritable; + } + + private function getRequiredPaths(): array + { + return [ + $this->tempDirectory => 'var/tmp/plugin-uploads', + $this->pluginsDirectory => 'plugins/', + $this->projectDir . '/var/cache' => 'var/cache', + $this->projectDir . '/var/log' => 'var/log', + $this->projectDir . '/public/plugins' => 'public/plugins', + ]; + } +} diff --git a/themes/default/panel/crud/plugin/upload.html.twig b/themes/default/panel/crud/plugin/upload.html.twig index f0dd268c..ebf782bc 100644 --- a/themes/default/panel/crud/plugin/upload.html.twig +++ b/themes/default/panel/crud/plugin/upload.html.twig @@ -25,6 +25,16 @@ {% endblock %} {% block main %} + {% if filesystem_issues is defined and filesystem_issues is not empty %} +
+ {{ 'pteroca.plugin.upload.filesystem_warning_title'|trans }} +

{{ 'pteroca.plugin.upload.filesystem_warning_body'|trans }}

+
    + {% for path in filesystem_issues %}
  • {{ path }}
  • {% endfor %} +
+ sudo chown -R www-data:www-data var/ plugins/ public/plugins/ +
+ {% endif %}
From d79bfc6ecbc458db2656166a628e5e25609ebbe1 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 00:12:55 +0100 Subject: [PATCH 25/34] Permission precheck during theme upload --- .../Panel/Setting/ThemeCrudController.php | 33 ++++++++++++ src/Core/Resources/config/services.yaml | 6 +++ .../Resources/translations/messages.en.yaml | 3 ++ .../Theme/ThemeFilesystemCheckService.php | 52 +++++++++++++++++++ .../default/panel/crud/theme/upload.html.twig | 10 ++++ 5 files changed, 104 insertions(+) create mode 100644 src/Core/Service/Theme/ThemeFilesystemCheckService.php diff --git a/src/Core/Controller/Panel/Setting/ThemeCrudController.php b/src/Core/Controller/Panel/Setting/ThemeCrudController.php index 28b661f2..83af01dd 100644 --- a/src/Core/Controller/Panel/Setting/ThemeCrudController.php +++ b/src/Core/Controller/Panel/Setting/ThemeCrudController.php @@ -41,6 +41,7 @@ use App\Core\Service\Template\TemplateService; use App\Core\Service\Template\ThemeCopyService; use App\Core\Service\Template\ThemeExportService; +use App\Core\Service\Theme\ThemeFilesystemCheckService; use App\Core\Service\Theme\ThemeUploadService; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; @@ -65,6 +66,7 @@ public function __construct( private readonly ThemeUploadService $themeUploadService, private readonly ThemeCopyService $themeCopyService, private readonly ThemeExportService $themeExportService, + private readonly ThemeFilesystemCheckService $themeFilesystemCheckService, ) { parent::__construct($panelCrudService, $requestStack); } @@ -313,6 +315,11 @@ public function deleteTheme(AdminContext $context): RedirectResponse throw $this->createAccessDeniedException('You do not have permission to delete themes.'); } + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction('index')->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $request = $context->getRequest(); $themeName = $request->request->get('themeName'); $themeContext = $request->request->get('context', 'panel'); @@ -442,6 +449,11 @@ public function copyTheme(AdminContext $context): RedirectResponse throw $this->createAccessDeniedException('You do not have permission to copy themes.'); } + $indexUrl = $this->adminUrlGenerator->setController(self::class)->setAction('index')->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($indexUrl)) { + return $redirect; + } + $request = $context->getRequest(); $sourceThemeName = $request->request->get('sourceThemeName'); $newThemeName = trim($request->request->get('newThemeName')); @@ -620,6 +632,8 @@ public function uploadTheme(AdminContext $context): Response ->setAction('index') ->generateUrl(); + $filesystemIssues = $this->themeFilesystemCheckService->getUnwritablePaths(); + return $this->renderWithEvent( ViewNameEnum::THEME_UPLOAD, 'panel/crud/theme/upload.html.twig', @@ -627,6 +641,7 @@ public function uploadTheme(AdminContext $context): Response 'form' => $form->createView(), 'page_title' => $this->translator->trans('pteroca.theme.upload.title'), 'back_url' => $backUrl, + 'filesystem_issues' => $filesystemIssues, ], $request ); @@ -639,6 +654,11 @@ public function processUpload(): Response throw $this->createAccessDeniedException('You do not have permission to upload themes.'); } + $uploadUrl = $this->adminUrlGenerator->setRoute('admin_theme_upload')->generateUrl(); + if ($redirect = $this->checkFilesystemPermissions($uploadUrl)) { + return $redirect; + } + $request = $this->requestStack->getCurrentRequest(); $form = $this->createForm(ThemeUploadFormType::class); $form->handleRequest($request); @@ -884,6 +904,19 @@ private function getThemeActions(ThemeDTO $theme): array return $actions; } + private function checkFilesystemPermissions(string $redirectUrl): ?RedirectResponse + { + $unwritable = $this->themeFilesystemCheckService->getUnwritablePaths(); + if (!empty($unwritable)) { + $this->addFlash('danger', $this->translator->trans( + 'pteroca.theme.upload.filesystem_permission_error', + ['%paths%' => implode(', ', $unwritable)] + )); + return new RedirectResponse($redirectUrl); + } + return null; + } + private function deleteDirectory(string $dir): bool { if (!is_dir($dir)) { diff --git a/src/Core/Resources/config/services.yaml b/src/Core/Resources/config/services.yaml index 095b6f7f..9abbb4b9 100644 --- a/src/Core/Resources/config/services.yaml +++ b/src/Core/Resources/config/services.yaml @@ -254,6 +254,12 @@ services: $tempDirectory: '%kernel.project_dir%/var/tmp' $currentPterocaVersion: '%version%' + App\Core\Service\Theme\ThemeFilesystemCheckService: + arguments: + $projectDir: '%kernel.project_dir%' + $themesDirectory: '%kernel.project_dir%/themes' + $tempDirectory: '%kernel.project_dir%/var/tmp' + App\Core\Service\Theme\TemplateManifestValidator: arguments: $currentPterocaVersion: '%version%' diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index b56b5f40..afdfcdc8 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -1911,6 +1911,9 @@ pteroca: invalid_manifest: 'Invalid template.json: %s' compatibility_error: 'Theme is incompatible with current PteroCA version' security_critical: 'Critical security issues detected. Upload blocked for safety.' + filesystem_permission_error: 'Theme operation aborted: the following directories are not writable by the web server: %paths%. Fix with: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Filesystem Permission Warning' + filesystem_warning_body: 'Some directories required for theme operations are not writable. Theme upload or management may fail:' warning: outdated_pteroca_version: 'Theme targets an older PteroCA version' missing_assets: 'No assets directory found - theme uploaded without static assets' diff --git a/src/Core/Service/Theme/ThemeFilesystemCheckService.php b/src/Core/Service/Theme/ThemeFilesystemCheckService.php new file mode 100644 index 00000000..3251ccb4 --- /dev/null +++ b/src/Core/Service/Theme/ThemeFilesystemCheckService.php @@ -0,0 +1,52 @@ +getRequiredPaths() as $absolutePath => $relativePath) { + if (!$this->filesystem->exists($absolutePath)) { + try { + $this->filesystem->mkdir($absolutePath, 0755); + } catch (\Exception) { + $unwritable[] = $relativePath; + continue; + } + } + + if (!is_writable($absolutePath)) { + $unwritable[] = $relativePath; + } + } + + return $unwritable; + } + + private function getRequiredPaths(): array + { + return [ + $this->tempDirectory => 'var/tmp', + $this->themesDirectory => 'themes/', + $this->projectDir . '/var/cache' => 'var/cache', + $this->projectDir . '/var/log' => 'var/log', + $this->projectDir . '/public/assets/theme' => 'public/assets/theme', + ]; + } +} diff --git a/themes/default/panel/crud/theme/upload.html.twig b/themes/default/panel/crud/theme/upload.html.twig index 724870ae..acbdfbb8 100644 --- a/themes/default/panel/crud/theme/upload.html.twig +++ b/themes/default/panel/crud/theme/upload.html.twig @@ -24,6 +24,16 @@ {% endblock %} {% block main %} + {% if filesystem_issues is defined and filesystem_issues is not empty %} +
+ {{ 'pteroca.theme.upload.filesystem_warning_title'|trans }} +

{{ 'pteroca.theme.upload.filesystem_warning_body'|trans }}

+
    + {% for path in filesystem_issues %}
  • {{ path }}
  • {% endfor %} +
+ sudo chown -R www-data:www-data var/ themes/ public/assets/theme/ +
+ {% endif %}
{{ form_start(form, {'action': path('admin_theme_upload_process'), 'method': 'POST'}) }} From 3cf51f06b45aaf9f59fbe6592fce1296947aec6f Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 00:19:09 +0100 Subject: [PATCH 26/34] Added AbstractFilesystemCheckService --- .../AbstractFilesystemCheckService.php | 43 ++++++++++++++++++ .../Plugin/PluginFilesystemCheckService.php | 45 +++++-------------- .../Theme/ThemeFilesystemCheckService.php | 35 +++------------ 3 files changed, 60 insertions(+), 63 deletions(-) create mode 100644 src/Core/Service/AbstractFilesystemCheckService.php diff --git a/src/Core/Service/AbstractFilesystemCheckService.php b/src/Core/Service/AbstractFilesystemCheckService.php new file mode 100644 index 00000000..028792a4 --- /dev/null +++ b/src/Core/Service/AbstractFilesystemCheckService.php @@ -0,0 +1,43 @@ +getRequiredPaths() as $absolutePath => $relativePath) { + if (!$this->filesystem->exists($absolutePath)) { + try { + $this->filesystem->mkdir($absolutePath, 0755); + } catch (\Exception) { + $unwritable[] = $relativePath; + continue; + } + } + + if (!is_writable($absolutePath)) { + $unwritable[] = $relativePath; + } + } + + return $unwritable; + } + + /** + * Returns array of [absolutePath => relativePath] entries to check. + */ + abstract protected function getRequiredPaths(): array; +} diff --git a/src/Core/Service/Plugin/PluginFilesystemCheckService.php b/src/Core/Service/Plugin/PluginFilesystemCheckService.php index cc3db980..a3cf4d33 100644 --- a/src/Core/Service/Plugin/PluginFilesystemCheckService.php +++ b/src/Core/Service/Plugin/PluginFilesystemCheckService.php @@ -2,51 +2,28 @@ namespace App\Core\Service\Plugin; +use App\Core\Service\AbstractFilesystemCheckService; use Symfony\Component\Filesystem\Filesystem; -class PluginFilesystemCheckService +class PluginFilesystemCheckService extends AbstractFilesystemCheckService { public function __construct( private readonly string $projectDir, private readonly string $pluginsDirectory, private readonly string $tempDirectory, - private readonly Filesystem $filesystem, - ) {} - - /** - * Returns list of relative paths that are not writable by the web server. - * Empty array means all required permissions are OK. - */ - public function getUnwritablePaths(): array - { - $unwritable = []; - - foreach ($this->getRequiredPaths() as $absolutePath => $relativePath) { - if (!$this->filesystem->exists($absolutePath)) { - try { - $this->filesystem->mkdir($absolutePath, 0755); - } catch (\Exception) { - $unwritable[] = $relativePath; - continue; - } - } - - if (!is_writable($absolutePath)) { - $unwritable[] = $relativePath; - } - } - - return $unwritable; + Filesystem $filesystem, + ) { + parent::__construct($filesystem); } - private function getRequiredPaths(): array + protected function getRequiredPaths(): array { return [ - $this->tempDirectory => 'var/tmp/plugin-uploads', - $this->pluginsDirectory => 'plugins/', - $this->projectDir . '/var/cache' => 'var/cache', - $this->projectDir . '/var/log' => 'var/log', - $this->projectDir . '/public/plugins' => 'public/plugins', + $this->tempDirectory => 'var/tmp/plugin-uploads', + $this->pluginsDirectory => 'plugins/', + $this->projectDir . '/var/cache' => 'var/cache', + $this->projectDir . '/var/log' => 'var/log', + $this->projectDir . '/public/plugins' => 'public/plugins', ]; } } diff --git a/src/Core/Service/Theme/ThemeFilesystemCheckService.php b/src/Core/Service/Theme/ThemeFilesystemCheckService.php index 3251ccb4..560e669a 100644 --- a/src/Core/Service/Theme/ThemeFilesystemCheckService.php +++ b/src/Core/Service/Theme/ThemeFilesystemCheckService.php @@ -2,44 +2,21 @@ namespace App\Core\Service\Theme; +use App\Core\Service\AbstractFilesystemCheckService; use Symfony\Component\Filesystem\Filesystem; -class ThemeFilesystemCheckService +class ThemeFilesystemCheckService extends AbstractFilesystemCheckService { public function __construct( private readonly string $projectDir, private readonly string $themesDirectory, private readonly string $tempDirectory, - private readonly Filesystem $filesystem, - ) {} - - /** - * Returns list of relative paths that are not writable by the web server. - * Empty array means all required permissions are OK. - */ - public function getUnwritablePaths(): array - { - $unwritable = []; - - foreach ($this->getRequiredPaths() as $absolutePath => $relativePath) { - if (!$this->filesystem->exists($absolutePath)) { - try { - $this->filesystem->mkdir($absolutePath, 0755); - } catch (\Exception) { - $unwritable[] = $relativePath; - continue; - } - } - - if (!is_writable($absolutePath)) { - $unwritable[] = $relativePath; - } - } - - return $unwritable; + Filesystem $filesystem, + ) { + parent::__construct($filesystem); } - private function getRequiredPaths(): array + protected function getRequiredPaths(): array { return [ $this->tempDirectory => 'var/tmp', From 69c03f33e7e81b71267a2da78fe2e7db1819d9d3 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 00:59:17 +0100 Subject: [PATCH 27/34] Translations for permission precheck --- src/Core/Resources/translations/messages.cn.yaml | 6 ++++++ src/Core/Resources/translations/messages.de.yaml | 6 ++++++ src/Core/Resources/translations/messages.de_CH.yaml | 6 ++++++ src/Core/Resources/translations/messages.es.yaml | 6 ++++++ src/Core/Resources/translations/messages.fr.yaml | 6 ++++++ src/Core/Resources/translations/messages.hi.yaml | 6 ++++++ src/Core/Resources/translations/messages.id.yaml | 6 ++++++ src/Core/Resources/translations/messages.it.yaml | 6 ++++++ src/Core/Resources/translations/messages.nl.yaml | 6 ++++++ src/Core/Resources/translations/messages.pl.yaml | 6 ++++++ src/Core/Resources/translations/messages.pt.yaml | 6 ++++++ src/Core/Resources/translations/messages.ru.yaml | 6 ++++++ src/Core/Resources/translations/messages.ua.yaml | 6 ++++++ 13 files changed, 78 insertions(+) diff --git a/src/Core/Resources/translations/messages.cn.yaml b/src/Core/Resources/translations/messages.cn.yaml index e0fdfe66..73a10be5 100644 --- a/src/Core/Resources/translations/messages.cn.yaml +++ b/src/Core/Resources/translations/messages.cn.yaml @@ -1835,6 +1835,9 @@ pteroca: failed: 上传失败:%s security_warnings_detected: 警告:检测到安全问题。启用前请检查。 invalid_mime_type: '请上传有效的 ZIP 文件' + filesystem_permission_error: '插件操作已中止:以下目录对 Web 服务器不可写:%paths%。修复方法:sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: '文件系统权限警告' + filesystem_warning_body: '插件操作所需的某些目录不可写。插件上传或激活可能会失败:' security: title: 插件安全 scan: 安全扫描 @@ -1992,6 +1995,9 @@ pteroca: invalid_manifest: '无效的 template.json:%s' compatibility_error: '主题与当前 PteroCA 版本不兼容' security_critical: 检测到严重安全问题。为安全起见,上传已被阻止。 + filesystem_permission_error: '主题操作已中止:以下目录对 Web 服务器不可写:%paths%。修复方法:sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: '文件系统权限警告' + filesystem_warning_body: '主题操作所需的某些目录不可写。主题上传或管理可能会失败:' warning: outdated_pteroca_version: '主题针对旧版 PteroCA 版本' missing_assets: '未找到资产目录 - 主题已上传但不包含静态资产' diff --git a/src/Core/Resources/translations/messages.de.yaml b/src/Core/Resources/translations/messages.de.yaml index b46e22b2..44487908 100644 --- a/src/Core/Resources/translations/messages.de.yaml +++ b/src/Core/Resources/translations/messages.de.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Hochladen fehlgeschlagen: %s' security_warnings_detected: 'Warnung: Sicherheitsprobleme erkannt. Überprüfen Sie vor der Aktivierung.' invalid_mime_type: 'Bitte laden Sie eine gültige ZIP-Datei hoch' + filesystem_permission_error: 'Plugin-Vorgang abgebrochen: Die folgenden Verzeichnisse sind für den Webserver nicht beschreibbar: %paths%. Beheben mit: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Dateisystem-Berechtigungswarnung' + filesystem_warning_body: 'Einige für Plugin-Vorgänge erforderliche Verzeichnisse sind nicht beschreibbar. Plugin-Upload oder Aktivierung kann fehlschlagen:' security: title: Plugin-Sicherheit scan: Sicherheits-Scan @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'Ungültige template.json: %s' compatibility_error: 'Theme ist mit der aktuellen PteroCA-Version inkompatibel' security_critical: 'Kritische Sicherheitsprobleme erkannt. Upload aus Sicherheitsgründen blockiert.' + filesystem_permission_error: 'Theme-Vorgang abgebrochen: Die folgenden Verzeichnisse sind für den Webserver nicht beschreibbar: %paths%. Beheben mit: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Dateisystem-Berechtigungswarnung' + filesystem_warning_body: 'Einige für Theme-Vorgänge erforderliche Verzeichnisse sind nicht beschreibbar. Theme-Upload oder -Verwaltung kann fehlschlagen:' warning: outdated_pteroca_version: 'Theme zielt auf eine ältere PteroCA-Version ab' missing_assets: 'Kein Assets-Verzeichnis gefunden - Theme ohne statische Assets hochgeladen' diff --git a/src/Core/Resources/translations/messages.de_CH.yaml b/src/Core/Resources/translations/messages.de_CH.yaml index 1bb2d424..6287a216 100644 --- a/src/Core/Resources/translations/messages.de_CH.yaml +++ b/src/Core/Resources/translations/messages.de_CH.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Hochladen fehlgeschlagen: %s' security_warnings_detected: 'Warnung: Sicherheitsprobleme erkannt. Überprüfen Sie vor der Aktivierung.' invalid_mime_type: 'Bitte laden Sie eine gültige ZIP-Datei hoch' + filesystem_permission_error: 'Plugin-Vorgang abgebrochen: Die folgenden Verzeichnisse sind für den Webserver nicht schreibbar: %paths%. Beheben mit: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Dateisystem-Berechtigungswarnung' + filesystem_warning_body: 'Einige für Plugin-Vorgänge erforderliche Verzeichnisse sind nicht schreibbar. Plugin-Upload oder Aktivierung kann fehlschlagen:' security: title: Plugin-Sicherheit scan: Sicherheits-Scan @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'Ungültige template.json: %s' compatibility_error: 'Theme ist mit der aktuellen PteroCA-Version nicht kompatibel' security_critical: 'Kritische Sicherheitsprobleme erkannt. Upload aus Sicherheitsgründen blockiert.' + filesystem_permission_error: 'Theme-Vorgang abgebrochen: Die folgenden Verzeichnisse sind für den Webserver nicht schreibbar: %paths%. Beheben mit: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Dateisystem-Berechtigungswarnung' + filesystem_warning_body: 'Einige für Theme-Vorgänge erforderliche Verzeichnisse sind nicht schreibbar. Theme-Upload oder -Verwaltung kann fehlschlagen:' warning: outdated_pteroca_version: 'Theme zielt auf eine ältere PteroCA-Version ab' missing_assets: 'Kein Assets-Verzeichnis gefunden - Theme ohne statische Assets hochgeladen' diff --git a/src/Core/Resources/translations/messages.es.yaml b/src/Core/Resources/translations/messages.es.yaml index 24cdca5a..a601c1a3 100644 --- a/src/Core/Resources/translations/messages.es.yaml +++ b/src/Core/Resources/translations/messages.es.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Subida fallida: %s' security_warnings_detected: 'Advertencia: Problemas de seguridad detectados. Revisa antes de habilitar.' invalid_mime_type: 'Por favor, sube un archivo ZIP válido' + filesystem_permission_error: 'Operación del plugin abortada: los siguientes directorios no son escribibles por el servidor web: %paths%. Corregir con: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Advertencia de permisos del sistema de archivos' + filesystem_warning_body: 'Algunos directorios necesarios para las operaciones del plugin no son escribibles. La subida o activación del plugin puede fallar:' security: title: 'Seguridad del Plugin' scan: 'Escaneo de Seguridad' @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'template.json inválido: %s' compatibility_error: 'El tema no es compatible con la versión actual de PteroCA' security_critical: 'Se detectaron problemas de seguridad críticos. Subida bloqueada por seguridad.' + filesystem_permission_error: 'Operación del tema abortada: los siguientes directorios no son escribibles por el servidor web: %paths%. Corregir con: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Advertencia de permisos del sistema de archivos' + filesystem_warning_body: 'Algunos directorios necesarios para las operaciones del tema no son escribibles. La subida o gestión del tema puede fallar:' warning: outdated_pteroca_version: 'El tema está diseñado para una versión anterior de PteroCA' missing_assets: 'No se encontró directorio de assets - tema subido sin recursos estáticos' diff --git a/src/Core/Resources/translations/messages.fr.yaml b/src/Core/Resources/translations/messages.fr.yaml index c5f22a6e..a0223108 100644 --- a/src/Core/Resources/translations/messages.fr.yaml +++ b/src/Core/Resources/translations/messages.fr.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Téléchargement échoué : %s' security_warnings_detected: "Avertissement : Problèmes de sécurité détectés. Vérifiez avant d'activer." invalid_mime_type: 'Veuillez télécharger un fichier ZIP valide' + filesystem_permission_error: 'Opération du plugin abandonnée : les répertoires suivants ne sont pas accessibles en écriture par le serveur web : %paths%. Corriger avec : sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Avertissement sur les permissions du système de fichiers' + filesystem_warning_body: "Certains répertoires requis pour les opérations du plugin ne sont pas accessibles en écriture. Le téléchargement ou l'activation du plugin peut échouer :" security: title: 'Sécurité du Plugin' scan: 'Scan de Sécurité' @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'template.json invalide : %s' compatibility_error: 'Le thème est incompatible avec la version actuelle de PteroCA' security_critical: 'Problèmes de sécurité critiques détectés. Téléchargement bloqué pour des raisons de sécurité.' + filesystem_permission_error: 'Opération du thème abandonnée : les répertoires suivants ne sont pas accessibles en écriture par le serveur web : %paths%. Corriger avec : sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Avertissement sur les permissions du système de fichiers' + filesystem_warning_body: 'Certains répertoires requis pour les opérations du thème ne sont pas accessibles en écriture. Le téléchargement ou la gestion du thème peut échouer :' warning: outdated_pteroca_version: 'Le thème cible une version plus ancienne de PteroCA' missing_assets: "Aucun répertoire d'assets trouvé - thème téléchargé sans ressources statiques" diff --git a/src/Core/Resources/translations/messages.hi.yaml b/src/Core/Resources/translations/messages.hi.yaml index 01527450..74592305 100644 --- a/src/Core/Resources/translations/messages.hi.yaml +++ b/src/Core/Resources/translations/messages.hi.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'अपलोड विफल: %s' security_warnings_detected: 'चेतावनी: सुरक्षा समस्याएँ पाई गईं। सक्षम करने से पहले समीक्षा करें।' invalid_mime_type: 'कृपया एक वैध ZIP फ़ाइल अपलोड करें' + filesystem_permission_error: 'प्लगइन ऑपरेशन रद्द: निम्नलिखित निर्देशिकाएँ वेब सर्वर द्वारा लिखने योग्य नहीं हैं: %paths%। ठीक करने के लिए: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'फाइलसिस्टम अनुमति चेतावनी' + filesystem_warning_body: 'प्लगइन ऑपरेशन के लिए आवश्यक कुछ निर्देशिकाएँ लिखने योग्य नहीं हैं। प्लगइन अपलोड या सक्रियण विफल हो सकता है:' security: title: 'प्लगइन सुरक्षा' scan: 'सुरक्षा स्कैन' @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'अमान्य template.json: %s' compatibility_error: 'थीम वर्तमान PteroCA संस्करण के साथ असंगत है' security_critical: 'गंभीर सुरक्षा समस्याएं मिलीं। सुरक्षा के लिए अपलोड अवरुद्ध किया गया।' + filesystem_permission_error: 'थीम ऑपरेशन रद्द: निम्नलिखित निर्देशिकाएँ वेब सर्वर द्वारा लिखने योग्य नहीं हैं: %paths%। ठीक करने के लिए: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'फाइलसिस्टम अनुमति चेतावनी' + filesystem_warning_body: 'थीम ऑपरेशन के लिए आवश्यक कुछ निर्देशिकाएँ लिखने योग्य नहीं हैं। थीम अपलोड या प्रबंधन विफल हो सकता है:' warning: outdated_pteroca_version: 'थीम पुराने PteroCA संस्करण को लक्षित करती है' missing_assets: 'कोई एसेट निर्देशिका नहीं मिली - थीम स्थिर एसेट के बिना अपलोड की गई' diff --git a/src/Core/Resources/translations/messages.id.yaml b/src/Core/Resources/translations/messages.id.yaml index 2b1626e5..9d7cb346 100644 --- a/src/Core/Resources/translations/messages.id.yaml +++ b/src/Core/Resources/translations/messages.id.yaml @@ -1837,6 +1837,9 @@ pteroca: failed: 'Unggah gagal: %s' security_warnings_detected: 'Peringatan: Masalah keamanan terdeteksi. Tinjau sebelum mengaktifkan.' invalid_mime_type: 'Silakan unggah file ZIP yang valid' + filesystem_permission_error: 'Operasi plugin dibatalkan: direktori berikut tidak dapat ditulis oleh server web: %paths%. Perbaiki dengan: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Peringatan Izin Sistem File' + filesystem_warning_body: 'Beberapa direktori yang diperlukan untuk operasi plugin tidak dapat ditulis. Pengunggahan atau aktivasi plugin mungkin gagal:' widget: plugin_status: title: 'Status Plugin' @@ -1922,6 +1925,9 @@ pteroca: invalid_manifest: 'template.json tidak valid: %s' compatibility_error: 'Tema tidak kompatibel dengan versi PteroCA saat ini' security_critical: 'Masalah keamanan kritis terdeteksi. Unggahan diblokir untuk keamanan.' + filesystem_permission_error: 'Operasi tema dibatalkan: direktori berikut tidak dapat ditulis oleh server web: %paths%. Perbaiki dengan: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Peringatan Izin Sistem File' + filesystem_warning_body: 'Beberapa direktori yang diperlukan untuk operasi tema tidak dapat ditulis. Pengunggahan atau pengelolaan tema mungkin gagal:' warning: outdated_pteroca_version: 'Tema menargetkan versi PteroCA yang lebih lama' missing_assets: 'Direktori aset tidak ditemukan - tema diunggah tanpa aset statis' diff --git a/src/Core/Resources/translations/messages.it.yaml b/src/Core/Resources/translations/messages.it.yaml index e81c5a76..4a31437b 100644 --- a/src/Core/Resources/translations/messages.it.yaml +++ b/src/Core/Resources/translations/messages.it.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Caricamento fallito: %s' security_warnings_detected: 'Avvertimento: Problemi di sicurezza rilevati. Rivedi prima di abilitare.' invalid_mime_type: 'Carica un file ZIP valido' + filesystem_permission_error: 'Operazione plugin interrotta: le seguenti directory non sono scrivibili dal server web: %paths%. Correggere con: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Avviso di autorizzazione del filesystem' + filesystem_warning_body: "Alcune directory richieste per le operazioni del plugin non sono scrivibili. Il caricamento o l'attivazione del plugin potrebbe fallire:" security: title: 'Sicurezza Plugin' scan: 'Scansione Sicurezza' @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'template.json non valido: %s' compatibility_error: 'Il tema non è compatibile con la versione attuale di PteroCA' security_critical: 'Rilevati problemi di sicurezza critici. Caricamento bloccato per sicurezza.' + filesystem_permission_error: 'Operazione tema interrotta: le seguenti directory non sono scrivibili dal server web: %paths%. Correggere con: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Avviso di autorizzazione del filesystem' + filesystem_warning_body: 'Alcune directory richieste per le operazioni del tema non sono scrivibili. Il caricamento o la gestione del tema potrebbe fallire:' warning: outdated_pteroca_version: 'Il tema è destinato a una versione precedente di PteroCA' missing_assets: 'Directory assets non trovata - tema caricato senza risorse statiche' diff --git a/src/Core/Resources/translations/messages.nl.yaml b/src/Core/Resources/translations/messages.nl.yaml index 22aeec96..a3bc3968 100644 --- a/src/Core/Resources/translations/messages.nl.yaml +++ b/src/Core/Resources/translations/messages.nl.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Upload mislukt: %s' security_warnings_detected: 'Waarschuwing: Beveiligingsproblemen gedetecteerd. Controleer voordat u inschakelt.' invalid_mime_type: 'Upload een geldig ZIP-bestand' + filesystem_permission_error: 'Plugin-bewerking afgebroken: de volgende mappen zijn niet beschrijfbaar door de webserver: %paths%. Oplossen met: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Bestandssysteem-machtigingswaarschuwing' + filesystem_warning_body: 'Sommige mappen die vereist zijn voor plugin-bewerkingen zijn niet beschrijfbaar. Plugin-upload of activering kan mislukken:' security: title: 'Plugin Beveiliging' scan: Beveiligingsscan @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'Ongeldige template.json: %s' compatibility_error: 'Thema is niet compatibel met de huidige PteroCA-versie' security_critical: 'Kritieke beveiligingsproblemen gedetecteerd. Upload geblokkeerd voor veiligheid.' + filesystem_permission_error: 'Thema-bewerking afgebroken: de volgende mappen zijn niet beschrijfbaar door de webserver: %paths%. Oplossen met: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Bestandssysteem-machtigingswaarschuwing' + filesystem_warning_body: 'Sommige mappen die vereist zijn voor thema-bewerkingen zijn niet beschrijfbaar. Thema-upload of beheer kan mislukken:' warning: outdated_pteroca_version: 'Thema is gericht op een oudere PteroCA-versie' missing_assets: 'Geen assets-map gevonden - thema geüpload zonder statische assets' diff --git a/src/Core/Resources/translations/messages.pl.yaml b/src/Core/Resources/translations/messages.pl.yaml index 35184117..f094c6a7 100644 --- a/src/Core/Resources/translations/messages.pl.yaml +++ b/src/Core/Resources/translations/messages.pl.yaml @@ -1815,6 +1815,9 @@ pteroca: failed: 'Przesłanie nie powiodło się: %s' security_warnings_detected: 'Ostrzeżenie: Wykryto problemy bezpieczeństwa. Przejrzyj przed włączeniem.' invalid_mime_type: 'Prześlij prawidłowy plik ZIP' + filesystem_permission_error: 'Operacja wtyczki przerwana: następujące katalogi nie są zapisywalne przez serwer WWW: %paths%. Napraw za pomocą: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Ostrzeżenie o uprawnieniach systemu plików' + filesystem_warning_body: 'Niektóre katalogi wymagane do operacji na wtyczkach nie są zapisywalne. Przesyłanie lub aktywacja wtyczki może się nie powieść:' security: title: 'Bezpieczeństwo Wtyczki' scan: 'Skan Bezpieczeństwa' @@ -1972,6 +1975,9 @@ pteroca: invalid_manifest: 'Nieprawidłowy template.json: %s' compatibility_error: 'Motyw jest niekompatybilny z aktualną wersją PteroCA' security_critical: 'Wykryto krytyczne problemy bezpieczeństwa. Przesyłanie zablokowane dla bezpieczeństwa.' + filesystem_permission_error: 'Operacja motywu przerwana: następujące katalogi nie są zapisywalne przez serwer WWW: %paths%. Napraw za pomocą: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Ostrzeżenie o uprawnieniach systemu plików' + filesystem_warning_body: 'Niektóre katalogi wymagane do operacji na motywach nie są zapisywalne. Przesyłanie lub zarządzanie motywem może się nie powieść:' warning: outdated_pteroca_version: 'Motyw jest przeznaczony dla starszej wersji PteroCA' missing_assets: 'Nie znaleziono katalogu zasobów - motyw przesłany bez zasobów statycznych' diff --git a/src/Core/Resources/translations/messages.pt.yaml b/src/Core/Resources/translations/messages.pt.yaml index a0e0a447..31ca21da 100644 --- a/src/Core/Resources/translations/messages.pt.yaml +++ b/src/Core/Resources/translations/messages.pt.yaml @@ -1817,6 +1817,9 @@ pteroca: failed: 'Carregamento falhou: %s' security_warnings_detected: 'Aviso: Problemas de segurança detectados. Revise antes de habilitar.' invalid_mime_type: 'Carregue um arquivo ZIP válido' + filesystem_permission_error: 'Operação do plugin abortada: os seguintes diretórios não são graváveis pelo servidor web: %paths%. Corrigir com: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Aviso de permissão do sistema de arquivos' + filesystem_warning_body: 'Alguns diretórios necessários para as operações do plugin não são graváveis. O upload ou ativação do plugin pode falhar:' security: title: 'Segurança do Plugin' scan: 'Verificação de Segurança' @@ -1974,6 +1977,9 @@ pteroca: invalid_manifest: 'template.json inválido: %s' compatibility_error: 'O tema é incompatível com a versão atual do PteroCA' security_critical: 'Problemas críticos de segurança detectados. Upload bloqueado por segurança.' + filesystem_permission_error: 'Operação do tema abortada: os seguintes diretórios não são graváveis pelo servidor web: %paths%. Corrigir com: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Aviso de permissão do sistema de arquivos' + filesystem_warning_body: 'Alguns diretórios necessários para as operações do tema não são graváveis. O upload ou gerenciamento do tema pode falhar:' warning: outdated_pteroca_version: 'O tema é direcionado para uma versão anterior do PteroCA' missing_assets: 'Nenhum diretório de assets encontrado - tema carregado sem assets estáticos' diff --git a/src/Core/Resources/translations/messages.ru.yaml b/src/Core/Resources/translations/messages.ru.yaml index 467afc66..fabfef6b 100644 --- a/src/Core/Resources/translations/messages.ru.yaml +++ b/src/Core/Resources/translations/messages.ru.yaml @@ -1849,6 +1849,9 @@ pteroca: failed: 'Загрузка не удалась: %s' security_warnings_detected: 'Предупреждение: Обнаружены проблемы безопасности. Проверьте перед включением.' invalid_mime_type: 'Загрузите допустимый ZIP-файл' + filesystem_permission_error: 'Операция плагина прервана: следующие директории недоступны для записи веб-сервером: %paths%. Исправить командой: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Предупреждение о правах доступа к файловой системе' + filesystem_warning_body: 'Некоторые директории, необходимые для работы плагинов, недоступны для записи. Загрузка или активация плагина может завершиться ошибкой:' security: title: 'Безопасность Плагина' scan: 'Сканирование Безопасности' @@ -2006,6 +2009,9 @@ pteroca: invalid_manifest: 'Недопустимый template.json: %s' compatibility_error: 'Тема несовместима с текущей версией PteroCA' security_critical: 'Обнаружены критические проблемы безопасности. Загрузка заблокирована для безопасности.' + filesystem_permission_error: 'Операция темы прервана: следующие директории недоступны для записи веб-сервером: %paths%. Исправить командой: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Предупреждение о правах доступа к файловой системе' + filesystem_warning_body: 'Некоторые директории, необходимые для работы с темами, недоступны для записи. Загрузка или управление темой может завершиться ошибкой:' warning: outdated_pteroca_version: 'Тема предназначена для более старой версии PteroCA' missing_assets: 'Каталог ассетов не найден - тема загружена без статических ассетов' diff --git a/src/Core/Resources/translations/messages.ua.yaml b/src/Core/Resources/translations/messages.ua.yaml index d11dc82a..064b33f1 100644 --- a/src/Core/Resources/translations/messages.ua.yaml +++ b/src/Core/Resources/translations/messages.ua.yaml @@ -1849,6 +1849,9 @@ pteroca: failed: 'Завантаження не вдалося: %s' security_warnings_detected: 'Попередження: Виявлено проблеми безпеки. Перевірте перед увімкненням.' invalid_mime_type: 'Завантажте допустимий ZIP-файл' + filesystem_permission_error: 'Операцію плагіна перервано: наступні директорії недоступні для запису веб-сервером: %paths%. Виправити командою: sudo chown -R www-data:www-data var/ plugins/ public/plugins/' + filesystem_warning_title: 'Попередження про права доступу до файлової системи' + filesystem_warning_body: 'Деякі директорії, необхідні для роботи плагінів, недоступні для запису. Завантаження або активація плагіна може завершитися помилкою:' security: title: 'Безпека Плагіна' scan: 'Сканування Безпеки' @@ -2006,6 +2009,9 @@ pteroca: invalid_manifest: 'Невірний template.json: %s' compatibility_error: 'Тема несумісна з поточною версією PteroCA' security_critical: 'Виявлено критичні проблеми безпеки. Завантаження заблоковано для безпеки.' + filesystem_permission_error: 'Операцію теми перервано: наступні директорії недоступні для запису веб-сервером: %paths%. Виправити командою: sudo chown -R www-data:www-data var/ themes/ public/assets/theme/' + filesystem_warning_title: 'Попередження про права доступу до файлової системи' + filesystem_warning_body: 'Деякі директорії, необхідні для роботи з темами, недоступні для запису. Завантаження або керування темою може завершитися помилкою:' warning: outdated_pteroca_version: 'Тема призначена для старішої версії PteroCA' missing_assets: 'Каталог ресурсів не знайдено - тему завантажено без статичних ресурсів' From 9a106bee4719ee611c5a1835c0371aebab33bc74 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 01:16:42 +0100 Subject: [PATCH 28/34] Added mailhog for dev environment --- docker-compose.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 028944a8..97add550 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,8 +34,16 @@ services: depends_on: - db + mailhog: + image: mailhog/mailhog + container_name: pteroca_mailhog_dev + ports: + - "1025:1025" # SMTP + - "8025:8025" # Web UI + restart: unless-stopped + web: - build: + build: context: . args: APP_ENV: dev @@ -50,6 +58,8 @@ services: depends_on: db: condition: service_healthy + mailhog: + condition: service_started volumes: # Live source code sync - changes on host immediately visible in container - ./src:/app/src From d421d888f47bfd46413797d7fbe18c0d4326eaae Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 01:18:05 +0100 Subject: [PATCH 29/34] Email when admin creates server --- .../Panel/ServerProductCrudController.php | 22 ++++++- src/Core/Enum/EmailTypeEnum.php | 1 + src/Core/Resources/config/services.yaml | 1 + .../Resources/translations/messages.en.yaml | 14 +++++ .../AdminServerCreationEmailService.php | 60 +++++++++++++++++++ src/Core/Service/Mailer/MailerService.php | 21 +++++-- .../email/admin_server_creation.html.twig | 41 +++++++++++++ .../default/email/components/alert.html.twig | 4 +- 8 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 src/Core/Service/Mailer/AdminServerCreationEmailService.php create mode 100644 themes/default/email/admin_server_creation.html.twig diff --git a/src/Core/Controller/Panel/ServerProductCrudController.php b/src/Core/Controller/Panel/ServerProductCrudController.php index ed3d206e..fea8fdff 100644 --- a/src/Core/Controller/Panel/ServerProductCrudController.php +++ b/src/Core/Controller/Panel/ServerProductCrudController.php @@ -18,6 +18,7 @@ use App\Core\Service\Product\NestEggsCacheService; use App\Core\Service\Pterodactyl\PterodactylApplicationService; use App\Core\Service\Pterodactyl\PterodactylRedirectService; +use App\Core\Service\Mailer\AdminServerCreationEmailService; use App\Core\Service\Server\AdminServerCreationService; use App\Core\Service\Server\DeleteServerService; use App\Core\Service\Server\UpdateServerService; @@ -75,6 +76,7 @@ public function __construct( private readonly UserRepository $userRepository, private readonly ProductRepository $productRepository, private readonly AdminServerCreationService $adminServerCreationService, + private readonly AdminServerCreationEmailService $adminServerCreationEmailService, ) { parent::__construct($panelCrudService, $requestStack); } @@ -153,6 +155,13 @@ public function configureFields(string $pageName): iterable ->setFormTypeOption('mapped', false) ->hideWhenUpdating(), + BooleanField::new('sendCreationEmail', $this->translator->trans('pteroca.admin.server_create.send_creation_email')) + ->onlyOnForms() + ->setColumns(12) + ->setHelp($this->translator->trans('pteroca.admin.server_create.send_creation_email_help')) + ->setFormTypeOptions(['mapped' => false, 'data' => true]) + ->hideWhenUpdating(), + IdField::new('server.id') ->hideOnForm() ->setColumns(3), @@ -389,7 +398,9 @@ private function createNewServer(ServerProduct $serverProduct): void $startingEggId = $this->extractStartingEggId($formData); - $this->adminServerCreationService->createServerForUser( + $isFreeServer = isset($formData['freeServer']) && $formData['freeServer']; + + $server = $this->adminServerCreationService->createServerForUser( user: $user, serverProduct: $serverProduct, serverName: $formData['newServerName'] ?? 'New Server', @@ -397,10 +408,17 @@ private function createNewServer(ServerProduct $serverProduct): void autoRenewal: isset($formData['newServerAutoRenewal']) && $formData['newServerAutoRenewal'], isSuspended: isset($formData['newServerIsSuspended']) && $formData['newServerIsSuspended'], eggId: $startingEggId, - freeServer: isset($formData['freeServer']) && $formData['freeServer'], + freeServer: $isFreeServer, createdByAdmin: $this->getUser() ); + $shouldNotify = isset($formData['sendCreationEmail']) && $formData['sendCreationEmail']; + if ($shouldNotify) { + $this->adminServerCreationEmailService->sendAdminServerCreationEmail( + $user, $server, $serverProduct, $isFreeServer + ); + } + $this->addFlash('success', $this->translator->trans( 'pteroca.admin.server_create.success', ['%user%' => $user->getEmail()] diff --git a/src/Core/Enum/EmailTypeEnum.php b/src/Core/Enum/EmailTypeEnum.php index 56384683..a43cc81c 100644 --- a/src/Core/Enum/EmailTypeEnum.php +++ b/src/Core/Enum/EmailTypeEnum.php @@ -11,4 +11,5 @@ enum EmailTypeEnum: string case RENEW_PRODUCT = 'renew_product'; case RESET_PASSWORD = 'reset_password'; case SERVER_SUSPENDED = 'server_suspended'; + case ADMIN_SERVER_CREATED = 'admin_server_created'; } diff --git a/src/Core/Resources/config/services.yaml b/src/Core/Resources/config/services.yaml index 9abbb4b9..6fa7d1e4 100644 --- a/src/Core/Resources/config/services.yaml +++ b/src/Core/Resources/config/services.yaml @@ -45,6 +45,7 @@ services: $twig: '@twig' $defaultLogoPath: '%default_logo_path%' $projectDir: '%kernel.project_dir%' + $appEnv: '%kernel.environment%' App\Core\Twig\AppExtension: tags: [ 'twig.extension' ] diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index afdfcdc8..ce9b133d 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -749,6 +749,7 @@ pteroca: renew_product: 'Product renewal' reset_password: 'Password reset' server_suspended: 'Server suspension' + admin_server_created: 'Server created by admin' crud: category: @@ -1567,6 +1568,17 @@ pteroca: auto_delete_title: 'Auto-Deletion Warning' auto_delete_warning: 'Important: Your server will be automatically deleted after %days% days (%deleteDate%) if not renewed.' panel_access: 'Access Panel' + admin_server_created: + subject: 'Your server has been created' + title: 'Server Created' + subtitle: 'A server has been set up for your account by an administrator' + server_details: 'Server Details' + server_name: 'Server Name' + product_name: 'Product' + expires_at: 'Expires At' + panel_url: 'Panel URL' + access_panel: 'Go to Panel' + note: 'This server was created for you by an administrator. If you have questions, please contact support.' verification: title: 'Email Verification Required' @@ -1618,6 +1630,8 @@ pteroca: base_product_help: 'Optionally select a product template to pre-fill configuration values' free_server: 'Free Server (do not charge user balance)' free_server_help: 'Enable this to create server without deducting from user balance' + send_creation_email: 'Notify user via email' + send_creation_email_help: 'Send an email to the user with information about the created server' success: 'Server created successfully for user %user%' eggs_required: 'At least one egg must be selected' use_as_starting_egg: 'Use as starting egg' diff --git a/src/Core/Service/Mailer/AdminServerCreationEmailService.php b/src/Core/Service/Mailer/AdminServerCreationEmailService.php new file mode 100644 index 00000000..28ee9cd6 --- /dev/null +++ b/src/Core/Service/Mailer/AdminServerCreationEmailService.php @@ -0,0 +1,60 @@ + $user, + 'server' => ['name' => $server->getName(), 'expiresAt' => $server->getExpiresAt()], + 'product' => ['name' => $serverProduct->getName()], + 'panel' => ['url' => $this->settingService->getSetting(SettingEnum::SITE_URL->value)], + 'isFreeServer' => $isFreeServer, + ]; + + $subject = $this->translator->trans('pteroca.email.admin_server_created.subject'); + + $this->messageBus->dispatch(new SendEmailMessage( + $user->getEmail(), + $subject, + 'email/admin_server_creation.html.twig', + $context + )); + + $this->emailNotificationService->logEmailSent( + $user, + EmailTypeEnum::ADMIN_SERVER_CREATED, + $server, + $subject, + [ + 'server_name' => $server->getName(), + 'expires_at' => $server->getExpiresAt()->format('Y-m-d H:i:s'), + 'is_free_server' => $isFreeServer, + ] + ); + } +} diff --git a/src/Core/Service/Mailer/MailerService.php b/src/Core/Service/Mailer/MailerService.php index 06d80e31..3dc0bfff 100644 --- a/src/Core/Service/Mailer/MailerService.php +++ b/src/Core/Service/Mailer/MailerService.php @@ -20,6 +20,9 @@ class MailerService implements MailerServiceInterface { + private const DEV_MAILHOG_DSN = 'smtp://mailhog:1025'; + private const DEV_MAILHOG_FROM = 'noreply@pteroca.local'; + private MailerInterface $mailer; private string $from = ''; @@ -31,6 +34,7 @@ public function __construct( private readonly EventDispatcherInterface $eventDispatcher, private readonly RequestStack $requestStack, private readonly string $projectDir, + private readonly string $appEnv = 'prod', ) {} /** @@ -111,13 +115,18 @@ public function sendEmail(string $to, string $subject, string $template, array $ private function setMailer(): void { - $smtpServer = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_SERVER->value); - $smtpPort = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_PORT->value); - $smtpUsername = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_USERNAME->value); - $smtpPassword = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_PASSWORD->value); - $this->from = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_FROM->value); + if ($this->appEnv === 'dev') { + $this->from = self::DEV_MAILHOG_FROM; + $dsn = self::DEV_MAILHOG_DSN; + } else { + $smtpServer = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_SERVER->value); + $smtpPort = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_PORT->value); + $smtpUsername = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_USERNAME->value); + $smtpPassword = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_PASSWORD->value); + $this->from = $this->settingsService->getSetting(SettingEnum::EMAIL_SMTP_FROM->value); + $dsn = sprintf('smtp://%s:%s@%s:%d', $smtpUsername, $smtpPassword, $smtpServer, $smtpPort); + } - $dsn = sprintf('smtp://%s:%s@%s:%d', $smtpUsername, $smtpPassword, $smtpServer, $smtpPort); $transport = Transport::fromDsn($dsn); $this->mailer = new Mailer($transport); } diff --git a/themes/default/email/admin_server_creation.html.twig b/themes/default/email/admin_server_creation.html.twig new file mode 100644 index 00000000..e39523e9 --- /dev/null +++ b/themes/default/email/admin_server_creation.html.twig @@ -0,0 +1,41 @@ +{% extends 'email/layout/base.html.twig' %} +{% trans_default_domain 'messages' %} + +{% set emailTitle = 'pteroca.email.admin_server_created.subject'|trans %} + +{% block content %} + {% include 'email/components/icon_section.html.twig' with { + icon: '🖥', + iconColor: '#3b82f6', + title: 'pteroca.email.admin_server_created.title'|trans, + message: 'pteroca.email.admin_server_created.subtitle'|trans + } %} + + {% include 'email/components/alert.html.twig' with { + type: 'success', + message: '' ~ server.name ~ '' + } %} + + {% include 'email/components/divider.html.twig' %} + +

+ {{ 'pteroca.email.admin_server_created.server_details'|trans }} +

+ {% include 'email/components/table.html.twig' with { + rows: [ + {label: 'pteroca.email.admin_server_created.server_name'|trans, value: '' ~ server.name ~ ''}, + {label: 'pteroca.email.admin_server_created.product_name'|trans, value: product.name}, + {label: 'pteroca.email.admin_server_created.expires_at'|trans, value: server.expiresAt|app_date}, + {label: 'pteroca.email.admin_server_created.panel_url'|trans, value: '' ~ panel.url ~ ''} + ] + } %} + + {% include 'email/components/button.html.twig' with { + url: panel.url, + text: 'pteroca.email.admin_server_created.access_panel'|trans + } %} + +

+ {{ 'pteroca.email.admin_server_created.note'|trans }} +

+{% endblock %} diff --git a/themes/default/email/components/alert.html.twig b/themes/default/email/components/alert.html.twig index dbe3d364..f8ecb366 100644 --- a/themes/default/email/components/alert.html.twig +++ b/themes/default/email/components/alert.html.twig @@ -43,11 +43,11 @@ {% if title is defined and title %}

- {{ title }} + {{ title }}

{% endif %}

- {{ message|raw }} + {{ message|raw }}

From d60aa36c0c8eed33dd9f7bda5e805c5cf6ea3faf Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 01:48:39 +0100 Subject: [PATCH 30/34] Translations for admin server creation email --- src/Core/Resources/translations/messages.cn.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.de.yaml | 14 ++++++++++++++ .../Resources/translations/messages.de_CH.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.es.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.fr.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.hi.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.id.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.it.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.nl.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.pl.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.pt.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.ru.yaml | 14 ++++++++++++++ src/Core/Resources/translations/messages.ua.yaml | 14 ++++++++++++++ 13 files changed, 182 insertions(+) diff --git a/src/Core/Resources/translations/messages.cn.yaml b/src/Core/Resources/translations/messages.cn.yaml index 73a10be5..b53d8b11 100644 --- a/src/Core/Resources/translations/messages.cn.yaml +++ b/src/Core/Resources/translations/messages.cn.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 产品续订 reset_password: 密码重置 server_suspended: 服务器暂停 + admin_server_created: 管理员创建的服务器 crud: category: name: 类别名称 @@ -1585,6 +1586,17 @@ pteroca: auto_delete_title: 自动删除警告 auto_delete_warning: '重要提示:如果未续费,您的服务器将在 %days% 天后(%deleteDate%)自动删除。' panel_access: 访问面板 + admin_server_created: + subject: 您的服务器已创建 + title: 服务器已创建 + subtitle: 管理员已为您的帐户创建了一台服务器 + server_details: 服务器详情 + server_name: 服务器名称 + product_name: 产品 + expires_at: 到期时间 + panel_url: 面板地址 + access_panel: 前往面板 + note: 此服务器由管理员为您创建。如有疑问,请联系支持团队。 verification: title: 需要电子邮件验证 subtitle: 请验证您的电子邮件地址以继续 @@ -1634,6 +1646,8 @@ pteroca: base_product_help: 可选选择产品模板以预填充配置值 free_server: 免费服务器(不从用户余额中扣除) free_server_help: 启用此项以创建服务器而不从用户余额中扣除 + send_creation_email: 通过电子邮件通知用户 + send_creation_email_help: 向用户发送有关已创建服务器信息的电子邮件 success: '为用户 %user% 成功创建服务器' eggs_required: 必须至少选择一个蛋 use_as_starting_egg: 用作起始蛋 diff --git a/src/Core/Resources/translations/messages.de.yaml b/src/Core/Resources/translations/messages.de.yaml index 44487908..ac83a844 100644 --- a/src/Core/Resources/translations/messages.de.yaml +++ b/src/Core/Resources/translations/messages.de.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: Produktverlängerung reset_password: 'Passwort zurücksetzen' server_suspended: Server-Suspension + admin_server_created: 'Vom Administrator erstellter Server' crud: category: name: Kategoriename @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Automatische Löschung Warnung' auto_delete_warning: 'Wichtig: Ihr Server wird automatisch nach %days% Tagen (%deleteDate%) gelöscht, falls nicht verlängert.' panel_access: 'Zugang zum Panel' + admin_server_created: + subject: 'Ihr Server wurde erstellt' + title: 'Server erstellt' + subtitle: 'Ein Administrator hat einen Server für Ihr Konto eingerichtet' + server_details: 'Serverdetails' + server_name: 'Servername' + product_name: 'Produkt' + expires_at: 'Läuft ab am' + panel_url: 'Panel-URL' + access_panel: 'Zum Panel gehen' + note: 'Dieser Server wurde von einem Administrator für Sie erstellt. Bei Fragen wenden Sie sich bitte an den Support.' verification: title: 'E-Mail-Verifizierung erforderlich' subtitle: 'Bitte verifizieren Sie Ihre E-Mail-Adresse, um fortzufahren' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Wählen Sie optional eine Produktvorlage, um Konfigurationswerte vorab auszufüllen' free_server: 'Kostenloser Server (Benutzerguthaben nicht belasten)' free_server_help: 'Aktivieren Sie dies, um einen Server zu erstellen, ohne vom Benutzerguthaben abzubuchen' + send_creation_email: 'Benutzer per E-Mail benachrichtigen' + send_creation_email_help: 'Eine E-Mail mit Informationen über den erstellten Server an den Benutzer senden' success: 'Server erfolgreich für Benutzer %user% erstellt' eggs_required: 'Mindestens ein Ei muss ausgewählt werden' use_as_starting_egg: 'Als Start-Ei verwenden' diff --git a/src/Core/Resources/translations/messages.de_CH.yaml b/src/Core/Resources/translations/messages.de_CH.yaml index 6287a216..911ee115 100644 --- a/src/Core/Resources/translations/messages.de_CH.yaml +++ b/src/Core/Resources/translations/messages.de_CH.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: Produktverlängerung reset_password: 'Passwort zurücksetzen' server_suspended: Server-Suspension + admin_server_created: 'Vom Administrator erstellter Server' crud: category: name: Kategoriename @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Automatische Löschung Warnung' auto_delete_warning: 'Wichtig: Ihr Server wird automatisch nach %days% Tagen (%deleteDate%) gelöscht, falls nicht erneuert.' panel_access: 'Zugang zum Panel' + admin_server_created: + subject: 'Ihr Server wurde erstellt' + title: 'Server erstellt' + subtitle: 'Ein Administrator hat einen Server für Ihr Konto eingerichtet' + server_details: 'Serverdetails' + server_name: 'Servername' + product_name: 'Produkt' + expires_at: 'Läuft ab am' + panel_url: 'Panel-URL' + access_panel: 'Zum Panel gehen' + note: 'Dieser Server wurde von einem Administrator für Sie erstellt. Bei Fragen wenden Sie sich bitte an den Support.' verification: title: 'E-Mail-Verifizierung erforderlich' subtitle: 'Bitte verifizieren Sie Ihre E-Mail-Adresse, um fortzufahren' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Wählen Sie optional eine Produktvorlage, um Konfigurationswerte vorab auszufüllen' free_server: 'Kostenloser Server (Benutzerguthaben nicht belasten)' free_server_help: 'Aktivieren Sie dies, um einen Server zu erstellen, ohne vom Benutzerguthaben abzubuchen' + send_creation_email: 'Benutzer per E-Mail benachrichtigen' + send_creation_email_help: 'Eine E-Mail mit Informationen über den erstellten Server an den Benutzer senden' success: 'Server erfolgreich für Benutzer %user% erstellt' eggs_required: 'Mindestens ein Ei muss ausgewählt werden' use_as_starting_egg: 'Als Start-Ei verwenden' diff --git a/src/Core/Resources/translations/messages.es.yaml b/src/Core/Resources/translations/messages.es.yaml index a601c1a3..28219176 100644 --- a/src/Core/Resources/translations/messages.es.yaml +++ b/src/Core/Resources/translations/messages.es.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 'Renovación de producto' reset_password: 'Restablecimiento de contraseña' server_suspended: 'Suspensión del servidor' + admin_server_created: 'Servidor creado por el administrador' crud: category: name: 'Nombre de la categoría' @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Advertencia de eliminación automática' auto_delete_warning: 'Importante: Tu servidor será eliminado automáticamente después de %days% días (%deleteDate%) si no se renueva.' panel_access: 'Acceso al panel' + admin_server_created: + subject: 'Tu servidor ha sido creado' + title: 'Servidor creado' + subtitle: 'Un administrador ha configurado un servidor para tu cuenta' + server_details: 'Detalles del servidor' + server_name: 'Nombre del servidor' + product_name: 'Producto' + expires_at: 'Expira el' + panel_url: 'URL del panel' + access_panel: 'Ir al panel' + note: 'Este servidor fue creado para ti por un administrador. Si tienes preguntas, por favor contacta con soporte.' verification: title: 'Verificación de correo electrónico requerida' subtitle: 'Por favor verifica tu dirección de correo electrónico para continuar' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Opcionalmente seleccione una plantilla de producto para prellenar los valores de configuración' free_server: 'Servidor gratuito (no cargar al saldo del usuario)' free_server_help: 'Habilite esto para crear un servidor sin deducir del saldo del usuario' + send_creation_email: 'Notificar al usuario por correo electrónico' + send_creation_email_help: 'Enviar un correo electrónico al usuario con información sobre el servidor creado' success: 'Servidor creado exitosamente para el usuario %user%' eggs_required: 'Debe seleccionarse al menos un huevo' use_as_starting_egg: 'Usar como huevo inicial' diff --git a/src/Core/Resources/translations/messages.fr.yaml b/src/Core/Resources/translations/messages.fr.yaml index a0223108..8f5f32ed 100644 --- a/src/Core/Resources/translations/messages.fr.yaml +++ b/src/Core/Resources/translations/messages.fr.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 'Renouveau de produit' reset_password: 'Réinitialisation du mot de passe' server_suspended: 'Suspension du serveur' + admin_server_created: "Serveur créé par l'administrateur" crud: category: name: 'Nom de la catégorie' @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Avertissement de suppression automatique' auto_delete_warning: "Important : Votre serveur sera automatiquement supprimé après %days% jours (%deleteDate%) s'il n'est pas renouvelé." panel_access: 'Accès au panneau' + admin_server_created: + subject: 'Votre serveur a été créé' + title: 'Serveur créé' + subtitle: "Un administrateur a configuré un serveur pour votre compte" + server_details: 'Détails du serveur' + server_name: 'Nom du serveur' + product_name: 'Produit' + expires_at: 'Expire le' + panel_url: 'URL du panneau' + access_panel: 'Aller au panneau' + note: "Ce serveur a été créé pour vous par un administrateur. Si vous avez des questions, veuillez contacter le support." verification: title: "Vérification d'e-mail requise" subtitle: 'Veuillez vérifier votre adresse e-mail pour continuer' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Sélectionnez optionnellement un modèle de produit pour pré-remplir les valeurs de configuration' free_server: "Serveur gratuit (ne pas débiter le solde de l'utilisateur)" free_server_help: "Activez ceci pour créer un serveur sans déduire du solde de l'utilisateur" + send_creation_email: "Notifier l'utilisateur par e-mail" + send_creation_email_help: "Envoyer un e-mail à l'utilisateur avec les informations sur le serveur créé" success: "Serveur créé avec succès pour l'utilisateur %user%" eggs_required: 'Au moins un œuf doit être sélectionné' use_as_starting_egg: 'Utiliser comme œuf de départ' diff --git a/src/Core/Resources/translations/messages.hi.yaml b/src/Core/Resources/translations/messages.hi.yaml index 74592305..de4dceb1 100644 --- a/src/Core/Resources/translations/messages.hi.yaml +++ b/src/Core/Resources/translations/messages.hi.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 'उत्पाद नवीनीकरण' reset_password: 'पासवर्ड रीसेट' server_suspended: 'सर्वर निलंबन' + admin_server_created: 'व्यवस्थापक द्वारा बनाया गया सर्वर' crud: category: name: 'श्रेणी का नाम' @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'स्वत: हटाने की चेतावनी' auto_delete_warning: 'महत्वपूर्ण: यदि नवीनीकृत नहीं किया गया तो आपका सर्वर %days% दिनों के बाद (%deleteDate%) स्वचालित रूप से हटा दिया जाएगा।' panel_access: 'पैनल पहुंच' + admin_server_created: + subject: 'आपका सर्वर बनाया गया है' + title: 'सर्वर बनाया गया' + subtitle: 'एक व्यवस्थापक ने आपके खाते के लिए एक सर्वर स्थापित किया है' + server_details: 'सर्वर विवरण' + server_name: 'सर्वर नाम' + product_name: 'उत्पाद' + expires_at: 'समाप्ति तिथि' + panel_url: 'पैनल URL' + access_panel: 'पैनल पर जाएं' + note: 'यह सर्वर एक व्यवस्थापक द्वारा आपके लिए बनाया गया था। यदि आपके प्रश्न हैं, तो कृपया सहायता से संपर्क करें।' verification: title: 'ईमेल सत्यापन आवश्यक' subtitle: 'कृपया जारी रखने के लिए अपना ईमेल पता सत्यापित करें' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'वैकल्पिक रूप से कॉन्फ़िगरेशन मानों को पूर्व-भरने के लिए एक उत्पाद टेम्प्लेट चुनें' free_server: 'मुफ्त सर्वर (उपयोगकर्ता शेष से न काटें)' free_server_help: 'उपयोगकर्ता शेष से काटे बिना सर्वर बनाने के लिए इसे सक्षम करें' + send_creation_email: 'उपयोगकर्ता को ईमेल द्वारा सूचित करें' + send_creation_email_help: 'बनाए गए सर्वर की जानकारी के साथ उपयोगकर्ता को एक ईमेल भेजें' success: 'उपयोगकर्ता %user% के लिए सर्वर सफलतापूर्वक बनाया गया' eggs_required: 'कम से कम एक अंडा चुना जाना चाहिए' use_as_starting_egg: 'प्रारंभिक अंडे के रूप में उपयोग करें' diff --git a/src/Core/Resources/translations/messages.id.yaml b/src/Core/Resources/translations/messages.id.yaml index 9d7cb346..419daeb9 100644 --- a/src/Core/Resources/translations/messages.id.yaml +++ b/src/Core/Resources/translations/messages.id.yaml @@ -757,6 +757,7 @@ pteroca: renew_product: 'Perpanjangan produk' reset_password: 'Pengaturan ulang kata sandi' server_suspended: 'Penangguhan server' + admin_server_created: 'Server dibuat oleh administrator' crud: category: name: 'Nama kategori' @@ -1585,6 +1586,17 @@ pteroca: suspension_date: 'Ditangguhkan pada: %suspensionDate%' auto_delete_warning: 'Penting: Server Anda akan dihapus secara otomatis setelah %days% hari (%deleteDate%) jika tidak diperpanjang.' panel_access: 'Akses Panel' + admin_server_created: + subject: 'Server Anda telah dibuat' + title: 'Server Dibuat' + subtitle: 'Administrator telah menyiapkan server untuk akun Anda' + server_details: 'Detail Server' + server_name: 'Nama Server' + product_name: 'Produk' + expires_at: 'Kedaluwarsa Pada' + panel_url: 'URL Panel' + access_panel: 'Buka Panel' + note: 'Server ini dibuat untuk Anda oleh administrator. Jika Anda memiliki pertanyaan, silakan hubungi dukungan.' verification: title: 'Verifikasi Email Diperlukan' subtitle: 'Silakan verifikasi alamat email Anda untuk melanjutkan' @@ -1634,6 +1646,8 @@ pteroca: base_product_help: 'Secara opsional pilih template produk untuk mengisi nilai konfigurasi sebelumnya' free_server: 'Server gratis (jangan potong dari saldo pengguna)' free_server_help: 'Aktifkan ini untuk membuat server tanpa memotong dari saldo pengguna' + send_creation_email: 'Beritahu pengguna melalui email' + send_creation_email_help: 'Kirim email kepada pengguna dengan informasi tentang server yang dibuat' success: 'Server berhasil dibuat untuk pengguna %user%' eggs_required: 'Setidaknya satu telur harus dipilih' use_as_starting_egg: 'Gunakan sebagai telur awal' diff --git a/src/Core/Resources/translations/messages.it.yaml b/src/Core/Resources/translations/messages.it.yaml index 4a31437b..c768ba6d 100644 --- a/src/Core/Resources/translations/messages.it.yaml +++ b/src/Core/Resources/translations/messages.it.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 'Rinnovo prodotto' reset_password: 'Reimpostazione password' server_suspended: 'Sospensione server' + admin_server_created: "Server creato dall'amministratore" crud: category: name: 'Nome categoria' @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Avviso di eliminazione automatica' auto_delete_warning: 'Importante: Il tuo server verrà eliminato automaticamente dopo %days% giorni (%deleteDate%) se non rinnovato.' panel_access: 'Accesso al pannello' + admin_server_created: + subject: 'Il tuo server è stato creato' + title: 'Server creato' + subtitle: "Un amministratore ha configurato un server per il tuo account" + server_details: 'Dettagli del server' + server_name: 'Nome del server' + product_name: 'Prodotto' + expires_at: 'Scade il' + panel_url: 'URL del pannello' + access_panel: 'Vai al pannello' + note: "Questo server è stato creato per te da un amministratore. Se hai domande, contatta l'assistenza." verification: title: 'Verifica e-mail richiesta' subtitle: 'Verifica il tuo indirizzo e-mail per continuare' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Seleziona opzionalmente un modello di prodotto per pre-compilare i valori di configurazione' free_server: "Server gratuito (non addebitare il saldo dell'utente)" free_server_help: "Abilita questo per creare un server senza dedurre dal saldo dell'utente" + send_creation_email: "Notifica l'utente via e-mail" + send_creation_email_help: "Invia un'e-mail all'utente con informazioni sul server creato" success: "Server creato con successo per l'utente %user%" eggs_required: 'Deve essere selezionato almeno un uovo' use_as_starting_egg: 'Usa come uovo iniziale' diff --git a/src/Core/Resources/translations/messages.nl.yaml b/src/Core/Resources/translations/messages.nl.yaml index a3bc3968..02b131dd 100644 --- a/src/Core/Resources/translations/messages.nl.yaml +++ b/src/Core/Resources/translations/messages.nl.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 'Product verlenging' reset_password: 'Wachtwoord reset' server_suspended: 'Server opschorting' + admin_server_created: 'Server aangemaakt door beheerder' crud: category: name: Categorienaam @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Automatische verwijderingswaarschuwing' auto_delete_warning: 'Belangrijk: Uw server wordt automatisch verwijderd na %days% dagen (%deleteDate%) als deze niet wordt verlengd.' panel_access: 'Toegang tot paneel' + admin_server_created: + subject: 'Uw server is aangemaakt' + title: 'Server aangemaakt' + subtitle: 'Een beheerder heeft een server voor uw account ingesteld' + server_details: 'Servergegevens' + server_name: 'Servernaam' + product_name: 'Product' + expires_at: 'Verloopt op' + panel_url: 'Paneel-URL' + access_panel: 'Ga naar paneel' + note: 'Deze server is voor u aangemaakt door een beheerder. Als u vragen heeft, neem dan contact op met de ondersteuning.' verification: title: 'E-mail verificatie vereist' subtitle: 'Verificeer uw e-mailadres om door te gaan' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Selecteer optioneel een productsjabloon om configuratiewaarden voor te vullen' free_server: 'Gratis server (gebruikerssaldo niet belasten)' free_server_help: 'Schakel dit in om een server aan te maken zonder af te schrijven van het gebruikerssaldo' + send_creation_email: 'Gebruiker per e-mail informeren' + send_creation_email_help: 'Stuur een e-mail naar de gebruiker met informatie over de aangemaakte server' success: 'Server succesvol aangemaakt voor gebruiker %user%' eggs_required: 'Er moet minimaal één ei worden geselecteerd' use_as_starting_egg: 'Gebruik als start-ei' diff --git a/src/Core/Resources/translations/messages.pl.yaml b/src/Core/Resources/translations/messages.pl.yaml index f094c6a7..a32981eb 100644 --- a/src/Core/Resources/translations/messages.pl.yaml +++ b/src/Core/Resources/translations/messages.pl.yaml @@ -733,6 +733,7 @@ pteroca: renew_product: 'Odnowienie produktu' reset_password: 'Reset hasła' server_suspended: 'Zawieszenie serwera' + admin_server_created: 'Serwer utworzony przez administratora' crud: category: name: 'Nazwa kategorii' @@ -1565,6 +1566,17 @@ pteroca: auto_delete_title: 'Ostrzeżenie o automatycznym usunięciu' auto_delete_warning: 'Ważne: Twój serwer zostanie automatycznie usunięty po %days% dniach (%deleteDate%), jeśli nie zostanie przedłużony.' panel_access: 'Dostęp do panelu' + admin_server_created: + subject: 'Twój serwer został utworzony' + title: 'Serwer utworzony' + subtitle: 'Administrator skonfigurował serwer dla Twojego konta' + server_details: 'Szczegóły serwera' + server_name: 'Nazwa serwera' + product_name: 'Produkt' + expires_at: 'Wygaśnie' + panel_url: 'Adres URL panelu' + access_panel: 'Przejdź do panelu' + note: 'Ten serwer został utworzony dla Ciebie przez administratora. Jeśli masz pytania, skontaktuj się z pomocą techniczną.' verification: title: 'Weryfikacja e-mail wymagana' subtitle: 'Zweryfikuj swój adres e-mail, aby kontynuować' @@ -1614,6 +1626,8 @@ pteroca: base_product_help: 'Opcjonalnie wybierz produkt szablonu, aby wstępnie wypełnić wartości konfiguracji' free_server: 'Serwer bezpłatny (nie obciążaj salda użytkownika)' free_server_help: 'Włącz to, aby utworzyć serwer bez odjęcia z salda użytkownika' + send_creation_email: 'Powiadom użytkownika przez e-mail' + send_creation_email_help: 'Wyślij e-mail do użytkownika z informacjami o utworzonym serwerze' success: 'Serwer utworzony pomyślnie dla użytkownika %user%' eggs_required: 'Co najmniej jedno jajko musi być wybrane' use_as_starting_egg: 'Użyj jako jajka startowego' diff --git a/src/Core/Resources/translations/messages.pt.yaml b/src/Core/Resources/translations/messages.pt.yaml index 31ca21da..f390d200 100644 --- a/src/Core/Resources/translations/messages.pt.yaml +++ b/src/Core/Resources/translations/messages.pt.yaml @@ -735,6 +735,7 @@ pteroca: renew_product: 'Renovação de produto' reset_password: 'Redefinição de senha' server_suspended: 'Suspensão do servidor' + admin_server_created: 'Servidor criado pelo administrador' crud: category: name: 'Nome da categoria' @@ -1567,6 +1568,17 @@ pteroca: auto_delete_title: 'Aviso de exclusão automática' auto_delete_warning: 'Importante: O seu servidor será automaticamente eliminado após %days% dias (%deleteDate%) se não for renovado.' panel_access: 'Acesso ao painel' + admin_server_created: + subject: 'O seu servidor foi criado' + title: 'Servidor criado' + subtitle: 'Um administrador configurou um servidor para a sua conta' + server_details: 'Detalhes do servidor' + server_name: 'Nome do servidor' + product_name: 'Produto' + expires_at: 'Expira em' + panel_url: 'URL do painel' + access_panel: 'Ir para o painel' + note: 'Este servidor foi criado para si por um administrador. Se tiver dúvidas, por favor contacte o suporte.' verification: title: 'Verificação de e-mail obrigatória' subtitle: 'Verifique seu endereço de e-mail para continuar' @@ -1616,6 +1628,8 @@ pteroca: base_product_help: 'Selecione opcionalmente um modelo de produto para pré-preencher os valores de configuração' free_server: 'Servidor gratuito (não cobrar do saldo do usuário)' free_server_help: 'Ative isso para criar um servidor sem deduzir do saldo do usuário' + send_creation_email: 'Notificar usuário por e-mail' + send_creation_email_help: 'Enviar um e-mail ao usuário com informações sobre o servidor criado' success: 'Servidor criado com sucesso para o usuário %user%' eggs_required: 'Pelo menos um ovo deve ser selecionado' use_as_starting_egg: 'Usar como ovo inicial' diff --git a/src/Core/Resources/translations/messages.ru.yaml b/src/Core/Resources/translations/messages.ru.yaml index fabfef6b..a0e94edc 100644 --- a/src/Core/Resources/translations/messages.ru.yaml +++ b/src/Core/Resources/translations/messages.ru.yaml @@ -757,6 +757,7 @@ pteroca: renew_product: 'Продление продукта' reset_password: 'Сброс пароля' server_suspended: 'Приостановка сервера' + admin_server_created: 'Сервер создан администратором' crud: category: name: 'Название категории' @@ -1599,6 +1600,17 @@ pteroca: suspension_date: 'Приостановлен: %suspensionDate%' auto_delete_warning: 'Важно: Ваш сервер будет автоматически удален через %days% дней (%deleteDate%), если не будет продлен.' panel_access: 'Доступ к панели' + admin_server_created: + subject: 'Ваш сервер был создан' + title: 'Сервер создан' + subtitle: 'Администратор настроил сервер для вашей учётной записи' + server_details: 'Детали сервера' + server_name: 'Имя сервера' + product_name: 'Продукт' + expires_at: 'Истекает' + panel_url: 'URL панели' + access_panel: 'Перейти в панель' + note: 'Этот сервер был создан для вас администратором. Если у вас есть вопросы, пожалуйста, свяжитесь с поддержкой.' verification: title: 'Требуется подтверждение электронной почты' subtitle: 'Подтвердите свой адрес электронной почты, чтобы продолжить' @@ -1648,6 +1660,8 @@ pteroca: base_product_help: 'При желании выберите шаблон продукта для предварительного заполнения значений конфигурации' free_server: 'Бесплатный сервер (не списывать с баланса пользователя)' free_server_help: 'Включите это, чтобы создать сервер без списания с баланса пользователя' + send_creation_email: 'Уведомить пользователя по электронной почте' + send_creation_email_help: 'Отправить пользователю электронное письмо с информацией о созданном сервере' success: 'Сервер успешно создан для пользователя %user%' eggs_required: 'Должно быть выбрано хотя бы одно яйцо' use_as_starting_egg: 'Использовать как начальное яйцо' diff --git a/src/Core/Resources/translations/messages.ua.yaml b/src/Core/Resources/translations/messages.ua.yaml index 064b33f1..1b5a6ed9 100644 --- a/src/Core/Resources/translations/messages.ua.yaml +++ b/src/Core/Resources/translations/messages.ua.yaml @@ -757,6 +757,7 @@ pteroca: renew_product: 'Продовження продукту' reset_password: 'Скидання пароля' server_suspended: 'Призупинення сервера' + admin_server_created: 'Сервер створено адміністратором' crud: category: name: 'Назва категорії' @@ -1599,6 +1600,17 @@ pteroca: suspension_date: 'Призупинено: %suspensionDate%' auto_delete_warning: 'Важливо: Ваш сервер буде автоматично видалено через %days% днів (%deleteDate%), якщо не буде подовжено.' panel_access: 'Доступ до панелі' + admin_server_created: + subject: 'Ваш сервер було створено' + title: 'Сервер створено' + subtitle: 'Адміністратор налаштував сервер для вашого облікового запису' + server_details: 'Деталі сервера' + server_name: 'Назва сервера' + product_name: 'Продукт' + expires_at: 'Спливає' + panel_url: 'URL панелі' + access_panel: 'Перейти до панелі' + note: 'Цей сервер було створено для вас адміністратором. Якщо у вас є питання, будь ласка, зверніться до служби підтримки.' verification: title: 'Потрібне підтвердження електронної пошти' subtitle: 'Підтвердіть свою електронну адресу, щоб продовжити' @@ -1648,6 +1660,8 @@ pteroca: base_product_help: 'За бажанням виберіть шаблон продукту для попереднього заповнення значень конфігурації' free_server: 'Безкоштовний сервер (не списувати з балансу користувача)' free_server_help: 'Увімкніть це, щоб створити сервер без списання з балансу користувача' + send_creation_email: 'Сповістити користувача електронною поштою' + send_creation_email_help: 'Надіслати користувачу електронного листа з інформацією про створений сервер' success: 'Сервер успішно створено для користувача %user%' eggs_required: 'Має бути вибрано принаймні одне яйце' use_as_starting_egg: 'Використовувати як початкове яйце' From 0f628971de37194d10cc77a926ee3762ebd59728 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 02:06:50 +0100 Subject: [PATCH 31/34] Price preview in pricing tab --- .../Resources/translations/messages.cn.yaml | 2 + .../Resources/translations/messages.de.yaml | 2 + .../translations/messages.de_CH.yaml | 2 + .../Resources/translations/messages.en.yaml | 2 + .../Resources/translations/messages.es.yaml | 2 + .../Resources/translations/messages.fr.yaml | 2 + .../Resources/translations/messages.hi.yaml | 2 + .../Resources/translations/messages.id.yaml | 2 + .../Resources/translations/messages.it.yaml | 2 + .../Resources/translations/messages.nl.yaml | 2 + .../Resources/translations/messages.pl.yaml | 2 + .../Resources/translations/messages.pt.yaml | 2 + .../Resources/translations/messages.ru.yaml | 2 + .../Resources/translations/messages.ua.yaml | 2 + .../panel/components/price_preview.html.twig | 155 ++++++++++++++++++ .../default/panel/crud/product/edit.html.twig | 1 + .../default/panel/crud/product/new.html.twig | 1 + .../panel/crud/server_product/edit.html.twig | 1 + .../panel/crud/server_product/new.html.twig | 1 + 19 files changed, 187 insertions(+) create mode 100644 themes/default/panel/components/price_preview.html.twig diff --git a/src/Core/Resources/translations/messages.cn.yaml b/src/Core/Resources/translations/messages.cn.yaml index b53d8b11..6594c02e 100644 --- a/src/Core/Resources/translations/messages.cn.yaml +++ b/src/Core/Resources/translations/messages.cn.yaml @@ -844,6 +844,8 @@ pteroca: days: 天 hours: 小时 minutes: 分钟 + price_preview_per: '每' + price_preview_per_slot: '每槽位' server_product: 服务器产品 server_products: 服务器产品列表 original_product: 原始产品 diff --git a/src/Core/Resources/translations/messages.de.yaml b/src/Core/Resources/translations/messages.de.yaml index ac83a844..2c0c075e 100644 --- a/src/Core/Resources/translations/messages.de.yaml +++ b/src/Core/Resources/translations/messages.de.yaml @@ -844,6 +844,8 @@ pteroca: days: Tage hours: Stunden minutes: Minuten + price_preview_per: 'pro' + price_preview_per_slot: 'pro Slot' server_product: Serverprodukt server_products: Serverprodukte original_product: 'Ursprüngliches Produkt' diff --git a/src/Core/Resources/translations/messages.de_CH.yaml b/src/Core/Resources/translations/messages.de_CH.yaml index 911ee115..bb159679 100644 --- a/src/Core/Resources/translations/messages.de_CH.yaml +++ b/src/Core/Resources/translations/messages.de_CH.yaml @@ -844,6 +844,8 @@ pteroca: days: Tage hours: Stunden minutes: Minuten + price_preview_per: 'pro' + price_preview_per_slot: 'pro Slot' server_product: Serverprodukt server_products: Serverprodukte original_product: Originalprodukt diff --git a/src/Core/Resources/translations/messages.en.yaml b/src/Core/Resources/translations/messages.en.yaml index ce9b133d..55af2240 100644 --- a/src/Core/Resources/translations/messages.en.yaml +++ b/src/Core/Resources/translations/messages.en.yaml @@ -864,6 +864,8 @@ pteroca: days: 'Days' hours: 'Hours' minutes: 'Minutes' + price_preview_per: 'per' + price_preview_per_slot: 'per slot' build_name: 'Build name' server_build: 'Server' server_builds: 'Servers' diff --git a/src/Core/Resources/translations/messages.es.yaml b/src/Core/Resources/translations/messages.es.yaml index 28219176..6082df4e 100644 --- a/src/Core/Resources/translations/messages.es.yaml +++ b/src/Core/Resources/translations/messages.es.yaml @@ -844,6 +844,8 @@ pteroca: days: Días hours: Horas minutes: Minutos + price_preview_per: 'por' + price_preview_per_slot: 'por ranura' server_product: 'Producto de servidor' server_products: 'Productos de servidor' original_product: 'Producto original' diff --git a/src/Core/Resources/translations/messages.fr.yaml b/src/Core/Resources/translations/messages.fr.yaml index 8f5f32ed..fc0244ac 100644 --- a/src/Core/Resources/translations/messages.fr.yaml +++ b/src/Core/Resources/translations/messages.fr.yaml @@ -846,6 +846,8 @@ pteroca: days: Jours hours: Heures minutes: Minutes + price_preview_per: 'par' + price_preview_per_slot: 'par slot' server_product: 'Produit serveur' server_products: 'Produits serveurs' original_product: 'Produit original' diff --git a/src/Core/Resources/translations/messages.hi.yaml b/src/Core/Resources/translations/messages.hi.yaml index de4dceb1..881622f0 100644 --- a/src/Core/Resources/translations/messages.hi.yaml +++ b/src/Core/Resources/translations/messages.hi.yaml @@ -844,6 +844,8 @@ pteroca: days: दिन hours: घंटे minutes: मिनट + price_preview_per: 'प्रति' + price_preview_per_slot: 'प्रति स्लॉट' server_product: 'सर्वर उत्पाद' server_products: 'सर्वर उत्पादों की सूची' original_product: 'मूल उत्पाद' diff --git a/src/Core/Resources/translations/messages.id.yaml b/src/Core/Resources/translations/messages.id.yaml index 419daeb9..d38e11ec 100644 --- a/src/Core/Resources/translations/messages.id.yaml +++ b/src/Core/Resources/translations/messages.id.yaml @@ -867,6 +867,8 @@ pteroca: days: Hari hours: Jam minutes: Menit + price_preview_per: 'per' + price_preview_per_slot: 'per slot' server_product: 'Produk server' server_products: 'Produk server' original_product: 'Produk asli' diff --git a/src/Core/Resources/translations/messages.it.yaml b/src/Core/Resources/translations/messages.it.yaml index c768ba6d..b6b12c75 100644 --- a/src/Core/Resources/translations/messages.it.yaml +++ b/src/Core/Resources/translations/messages.it.yaml @@ -844,6 +844,8 @@ pteroca: days: Giorni hours: Ore minutes: Minuti + price_preview_per: 'per' + price_preview_per_slot: 'per slot' server_product: 'Prodotto server' server_products: 'Prodotti server' original_product: 'Prodotto originale' diff --git a/src/Core/Resources/translations/messages.nl.yaml b/src/Core/Resources/translations/messages.nl.yaml index 02b131dd..9431c047 100644 --- a/src/Core/Resources/translations/messages.nl.yaml +++ b/src/Core/Resources/translations/messages.nl.yaml @@ -846,6 +846,8 @@ pteroca: days: Dagen hours: Uren minutes: Minuten + price_preview_per: 'per' + price_preview_per_slot: 'per slot' server_product: 'Server product' server_products: 'Server producten' original_product: 'Oorspronkelijk product' diff --git a/src/Core/Resources/translations/messages.pl.yaml b/src/Core/Resources/translations/messages.pl.yaml index a32981eb..b24dfcd9 100644 --- a/src/Core/Resources/translations/messages.pl.yaml +++ b/src/Core/Resources/translations/messages.pl.yaml @@ -844,6 +844,8 @@ pteroca: days: Dni hours: Godziny minutes: Minuty + price_preview_per: 'za' + price_preview_per_slot: 'za slot' server_product: 'Produkt serwera' server_products: 'Produkty serwera' original_product: 'Oryginalny produkt' diff --git a/src/Core/Resources/translations/messages.pt.yaml b/src/Core/Resources/translations/messages.pt.yaml index f390d200..7d7d3385 100644 --- a/src/Core/Resources/translations/messages.pt.yaml +++ b/src/Core/Resources/translations/messages.pt.yaml @@ -848,6 +848,8 @@ pteroca: days: Dias hours: Horas minutes: Minutos + price_preview_per: 'por' + price_preview_per_slot: 'por slot' server_product: 'Produto de servidor' server_products: 'Produtos de servidor' original_product: 'Produto original' diff --git a/src/Core/Resources/translations/messages.ru.yaml b/src/Core/Resources/translations/messages.ru.yaml index a0e94edc..144044f6 100644 --- a/src/Core/Resources/translations/messages.ru.yaml +++ b/src/Core/Resources/translations/messages.ru.yaml @@ -868,6 +868,8 @@ pteroca: days: Дни hours: Часы minutes: Минуты + price_preview_per: 'за' + price_preview_per_slot: 'за слот' server_product: 'Серверный продукт' server_products: 'Серверные продукты' original_product: 'Исходный продукт' diff --git a/src/Core/Resources/translations/messages.ua.yaml b/src/Core/Resources/translations/messages.ua.yaml index 1b5a6ed9..7ea86daf 100644 --- a/src/Core/Resources/translations/messages.ua.yaml +++ b/src/Core/Resources/translations/messages.ua.yaml @@ -872,6 +872,8 @@ pteroca: days: Дні hours: Години minutes: Хвилини + price_preview_per: 'за' + price_preview_per_slot: 'за слот' server_product: 'Серверний продукт' server_products: 'Серверні продукти' original_product: 'Оригінальний продукт' diff --git a/themes/default/panel/components/price_preview.html.twig b/themes/default/panel/components/price_preview.html.twig new file mode 100644 index 00000000..616e0fc1 --- /dev/null +++ b/themes/default/panel/components/price_preview.html.twig @@ -0,0 +1,155 @@ +{% block body_javascripts %} + +{% endblock %} diff --git a/themes/default/panel/crud/product/edit.html.twig b/themes/default/panel/crud/product/edit.html.twig index 999a95ad..f98d856f 100644 --- a/themes/default/panel/crud/product/edit.html.twig +++ b/themes/default/panel/crud/product/edit.html.twig @@ -4,4 +4,5 @@ {{ parent() }} {% include 'components/egg_manager.html.twig' %} {% include 'components/dynamic_prices_validator.html.twig' %} + {% include 'components/price_preview.html.twig' %} {% endblock %} \ No newline at end of file diff --git a/themes/default/panel/crud/product/new.html.twig b/themes/default/panel/crud/product/new.html.twig index 153e7251..e8178612 100644 --- a/themes/default/panel/crud/product/new.html.twig +++ b/themes/default/panel/crud/product/new.html.twig @@ -4,4 +4,5 @@ {{ parent() }} {% include 'components/egg_manager.html.twig' %} {% include 'components/dynamic_prices_validator.html.twig' %} + {% include 'components/price_preview.html.twig' %} {% endblock %} \ No newline at end of file diff --git a/themes/default/panel/crud/server_product/edit.html.twig b/themes/default/panel/crud/server_product/edit.html.twig index 999a95ad..f98d856f 100644 --- a/themes/default/panel/crud/server_product/edit.html.twig +++ b/themes/default/panel/crud/server_product/edit.html.twig @@ -4,4 +4,5 @@ {{ parent() }} {% include 'components/egg_manager.html.twig' %} {% include 'components/dynamic_prices_validator.html.twig' %} + {% include 'components/price_preview.html.twig' %} {% endblock %} \ No newline at end of file diff --git a/themes/default/panel/crud/server_product/new.html.twig b/themes/default/panel/crud/server_product/new.html.twig index 153e7251..e8178612 100644 --- a/themes/default/panel/crud/server_product/new.html.twig +++ b/themes/default/panel/crud/server_product/new.html.twig @@ -4,4 +4,5 @@ {{ parent() }} {% include 'components/egg_manager.html.twig' %} {% include 'components/dynamic_prices_validator.html.twig' %} + {% include 'components/price_preview.html.twig' %} {% endblock %} \ No newline at end of file From 02e36fe40351b5a15344ab22d8e5ed9f125b65f6 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Sun, 1 Mar 2026 18:55:36 +0100 Subject: [PATCH 32/34] Updated version files --- CHANGELOG.md | 36 +++++++++++++++++++++++++ composer.json | 2 +- src/Core/Resources/config/services.yaml | 2 +- themes/default/template.json | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7766a9f3..671bb51a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## [0.6.4] - 2026-03-01 + +### Added +- Added price preview widget in the product pricing tab showing formatted prices for each billing period. +- Added email notification sent to the user when an admin creates a server on their behalf. +- Added filesystem permission precheck before plugin upload to verify write access and show clear error messages. +- Added filesystem permission precheck before theme upload to verify write access and show clear error messages. +- Added Marketplace integration in the admin panel - browse, search, and install plugins directly from marketplace.pteroca.com. +- Added support for required user-provided server variables during the server purchase flow. +- Added product egg variable validation rules support - rules defined in egg configuration are now enforced on user input. +- Added server health status badge in the admin server CRUD list view. +- Added configurable "Manage in Pterodactyl" button on the user server management page (can be enabled/disabled in settings). +- Added configurable price format settings - customizable decimal and thousands separators for price display. +- Added configurable datetime format settings - date format, timezone, and timezone visibility can be set per installation. +- Added configurable minimum top-up amount setting for wallet balance. +- Added custom head scripts setting - arbitrary scripts can be injected into the `` of the landing page and panel. +- Added code editor field type for settings with syntax highlighting support. +- Added short description field to products for use in SEO metadata and store listing previews. + +### Changed +- Improved admin server CRUD list view - Pterodactyl server identifier is now shown in a shortened form on the index page. +- Updated contribution documentation - improved CONTRIBUTING.md, PR template, issue templates, and code of conduct. +- Updated help link in product CRUD controllers. +- Added Mailhog to the development Docker environment for local email testing. + +### Fixed +- Fixed server state in the servers list showing as unknown when the status check cannot be performed. +- Fixed doubled server ID appearing in the server name after migration from Pterodactyl. +- Fixed invalid YAML escaping in the Italian translation file. +- Fixed theme export functionality that was producing invalid archives. +- Fixed plugin migration not wrapping schema changes in a database transaction. +- Fixed saving the default theme context in theme settings. +- Fixed EasyAdmin CRUD controllers not calling the parent `configureFields` method, causing plugin-registered fields to be dropped. + +--- + ## [0.6.3] - 2026-02-03 ### Added diff --git a/composer.json b/composer.json index b5cea686..e44f0f21 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "pteroca/panel", "description": "PteroCA.com is a free, open-source client area and management panel designed specifically for Pterodactyl server users and hosting providers. The platform simplifies and automates server management with a user-friendly interface and robust billing features.", - "version": "0.6.3", + "version": "0.6.4", "type": "project", "license": "MIT", "minimum-stability": "stable", diff --git a/src/Core/Resources/config/services.yaml b/src/Core/Resources/config/services.yaml index 6fa7d1e4..9772684c 100644 --- a/src/Core/Resources/config/services.yaml +++ b/src/Core/Resources/config/services.yaml @@ -1,5 +1,5 @@ parameters: - version: '0.6.3' + version: '0.6.4' categories_base_path: '/uploads/categories' categories_directory: 'public/uploads/categories' products_base_path: '/uploads/products' diff --git a/themes/default/template.json b/themes/default/template.json index 47425a0a..d1e87198 100644 --- a/themes/default/template.json +++ b/themes/default/template.json @@ -5,7 +5,7 @@ "author": "PteroCA.com", "version": "1.2.0", "license": "MIT", - "pterocaVersion": "0.6.3", + "pterocaVersion": "0.6.4", "phpVersion": ">=8.2", "contexts": ["panel", "landing", "email"], "translations": [], From 99359d6cf5bc5f3b862fd41f1606b17ade7a5236 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Mon, 2 Mar 2026 19:12:41 +0100 Subject: [PATCH 33/34] Updated CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671bb51a..59492abd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [0.6.4] - 2026-03-01 +## [0.6.4] - 2026-03-02 ### Added - Added price preview widget in the product pricing tab showing formatted prices for each billing period. From 7128ff15293c38ec8af97b8ab9b122e2d504c334 Mon Sep 17 00:00:00 2001 From: Konrad Sroga Date: Mon, 2 Mar 2026 19:37:45 +0100 Subject: [PATCH 34/34] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59492abd..2d8ba778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - Fixed plugin migration not wrapping schema changes in a database transaction. - Fixed saving the default theme context in theme settings. - Fixed EasyAdmin CRUD controllers not calling the parent `configureFields` method, causing plugin-registered fields to be dropped. +- Fixed node selection not respecting Pterodactyl maintenance mode - nodes in maintenance are now skipped during automatic selection and rejected when chosen manually. ---