Skip to content

Commit 92f7c8b

Browse files
authored
[v0.5.7] Version release (#72)
## [0.5.7] - 2025-09-04 ### Added - Added new **per-slot pricing type** with egg configuration support. - Added server software tile on the server management page. - Added automatic **Copy IP Address** button on the server management page. - Added SMTP connection test button on the email settings page. - Added support for the `TRUSTED_HOSTS` variable for Symfony host validation. ### Changed - Improved server IP display: the correct allocation is now shown even when a domain is assigned. - Rewritten email logic for server purchase and renewal notifications. - Improved session configuration for better compatibility with Cloudflare. ### Fixed - Fixed issue where administrators could not update server egg variables. - Fixed bug causing suspended servers not to appear in the user’s server list.
1 parent 3fa26e8 commit 92f7c8b

File tree

70 files changed

+2330
-258
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2330
-258
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
---
44

5+
## [0.5.7] - 2025-09-04
6+
7+
### Added
8+
- Added new **per-slot pricing type** with egg configuration support.
9+
- Added server software tile on the server management page.
10+
- Added automatic **Copy IP Address** button on the server management page.
11+
- Added SMTP connection test button on the email settings page.
12+
- Added support for the `TRUSTED_HOSTS` variable for Symfony host validation.
13+
14+
### Changed
15+
- Improved server IP display: the correct allocation is now shown even when a domain is assigned.
16+
- Rewritten email logic for server purchase and renewal notifications.
17+
- Improved session configuration for better compatibility with Cloudflare.
18+
19+
### Fixed
20+
- Fixed issue where administrators could not update server egg variables.
21+
- Fixed bug causing suspended servers not to appear in the user’s server list.
22+
23+
---
24+
525
## [0.5.6] - 2025-08-15
626

727
### Added

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "pteroca/panel",
33
"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.",
4-
"version": "0.5.6",
4+
"version": "0.5.7",
55
"type": "project",
66
"license": "MIT",
77
"minimum-stability": "stable",

config/packages/framework.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@
22
framework:
33
secret: '%env(APP_SECRET)%'
44

5-
# Note that the session will be started ONLY if you read or write from it.
6-
session: true
5+
session:
6+
cookie_secure: auto
7+
cookie_samesite: lax
8+
cookie_domain: null
79

810
default_locale: en
911
translator:
1012
default_path: '%kernel.project_dir%/translations'
1113
fallbacks:
1214
- en
1315

14-
#esi: true
15-
#fragments: true
16-
1716
when@test:
1817
framework:
1918
test: true

public/index.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
1010

11-
$trustedProxies = $_ENV['TRUSTED_PROXIES'] ?? '';
11+
$trustedProxies = $_ENV['TRUSTED_PROXIES'] ?? null;
1212
if (!empty($trustedProxies)) {
1313
$proxiesArray = array_map('trim', explode(',', $trustedProxies));
1414

@@ -23,6 +23,12 @@
2323
);
2424
}
2525

26+
$trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? null;
27+
if (!empty($trustedHosts)) {
28+
$hostsArray = array_map('trim', explode(',', $trustedHosts));
29+
Request::setTrustedHosts($hostsArray);
30+
}
31+
2632
return function (array $context) {
2733
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
2834
};

src/Core/Controller/CartController.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use App\Core\Service\Payment\PaymentService;
1313
use App\Core\Service\Server\CreateServerService;
1414
use App\Core\Service\Server\RenewServerService;
15+
use App\Core\Service\Server\ServerSlotPricingService;
1516
use App\Core\Service\SettingService;
1617
use App\Core\Service\StoreService;
1718
use Exception;
@@ -27,6 +28,7 @@ public function __construct(
2728
private readonly ServerRepository $serverRepository,
2829
private readonly ServerSubuserRepository $serverSubuserRepository,
2930
private readonly TranslatorInterface $translator,
31+
private readonly ServerSlotPricingService $serverSlotPricingService,
3032
) {}
3133

