Skip to content

Commit b6fa080

Browse files
committed
feat: update
- Added configurable default avatar provider in admin settings, supporting multiple options including Gravatar and custom URLs. - Introduced admin SSO token settings to configure default token lifetime and expiration parameters. - Enhanced admin list pages to remember search, filters, sort, and pagination using localStorage. - Implemented configurable default Docker image for spells, ensuring correct runtime on server installs. - Updated server startup image selection to lock to spell-configured images by default, with an option for custom input. - Improved admin user edit page with a Potential Alts tab to identify accounts sharing IP addresses. - Added functionality to block new registrations based on device/browser limits and allow admins to clear device fingerprints. - Enhanced CHANGELOG to reflect these updates.
1 parent b14efdd commit b6fa080

91 files changed

Lines changed: 4014 additions & 956 deletions

File tree

Some content is hidden

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

.github/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ For installation instructions, system requirements, and complete guides, please
4747

4848
<!-- COUNT-STATS:START -->
4949

50-
_Last updated: 2026-06-07T11:19:53.648Z_
50+
_Last updated: 2026-06-07T20:45:33.009Z_
5151

5252
| Extension | Files | Lines |
5353
| --- | ---: | ---: |
54-
| `.php` | 519 | 132,432 |
55-
| `.tsx` | 368 | 118,265 |
56-
| `.ts` | 78 | 9,438 |
54+
| `.php` | 525 | 133,954 |
55+
| `.tsx` | 374 | 119,816 |
56+
| `.ts` | 81 | 9,861 |
5757
| `.yaml` | 3 | 5,908 |
5858
| `.rs` | 16 | 3,395 |
59-
| `.sql` | 140 | 2,403 |
59+
| `.sql` | 142 | 2,454 |
6060
| `.yml` | 18 | 1,877 |
61-
| `.css` | 7 | 445 |
62-
| **Total** | 1,149 | 274,163 |
61+
| `.css` | 7 | 459 |
62+
| **Total** | 1,166 | 277,724 |
6363

6464
<!-- COUNT-STATS:END -->
6565

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
### Added
66

7+
- Configurable default avatar provider in admin settings (Gravatar by default, with panel logo, UI Avatars, RoboHash, DiceBear, and custom URL template support). by @nayskutzu
8+
- Admin SSO token settings: configure the default token lifetime in App settings, and optionally pass `expires_in` (minutes, 1–1440) when calling `POST /api/admin/users/{uuid}/sso-token`. by @nayskutzu
9+
- Admin list pages (users, servers, VDS nodes, nodes, VM instances, spells, roles, tickets, and others) now remember search, filters, sort, and pagination in localStorage. by @nayskutzu
10+
- Spells now support a configurable default Docker image (star icon on the spell Docker tab) so new servers use the correct runtime on first install when game images are updated. by @nayskutzu
11+
- Server startup Docker image selection is locked to spell-configured images by default; admins can enable custom Docker image input under Settings → Servers (`server_allow_custom_docker_image`, default off). by @nayskutzu
12+
- Admin server edit now configures Docker image on the Startup tab; create validates the selected image on submit so spell defaults (e.g. Java 25) persist correctly. by @nayskutzu
713
- Admin settings to configure the login page default sign-in method, display order of authentication options, and hidden methods (local, LDAP, passkey, email code, Discord, OIDC). by @nayskutzu
814
- Admins now get clearer notifications (including email notifications) when there are open support tickets; the ticket section can show all open tickets if the user is an admin. by @nayskutzu
915
- Users now receive an email notification when their support ticket is replied to, closed, or reopened. by @nayskutzu
@@ -17,11 +23,20 @@
1723
- Styles for the auth LDAP button were improved. by @nayskutzu
1824
- Improved the amazing new role editor page that allows you to edit roles and permissions and thats idiot proof (I HOPE). by @nayskutzu
1925
- Timezones were missmatched into the server backups page. by @nayskutzu
26+
- Now you can sort users by newest and oldest and by username. by @nayskutzu
27+
- Now you can click on the owner's name in the server details page to view the user's profile. by @nayskutzu
28+
- Admin user edit page now includes a Potential Alts tab that finds other accounts sharing IP addresses with the viewed user. by @nayskutzu
29+
- Potential alt detection now also compares server activity IPs and hidden browser sync identifiers (localStorage + cookie) collected across the panel. by @nayskutzu
30+
- Registration can block new accounts when a browser/device already has the maximum allowed panel accounts, pointing users to their main account or support. by @nayskutzu
31+
- Admins can clear device fingerprint records per user or globally from the Users admin area. by @nayskutzu
2032

2133
### Fixed
2234