3234
#[Route('/cart/topup', name: 'cart_topup', methods: ['GET', 'POST'])]
@@ -80,11 +82,15 @@ public function configure(Request $request): Response
8082
$preparedEggs = $this->storeService->getProductEggs($product);
8183
$request = $request->query->all();
8284

85+
$hasSlotPrices = $this->serverSlotPricingService->hasSlotPrices($product);
86+
8387
return $this->render('panel/cart/configure.html.twig', [
8488
'product' => $product,
8589
'eggs' => $preparedEggs,
8690
'request' => $request,
8791
'isProductAvailable' => $this->storeService->productHasNodeWithResources($product),
92+
'hasSlotPrices' => $hasSlotPrices,
93+
'initialSlots' => $request['slots'] ?? null,
8894
]);
8995
}
9096

@@ -108,12 +114,15 @@ public function buy(
108114
$priceId = $request->request->getInt('duration');
109115
$serverName = $request->request->getString('server-name');
110116
$autoRenewal = $request->request->getBoolean('auto-renewal');
117+
$slots = $request->request->get('slots') ? $request->request->getInt('slots') : null;
111118

112119
try {
113120
$this->storeService->validateBoughtProduct(
114121
$product,
115122
$eggId,
116-
$priceId
123+
$priceId,
124+
null,
125+
$slots
117126
);
118127

119128
$createServerService->createServer(
@@ -124,6 +133,7 @@ public function buy(
124133
$autoRenewal,
125134
$this->getUser(),
126135
$request->request->getString('voucher'),
136+
$slots
127137
);
128138

129139
$this->addFlash('success', $this->translator->trans('pteroca.store.successful_purchase'));
@@ -146,10 +156,17 @@ public function renew(
146156
{
147157
$server = $this->getServerByRequest($request);
148158
$isOwner = $server->getUser() === $this->getUser();
159+
$hasSlotPrices = $this->serverSlotPricingService->hasSlotPricing($server);
160+
161+
if ($hasSlotPrices) {
162+
$serverSlots = $this->serverSlotPricingService->getServerSlots($server);
163+
}
149164

150165
return $this->render('panel/cart/renew.html.twig', [
151166
'server' => $server,
152167
'isOwner' => $isOwner,
168+
'hasSlotPrices' => $hasSlotPrices,
169+
'serverSlots' => $serverSlots ?? null,
153170
]);
154171
}
155172

@@ -170,17 +187,24 @@ public function renewBuy(
170187
}
171188

172189
try {
190+
$hasActiveSlotPricing = $this->serverSlotPricingService->hasActiveSlotPricing($server);
191+
if ($hasActiveSlotPricing) {
192+
$serverSlots = $this->serverSlotPricingService->getServerSlots($server);
193+
}
194+
173195
$this->storeService->validateBoughtProduct(
174196
$server->getServerProduct(),
175197
null,
176198
$server->getServerProduct()->getSelectedPrice()->getId(),
177199
$server,
200+
$serverSlots ?? null
178201
);
179202

180203
$renewServerService->renewServer(
181204
$server,
182205
$this->getUser(),
183206
$request->request->getString('voucher'),
207+
$serverSlots ?? null,
184208
);
185209

186210
$this->addFlash('success', $this->translator->trans('pteroca.store.successful_purchase'));

src/Core/Controller/Panel/ProductCrudController.php

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Core\Enum\UserRoleEnum;
99
use App\Core\Form\ProductPriceDynamicFormType;
1010
use App\Core\Form\ProductPriceFixedFormType;
11+
use App\Core\Form\ProductPriceSlotFormType;
1112
use App\Core\Service\Crud\PanelCrudService;
1213
use App\Core\Service\Crud\ProductCopyService;
1314
use App\Core\Service\Pterodactyl\PterodactylService;
@@ -68,6 +69,7 @@ public function configureFields(string $pageName): iterable
6869
);
6970
$internalCurrency = $this->settingService
7071
->getSetting(SettingEnum::INTERNAL_CURRENCY_NAME->value);
72+
7173
$fields = [
7274
FormField::addTab($this->translator->trans('pteroca.crud.product.details'))
7375
->setIcon('fa fa-info-circle'),
@@ -94,6 +96,7 @@ public function configureFields(string $pageName): iterable
9496
->setRequired(false)
9597
->setHelp($this->translator->trans('pteroca.crud.product.banner_help'))
9698
->setColumns(5),
99+
$this->getProductHelpPanel(),
97100

98101
FormField::addTab($this->translator->trans('pteroca.crud.product.server_resources'))
99102
->setIcon('fa fa-server'),
@@ -133,6 +136,39 @@ public function configureFields(string $pageName): iterable
133136
->hideOnIndex()
134137
->setHelp($this->translator->trans('pteroca.crud.product.schedules_hint'))
135138
->setColumns(4),
139+
$this->getProductHelpPanel(),
140+
141+
FormField::addTab($this->translator->trans('pteroca.crud.product.pricing'))
142+
->setIcon('fa fa-money'),
143+
CollectionField::new('staticPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_static_plan'), $internalCurrency))
144+
->setEntryType(ProductPriceFixedFormType::class)
145+
->allowAdd()
146+
->allowDelete()
147+
->onlyOnForms()
148+
->setColumns(6)
149+
->setHelp($this->translator->trans('pteroca.crud.product.price_static_plan_hint'))
150+
->setRequired(true)
151+
->setEntryIsComplex(),
152+
CollectionField::new('dynamicPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_dynamic_plan'), $internalCurrency))
153+
->setEntryType(ProductPriceDynamicFormType::class)
154+
->allowAdd()
155+
->allowDelete()
156+
->setSortable(true)
157+
->onlyOnForms()
158+
->setColumns(6)
159+
->setHelp($this->translator->trans('pteroca.crud.product.price_dynamic_plan_hint') . $this->getExperimentalFeatureMessage())
160+
->setRequired(true)
161+
->setEntryIsComplex(),
162+
CollectionField::new('slotPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_slot_plan'), $internalCurrency))
163+
->setEntryType(ProductPriceSlotFormType::class)
164+
->allowAdd()
165+
->allowDelete()
166+
->onlyOnForms()
167+
->setColumns(6)
168+
->setHelp($this->translator->trans('pteroca.crud.product.price_slot_plan_hint'))
169+
->setRequired(true)
170+
->setEntryIsComplex(),
171+
$this->getProductHelpPanel(),
136172

137173
FormField::addTab($this->translator->trans('pteroca.crud.product.product_connections'))
138174
->setIcon('fa fa-link'),
@@ -163,31 +199,10 @@ public function configureFields(string $pageName): iterable
163199
->setFormTypeOption('attr', ['class' => 'egg-selector'])
164200
->setColumns(12),
165201

166-
FormField::addTab($this->translator->trans('pteroca.crud.product.pricing'))
167-
->setIcon('fa fa-money'),
168-
CollectionField::new('staticPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_static_plan'), $internalCurrency))
169-
->setEntryType(ProductPriceFixedFormType::class)
170-
->allowAdd()
171-
->allowDelete()
172-
->onlyOnForms()
173-
->setColumns(6)
174-
->setHelp($this->translator->trans('pteroca.crud.product.price_static_plan_hint'))
175-
->setRequired(true)
176-
->setEntryIsComplex(),
177-
CollectionField::new('dynamicPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_dynamic_plan'), $internalCurrency))
178-
->setEntryType(ProductPriceDynamicFormType::class)
179-
->allowAdd()
180-
->allowDelete()
181-
->setSortable(true)
182-
->onlyOnForms()
183-
->setColumns(6)
184-
->setHelp($this->translator->trans('pteroca.crud.product.price_dynamic_plan_hint') . $this->getExperimentalFeatureMessage())
185-
->setRequired(true)
186-
->setEntryIsComplex(),
187-
188202
DateTimeField::new('createdAt', $this->translator->trans('pteroca.crud.product.created_at'))->onlyOnDetail(),
189203
DateTimeField::new('updatedAt', $this->translator->trans('pteroca.crud.product.updated_at'))->onlyOnDetail(),
190204
DateTimeField::new('deletedAt', $this->translator->trans('pteroca.crud.product.deleted_at'))->onlyOnDetail(),
205+
$this->getProductHelpPanel(),
191206
];
192207