35+
- User server startup page now shows admin-assigned Docker images (e.g. Java 25) even when they are not in the spell image list, matching admin server edit behavior. by @nayskutzu
36+
- Admin spell export now downloads the PTDL_v2-compatible export file instead of the raw database record, and legacy raw exports can still be imported. by @nayskutzu
2337
- Admin Updates page plugin bulk updates now call the correct online install API (`/api/admin/plugins/online/install`) instead of a non-existent route. by @nayskutzu
2438
- Issues related to Discord OAuth2 account linking and registration were fixed. by @nayskutzu
39+
- Small issues regarding spells export behavior were fixed. by @nayskutzu
2540

2641
## v1.3.7.3 STABLE
2742

backend/app/Chat/Activity.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ public static function getActivitiesByContextLikeAndNameIn(string $contextLike,
102102
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
103103
}
104104

105+
/**
106+
* @return string[]
107+
*/
108+
public static function getDistinctIpsByUserUuid(string $userUuid): array
109+
{
110+
$pdo = Database::getPdoConnection();
111+
$stmt = $pdo->prepare(
112+
'SELECT DISTINCT ip_address FROM ' . self::$table
113+
. ' WHERE user_uuid = :user_uuid AND ip_address IS NOT NULL AND ip_address != \'\' ORDER BY ip_address'
114+
);
115+
$stmt->execute(['user_uuid' => $userUuid]);
116+
117+
return array_column($stmt->fetchAll(\PDO::FETCH_ASSOC), 'ip_address');
118+
}
119+
105120
public static function getCountByUserUuid(string $userUuid, ?string $search = null): int
106121
{
107122
$pdo = Database::getPdoConnection();

backend/app/Chat/ServerActivity.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,25 @@ public static function getActivitiesByEvent(string $event, int $limit = 100): ar
203203
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
204204
}
205205

206+
/**
207+
* @return string[]
208+
*/
209+
public static function getDistinctIpsByUserId(int $userId): array
210+
{
211+
if ($userId <= 0) {
212+
return [];
213+
}
214+
215+
$pdo = Database::getPdoConnection();
216+
$stmt = $pdo->prepare(
217+
'SELECT DISTINCT ip FROM ' . self::$table
218+
. ' WHERE user_id = :user_id AND ip IS NOT NULL AND ip != \'\' ORDER BY ip'
219+
);
220+
$stmt->execute(['user_id' => $userId]);
221+
222+
return array_column($stmt->fetchAll(\PDO::FETCH_ASSOC), 'ip');
223+
}
224+
206225
/**
207226
* Get activities by user ID.
208227
*

backend/app/Chat/Spell.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,73 @@ public static function count(array $conditions): int
599599
return (int) $stmt->fetchColumn();
600600
}
601601

602+
/**
603+
* Parse docker_images JSON into a list of image tags.
604+
*
605+
* @return list<string>
606+
*/
607+
public static function parseDockerImages(?string $dockerImagesJson): array
608+
{
609+
if ($dockerImagesJson === null || trim($dockerImagesJson) === '') {
610+
return [];
611+
}
612+
613+
try {
614+
$dockerImages = json_decode($dockerImagesJson, true);
615+
if (!is_array($dockerImages) || $dockerImages === []) {
616+
return [];
617+
}
618+
619+
return array_values(array_filter(
620+
$dockerImages,
621+
static fn ($value) => is_string($value) && trim($value) !== ''
622+
));
623+
} catch (\Exception $e) {
624+
return [];
625+
}
626+
}
627+
628+
/**
629+
* Resolve the default Docker image for a spell.
630+
* Uses default_docker_image when set and valid, otherwise the first docker_images entry.
631+
*/
632+
public static function resolveDefaultDockerImage(array $spell): ?string
633+
{
634+
$dockerImages = self::parseDockerImages($spell['docker_images'] ?? null);
635+
636+
if (!empty($spell['default_docker_image']) && is_string($spell['default_docker_image'])) {
637+
$default = trim($spell['default_docker_image']);
638+
if ($default !== '' && ($dockerImages === [] || in_array($default, $dockerImages, true))) {
639+
return $default;
640+
}
641+
}
642+
643+
return $dockerImages[0] ?? null;
644+
}
645+
646+
/**
647+
* Ensure default_docker_image references a value from docker_images when possible.
648+
*/
649+
public static function sanitizeDefaultDockerImage(?string $defaultDockerImage, ?string $dockerImagesJson): ?string
650+
{
651+
$images = self::parseDockerImages($dockerImagesJson);
652+
if ($images === []) {
653+
return null;
654+
}
655+
656+
if ($defaultDockerImage === null || trim($defaultDockerImage) === '') {
657+
return $images[0];
658+
}
659+
660+
$defaultDockerImage = trim($defaultDockerImage);
661+
662+
if (!in_array($defaultDockerImage, $images, true)) {
663+
return $images[0];
664+
}
665+
666+
return $defaultDockerImage;
667+
}
668+
602669
/**
603670
* Sanitize data for logging by excluding sensitive fields.
604671
*/

0 commit comments

Comments
 (0)