193208
if (!empty($this->flashMessages)) {

src/Core/Controller/Panel/ServerProductCrudController.php

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Core\Enum\UserRoleEnum;
99
use App\Core\Form\ServerProductPriceDynamicFormType;
1010
use App\Core\Form\ServerProductPriceFixedFormType;
11+
use App\Core\Form\ServerProductPriceSlotFormType;
1112
use App\Core\Repository\ServerProductRepository;
1213
use App\Core\Service\Crud\PanelCrudService;
1314
use App\Core\Service\Pterodactyl\PterodactylClientService;
@@ -107,6 +108,37 @@ public function configureFields(string $pageName): iterable
107108

108109
...$this->getServerBuildFields(),
109110

111+
FormField::addTab($this->translator->trans('pteroca.crud.product.pricing'))
112+
->setIcon('fa fa-money'),
113+
CollectionField::new('staticPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_static_plan'), $internalCurrency))
114+
->setEntryType(ServerProductPriceFixedFormType::class)
115+
->allowAdd()
116+
->allowDelete()
117+
->onlyOnForms()
118+
->setColumns(6)
119+
->setHelp($this->translator->trans('pteroca.crud.product.price_static_plan_hint'))
120+
->setRequired(true)
121+
->setEntryIsComplex(),
122+
CollectionField::new('dynamicPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_dynamic_plan'), $internalCurrency))
123+
->setEntryType(ServerProductPriceDynamicFormType::class)
124+
->allowAdd()
125+
->allowDelete()
126+
->setSortable(true)
127+
->onlyOnForms()
128+
->setColumns(6)
129+
->setHelp($this->translator->trans('pteroca.crud.product.price_dynamic_plan_hint') . $this->getExperimentalFeatureMessage())
130+
->setRequired(true)
131+
->setEntryIsComplex(),
132+
CollectionField::new('slotPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_slot_plan'), $internalCurrency))
133+
->setEntryType(ServerProductPriceSlotFormType::class)
134+
->allowAdd()
135+
->allowDelete()
136+
->onlyOnForms()
137+
->setColumns(6)
138+
->setHelp($this->translator->trans('pteroca.crud.product.price_slot_plan_hint'))
139+
->setRequired(true)
140+
->setEntryIsComplex(),
141+
110142
FormField::addTab($this->translator->trans('pteroca.crud.product.product_connections'))
111143
->setIcon('fa fa-link'),
112144
FormField::addPanel(sprintf(
@@ -141,28 +173,6 @@ public function configureFields(string $pageName): iterable
141173
->setRequired(true)
142174
->setFormTypeOption('attr', ['class' => 'egg-selector'])
143175
->setColumns(12),
144-
145-
FormField::addTab($this->translator->trans('pteroca.crud.product.pricing'))
146-
->setIcon('fa fa-money'),
147-
CollectionField::new('staticPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_static_plan'), $internalCurrency))
148-
->setEntryType(ServerProductPriceFixedFormType::class)
149-
->allowAdd()
150-
->allowDelete()
151-
->onlyOnForms()
152-
->setColumns(6)
153-
->setHelp($this->translator->trans('pteroca.crud.product.price_static_plan_hint'))
154-
->setRequired(true)
155-
->setEntryIsComplex(),
156-
CollectionField::new('dynamicPrices', sprintf('%s (%s)', $this->translator->trans('pteroca.crud.product.price_dynamic_plan'), $internalCurrency))
157-
->setEntryType(ServerProductPriceDynamicFormType::class)
158-
->allowAdd()
159-
->allowDelete()
160-
->setSortable(true)
161-
->onlyOnForms()
162-
->setColumns(6)
163-
->setHelp($this->translator->trans('pteroca.crud.product.price_dynamic_plan_hint') . $this->getExperimentalFeatureMessage())
164-
->setRequired(true)
165-
->setEntryIsComplex(),
166176
];
167177

168178
return $fields;

0 commit comments

Comments
 (0)