Compare commits

...

577 Commits

Author SHA1 Message Date
Charles
7a95712ed0
composer update (#1966) 2025-12-08 10:46:33 -05:00
MartinOscar
b6aeb954c4
Disable Captcha & Oauth Settings actions when read only (#1968) 2025-12-08 11:33:29 +01:00
MartinOscar
7c0d53c796
Use Policies rather then overriding can*() functions (#1837)
Co-authored-by: Boy132 <mail@boy132.de>
2025-12-07 14:53:13 -05:00
MartinOscar
71bd267166
Fix docker entrypoint ASSET_URL not APP_ASSET (#1965) 2025-12-06 20:54:40 +01:00
MartinOscar
25d8adbcc6
Add ignoreRecord to CopyFrom relationships (#1964) 2025-12-06 20:17:05 +01:00
Michael (Parker) Parker
27b896c6d2
Update docker image (#1917)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-12-05 22:50:49 -05:00
MartinOscar
bda2f9a699
Fix Save Notification icon & Cleanup (#1959) 2025-12-03 02:23:09 +01:00
Boy132
04375439d7
Add pagination to server list (#1955) 2025-12-02 08:26:45 +01:00
Boy132
0fe8917668
Only allow server transfers to accessible nodes (#1951) 2025-12-02 08:26:19 +01:00
Boy132
c312ef493f
Replace file_get_contents with Http (#1953) 2025-12-02 08:25:53 +01:00
PalmarHealer
6c02f9a663
feat: Add toggle for automatic allocation creation in panel settings (#1884) 2025-12-01 08:59:07 +01:00
Charles
2dd6e3d4fc
Add progress bars to client area (#1924) 2025-11-28 18:04:40 -05:00
Quinten
575e5bdb0d
Fix typo in suspend method documentation (#1944) 2025-11-28 18:39:49 +01:00
Boy132
efa8eef57c
Add custom render hooks to our footer (#1942) 2025-11-27 23:55:59 +01:00
MartinOscar
d16e7dd876
Better Role icons (#1936)
Fix `Role` class path for `::getNavigationIcon()`
Allow to register custom model icons
Co-authored-by: Boy132 <mail@boy132.de>
2025-11-27 23:51:57 +01:00
Charles
897b95ec13
Change Admin Actions to IconButtons (#1900) 2025-11-27 16:44:05 -05:00
MartinOscar
97f5a0f20b
Fix Policies modelname are case sensitive (#1937) 2025-11-27 17:51:16 +01:00
MartinOscar
d0af45a0c7
Delete ssh keys shouldn't be a POST & Cleanup routes (#1934) 2025-11-27 16:26:47 +01:00
MartinOscar
78ab098d02
Fix Egg select_startup default & update state (#1933) 2025-11-27 16:26:40 +01:00
Charles
cdccca8fa2
composer update (#1928) 2025-11-24 15:34:33 -05:00
Boy132
bb33bcca4f
Refactor schedule tasks (#1911) 2025-11-24 14:42:47 +01:00
Boy132
611b8649e0
Improve "first task" checks (#1926) 2025-11-24 00:48:32 +01:00
MartinOscar
b1b723485f
Fix EditFiles breadcrumbs incorrect url (#1925) 2025-11-24 00:42:04 +01:00
hallo123wert
25c8ff3f1f
Fix: No live preview for fonts (#1921) 2025-11-24 00:06:08 +01:00
Boy132
07763d912b
Add back 2fa requirement middleware (#1897)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-11-24 00:01:29 +01:00
Charles
65bb99e2b0
Add server icons (#1906)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-11-21 16:48:20 -05:00
MartinOscar
a195b56f93
Fix permission checks on Client side (#1913) 2025-11-19 22:28:13 +01:00
Boy132
d78c977d75
Make sure to load FilamentServiceProvider before panel providers (#1907) 2025-11-17 11:41:11 +01:00
PalmarHealer
5e25ea4a43
fix: use port range on free allocation lookup (#1882)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-11-17 10:56:48 +01:00
Luke
886836c60a
Remove 'required' rule from egg-garrys-mod.yaml (#1902) 2025-11-16 11:59:01 -05:00
Charles
f575e3edfa
composer update (#1901) 2025-11-15 07:17:29 -05:00
Boy132
1a66b3fab4
Encode file contents to utf-8 (#1896) 2025-11-13 19:05:23 +01:00
Boy132
0f1efcfd15
Remove old update command (#1898) 2025-11-13 19:05:04 +01:00
PalmarHealer
3f89c6ddd8
fix: bypass tenant scoping in allocation queries (#1883) 2025-11-13 04:48:25 +00:00
mristau
20cb7850ef
don't try to bulk update if egg doesn't even have a url (#1887) 2025-11-13 04:47:38 +00:00
hallo123wert
108dad09fb
Fix: Duplicate bulk deletion notifications (#1881) 2025-11-13 04:46:55 +00:00
Boy132
445c9364bc
Make sure case for role permissions is correct (#1892) 2025-11-11 18:18:29 +01:00
MartinOscar
acec117b1e
Use public disk for console fonts upload (#1893) 2025-11-11 18:13:52 +01:00
Boy132
89199dfbe5
Fix jar mime type (#1891) 2025-11-11 11:23:56 +01:00
Boy132
216a3484f1
Fix node_ids rule for database host (#1885) 2025-11-10 12:25:58 +01:00
Boy132
5c3b0919aa
Fix allocations by admins aren't locked by default (#1879) 2025-11-09 18:29:46 +01:00
Charles
f4ee33fa4f
Hide new allocation action if server has 0 allocations. (#1878) 2025-11-09 12:11:14 -05:00
Charles
d8368c4cec
Do no use stock notifications on actions (#1877) 2025-11-09 12:08:25 -05:00
Charles
aa35d7d001
Fix creating mounts (#1876) 2025-11-09 11:14:44 -05:00
JoanFo
3c25b43b46
Repair webhooks once again (#1815)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-11-09 09:35:00 -05:00
Charles
0891db5342
Reimplement Drag & Drop for file uploading 🎉 (#1858) 2025-11-09 09:24:12 -05:00
exefer
172436e012
Fix typo in failed upload message (#1874) 2025-11-09 12:58:56 +00:00
Charles
2b5403a4da
Replace current panel log viewer with new and improved log viewer (#1834) 2025-11-08 19:31:51 -05:00
Charles
a30c45fbbe
Add session key to use last used node, instead of latest created node (#1869)
Co-authored-by: Lance Pioch <git@lance.sh>
2025-11-08 17:09:41 -05:00
Copilot
b06df23823
Add bulk IP update action for node allocations (#1845)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: notAreYouScared <1757840+notAreYouScared@users.noreply.github.com>
Co-authored-by: Charles <charles@pelican.dev>
2025-11-08 16:53:12 -05:00
exefer
1ff965611e
Fix typo in DNS help text (#1868)
Authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-11-08 22:40:23 +01:00
Boy132
cec141889a
Allow admins to "lock" allocations (#1811) 2025-11-08 21:54:41 +01:00
Charles
6ed84b5584
Add wings diagnostics retrieving to Edit Node page (#1865)
Co-authored-by: Boy132 <mail@boy132.de>
2025-11-08 15:47:40 -05:00
Lance Pioch
49f24e37b6
Laravel 12.37.0 Shift (#1864)
Co-authored-by: Shift <shift@laravelshift.com>
2025-11-06 08:43:02 -05:00
Boy132
e0c4e47a6c
Fix directAccessibleServers returning duplicates (#1862) 2025-11-05 16:19:03 +01:00
Boy132
4bda7cba75
Allow to "embed" server list (#1860) 2025-11-05 16:18:44 +01:00
Boy132
852f7beb39
Allow to register "special file" alert banners (#1861) 2025-11-04 12:48:18 +01:00
mristau
d61583cd7b
add server description to grid view too (#1851) 2025-11-04 06:03:50 -05:00
Charles
21f9f259d0
Add Egg Images (#1849) 2025-11-03 12:32:11 -05:00
M41den
b2aff5445b
Fix admin serverlist search (#1854) 2025-11-03 06:50:08 -05:00
Boy132
1f26750a2a
Add api endpoint for updating username (#1826) 2025-11-03 08:31:07 +01:00
Charles
6d83c6d908
composer update (#1856) 2025-11-02 18:53:24 -05:00
Copilot
574a391e73
Add border-radius to activity log avatars (#1848)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: notAreYouScared <1757840+notAreYouScared@users.noreply.github.com>
2025-11-02 15:13:36 -05:00
PalmarHealer
605fcbe61a
feat: Add mixed navigation type with admin-configurable defaults (#1850) 2025-10-31 14:12:54 -04:00
Letter N
0214b127e4
Add setup wizard to all oauth providers (#1801) 2025-10-31 14:09:20 -04:00
MartinOscar
e6aa76ef2c
Refactor: add FilamentServiceProvider & globally make Select native(false) (#1836) 2025-10-29 23:23:18 +01:00
Boy132
d38075e3cb
Add boolean cast to read_only toggle buttons (#1844) 2025-10-28 16:06:33 +01:00
M41den
0fec6adc3e
Fix 500 "No route found" when creating db host (#1841) 2025-10-28 08:48:46 -04:00
M41den
5e3c22ea5e
Fix weird postgres behavior when selecting mounts (#1842)
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-10-28 08:48:35 -04:00
MartinOscar
d1a808a746
Hide User reset password Action on create Operation (#1840) 2025-10-28 01:38:37 +01:00
MartinOscar
3bcdeea800
Leverage user() helper (#1832) 2025-10-26 16:24:34 +01:00
Charles
e6bd6e416f
Add archive extension selection (#1828) 2025-10-24 12:39:30 -04:00
Boy132
8e006ac32d
Fix user permissions service (#1819) 2025-10-22 16:00:51 +02:00
Boy132
430f28a847
Add "cancel" button to profile (#1821) 2025-10-22 16:00:31 +02:00
Charles
1a4fa5e67a
Replace Xtermjs canvas with webgl (#1807)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-10-14 20:35:26 -04:00
Charles
a65469b33b
Remove duplicate translation entries (#1812) 2025-10-14 06:58:33 -04:00
Charles
d587cf3ee5
composer update (#1806) 2025-10-13 17:34:21 -04:00
Boy132
2cd9fa2cde
Only keep the last 120 stored stats (#1805) 2025-10-13 22:50:16 +02:00
MartinOscar
d735e858a2
Rename Create actions in EditProfile (#1804) 2025-10-13 00:58:22 +02:00
MartinOscar
317fa46894
Use tenantMiddleware instead of manually fetching tenant query param (#1799) 2025-10-12 18:07:10 +02:00
Letter N
e589f972fb
Add changelog preview when a new update is available (#1792)
Co-authored-by: Boy132 <mail@boy132.de>
2025-10-11 21:34:38 -04:00
MartinOscar
266e3779d5
Fix 500 when oauth is null (#1798) 2025-10-11 22:06:51 +02:00
MartinOscar
4652680a7b
Add cpu helper on EditServer & move helperText to hintIcon on Create (#1795) 2025-10-10 22:46:47 +02:00
JoanFo
e99f7179c6
Topbar removed if using sidebar (#1789)
Co-authored-by: Boy132 <mail@boy132.de>
2025-10-10 16:37:14 -04:00
Charles
1f56b8e114
Language Update (#1784) 2025-10-08 16:00:47 -04:00
Charles
574e03a986
composer update (#1782) 2025-10-08 11:12:13 -04:00
Charles
05f3422dda
Add Laravel/Filament Log Viewer (#1778)
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-10-08 06:18:20 -04:00
Charles
dbe4bdd62d
General Edit User Improvements (#1779)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: Boy132 <mail@boy132.de>
2025-10-08 05:04:52 -04:00
Boy132
f6710dbbe4
Improve time offset ux (#1772)
Co-authored-by: Lance Pioch <git@lance.sh>
2025-10-08 08:55:37 +02:00
Charles
e4f807b297
Change node config to use Code Entry (#1781) 2025-10-07 22:25:16 -04:00
Boy132
cd965678b7
Allow multiple startup commands per egg (#1656) 2025-10-07 23:42:28 +02:00
Boy132
a58ae874f3
Add own endpoint for exporting eggs (#1760) 2025-10-07 23:41:28 +02:00
Charles
432fb8a514
Filament v4.1.4 (#1780) 2025-10-07 17:40:26 -04:00
MartinOscar
bb02ec4c6c
Add user() helper (#1768) 2025-10-07 17:12:31 -04:00
Charles
69b669e345
v4.1.2 + upgrade (#1775) 2025-10-06 06:20:18 -04:00
Boy132
80993f38a9
Add sudo to crontab command (#1773) 2025-10-03 00:03:22 +02:00
Boy132
19103b16b8
Allow both nodes for server requests when doing transfers (#1701) 2025-10-02 17:55:20 +02:00
Boy132
246997754e
Remove "custom" email views (#1763) 2025-10-01 10:31:01 +02:00
Boy132
df75dbe2ad
Fix mime type for jar files (#1757) 2025-10-01 10:30:49 +02:00
Charles
f02b58c320
Filament v4.1 (#1761) 2025-09-29 09:29:16 -04:00
Boy132
8aa0fc7fc2
Refresh page after file updates (#1759)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-09-29 15:26:17 +02:00
Boy132
2fc30e14fd
Make sure default variable value is set and that variables are created when viewing server (#1758) 2025-09-29 15:14:18 +02:00
Charles
ec5fd3262a
Add xtermjs Canvas (#1756) 2025-09-28 15:17:02 -04:00
Boy132
81178f81b4
Redirect to previous page when clicking "cancel" on EditFiles page (#1747) 2025-09-28 19:12:05 +02:00
Boy132
5373f1e30a
Switch tenant slug back to short uuid (#1732) 2025-09-28 19:11:41 +02:00
Boy132
9f35f1c3ee
Enable "ordered imports" (#1746) 2025-09-24 13:34:19 +02:00
MartinOscar
a5858a6d9b
Allow clipboard.writeText without HTTPS (#1723) 2025-09-24 01:22:29 +02:00
MartinOscar
e3b3c92dcb
Make tests fail-fast & common env (#1724) 2025-09-24 01:22:19 +02:00
Lance Pioch
42c84c2df5
Laravel 12.31.1 Shift (#1739)
Co-authored-by: Shift <shift@laravelshift.com>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-09-24 01:22:15 +02:00
Boy132
4792542f20
Fix refresh action for egg index select & add refresh action to allocation ip selects (#1736) 2025-09-23 14:56:49 +02:00
Boy132
bb40a5273f
Url encode username in sftp connection string (#1731) 2025-09-22 12:58:54 +02:00
Boy132
e5c24fe8b6
Remove username rules and allow to change it in profile (#1702) 2025-09-21 00:37:42 +02:00
Boy132
c10280af4b
Make allocation select on users server relation manager functional (#1719) 2025-09-19 08:43:29 +02:00
JoanFo
6db1d82738
Fixed webhooks on v4 and nested values (#1704)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-09-18 16:40:24 +02:00
MartinOscar
68f8244298
Fix powerActions visible while loading (#1708) 2025-09-18 16:22:23 +02:00
Boy132
ce393af7a6
Fix join_paths for absolute linux paths (#1715) 2025-09-17 12:35:20 +02:00
Boy132
932809fec5
Add state cast for server condition (#1713) 2025-09-16 21:34:23 +02:00
Charles
3d2390dbcc
Remove table row icons (#1710) 2025-09-16 11:44:59 -04:00
Boy132
d5d50d4150
Collection of smaller v4 fixes (#1684)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: notCharles <charles@pelican.dev>
2025-09-15 23:28:57 +02:00
Boy132
cba8717188
Update security policy (#1707)
Co-authored-by: Lance Pioch <git@lance.sh>
2025-09-15 21:16:03 +02:00
danielkurek
df4543a079
Fix server owner permissions (#1703) 2025-09-15 14:13:00 -04:00
Boy132
8dc99e6390
Sanitize activity log meta data values (on frontend) (#1705) 2025-09-15 15:54:50 +02:00
MartinOscar
8f1ec20e96
Prevent rootAdmins from having other roles & being deleted via the API (#1699) 2025-09-11 12:56:21 +02:00
JoanFo
61dcb9a3ba
Fixed Allocations not calling webhooks on server creation & Object events (#1595) 2025-09-10 10:39:50 -04:00
NerdsCorpx
0e34886d7e
Fix Docker versioning (#1663) 2025-09-10 10:39:22 -04:00
Boy132
806820592f
Only disable "delete backup" when backup hasn't failed (#1686) 2025-09-09 15:01:45 +02:00
Charles
1900c04b71
Filament v4 🎉 (#1651)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: Lance Pioch <git@lance.sh>
2025-09-08 13:12:33 -04:00
Boy132
32eb1abd4a
Improve join_paths helper method (#1668) 2025-09-08 09:03:23 +02:00
MartinOscar
47557021fd
Remove DaemonPowerRepository (#1673) 2025-09-08 08:56:59 +02:00
MartinOscar
2ef81eae1a
Refactor & Catch DatabaseManagementService (#1671)
Co-authored-by: notCharles <charles@pelican.dev>
2025-09-06 22:57:11 +02:00
Charles
420730ba1f
Replace str_random with Str::random (#1676)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-09-06 16:47:54 -04:00
Charles
925ab26fb4
Encode file path in url for folders (#1662) 2025-09-04 17:24:58 -04:00
Charles
2952e22619
Encode file path in url (#1661) 2025-09-04 17:15:46 -04:00
MartinOscar
079eaed010
Fix finish & add translation for Installer title (#1659) 2025-09-04 21:39:10 +02:00
MartinOscar
6671d45651
Fix various Translations & add Installer & add Notifications (#1632) 2025-09-04 20:17:59 +02:00
Boy132
3543b4773a
Rename api key prefixes for better clarity (#1650) 2025-09-04 08:43:06 +02:00
IThundxr
02f788a659
Fix auto deploy docker command not including the container argument (#1584)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-09-03 22:30:18 +02:00
Boy132
7ace3978d8
Remove leftovers from activity log batch (#1649) 2025-09-03 22:26:17 +02:00
Boy132
8f277aaca0
Create custom startup variable field (#1615) 2025-09-02 09:05:36 +02:00
SaurFort
76451fa0ad
fix: Wrong conversion if decimal prefix selected (#1626)
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-08-31 13:51:27 +02:00
Boy132
0104a08ba4
Create custom number format method to catch invalid languages on php 8.4 (#1623) 2025-08-31 13:48:47 +02:00
MartinOscar
5eff006843
Fix activityLog permission name (#1641) 2025-08-31 12:59:48 +02:00
MartinOscar
a8241bf9f3
Fix Installer, Admin & Exit admin redirect (#1640) 2025-08-30 14:37:59 +02:00
MartinOscar
4aae2562ea
Update bug-report logs url (#1630) 2025-08-25 12:13:27 +02:00
Boy132
42db5b328a
Fix translation for invalid schedule cron + cleanup translations for import modal (#1618) 2025-08-18 23:54:25 +02:00
Boy132
bc4dfb3e92
Fix 500 for closeable alert banners (#1620) 2025-08-18 23:53:59 +02:00
Michael (Parker) Parker
3b9c81534f
fix php ini permissions (#1619) 2025-08-17 09:34:41 -05:00
Boy132
f31aa78f6f
Fix gap for profile repeaters (api keys, ssh keys, activity logs) (#1613) 2025-08-15 14:07:23 +02:00
Boy132
b5ebd544f4
Improve translation for "link" and "unlink" (oauth) (#1612) 2025-08-15 14:06:53 +02:00
Boy132
c77a37ec89
Fix & cleanup OAuthController (#1599) 2025-08-14 08:29:58 +02:00
Michael (Parker) Parker
4d78e5dcd1
Merge pull request #1609 from parkervcp/add_fcgi_healthcheck
add missing package for healthcheck
2025-08-13 14:15:44 -05:00
Michael (Parker) Parker
15075b6ab8 re-add file server directive 2025-08-13 13:44:21 -05:00
Lance Pioch
a8f233e204
Laravel 12.23.1 Shift (#1604)
Co-authored-by: Shift <shift@laravelshift.com>
2025-08-13 08:01:48 -04:00
Boy132
795cad43b9
Server creation: Only get node_id from allocation if it is missing (#1598) 2025-08-12 15:02:49 -04:00
Charles
46934d7a85
fix eggs with [] (#1596) 2025-08-12 15:02:41 -04:00
Michael (Parker) Parker
06067f375c Add fcgi package for healthcheck
I missed adding the package to the dockerfile so the healthcheck is failing
2025-08-12 09:08:10 -05:00
Charles
d1df53c683
fix lang (#1590) 2025-08-11 18:12:33 -04:00
Charles
b03d2cf919
composer update + update jwt (#1587) 2025-08-11 16:57:59 -04:00
Boy132
27a8423f55
Fix container status caching (#1588) 2025-08-11 22:21:52 +02:00
Michael (Parker) Parker
ad70934430
Update healthcheck (#1571) 2025-08-10 15:30:58 -04:00
Boy132
900f8d0fe1
Cleanup remote api requests (#1579) 2025-08-09 17:53:45 -04:00
Lance Pioch
6a4ac515a7
Laravel 12.22.1 Shift (#1580)
Co-authored-by: Shift <shift@laravelshift.com>
2025-08-09 17:53:29 -04:00
Boy132
7c315ac995
Auto create missing users when using oauth (#1573) 2025-08-07 11:22:30 +02:00
Boy132
49e9440e0f
Fix server creation without deployment (#1569) 2025-08-07 11:16:32 +02:00
Alex Smith
02e3e43f1e
Update egg-vanilla-minecraft.yaml (#1574)
Co-authored-by: Charles <charles@pelican.dev>
2025-08-05 17:27:00 -04:00
Charles
8eddef6f04
Update minecraft eggs to support ipv4/ipv6 (#1577) 2025-08-05 17:26:49 -04:00
Boy132
d2f1936bbf
Add abstract base class for panel providers (#1576) 2025-08-05 23:17:34 +02:00
Charles
36863f94c0
Allow user selectable navigation type (#1572)
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-08-05 08:56:31 -04:00
Charles
75863c50d1
Load app.css before filament styles (#1575) 2025-08-04 18:11:34 -04:00
Charles
ec0727b406
Allow eggs to be exported/imported as YAML (#1535) 2025-08-04 07:32:10 -04:00
Boy132
5b2e9d94ca
Cleanup and update node packages (#1557) 2025-08-04 11:51:18 +02:00
Charles
8840d109ef
Client area translations (#1554) 2025-08-01 07:26:14 -04:00
Boy132
71225bd2dc
Refactor AlertBanner to be ViewComponent (#1555) 2025-07-31 23:54:53 +02:00
JoanFo
bab8ec6e18
Fixed not working variables on DiscordWebhooks and headers. (#1516)
Co-authored-by: notCharles <charles@pelican.dev>
2025-07-31 15:47:46 -06:00
Awhikax
d307a2095b
Allow for backups to be renamed (#1546) 2025-07-31 15:47:15 -06:00
Hasyirin Fakhriy
a777f4e0ff
remove maxlength rule from egg variable's default_value field (#1559) 2025-07-31 15:45:28 -06:00
Boy132
86a71afc6c
Cleanup formatResource (#1563) 2025-07-31 23:02:27 +02:00
Hasyirin Fakhriy
88943563c7
Add tags field to eggs transformer. (#1550) 2025-07-22 14:39:18 -04:00
Lance Pioch
20071a64fa
Laravel 12.21.0 Shift (#1551)
Co-authored-by: Shift <shift@laravelshift.com>
2025-07-22 14:39:02 -04:00
Charles
d0d3418e03
Move header actions to iconbuttons (#1541) 2025-07-22 12:31:23 -04:00
Boy132
083e3dc62a
Update contributing guide (#1548) 2025-07-22 15:45:29 +02:00
Charles
d7e60f2456
Fix Console Fit... again (#1537) 2025-07-19 15:40:18 -04:00
Charles
38e746240d
Fix delayed status update, and graphs (#1536) 2025-07-19 14:45:50 -04:00
Lance Pioch
986063dce4
Use default startup variable value when creating server via api (#1518)
Co-authored-by: Boy132 <mail@boy132.de>
2025-07-19 13:58:04 -04:00
Charles
71d0326cb2
Call FitConsole after page load (#1534) 2025-07-19 13:04:22 -04:00
Boy132
62ca53eeaf
Server Policy: Only do owner check if checking for subuser permissions (#1521) 2025-07-19 18:52:28 +02:00
Boy132
9f2305f351
Use filaments password broker for reset link token when creating subuser (#1498) 2025-07-19 18:51:42 +02:00
Boy132
340d1b543c
Add import & export for schedules (#1530) 2025-07-19 16:48:21 +02:00
Boy132
61098b11f2
Add migration to clear password from auth:fail logs (#1533) 2025-07-19 16:47:49 +02:00
Boy132
4d03d6b948
Improve Mounts API (#1531) 2025-07-18 13:50:31 +02:00
Boy132
1f67054777
Fix phpstan (#1532) 2025-07-18 13:49:26 +02:00
Charles
4a9814f16c
Move fullscreen file editor down to not cover top bar (#1527) 2025-07-18 05:05:09 -04:00
Boy132
e0697d3288
Cleanup & fix server deployment (#1497) 2025-07-18 08:23:48 +02:00
Boy132
d165da20ec
Improve schedule form (#1514) 2025-07-18 08:23:08 +02:00
Charles
ae27b179fe
Fix memory leak caused by shift pr (#1528) 2025-07-17 17:41:41 -04:00
Rain
1113ffe0f7
Filters sensitive credential fields from auth:fail logs (#1504) 2025-07-17 16:45:38 -04:00
Lance Pioch
5531bc0ba1
Laravel 12.20.0 Shift (#1500)
Co-authored-by: Shift <shift@laravelshift.com>
2025-07-17 16:44:27 -04:00
Charles
a3819122db
Fix power actions (#1517) 2025-07-15 05:02:55 -04:00
MartinOscar
c5528a61f3
Filter out already used ips with the same port (#1496) 2025-07-10 08:59:46 +02:00
Boy132
5a7c6ac6e5
Improve turnstile error handling (+ cleanup) (#1501) 2025-07-09 13:51:43 +02:00
Boy132
5e8cccef19
Fix options for script_entry Select (#1505) 2025-07-09 09:14:46 +02:00
Charles
0ccb248d91
Add Languages (#1499)
Co-authored-by: Boy132 <mail@boy132.de>
2025-07-08 21:16:11 -04:00
Boy132
514d961c24
Add migration to match node ports (#1489) 2025-07-07 08:37:45 +02:00
Charles
f8e802afcd
Fix table view power actions (#1490) 2025-07-06 19:03:09 -04:00
Boy132
556551b4f3
Add SSH Keys to Profile (#1478) 2025-07-06 22:51:45 +02:00
Boy132
23ddded61e
Replace gethostbynamel with dns_get_record (#1479) 2025-07-06 22:42:59 +02:00
JoanFo
c5aa8a3980
DiscordWebhooks (#1355)
Co-authored-by: notCharles <charles@pelican.dev>
Co-authored-by: Lance Pioch <lancepioch@gmail.com>
Co-authored-by: Boy132 <mail@boy132.de>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-07-05 12:42:34 -04:00
MartinOscar
21ac75efae
Nullable eggFeatures in FeatureService (#1485) 2025-07-05 14:57:08 +02:00
JoanFo
9655700cde
Nullable allocation in server-entry blade² (#1486) 2025-07-05 14:25:33 +02:00
JoanFo
c9b7e979c0
Nullable allocation in server-entry blade (#1484) 2025-07-05 14:14:43 +02:00
MartinOscar
77a3b0640d
Add dehydratedWhenHidden to serverVariable TextInput & Select (#1476) 2025-07-03 08:55:18 +02:00
pelican-vehikl
de4cb38766
Refactor Providers to be a singleton (#1327) 2025-07-01 21:33:11 -04:00
Charles
74bd7f9991
Move console js to built app.js file. (#1471) 2025-07-01 17:13:44 -04:00
Charles
ba7f814300
back port power actions from v4 branch (#1470) 2025-06-28 10:41:16 -04:00
MartinOscar
cdcd1c521e
Add FileExistsException & Fix error reporting (#1417) 2025-06-26 21:04:33 +02:00
Boy132
4d0aabe91e
Schedule task improvements (#1468) 2025-06-26 17:00:37 +02:00
Boy132
68f72b9b4d
Add "egg index" and dropdown to egg importer (#1451)
Co-authored-by: notCharles <charles@pelican.dev>
2025-06-25 19:50:09 -04:00
JoanFo
dca37ccc95
Server Without Allocations (#1432)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-06-25 19:49:43 -04:00
Charles
6a088d0c4f
Tweak Grid View, Use Memory Limit, not wings reported allocation (#1462) 2025-06-25 19:49:00 -04:00
Walter van der Broek
7731f16b0f
Fix: Search for tags in correct variable (#1461) 2025-06-25 19:48:39 -04:00
Lance Pioch
9a1e7de4ae
Laravel 12.19.3 Shift (#1455)
Co-authored-by: Shift <shift@laravelshift.com>
2025-06-22 15:46:29 -04:00
pelican-vehikl
c61b6920b9
Fix some tests (#1450) 2025-06-19 21:36:50 +02:00
Boy132
6107524522
Trait-ify resources and add customizable options (#1396) 2025-06-19 18:24:25 +02:00
Boy132
57a13a2701
Refactor admin dashboard widgets to use forms (#1452) 2025-06-19 18:23:32 +02:00
Boy132
4dd414ad87
Delete old csgo egg (#1448) 2025-06-19 18:18:06 +02:00
Boy132
0156ac1509
Role icons: Use correct capitalization for class names (#1447) 2025-06-12 20:27:02 +02:00
MartinOscar
387471716b
Fully remove the filament-context-menu package (#1449) 2025-06-12 20:26:39 +02:00
Boy132
1dc5ec027e
Cleanup & fix server list (#1433) 2025-06-12 08:54:00 +02:00
MartinOscar
b05eabfdb0
Fix Users seeing Open in admin (#1444) 2025-06-11 03:51:08 +02:00
Lance Pioch
3039c1c698
Laravel 12.18.0 Shift (#1443)
Co-authored-by: Shift <shift@laravelshift.com>
2025-06-10 21:48:21 -04:00
MartinOscar
de166bca03
Use supervisorctl instead of systemctl when running in docker (#1378) 2025-06-08 09:12:15 +02:00
JoanFo
af609994b6
Fix missing font (#1404)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-06-08 09:11:56 +02:00
Boy132
bd2a00760d
Fix error handling for deleting backups (#1434) 2025-06-07 14:16:01 +02:00
pelican-vehikl
65deffc6e6
Create new description endpoint (#1136)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-06-06 23:06:28 -04:00
Boy132
34865d4288
Fix hostname env variable name in rust egg (#1435) 2025-06-06 14:19:09 +02:00
MartinOscar
2961c3e88b
Refactor EnvironmentTrait to use Env Facade (#1430) 2025-06-04 22:24:17 +02:00
MartinOscar
e7a950ffcb
Replace $allocation->toString() with $allocation->address (#1431) 2025-06-04 22:13:59 +02:00
Lance Pioch
ece732d9e5
Laravel 12.17.0 Shift (#1429)
Co-authored-by: Shift <shift@laravelshift.com>
2025-06-04 15:06:54 -04:00
Boy132
456c4f46bc
Make sure daemon_listen and daemon_connect match when not behind proxy (#1428) 2025-06-04 08:37:04 +02:00
Boy132
0ba497a2eb
Add separate port field for node connections (#1423) 2025-06-03 14:33:57 +02:00
Boy132
3b744f37dd
Lazy load server entries (Grid only) (#1413) 2025-06-03 14:33:43 +02:00
Charles
b34778f736
Refactor Node Stats (#1145)
Co-authored-by: Boy132 <mail@boy132.de>
2025-06-03 07:33:08 -04:00
MartinOscar
84c351d0ae
Deselect records for ListFiles DeleteAction (#1411) 2025-05-31 17:48:17 +02:00
MartinOscar
520cea7f09
Use translation for ListFiles DeleteAction (#1410) 2025-05-31 17:48:00 +02:00
Boy132
35ce1d34ab
Permission check fixes (#1406) 2025-05-27 19:30:30 +02:00
Boy132
17555a1d09
Make server name and server address clickable (and copyable) (#1395) 2025-05-27 19:30:07 +02:00
Lance Pioch
837121b1fb
Laravel 12.16.0 Shift (#1408)
Co-authored-by: Shift <shift@laravelshift.com>
2025-05-27 13:08:51 -04:00
Boy132
af9f2c653e
Add missing </div> to monaco editor view (#1399) 2025-05-23 06:02:29 -04:00
Boy132
c22e7456b5
Move tables & forms to resources in client area (#1388) 2025-05-22 08:41:17 +02:00
Boy132
97fb66f5d6
Use app panel for password link in AccountCreated notification (#1389) 2025-05-21 08:46:27 +02:00
Lance Pioch
51037c5c20
Laravel 12.15.0 Shift (#1390)
Co-authored-by: Shift <shift@laravelshift.com>
2025-05-20 16:32:43 -04:00
MartinOscar
23d13d9e83
Fix Mount translation (#1382) 2025-05-20 11:58:16 -04:00
Boy132
6c20426757
Put whereHas-orDoesntHave in own where (#1387) 2025-05-20 08:33:33 +02:00
Boy132
1224210668
Only include "server" subjects in activity log query (#1386) 2025-05-20 08:33:16 +02:00
Boy132
258c97bf14
Add missing auth activity logs (#1372) 2025-05-19 09:12:58 +02:00
C0D3 M4513R
7034c4d013
Fix Composer warnings (#1376)
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-05-15 14:39:59 -05:00
MartinOscar
e5cba893e4
Check against 2fa backup codes too in Login (#1366)
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-05-12 16:14:09 +02:00
Boy132
fd49f472c3
Remove packs folders in storage (#1367) 2025-05-12 14:30:16 +02:00
MartinOscar
c8556a4c56
Use placeholder for EditServer db_delete (#1362) 2025-05-10 00:01:58 +02:00
MartinOscar
6de6306a19
Fix GSLToken id, label & query (#1361) 2025-05-09 17:57:18 -04:00
Charles
1f8a5cdd1d
Fix font dropdown on EditProfile Page (#1360) 2025-05-09 17:42:39 -04:00
Charles
30ae860d69
Fix server notification body translation key (#1359) 2025-05-09 17:39:15 -04:00
Boy132
f400e2db76
Fix TRUSTED_PROXIES with * (#1358) 2025-05-09 16:22:33 -04:00
Boy132
1f7562563a
Use github error format for phpstan tests (#1357) 2025-05-09 21:03:50 +02:00
Boy132
2296e41a8b
Add button to view install logs (#1356)
Co-authored-by: notCharles <charles@pelican.dev>
2025-05-09 21:03:32 +02:00
MartinOscar
7971dc13fc
chore: Refactor Mounts (#1236) 2025-05-09 13:18:20 -04:00
Boy132
8406f4686c
Enable ipv6 on frontend (#1350) 2025-05-09 08:44:18 +02:00
Charles
67705b14b4
remove ComicMono as default set to monospace (#1352) 2025-05-08 18:00:51 -04:00
Boy132
bc115af5fd
Replace File with Storage on EditProfile (#1353) 2025-05-08 22:14:53 +02:00
MartinOscar
da35703f75
Hide ChartWidgets when Server isInConflictState or Offline (#1348) 2025-05-08 20:42:14 +02:00
MartinOscar
c54bfd714b
Make Tags work in StoreNodeRequest (#1349) 2025-05-08 19:08:13 +02:00
Lance Pioch
b83e3657d6
Laravel 12.13.0 Shift (#1347)
Co-authored-by: Shift <shift@laravelshift.com>
2025-05-07 15:50:41 -05:00
Boy132
e2c87a8206
Add back network chart (#1283)
* add back network chart

* don't show timestamp

* convert "total" to "real time"

* fix typo

* set min to 0

* sort data to make sure we actually get the previous value

* Fix `ServerNetworkChart`

* Many changes...

* small cleanup

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: notCharles <charles@pelican.dev>
2025-05-06 23:32:01 +02:00
Boy132
e38a736b61
Small cleanup for new egg features (#1343) 2025-05-06 13:01:34 +02:00
Boy132
26e20453bf
Prevent primary allocation overwrite on save (#1344) 2025-05-06 13:01:09 +02:00
Boy132
292523d153
Cleanup files mount and fix path for global search (#1341) 2025-05-06 08:36:51 +02:00
PalmarHealer
85d625d118
Rework subuser permission loading (#1311)
* Remove open in new tab since both are on filament now.

Removing the open in new tab since both are on filament now. And the tenant: null was function default so not needed aswell

* Rework permission tab loading

Reworked permission tab loading to make it easier to expand on it in the future. This is way more friendly if extensions are planned in the future.

* Rework permission tab loading

Reworked permission tab loading to make it easier to expand on it in the future. This is way more friendly if extensions are planned in the future.

* Rework permission tab loading

Reworked permission tab loading to make it easier to expand on it in the future. This is way more friendly if extensions are planned in the future.

* Update UserResource.php

Used wrong name. It's not the name, the label has to be checked there.

* Fix: wrong name used

Used wrong name. It's not the name, the label has to be checked there.

* Update permission loading
Moved permission list to app/Models/Permission.php and made UserResource.php and ListUsers.php use it.

* Fix Pint and PHPStan error
Added comments

* Update array key
Updated array key using the lowercase name. Suggested by https://github.com/Boy132

* Correct array key
Updated array key using the lowercase. Suggested by https://github.com/Boy132

* Revert/correct array key
Updated array key using the lowercase and the correct label.

* Add 'user' key
In the old $permission array was user an entry witch is missing in permissionTabs()

* Style and return
Added @return and removed empty lines

* pin
fix pint

* fix pint
remove @return

* fix pint
add () since pint is still not happy

* remove mb_strtolower
mb_strtolower is not necessary

* remove schema for control
remove ->schema for control tab.

* Remove import

Remove unused import

* correct translation key

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* make columns optional,
checkboxList => columns is now optional and default to 2

* move user and control registration
removed control registration since it was duplicate and move user registration to permissionTabs

* update @return on permissionTabs()

* Fix array key warning

* simplify permissions data

* revert this

* fix edit modal

* update icons

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: Boy132 <mail@boy132.de>
2025-05-05 17:35:17 -04:00
Boy132
c8230771ec
Fix 500 when searching for empty term (#1340) 2025-05-05 23:31:36 +02:00
Charles
79691ba663
move redis only command to if statement (#1337) 2025-05-05 16:43:27 -04:00
Boy132
a6326f64fb
Add back behind_proxy to ui (#1263)
* add back `behind_proxy` to ui

* combine `scheme` and `behind_proxy` into one component

* remove debug stuff

* update translations

* make bulky
2025-05-05 13:00:34 +02:00
Boy132
03745eb4be
Allow to assign nodes to roles (node ownership) (#1231)
* allow to assign nodes to roles

* fix typo

* fix node policy

* small ui improvements

* add missing translation

* make phpstan happy

* fix migration on mysql

* also restrict mounts & database hosts to allowed nodes

* fix migration on mysql v2

* changes from review

* fix hasManyThrough

* change `accessibleNodes` to builder

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-05-05 12:58:55 +02:00
Charles
c0fda71e20
Font Saga Continues... (#1339)
Add back removed ??
2025-05-04 17:22:18 -04:00
Charles
f2f1026a97
Font Saga Continues (#1338)
Nuke comic, just use monospace..... make life easy
2025-05-04 17:03:45 -04:00
Charles
e1eaf805ea
composer update (#1335) 2025-05-04 09:15:25 -04:00
Charles
03ec20e3a0
fix settings on mobile (#1336) 2025-05-04 09:15:12 -04:00
Charles
a5ffff8c8c
Add Comic Mono to the list (#1330)
* Add Comic Mono to list and make default

* Update preview

* Create folder if missing.

* match composer lock from pr
2025-05-03 08:21:02 -04:00
Charles
82ef6c1408
Add server power actions to new context menu (#1321)
* add server power action context menu

* Update app/Filament/App/Resources/ServerResource/Pages/ListServers.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* Cleanup

* Add missed enable

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-05-02 12:15:05 -04:00
Charles
2d581c7cbd
Remove get_fonts, Fix docker container console font selection (#1329)
* Update `get_fonts`

This should fix docker, Has to be changed as we use alpine for docker which does not support GLOB_BRACE

* #2?

* #3

* FINAL BOSS FIGHT

Fixes Docker image <3

* Update resources/views/filament/components/server-console.blade.php

Co-authored-by: Lance Pioch <git@lance.sh>

---------

Co-authored-by: Lance Pioch <git@lance.sh>
2025-05-02 08:37:27 -04:00
Lance Pioch
7f0266be5e
Laravel 12.12.0 Shift (#1325)
Co-authored-by: Shift <shift@laravelshift.com>
2025-05-02 03:21:21 -04:00
Charles
1ae9490b8f
update filament assets (#1328) 2025-05-01 19:20:54 -04:00
MartinOscar
a53b3fda10
Append / to EditFiles (#1322) 2025-05-01 21:26:16 +02:00
MartinOscar
e9ddf80d10
Use $id as primaryKey for File Model (#1323) 2025-05-01 21:26:01 +02:00
Lance Pioch
3f1e99f1df
composer update (#1320)
Co-authored-by: Shift <shift@laravelshift.com>
2025-05-01 14:28:44 -04:00
MartinOscar
435c615ff1
Add throwIf to daemonRepository (#1301) 2025-05-01 15:49:35 +02:00
Charles
3effd98013
Allow changing of the console font (#1277)
* Custom Fonts

* Update app/Filament/Pages/Auth/EditProfile.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* wip

* wip

* Update app/Filament/Pages/Auth/EditProfile.php

Co-authored-by: Lance Pioch <git@lance.sh>

* Update app/helpers.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* update

* add fonts folder for docker

* Add default font

* Update server console to preload the font

* Update settings/trans

---------

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
Co-authored-by: Lance Pioch <git@lance.sh>
2025-05-01 09:47:59 -04:00
Lance Pioch
e354bc9be7
Laravel 12.11.0 Shift (#1317)
Co-authored-by: Shift <shift@laravelshift.com>
2025-04-29 21:01:28 -04:00
Boy132
14d351103c
Fix database & user not being deleted (#1315) 2025-04-29 17:05:49 +02:00
Boy132
92c23451af
Improve file error handling (#1314)
* improve file error handling

* small cleanup

* fix typo
2025-04-29 17:05:29 +02:00
pelican-vehikl
2046fa453a
Pest Test Improvements (#1137)
Co-authored-by: Lance Pioch <git@lance.sh>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-04-28 10:20:33 -04:00
Michael (Parker) Parker
b39a8186ae
Resolve issue with avatar storage (#1281)
* Resolve issue with avatar storage

This resolves the issue with getting avatar storage working

updates the entrypoint to create the `pelican-data/storage` folder on start.

Adds a dev dockerfile to build locally instead of needing to update the standard dockerfile.

* Move avatar folder

Moves the avatars folder in the storage folder in-case anything else needs storage as well.

Fixes an issue in the entrypoint where it wasn't creating the sub-folder correctly.
2025-04-27 20:56:10 -04:00
Letter N
8ae3c88c91
generalize sponge installation (#1300) 2025-04-26 14:06:30 -04:00
MartinOscar
329a29f7da
Add missing disabled in AllocationsRelationManager (#1304) 2025-04-26 06:42:29 -04:00
MartinOscar
98a2cab5ca
Case insensitive EggFeature Listeners (#1303) 2025-04-26 06:41:59 -04:00
pelican-vehikl
8407547574
Add back Egg Features (#1271)
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: Lance Pioch <git@lance.sh>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-04-24 18:24:18 -04:00
Lance Pioch
fccd7e5e75
composer update (#1298)
Co-authored-by: Shift <shift@laravelshift.com>
2025-04-24 15:33:26 -04:00
Lance Pioch
c0225b9e10
Laravel 12.10.1 Shift (#1294)
Co-authored-by: Shift <shift@laravelshift.com>
2025-04-24 10:08:49 -04:00
Boy132
544aaab960
Make sure 2fa requirement is enforced (#1289) 2025-04-23 16:03:10 +02:00
Boy132
914e215bc0
Separate user uploadable avatars into own setting (#1286) 2025-04-23 16:02:52 +02:00
Sebastien Green
90fd73f6a4
Change section header icon self alignment to centre (#1279) 2025-04-23 10:02:44 -04:00
Boy132
0037b4a1d4
Only use navigation groups when using sidebar (#1288)
* Revert "Remove `NavigationGroups` for Admin Navbar (#1248)"

This reverts commit a1869002629b18500b346e5c505869bc45d43456.

* make navigation groups conditional
2025-04-23 16:02:21 +02:00
Boy132
3deada57c6
Remove DynamicDatabaseConnection (#1290) 2025-04-23 16:02:08 +02:00
Gabriel
6427903f9f
feat(console): save command history in session (#1282) 2025-04-22 17:29:17 -04:00
PalmarHealer
b16e19b4fb
Remove open in new tab since both are on filament now. (#1292) 2025-04-22 17:28:00 -04:00
Boy132
7e99d5cd8e
Use Arr::dot to display multi-dimensional activity log properties (#1285) 2025-04-22 22:27:50 +02:00
Boy132
05b1a44a34
Fix metadata coming from wings activity logs (#1284) 2025-04-22 22:27:31 +02:00
Letter N
058b613c98
handle failed oauth (#1264)
* handle failed oauths

* fix linter

* small cleanup

---------

Co-authored-by: Boy132 <mail@boy132.de>
2025-04-22 15:57:44 -04:00
Boy132
0e2ab4b711
Fix activity log query (#1258) 2025-04-22 08:28:24 +02:00
Quinten
ee838316e6
Make avatars work (#1251) 2025-04-21 11:25:36 +02:00
MartinOscar
ffd94b8892
Fix develop Node Version reported as outdated (#1272) 2025-04-18 16:41:10 +02:00
MartinOscar
a186900262
Remove NavigationGroups for Admin Navbar (#1248) 2025-04-18 10:39:25 -04:00
Lance Pioch
bf14755287
Laravel 12.9.2 Shift (#1266)
Co-authored-by: Shift <shift@laravelshift.com>
2025-04-18 10:37:21 -04:00
MartinOscar
038504fbec
Only chunk if rows exceeds sqlite variables limit (999) (#1270) 2025-04-17 16:24:57 -04:00
MartinOscar
22a0a52f7b
Chunk Sushi inserts based on rows count (#1259) 2025-04-17 00:04:58 +02:00
Boy132
862afaa0e9
Fix api docs for server update requests (#1262)
* workaround for api docs error

* add deprecated notice
2025-04-15 23:47:31 +02:00
MartinOscar
a4dd8cca4c
Add live() to KeyValue on CreateServer & EditServer (#1261) 2025-04-15 16:06:37 +02:00
Letter N
e67e0830eb
Fix Node graph not rendering correctly (#1253)
* use round instead of `Number::format`

* remove unused

* also replace `Number::format` in cpu & memory charts

---------

Co-authored-by: Boy132 <mail@boy132.de>
2025-04-15 01:27:35 +02:00
Boy132
b444112085
Correctly display backup status (#1256)
* add status attribute to backup

* hide actions when backup is not successful

* small cleanup
2025-04-14 12:59:03 +02:00
Boy132
f23d4d6971
Fix action in notifications (#1257) 2025-04-14 12:57:38 +02:00
MartinOscar
2a3781f5a8
Add pdo_pgsql to Docker (#1244) 2025-04-13 02:34:27 +02:00
MartinOscar
cb245dc722
Use recommended PHP 8.4 for Docker (#1245) 2025-04-13 02:30:09 +02:00
MartinOscar
3ffbf9e46a
Allow users to remove their Avatar (#1247) 2025-04-13 02:29:46 +02:00
MartinOscar
8221c80ec2
Only allow image/png mimetype for Avatar (#1246) 2025-04-13 02:27:36 +02:00
MartinOscar
702a6bb750
Restore exception_handler & error_handler for Tests (#1239) 2025-04-12 16:44:46 +02:00
MartinOscar
02d7ad04ad
Fix serverVariables not saving due to join (#1235)
* Fix `serverVariables` not saving due to `join`

* Remove deprecated `viewableServerVariables`
2025-04-12 16:44:24 +02:00
Boy132
7409f020ba
Add storage:link to setup command (#1233) 2025-04-11 23:23:23 +02:00
Lance Pioch
98d8510f11
Laravel 12.8.1 Shift (#1226) 2025-04-11 09:29:33 -04:00
Lance Pioch
6c6d458445
Laravel 12.7.2 Shift (#1213)
* Bump Laravel version constraint

* Bump community package dependencies

* composer update

---------

Co-authored-by: Shift <shift@laravelshift.com>
2025-04-07 21:08:27 -04:00
Lance Pioch
51fda2eaf4
These have to be nullable originally (#1222) 2025-04-07 21:08:03 -04:00
Boy132
92fbd75772
Show different roles CheckboxList for root admins and non root admins (#1219)
* show different roles checkbox list for root admins and non root admins

* simplify saveRelationshipsUsing

* remove disableOptionWhen

* add migration to remove additional roles from root admins
2025-04-07 16:10:31 +02:00
Boy132
fa8ae0aea5
Add avatar providers (#1192)
* Add avatar providers

* fix exists check for local avatar

* Use avatar in user lists

---------

Co-authored-by: Charles <charles@pelican.dev>
2025-04-07 16:06:19 +02:00
Charles
377b3f170d
Change table row options (#1220)
It's a known filament issue that large tables are SUPER slow.
2025-04-06 15:03:40 -04:00
MartinOscar
566e7c1b24
Allow user to choose archive name in FileManager (#1206)
* Allow user to choose `archive` name in `FileManager`

* Rollback `file.compress` activity translation
2025-04-06 14:52:25 -04:00
tfcprivt
b9d4773bd7
Fixed the Select dropdown to use searchable on the Edit Files Page. (#1204)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-04-06 14:05:54 -04:00
Boy132
49638e75e5
Add setup wizard to database host (#1216)
* add setup wizard to database host

* make phpstan happy

* remove `.` in lang

---------

Co-authored-by: notCharles <charles@pelican.dev>
2025-04-06 14:04:20 -04:00
MartinOscar
80c404a48c
Chore filament:upgrade (#1210) 2025-04-05 02:56:52 +02:00
Charles
befe6be80b
Update Overview, Again. Add some customization (#1200)
* wip

* wip

* wip

* overview 2.1

* Combine 2 branches into one

* updates

* Fix 500

* use my friend JSON

* Use switch
2025-04-04 12:08:43 -04:00
Boy132
3639d7ccec
Fix file writing (#1218) 2025-04-04 14:38:08 +02:00
Boy132
20f271041a
Allow to register custom role permissions (#1208) 2025-04-04 09:30:45 +02:00
Boy132
c3b8b71f9c
Allow to register custom console widgets (#1209) 2025-04-04 09:30:25 +02:00
Boy132
c73d0544d9
Refactor admin dashboard to use widgets (#1207) 2025-04-04 09:30:00 +02:00
MartinOscar
484a3b445a
Prevent Server primary allocation dissociation (#1197) 2025-04-04 00:56:15 +02:00
MartinOscar
c0fa8c1cd8
Use afterSave instead of handleRecordUpdate & move transferServer (#1195)
* Use `afterSave` instead of `handleRecordUpdate` & move `transferServer`

* Override `getSavedNotification` instead of `save`
2025-04-03 15:59:10 +02:00
MartinOscar
e562a35057
Add unique foreign keys for EggVariable (#1196)
* Fix tests \`egg_variable\` order

* Add `EggVariable` unique foreign key for `env_variable` & `name`
2025-04-03 15:58:49 +02:00
MartinOscar
636279c6eb
Add FileNotEditableException (#1135)
* Add `FileNotEditableException`

* Send `Notification` instead of Throwing

* Remove useless `function`

* Make them all `AlertBanner`
2025-04-02 21:44:51 -04:00
Boy132
ed88ce9ae3
cleanup panel config file (#1198) 2025-04-02 21:44:33 -04:00
Lance Pioch
0cce716e2c
Laravel 12.6.0 Shift (#1205)
* Bump Laravel version constraint

* composer update

* Force PHP `8.2` platform

* Fix `SplFileInfo` cast in `CleanServiceBackupFilesCommand`

* Bump larastan to dev commit

* Unpin filament

---------

Co-authored-by: Shift <shift@laravelshift.com>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-04-03 03:43:01 +02:00
tfcprivt
3639f0cb50
Added Icons for the Power Actions (#1203) 2025-04-02 21:29:29 -04:00
Boy132
9c3f47590c
Fix server transfer backend (#1139)
* fix notify in transfer service

* remove magical array

* fix phpstan

* better validation for allocation_additional and better docs generation

* update transfer ui

* update request body
2025-04-01 11:19:14 +02:00
Boy132
630031e1c2
Add some refreshs & notifications to EditServer action buttons (#1174)
* add some refreshs & notifications to EditServer action buttons

* reinstall server when trying to toggle failed state

* don't show modal on normal toggle install

* don't print raw exception on reinstall & suspension
2025-04-01 08:36:19 +02:00
Boy132
2c00f90ba6
remove codeowners (#1193) 2025-03-31 07:32:01 -04:00
Lance Pioch
875dca54f5
Switch inserts to proper creates (#1190)
* Switch inserts to proper creates

* Push `$token` to `$tokens[]` in `ToggleTwoFactorService`

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-03-30 21:56:49 -04:00
Michael (Parker) Parker
a03b604f2d
Merge pull request #1184 from JoanFo1456/main
Change console font
2025-03-30 14:53:28 -04:00
Lance Pioch
8261184b57
Officially support PostgreSQL database (#1066)
* Just skip this table because it no longer exists

* Add postgresql

* This no longer needs to be there

* These are the same output in mysql, but different in postgresql

* Fix these migrations for postgresql

* This table no longer exists

* This is expected to be a json column for json operations, required for postgresql

* Shoot for the stars

* Fix pint

* Why was this missing

* Updates

* Restore this

* This needs to be explicit

* Don’t like strings

* Fix these classes

* Use different method to compare dates

* Apparently postgresql doesn’t like case insensitivity

* Postgresql orders it backwards

* Ordered different by postgresql

* Unnecessary and breaking

* Make sure the order is correct for postresql

* Fix this with the order too

* Remove this

* Force email to be lowercased

* Update app/Models/User.php
2025-03-30 14:44:03 -04:00
MartinOscar
bca02ced86
Fix typo for SESSION_DRIVER in RedisSetupCommand (#1188) 2025-03-29 19:58:36 +01:00
Boy132
a768fadaea
Reimplement password reset (#1182)
* add password reset to all panels

* remove old leftovers

* fix reset url in account created mail
2025-03-28 23:51:42 +01:00
Boy132
7471347b55
Improve alert banner fetching (#1173)
* use events for alert banner pulling

* add ids to alert banners to prevent duplicates
2025-03-28 23:50:34 +01:00
MartinOscar
1457c4bd06
Lint console.css 2025-03-28 23:09:32 +01:00
JoanFo
8b943fa160
Update server-console.blade.php
Removed font.css referrence (removed file)
2025-03-28 21:54:15 +01:00
JoanFo
5c5c9654b4
Update console.css
Added content from ./font.css
2025-03-28 21:53:29 +01:00
JoanFo
dd20cb0f11
Delete public/css/filament/server/font.css
Adding the contents on console.css
2025-03-28 21:50:50 +01:00
JoanFo
88deb35dc8
Create font.css
Forgot the font css file
2025-03-28 18:47:06 +01:00
JoanFo
0f92632c06
Update server-console.blade.php 2025-03-28 18:26:38 +01:00
JoanFo
a85fc5c88e
Add files via upload 2025-03-28 18:23:36 +01:00
Charles
8d7eff13fb
Update Overview and Server List (#1151)
* Update Overview and Server List

* Fix background on light mode
2025-03-28 11:57:40 -04:00
MartinOscar
c39c29e50b
Must use Closure since MenuItem does not leverage Concerns (#1181)
* Must use `Closure` since `MenuItem` does not leverage Concerns

* Translate `Profile` in `ServerPanelProvider`

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-03-28 12:50:34 +01:00
MartinOscar
db3b16e609
Add Owner Filter to ListServers (#1180)
* Add `Owner` Filter to `ListServers`

* Make `Owner` filter show on `other` & `all` tabs

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-03-28 12:27:51 +01:00
MartinOscar
72b9c309d3
Fix EditProfile cannot use / in trans() (#1178) 2025-03-27 22:52:39 +01:00
MartinOscar
68a6dc45cb
Set Schedule next_run_at in ListSchedules to Never if disabled (#1176) 2025-03-27 20:24:33 +01:00
MartinOscar
9a258efe53
Force app panel for EditProfile (#1162)
* Force `app` panel for `EditProfile`

* Force `app` panel for `OAuthController`

* Use translation in `AdminPanelProvider`

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-03-27 18:54:08 +01:00
MartinOscar
3310746107
Show different emptyStateHeading when activeTab is my in ListServers (#1157) 2025-03-27 15:43:07 +01:00
MartinOscar
42706dba14
Remove duplicated activity.read & use relations in EditUser (#1165) 2025-03-27 15:40:19 +01:00
MartinOscar
ec6529ac4c
Fix \compose.yml\ spelling (#1170) 2025-03-27 05:46:40 -04:00
Morpheus636
bced93c5be
Removed outdated docker documentation (#1166) 2025-03-27 05:46:27 -04:00
Charles
cb1c953540
Fix Egg Variable Order (#1172)
* Fix Egg Variable Order

* Fix Client Side Startup variable order
2025-03-27 05:45:59 -04:00
Boy132
c689f6860b
Disallow 0.0.0.0, 127.0.0.1 and localhost as node fqdn (#1158)
* disallow `0.0.0.0`, `127.0.01` and `localhost` as node fqdn

* use rules of model
2025-03-26 09:03:13 +01:00
MartinOscar
a73404c1b4
Fix Server ForceDelete by adding missing redirect (#1156) 2025-03-24 22:28:40 +01:00
Boy132
61cbe5465f
Schedules: Update next_run_at when editing & show notification if cron is invalid (#1141)
* update `next_run_at` when editing & show notification if cron is invalid

* move getNextRun to resource
2025-03-24 09:08:51 +01:00
Charles
5bea1ea80a
Fix 500 when viewing node (#1144)
* Fix node 500

* this feels better

* Update app/Models/Node.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* Update app/Models/Node.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* pint

---------

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-03-23 17:43:51 -04:00
MartinOscar
b69136d7a4
Add Server ForceDelete (#1134) 2025-03-23 17:08:59 -04:00
Boy132
a8c3082b79
Add UI for server transfers (#1119)
* add ui for server transfers

* disable transfer button when server is in conflict state
2025-03-23 17:02:22 -04:00
Boy132
a47ad071c9
Fix activity log on rename api endpoint (#1149) 2025-03-22 17:01:11 +01:00
MartinOscar
ab953b2f4d
Use composer to copy .env.example after its done installing packages. (#1073) 2025-03-22 14:06:47 +01:00
MartinOscar
03d6c88f65
Fix OAuth Modals CopyAction & use x-filament::link (#1146)
* Fix `CopyAction` & use `x-filament::link`

* PHPStan
2025-03-22 14:05:14 +01:00
PalmarHealer
b4eab02254
Remove cursor not allowed rule (#1147)
* remove cursor not allowed rule

* re-add css rule
Added the previously removed rule back and specified selector.

* Apply suggestions from code review

---------

Co-authored-by: Lance Pioch <lancepioch@gmail.com>
2025-03-22 08:31:57 -04:00
Boy132
23f39acd4e
Add host config to gitlab oauth (& add setup steps) (#1142)
* add custom provider class for gitlab to allow custom hosts

* add setup steps
2025-03-21 21:23:38 +01:00
Boy132
82b0aff105
Fix SMTP scheme/ encryption (#1120) 2025-03-21 08:42:42 +01:00
Charles
adca50a372
Catch 500 on backup page when you hit the backup rate limit (#1132)
* Catch backup throwable

* phpstan

* Update notification
2025-03-20 10:38:24 -04:00
MartinOscar
c5230efad6
Catch NodeUpdateService in EditNode & NodeController (#1106) 2025-03-18 23:07:40 +01:00
Charles
e5d9d53aa3
Remove unused groupBy (#1130)
Fixes #1107
2025-03-18 18:07:35 -04:00
MartinOscar
29f3defc73
Catch DaemonFileRepository & show Alert (#1129)
* Catch `DaemonFileRepository`

Co-authored-by: notCharles <charles@pelican.dev>

* Pint

---------

Co-authored-by: notCharles <charles@pelican.dev>
2025-03-18 23:07:21 +01:00
Boy132
2dbb9a5f9b
Add update egg bulk action (#1122)
* add update egg bulk action

* make phpstan happy

* use `before`
2025-03-18 17:42:04 +01:00
Boy132
a05e330b19
Fix path resolving when moving files (#1116)
* don't resolve new path when moving file

* use full path in activity log

* don't require file name when moving
2025-03-18 17:36:27 +01:00
Boy132
4a7951995e
Add bulk move (#1117) 2025-03-18 17:36:13 +01:00
Boy132
3d29243cf0
Add tag filter to lists (#1124) 2025-03-18 17:35:36 +01:00
Boy132
c52439132d
Add tags to egg importer & exporter, add tags to egg jsons (#1125)
* add tags to egg importer & exporter

* add tags to stock eggs
2025-03-18 17:35:15 +01:00
Boy132
517f17cbcc
Add redirect after clicking reinstall (#1126) 2025-03-18 17:35:01 +01:00
Lance Pioch
f8d119b458
Update readme.md 2025-03-18 10:06:51 -04:00
MartinOscar
fbeb747fc3
Fix ImportEggAction (#1110)
* `Arr::Wrap` `data.files` cause if its unique its a string

* Use `data.url` first so it gets overwritten by `data.files`
2025-03-17 18:07:36 +01:00
Boy132
f563128237
Make sure to not sync root admin role (#1113) 2025-03-17 17:23:44 +01:00
MartinOscar
f2f3ee548f
Add App Logo (#1104)
* Add `app.logo` to `Settings`

* Use `app.name` if `app.logo` is null
2025-03-17 13:28:32 +01:00
MartinOscar
0b3dce132f
Add header, footer & body-end views (#1111) 2025-03-17 13:28:18 +01:00
MartinOscar
5bf23b972d
Fix DaemonFileRepository in ListFiles (#1109)
* Fix `DaemonFileRepository` in `ListFiles`

* Use match for `getPermissionsFromModeBit`
2025-03-17 12:17:05 +01:00
MartinOscar
22d02c0df5
Remove NodeCreationService (#1092) 2025-03-17 05:46:33 +01:00
MartinOscar
253abf65b1
Hide Directory size in ListFiles (#1102)
* Hide `directory` size in `ListFiles`

* Use only one `DaemonFileRepository` in `ListFiles`
2025-03-16 15:51:02 +01:00
MartinOscar
d452e3d2f2
Use ContainerStatus::tryFrom in ProcessScheduleService (#1101) 2025-03-16 15:36:15 +01:00
Charles
0051370f24
Reduce svg size (#1100) 2025-03-16 10:20:19 -04:00
Charles
4e85180b3d
Fix Release Build (#1089) 2025-03-15 16:21:31 -04:00
Charles
9f4a3b1c0d
Fix Releases (#1088) 2025-03-15 16:13:55 -04:00
Boy132
45db06a1bd
Refactor captcha (#1068)
* refactor captcha

* add default error message

* prevent rule from being called multiple times

* fixes

* use config

* Update this to latest

* Remove this

---------

Co-authored-by: Lance Pioch <git@lance.sh>
2025-03-15 15:52:38 -04:00
Charles
3e26a1cf09
save record, then try to update (#1087) 2025-03-15 20:33:20 +01:00
Lance Pioch
44111696df
Laravel 12.2.0 Shift (#1082)
* Bump Laravel version constraint

* composer update

* Fix php8.2

* Pin filament for now

---------

Co-authored-by: Shift <shift@laravelshift.com>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-03-15 15:27:06 -04:00
MartinOscar
e04abcbcf9
Replace existing Egg Reserved_Env_Variables with SERVER_ prefix (#1070)
* Add migration that updates egg->variables->env_variable, egg->startup, egg->servers->startup

* Update `EggImporterService` to replace `EggVariable::RESERVED_ENV_NAMES`

* Use `EggImporterService::parseReservedEnvNames`

* Refactor & Remove `Migration`
2025-03-15 14:51:10 -04:00
MartinOscar
ea5914f362
Add url Repeater to ImportEggHeaderAction (#1071)
* Add url `Repeater` to `ImportEggAction`

* Addtranslation

* Requested changes

* Only allow `multiple` when not editing `Egg`

* Only `deletable` & `grid` if `multiple`

* Fix `FileUpload` & Make sure its a json file
2025-03-15 14:46:10 -04:00
MartinOscar
98c36c4cc3
Fix revamp api_keys migration (#987) 2025-03-15 14:42:43 -04:00
MartinOscar
6bc55b1039
Silent file_exists when its not in defaults allowed open_basedir (#1086) 2025-03-15 14:28:59 -04:00
MartinOscar
11b153d23c
Fix null Node Stats (#1075)
* Make sure we are talking to the right wings using `getSystemInformation` as a gate keeper

* Re use method

Co-authored-by: Lance Pioch <git@lance.sh>

---------

Co-authored-by: Lance Pioch <git@lance.sh>
2025-03-15 14:28:15 -04:00
Charles
998ad2ee31
Add hint about overhead when using memory limit (#1069)
* Add hint about overhead when using memory limit

* Update lang/en/admin/server.php

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* escape `'`

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-03-15 13:10:25 -04:00
Thibault Junin
7f0c7da37f
Fix FindViableNodeService to actually filter Tags (#1080)
* fix viable node service to take into account tags

* Update app/Services/Deployment/FindViableNodesService.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

---------

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-03-13 17:04:44 +01:00
MartinOscar
e93d122a27
Server does not use SoftDelete so deleted_at does not exist (#1083) 2025-03-13 01:00:55 +01:00
MartinOscar
9aaf6b3798
Make redirect & callback public instead of private as required by Laravel 12 (#1081) 2025-03-12 19:32:16 +01:00
MartinOscar
fd6e7eb314
Fix missing space in OAuth modal (#1078) 2025-03-10 19:28:39 +01:00
Lance Pioch
4e694b50ca
Make sure the app key is always set (#1074)
* Make sure the key is always set
2025-03-08 21:32:28 -05:00
MartinOscar
3a24edfe1d
Tests: Make PHPstan run in 8.2, 8.3 & 8.4 (#1072)
* Add PHP [8.2,8.3,8.4] matrices to `phpstan`

* Use a pointer with `unset($this)` to make PHP 8.4 happy
2025-03-09 01:58:50 +01:00
Lance Pioch
0179ade557
Add Laravel Data package, also some small fixes (#1065)
* Simplify

* Update these

* Add Laravel Data

* Remove unused imports

* Quick fix

* Fix double array

* Update app/Console/Commands/Egg/CheckEggUpdatesCommand.php
2025-03-08 19:56:06 -05:00
MartinOscar
05d74232af
Fix Build UI Tests running twice (#1067) 2025-03-08 16:13:55 +01:00
Boy132
a2b2e373be
Fix subuser activity log (#1063)
* use user for subject

* add permissions to properties

* always add websocket.connect permission (because it's default)

* small cleanup

* also update editing
2025-03-07 17:29:09 +01:00
MartinOscar
0a17e78f33
Force 2fa_required to no one by default (#1058) 2025-03-06 20:53:29 -05:00
Lance Pioch
c3a65aed07
Laravel 12.1.1 Shift (#1057)
* Bump Laravel version constraint

* Bump community package dependencies

* composer update

---------

Co-authored-by: Shift <shift@laravelshift.com>
2025-03-06 18:37:45 -05:00
MartinOscar
d438e29154
Add missing Database address field (#1049)
* Add address field to display `host:port` to enduser on `ListDatabases` & `EditServer`

* Add `CopyAction` to `EditServer`

* Update databaseHost `display_name_help`
2025-03-06 15:55:40 +01:00
MartinOscar
1fdc428f3e
Allow sendCommand on Starting or Running Servers (#1061)
* Replace `string` with `enum`

* Add title

* Allow sendCommand on `Starting` or `Running` servers

* refactor: Use Filament interfaces

* Use `getLabel` instead of `str->headline`

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-03-06 15:55:00 +01:00
Charles
a9e4495c91
Add missing activity loggers on client area (#1060)
* Update Subuser

Adds user deleted notification, Adds logger for creating subusers.

* Update Tasks

* ...

* Update Schedule

* Update Files

* Update Database

* Move `reinstall` to proper array

* Add `:action` to deleted task log

* Updates

* Fix CreateSchedule

* Fix Editing/Saving

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-03-06 09:28:45 -05:00
MartinOscar
98ddb65509
Revert Monaco Changes... (#1062)
Reintroduced the ever expanding editor.

Co-authored-by: notCharles <charles@pelican.dev>
2025-03-06 12:50:34 +01:00
MartinOscar
6caa741798
Make restart the default payload when using PowerAction in Schedules (#1059) 2025-03-05 22:10:48 +01:00
MartinOscar
5512c10ee1
Use daemonRepository instead of BuildModificationService (#1053) 2025-03-04 00:48:22 +01:00
MartinOscar
5331c5abfa
Use predis as default redis driver (#1054) 2025-03-03 22:47:01 +01:00
Lance Pioch
36a38ab947
Basic two factor auth implementation (#1050)
* Basic two factor auth

* Remove unused import

* Add translation
2025-03-03 15:22:12 -05:00
Lance Pioch
da195fd2fe
PHPstan updates (#1047)
* Not found property rule

* Make these “better”

* Day 1

* Day 2

* Day 3

* Dat 4

* Remove disabled check

* Day 4 continued

* Run pint

* Final changes hopefully

* Pint fixes

* Fix again

* Reset these

* Update app/Filament/Admin/Pages/Health.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* Update app/Traits/CheckMigrationsTrait.php

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

---------

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-03-03 14:41:19 -05:00
Lance Pioch
82409f2fba
Laravel 12.x Shift (#1045)
* Convert route options to fluent methods

Laravel 8 adopts the tuple syntax for controller actions. Since the old options array is incompatible with this syntax, Shift converted them to use modern, fluent methods.

* Slim `lang` files

* Shift core files

* Validate via object directly within Controllers

* Use `Gate` facade for controller authorization

* Dispatch jobs directly

* Remove base controller inheritance

* Default config files

In an effort to make upgrading the constantly changing config files easier, Shift defaulted them and merged your true customizations - where ENV variables may not be used.

* Set new `ENV` variables

* Add new Laravel `composer run dev` script

* Add `storage/app/private` folder

* Bump Composer dependencies

* Convert `$casts` property to method

* Adopt Laravel type hints

* Shift cleanup

* Apply suggestions from code review

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* Add old key as backup

* Update composer

* Remove extra line

* Update this

---------

Co-authored-by: Shift <shift@laravelshift.com>
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
2025-03-03 14:41:00 -05:00
MartinOscar
839be53231
Use BuildModificationService on EditServer (#1042)
* Use `BuildModificationService` on `EditServer` & make it throw if we can't reach wings

* Use Node name on `EditServer` & `EditNode`
2025-03-03 19:49:42 +01:00
Charles
d79d461e7c
Fix total disk storage (#1040) 2025-03-01 07:30:29 -05:00
Boy132
d8e8240756
Fix EditUser (#1046)
* fix unique when editing user

* unset roles when editing
2025-02-28 13:28:18 +01:00
MartinOscar
0b84b0c08c
Make sure tests fails on composer error (#1034)
* Remove `--prefer-dist`

* Add missing args `--no-autoloader` `--no-suggest` `--no-progress` `--no-scripts` `--no-dev`
2025-02-28 02:59:51 +01:00
Lance Pioch
e2045e334f
This has been replaced with pint (#1044) 2025-02-27 20:18:09 -05:00
Boy132
5e2d106bb9
Call parent constructor in custom oauth provider classes (#1039) 2025-02-27 17:22:32 +01:00
Charles
40c138f086
Update admin resources (#1038) 2025-02-27 09:28:00 -05:00
Boy132
ab543a399b
Fix composer.lock (#1036) 2025-02-27 15:10:21 +01:00
Charles
0308045738
Delete mysql-schema (#1037)
Just build the database from migrations... remove  the requirement for mysql-client on installs
2025-02-27 08:17:17 -05:00
Boy132
cd9cbf20ce
Downgrade myclabs/deep-copy back to 1.12.1 (#1033) 2025-02-26 16:27:31 +01:00
Boy132
e1308cb04d
Small api docs improvements (#1032)
* update scramble

* cleanup application api endpoints

* cleanup client api endpoints

* fix security schema and make docs homepage nicer

* remove duplicate myclabs/deep-copy

* style(api-docs): use Blade template and Tailwind for styling

* Publish scramble view

* Use localStorage theme instead of config

* Update routes/docs.php

Co-authored-by: Lance Pioch <git@lance.sh>

---------

Co-authored-by: Quinten <67589015+QuintenQVD0@users.noreply.github.com>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: Lance Pioch <git@lance.sh>
2025-02-26 16:12:19 +01:00
Charles
2d937229fb
Add Custom StatBlocks, Add Stats (#1027)
* add custom statblock

* add custom datablock

* Use real values, not placeholders

* More Changes

* remove unused var

* Remove old code

* Remove more

* Updates

* Add LineHeight

Changing the font size cut off the j/g and _

* Fix invisible console selection

Closes #874

* Add Missing to `offline` detection

* Use helper

* Update

* Removals

* Move to `SmallStatBlock`
2025-02-26 10:08:42 -05:00
MartinOscar
3d764a89f7
chore: Upgrade Dependencies (#1005)
* chore: yarn upgrade

* chore: composer upgrade

* chore: php artisan filament:upgrade

* chore: update filament-monaco-editor-views

* chore: update filament-monaco-editor-configs

* chore: move turnstile-views to plugins

* fix monaco-editor loader & css
2025-02-25 14:22:07 +01:00
Boy132
2f56ca5ed5
Add deleteAny and replicate to policies (#1030)
* add `deleteAny` to policies

* add `replicate` to policies
2025-02-25 13:50:15 +01:00
Boy132
fe8e6fcfda
Fix StoreServerRequest for deployment (#1031) 2025-02-25 13:49:55 +01:00
MartinOscar
1e7a901371
Don't log duplicated OauthProviders during tests (#1015)
* Make sure OauthProviders we only log if not running tests

* Dependency inject
2025-02-24 19:37:41 +01:00
Boy132
d53820bbdc
Add view pages for "simple" resources (#963)
* update ApiKeyResource

* update DatabaseHostResource

* update MountResource

* update RoleResource

* update UserResource

* WebhookResource

* fix phpstan

* add back label translations for resources

* add back other labels

* upstream changes
2025-02-24 15:44:47 +01:00
MartinOscar
d03366cf3d
Enhance Node health column (#1023)
* Make sure we are talking to a `Pelican Wings` instance

* Enforce matching `token_id`

* Refactor `NodeSystemInformation`
2025-02-22 21:44:49 +01:00
MartinOscar
7d68da41f4
Add HOSTNAME TERM LANG PWD TZ TIMEZONE to Egg RESERVED_ENV_NAMES (#1026) 2025-02-22 21:44:07 +01:00
MartinOscar
599d53b4f2
Fix Node & Server Create/Edit Page (#1019)
* Add missing `dehydrated` on `Node`

* Add missing `dehydrated` on `Server`
2025-02-21 11:55:11 +01:00
Boy132
f0f04fd86a
Add backend validation to subuser permissions (#1014)
* add backend validation to subuser permissions

* always allow websocket.connect

* use collection to clean permissions
2025-02-21 11:02:08 +01:00
MartinOscar
324fc4b7d5
Add Egg copy from & ReplicateAction (#1013)
* Add `Egg` `copy from` for Process & Install Script

* Add builtin `ReplicateAction`

* Use `CopyFrom` for less duplicated code

* Hide label & add tooltip to `ReplicateAction`

* use `iconButton()` instead of `hiddenLabel()`

* use `iconButton()` for every Actions

* Use our translation instead

* Copy egg_variables aswell

* remove `get()`

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-02-19 19:52:10 +01:00
Quinten
5be4e22a0c
Merge pull request #1012 from QuintenQVD0/docker-schedule-health
(docker) supercronic: allow overlapping
2025-02-16 19:32:40 +01:00
Quinten
75aae3e45b
supercronic: allow overlapping 2025-02-16 18:44:54 +01:00
MartinOscar
c1704eef3b
Interpret Server StartupCommand variables (#1009)
* Use `StartupCommandService`

* Simplify variable name

* Add `PreviewStartupAction`
2025-02-15 17:46:25 -05:00
Quinten
09abec6ee6
fix(docker): enable multi-arch builds (#993)
* fix(docker): enable multi-arch builds

* Remove workflow_dispatch and add missing space

* There is no need for a matrix in the job build-and-push

* Update docker-publish.yml

* Only keep the artifacts for 7 days

* Bump dockerfile labs version to 1.13

* Added a comment in the Dockerfile explaining how to self-build it

* build-php-base cache should not be tagged
2025-02-15 23:32:15 +01:00
David Groselj
206cc76a8b
Fix deleted users being shown as "System" in activity log (#1010)
* Show deleted users as "Deleted user"

* Update shown icon

* Apply suggestions from code review

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

* Update app/Models/ActivityLog.php

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-02-15 17:43:32 +01:00
MartinOscar
b355830db4
Fix File Upload (#952)
* Log correct file name

* Remove duplicated throws comment

* Set maxSize

* Add hints

* Fix unit conversion

* Add translations
2025-02-14 11:11:52 +01:00
MartinOscar
09375df8a7
Add missing selectablePlaceholder(false) & native(false) Fix 500 (#1008)
* Add missing `selectablePlaceholder(false)`

* Add missing `native(false)`
2025-02-14 11:11:16 +01:00
Boy132
96ec2eb3c2
Small translation fixes (#1006)
* display_name_help: replace location with node

* cpu_helper: 100% is one thread, not core

* remove unnecessary "create_action" translation

* nobody saw anything
2025-02-13 21:50:23 +01:00
MartinOscar
b464bb4d25
Add ignoreRecord: true to Server ExternalId (#1004) 2025-02-13 00:35:20 +01:00
MartinOscar
c561035c75
Fix incorrect Allocation permission in Node's AllocationsRelationManager (#995) 2025-02-12 20:35:55 +01:00
MartinOscar
48d1ef5d26
Add WordWrap to MonacoEditor (#1001) 2025-02-12 20:35:23 +01:00
MartinOscar
1f6b659546
Fix Translations (#994)
* Fix copy paste AllocationsRelationManager

* We shouldn't let the user know if the user is correct but the password isn't

* Add missing `trans()` `EditServer`

* Add missing `trans()` User `ServersRelationManager`

* Replace every `__()` with `trans()` helper

* Fix `exceptions` `User` Model

* Replace `Translator->get()` with `trans()` helper

* Revert "We shouldn't let the user know if the user is correct but the password isn't"

This reverts commit e156ee4b38e9e969662a532648c78fdc1e9b0166.
that's stock laravel, therefore it needs to stay
2025-02-11 22:16:48 +01:00
MartinOscar
8f47ccfbf7
Fix Health ScheduleCheck (#999)
* Use `ScheduleCheck` instead of a blank `Check`
2025-02-11 22:11:07 +01:00
MartinOscar
35d25d216e
Cleanup OAuth _noenv & enabled providers (#989) 2025-02-11 22:10:27 +01:00
MartinOscar
a6963ad802
Remove Deprecated PHPDoc comment & AuditLog Model (#997)
* Remove missleading deprecation, you cant use can/cannot on apikeys

* Remove unused `AuditLog` Model
2025-02-11 19:25:36 +01:00
Thibault Junin
d48cf6b722
Add Webhook Event header (#996)
* Add Webhook Event header
2025-02-11 13:43:40 +01:00
MartinOscar
cba4cf11aa
Fix Admin Area translations (#991)
* Fix button

* Replace array with index

* Fix Server ToggleInstallService

* FiNodeVersionsCheck

* Fix CreateWebhookConfiguration

* Fixdatabasehost post_help > port_help

* Fix User CreateServer

* Fix Profile language_help

* Fix Role permission UserResource

* Remove debug & Pint
2025-02-10 10:28:14 -05:00
MartinOscar
96c09acc52
Fix translation (#990) 2025-02-10 00:06:11 -05:00
Charles
7f697017a7
Fix flipped translation keys (#988) 2025-02-10 00:58:08 +01:00
Charles
f8ad720f52
Admin Area Translations (#965)
* Init

* Health Page

* Admin API Keys

* Update API Keys

* Database Hosts

* Mounts

* remove `s`

* Users

* Webhooks

* Server

never again...

* Fix Server

* Settings

* Update Mounts

* Update Databasehost

* Update Server

* Oops, Update Server

* Nodes

* Update User

* Dashboard

* Update Server

* Profile

* Egg

* Role & Update Egg

* Add base Laravel lang files

* update apikey

* remove html back to settings, remove comment

* add `:resource` to create_action

* Update Egg

* Update Egg v2

* Update 1

* trans cf info label

* Update charts

* more trans

* Update Webhook

* update Health

* Update Server

* Update Role

* Fixes

* Bulk Update

* AnotherOne

* Fix relation button label

* rename `admin1` to `admin`

Leftover from testing... oops

* More Translations

* Updates

* `pint` + Relation Manager Titles
2025-02-08 23:16:54 -05:00
Boy132
513117cc42
Fix event listeners for notifications (#971)
* fix event listeners for notifications

* fix "visit panel" url
2025-02-08 14:32:56 +01:00
MartinOscar
5797b790fd
Fix ServerList Filter query (#977) 2025-02-08 12:45:36 +01:00
MartinOscar
9ec2f6eae1
Fix OAuthProvider & Add ColorPicker for Authentik (#975)
* Fix driver name

* Fix AuthentikProvider config & Add ColorPicker

* Add sqlite-journal to .gitignore
2025-02-07 17:28:06 +01:00
MartinOscar
77bf70b063
Add default Egg import url (#972) 2025-02-07 15:38:25 +01:00
MartinOscar
b8c1b68328
Add back TransientToken check (#968) 2025-02-05 12:58:10 +01:00
MartinOscar
431c1977e3
Filter out wings metadata in ListActivities (#961) 2025-02-02 15:07:03 +01:00
Lance Pioch
f8ad9a1805
Use PestPHP (#962)
* Install Pest

* Don’t use bootstrap file anymore

* Fix comment

* Think this is needed

* Reset this

* Switch dataproviders to attributes

* Fix these

* Support in memory databases

* Fix this migration

* Switch this back for now

* Add missing import

* Truncate and reseed database

* These are replaced now

* Switch ci to use pest
2025-01-30 16:39:17 -05:00
Lance Pioch
635cc6a029
Add PHP 8.4 Support (#858)
* Add php 8.4

* Update ide helper

* Add php 8.4

* Update laravel sanctum

* Update laravel framework

* Hash rounds were increased

* This is always false

* Extend model now

* This does nothing

* Move model validation methods to trait

* Remove base model

* Backup routes were previously referenced by uuids

* Remove commented code

* Upgrade laravel/framework

* Fix migration

* Update ide helper

* Update sanctum

* Add version to composer

* Add this back in, fixed

* Make this protected to be safer
2025-01-30 16:39:00 -05:00
Charles
20125dbc6f
Add front end badges (#960)
* Add front end badges

* I identify as a `string`

* Display even if there's no limit

* use `const`'s

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-30 06:21:28 -05:00
Boy132
d5b8a4c501
Fix file download link (#959)
* fix mount of DownloadFiles

* fix path in download url
2025-01-29 08:32:51 +01:00
MartinOscar
dde5305b3f
Add validation & missing reserved vars to EggVariables (#954)
* Add validation & Add missing reserved vars

* env_var not env_name 🤦‍

* Custom validationMessages
2025-01-28 14:22:03 +01:00
MartinOscar
e352754e6f
Fix CopyAction & Add to Server Settings page (#950)
* Fix & Add to Server Settings page

* Add `request()->isSecure()`

CopyAction only works on SSL, no point in showing it when its not SSL

---------

Co-authored-by: notCharles <charles@pelican.dev>
2025-01-27 19:41:57 +01:00
MartinOscar
7cde90a39a
Fix schedules (#949)
* Fix schedules

* Only explode when payload isn't a power action

* Run only on first day of the month

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-01-27 17:57:17 +01:00
Boy132
3202a59b07
Activity log list improvements (#939)
* handle "server:crashed" log

* update activity log list

* add event filter

* add email to user column

* fix phpstan

* only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin

* Apply same logic from ViewAction & make sure user is admi for url

* Add pagination to avoid showing 2000 records at once

* update can check & pagination

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-27 09:46:39 +01:00
Boy132
71f3abe464
File manager improvements (#936)
* add separate button for "save & close"

* make language selection for editor work

* fix download url

* add info banner for .pelicanignore files

* small cleanup

* fix import

* Move File Lang

* add `ctrl+shift+s` for save & close

* fix keybind

* cleanup and fix default value for edit

* remove unnecessary File::get & trait

* More EditorLanguages not matching their names

* mdx has its own highlighter

---------

Co-authored-by: notCharles <charles@pelican.dev>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-26 14:29:53 +01:00
Alexander Featherson
401026efa1
[Fix] Websocket Tokens Refresh issue (#944)
* - Temporary fix for token refresh issue.

More testing is needed.

* Update server-console.blade.php

Removal of final old token var (no longer needed as livewire will handle it through piping)
2025-01-25 22:29:01 +01:00
MartinOscar
654143addc
Fix ServerList Filter badge count (#946) 2025-01-25 22:24:55 +01:00
Scai
37f9725f27
chore: add codeowners (#941) 2025-01-24 21:00:15 +02:00
dependabot[bot]
98c915490d
Bump vite from 6.0.7 to 6.0.9 (#940)
* Bump vite from 6.0.7 to 6.0.9

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.0.7 to 6.0.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.0.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Also bump laravel-vite-plugin

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-24 14:06:00 +02:00
Boy132
6fb54e32f1
Use tabs instead of filter for server list (#937)
* use tabs instead of filter for server list

* move "all servers" to end
2025-01-24 08:29:07 +01:00
Boy132
fef19b9fdd
files tooltip for activity logs (#938)
* add files tooltip to activity logs

* fix when "files" isn't an array
2025-01-24 08:28:40 +01:00
Josh
6a4963200c
Rootless Docker/Optimized build (#932)
* Rootless Dockerfile/Optimized build

Add unneeded files to .dockerignore
Split Dockerfile into more stages to allow Composer/Yarn to run concurrently
Don't log supervisord to a file, as file logging in a Docker container makes no sense
Redirect process output to container output for log processors
Run all processes as non-root
Minimize files with write permission for non-root user
Move docker folder out of .github, as it has nothing to do with GitHub

* Remove install-php-extensions utility after use and name final stage

* Test arm64 runner

* Allow Docker workflow caching multi-arch separately

* Fix Docker publish workflow branches

* Move Caddyfile/crontab config into docker directory, remove redundant supervisord user

* Further restrict permissions

* Supervisord logs
2025-01-23 11:01:14 +02:00
Boy132
37ba62410f
Fix translations for activity logs (#907)
* fix translations for activity logs

* add backwards compatibility for old logs

* update lang file

* small cleanup

* fix singular/ plural for "file"

* fix for "rename" + disable bulk move (because it's not working)
2025-01-23 09:05:23 +01:00
MartinOscar
262e2fd09a
Add roles to owner selector on Create/Edit Server page (#935)
* Add roles to owner selector on Create/Edit Server page
2025-01-23 02:47:13 +01:00
Boy132
9e8b9cd599
Update node record after updating (#929)
* refresh node model after updating

* update record so form is correctly filled
2025-01-19 01:28:52 +01:00
Boy132
3411e5e65c
NodeStorageChart: Format data after math (#931) 2025-01-19 01:09:54 +01:00
Charles
7e6769c96e
Match the owner selection on create server (#927) 2025-01-19 00:21:58 +01:00
Boy132
03eaddb126
Fix server access for admins without subuser (#919)
* fix server access for admins without subuser

* add permission checks to power buttons

* add permission check for console command sending

* fix tests

* fix websocket token permissions

* fix sftp access

* fix server api + small cleanup

* it's "update", not "edit"...

* fix tests

* fix permission const for "activity read"

* fix activity subuser permission
2025-01-17 23:04:22 +01:00
Boy132
61bdf0dcd7
Alert banner improvements: auto-refresh, fixes & "closeable" (#924)
* fix websocket error always displaying

* use livewire component with polling for alert banner container

* add id to alert banner

* cleanup blade file and add "closeable" property
2025-01-17 23:03:34 +01:00
Charles
cbacc18e56
get value of suspended (#922) 2025-01-16 21:18:00 -05:00
Lance Pioch
ad1a9cd33f
Update phpstan to latest (#804)
* Fix these

* Update phpstan

* Transform these into their identifiers instead

* Fix custom rule

* License is wrong

* Update these

* Pint fixes

* Fix this

* Consolidate these

* Never supported PHP 7

* Better evaluation

* Fixes

* Don’t need ignore

* Replace trait with service

* Subusers are simply the many to many relationship between Servers and Users

* Adjust to remove ignores

* Use new query builder instead!

* wip

* Update composer

* Quick fixes

* Use realtime facade

* Small fixes

* Convert to static to avoid new

* Update to statics

* Don’t modify protected properties directly

* Run pint

* Change to correct method

* Give up and use the facade

* Make sure this route is available

* Filament hasn’t been loaded yet

* This can be readonly

* Typehint

* These are no longer used

* Quick fixes

* Need doc block help

* Always true

* We use caddy with docker

* Pint

* Fix phpstan issues

* Remove unused import

---------

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-16 14:53:50 -05:00
Quinten
02c4eb19f0
ci: move ARM Docker builds to native ARM runner (#920) 2025-01-16 20:26:31 +02:00
MartinOscar
3a25d0f976
Actually use nodeUpdateService not only for keys (#914)
* Actually use nodeUpdateService not only for keys

* Add behind proxy & ignore panel config updates

* Don't Halt

* Prevent double notification

* Revert "Add behind proxy & ignore panel config updates"

This reverts commit 0147888c6cbda54be3375ee91ba8600e0d238b78.
2025-01-16 11:50:08 +01:00
Scai
634b8dec55
Merge pull request #918 from QuintenQVD0/speedup-docker
feat(docker): copy PHP extensions from builder stage to speedup the b…
2025-01-16 10:23:33 +02:00
Quinten
43d0b78742
feat(docker): copy PHP extensions from builder stage to speedup the build
- Reuse compiled PHP extensions from composer stage instead of building them twice
2025-01-16 09:20:54 +01:00
Scai
6b77e69e43
Merge pull request #917 from QuintenQVD0/docker
Fix the docker build
2025-01-16 09:45:36 +02:00
Quinten
efbf4df2a2
Fix the docker build 2025-01-16 08:24:58 +01:00
Boy132
4ec9171017
OAuth improvements (#903)
* rework oauth provider creation & lodaing

* add separate setup form

* use wizard for setup

* add provider class for discord

* cleanup and fixes

* don't throw exception when creating duplicate provider

* update profile and login pages

* did not mean to remove the whole else, oops

* use import
2025-01-15 18:29:06 +01:00
Boy132
885e03ee06
Alert banners (#892)
* add alert banner

* replace old server conflict banner with alert banner

* improve color and icon size

* add alert for websocket errors

* update file loading error to alert banner

* remove old events

* add back `console-status` event

* move @php block under @isset

* remove phpstan ignore

so I'm not getting force choked
2025-01-15 18:23:09 +01:00
MartinOscar
7c6b3a03db
Fix Suspendall & Server Condition (#913) 2025-01-15 17:46:27 +01:00
MartinOscar
fe43539ea7
Use temp config for mail testing (#912)
* Use temp config

* Change port when changing encryption

* Pint

* Use finally

* Pint please do your job next time

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
2025-01-15 16:07:12 +01:00
Charles
e145fcdc56
Use Filament labels. (#906)
* Use Filament labels.

* use `trans`

* Show more files

No reason for this to be its own pr...
2025-01-13 09:31:37 -05:00
Charles
8078f2ca4e
Edit Node Listing, Enable Storage Graph (#905)
* Remove limits in listing

* Enable Storage Graph

* Wings gives us bytes, use helper function

* Use Node Model

* Remove `?? 0`

* Re-Add `?? 0` remove local

* Add Locale on chart

* We should convert these too...

convert_bytes_to_readable follows the prefix config, so we should do it here too.

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-13 09:31:31 -05:00
MartinOscar
d1007ad2fe
Make sure variables are unique per egg (#902)
* Add unique validation

* Also make their name unique

* Custom message
2025-01-10 22:22:47 +01:00
Boy132
7f3b1fd758
Fix server reinstall action (#901)
* fix server reinstall action

* use reinstall service
2025-01-09 23:25:36 +01:00
Josh
d088e79e5e
Fix deleting database host when it has assigned nodes (#899)
* Cascade delete from database_host_node when the database host is deleted

* Update database/migrations/2025_01_09_143607_database_host_node_foreign_delete_cascade.php

Remove migration rollback

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>

* Update 2025_01_09_143607_database_host_node_foreign_delete_cascade.php

Fix brace position

---------

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-09 20:21:44 +01:00
Boy132
9cfd87090f
Update health page with tailwind classes (#893)
* update health page with tailwind classes

* Move php from Blade to Page

---------

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2025-01-09 08:25:10 +01:00
MartinOscar
a7a7c5ba4d
Fix Latest version error (#890)
* Retry if it fails

* Pint
2025-01-08 13:48:36 +01:00
Charles
b14e8fd724
Update colors (#891) 2025-01-07 21:11:05 -05:00
Boy132
c93a836ad8
Remove DaemonConnectionException (#885)
* remove DaemonConnectionException

* update tests
2025-01-07 22:58:04 +01:00
Boy132
6fcf4173d3
Strip http/ https from steam oauth allowed_host (#889)
* strip http/ https from steam oauth allowed_host

* fix param order
2025-01-07 22:47:23 +01:00
Boy132
7449b82f41
adjust path for server panel (#884) 2025-01-07 09:34:13 +01:00
Boy132
af4ac1db92
Update admin area navigation (#881) 2025-01-07 08:24:43 +01:00
Scai
6707d1ccf6
Merge pull request #880 from pelican-dev/feature/vite
Remove old client area and switch to vite
2025-01-07 02:06:27 +02:00
Lance Pioch
b197e73173 Use route instead 2025-01-06 17:35:05 -05:00
Scai
e5418491c8 chore: lint files 2025-01-06 20:08:32 +02:00
Scai
98ebc75965 fix: wrong class used on auth 2025-01-06 20:06:17 +02:00
Scai
121ebe6017 refactor: move assets to service provider 2025-01-06 20:03:10 +02:00
Scai
fc27b24783 fix: remove path on panel default 2025-01-06 20:01:41 +02:00
Scai
8049ef462e refactor: revert oauth routes 2025-01-06 20:01:29 +02:00
Scai
17bb23b5b8 refactor: route redirect links 2025-01-06 19:58:32 +02:00
MartinOscar
8926f9712f
Add back denylist (#872) 2025-01-06 16:54:19 +01:00
Scai
e4849d89d7 refactor: replace old index with new filament app 2025-01-06 17:33:32 +02:00
Scai
af11888b82 chore: lint files 2025-01-06 17:15:53 +02:00
Scai
1845f2955f fix: job workflows for releasing 2025-01-06 17:15:44 +02:00
Scai
a2b315ba74 fix: build workflows #1 try 2025-01-06 17:13:06 +02:00
Scai
76c3632d14 chore: update git workflows 2025-01-06 17:06:58 +02:00
Scai
4facaecea0 feat: register assets js/css 2025-01-06 17:04:43 +02:00
Scai
a55a2cce6e feat: impl vite tailwindcss 2025-01-06 17:04:33 +02:00
Boy132
448fe41e78
Add role permission for health page (#878) 2025-01-06 15:43:29 +01:00
Boy132
7f37b3b099
Fix namespace for role permission icons (#877) 2025-01-06 15:42:47 +01:00
Scai
ef54d52866 refactor: remove old provider 2025-01-06 15:49:45 +02:00
Scai
7bd66c3d85 refactor: unused files 2025-01-06 15:48:50 +02:00
Scai
74efc6e8c1 refactor: redirect to new login page 2025-01-06 15:47:16 +02:00
Scai
a7b767ae78 chore: delete old assets 2025-01-06 15:46:54 +02:00
Scai
a3ecf3994b feat: set filament main client ui 2025-01-06 15:46:43 +02:00
Scai
158fa24fff feat: add logo to filament 2025-01-06 15:46:26 +02:00
Scai
e5069e754d chore: unused files & code related to old auth 2025-01-06 15:42:49 +02:00
Scai
cdd46de274 chore: clean base routes 2025-01-06 15:38:44 +02:00
Scai
ff5812e87b chore: remove old auth 2025-01-06 15:38:04 +02:00
Scai
20ce0ca8e6 chore: purge old configs 2025-01-06 15:22:41 +02:00
Scai
66ec86694f chore: delete old client ui 2025-01-06 15:20:20 +02:00
Boy132
295134fb6c
Add client_id to steam oauth config (#875) 2025-01-06 12:32:35 +01:00
MartinOscar
ae445840f7
Discard ipAddresses cache if wings is offline + Switch to Select (#862)
* Change TextInputColumn to SelectColumn

* Discard cache if wings is offline

* Return 0.0.0.0 instead of an empty array

* Adjustment & remove dns resolve
2025-01-06 03:37:39 +01:00
MartinOscar
77fd54fdc2
Fix/suspend server offline node (#871)
* Use handle instead of toggle & use const isnstead of string

* Avoid rollback if node is unreachable

* Use Enum & remove default action

* Remove useless test
2025-01-06 03:07:06 +01:00
MartinOscar
18fe4f1123
Show suspended servers (#870) 2025-01-06 01:48:04 +01:00
Charles
2525af8f02
Revert "Listen to more framework webhook events (#728)" (#866)
This reverts commit 7a4c4ce02a8e55413ebee4c470a8245c1a767f1f.
2025-01-05 19:07:01 -05:00
2470 changed files with 63581 additions and 71317 deletions

View File

@ -1,10 +1,29 @@
**.DS_Store
.env
.devcontainer
.dockerignore
.editorconfig
.git .git
node_modules .github
vendor **.gitignore
.php-cs-fixer.dist.php
.prettierrc.json
.vscode
Dockerfile
bounties.md
compose.yml
contributing.md
contributor_license_agreement.md
database/database.sqlite database/database.sqlite
docker/README.md
node_modules
phpstan.neon
phpunit.xml
readme.md
storage/debugbar/*.json storage/debugbar/*.json
storage/logs/*.log
storage/framework/cache/data/* storage/framework/cache/data/*
storage/framework/sessions/* storage/framework/sessions/*
storage/framework/testing storage/framework/testing
storage/framework/views/*.php storage/framework/views/*.php
storage/logs/*.log
vendor

View File

@ -3,5 +3,4 @@ APP_DEBUG=false
APP_KEY= APP_KEY=
APP_URL=http://panel.test APP_URL=http://panel.test
APP_INSTALLED=false APP_INSTALLED=false
APP_TIMEZONE=UTC
APP_LOCALE=en APP_LOCALE=en

View File

@ -1,6 +0,0 @@
public
node_modules
resources/views
babel.config.js
tailwind.config.js
webpack.config.js

View File

@ -1,52 +0,0 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json',
tsconfigRootDir: './',
},
settings: {
react: {
pragma: 'React',
version: 'detect',
},
linkComponents: [
{ name: 'Link', linkAttribute: 'to' },
{ name: 'NavLink', linkAttribute: 'to' },
],
},
env: {
browser: true,
es6: true,
},
plugins: ['react', 'react-hooks', 'prettier', '@typescript-eslint'],
extends: [
// 'standard',
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:jest-dom/recommended',
],
rules: {
eqeqeq: 'error',
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
// TypeScript can infer this significantly better than eslint ever can.
'react/prop-types': 0,
'react/display-name': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-non-null-assertion': 0,
// 'react/no-unknown-property': ['error', { ignore: ['css'] }],
// This setup is required to avoid a spam of errors when running eslint about React being
// used before it is defined.
//
// @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use
'no-use-before-define': 0,
'@typescript-eslint/no-use-before-define': 'warn',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }],
},
};

View File

@ -64,10 +64,9 @@ body:
label: Error Logs label: Error Logs
description: | description: |
Run the following command to collect logs on your system. Run the following command to collect logs on your system.
Wings: `sudo wings diagnostics --hastebin-url=https://logs.pelican.dev`
Wings: `sudo wings diagnostics` Panel: `tail -n 300 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl --data-binary @- https://logs.pelican.dev`
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl -X POST -F 'c=@-' paste.pelistuff.com` placeholder: "https://logs.pelican.dev/c17f750e"
placeholder: "https://pelipaste.com/a1h6z"
render: bash render: bash
validations: validations:
required: false required: false

View File

@ -1,76 +0,0 @@
# Pelican Panel - Docker Image
This is a ready to use docker image for the panel.
## Requirements
This docker image requires some additional software to function. The software can either be provided in other containers (see the [docker-compose.yml](https://github.com/pelican-dev/panel/blob/develop/docker-compose.example.yml) as an example) or as existing instances.
A mysql database is required. We recommend the stock [MariaDB Image](https://hub.docker.com/_/mariadb/) image if you prefer to run it in a docker container. As a non-containerized option we recommend mariadb.
A caching software is required as well. We recommend the stock [Redis Image](https://hub.docker.com/_/redis/) image. You can choose any of the [supported options](#cache-drivers).
You can provide additional settings using a custom `.env` file or by setting the appropriate environment variables in the docker-compose file.
## Setup
Start the docker container and the required dependencies (either provide existing ones or start containers as well, see the [docker-compose.yml](https://github.com/pelican-dev/panel/blob/develop/docker-compose.example.yml) file as an example.
After the startup is complete you'll need to create a user.
If you are running the docker container without docker-compose, use:
```
docker exec -it <container id> php artisan p:user:make
```
If you are using docker compose use
```
docker-compose exec panel php artisan p:user:make
```
## Environment Variables
There are multiple environment variables to configure the panel when not providing your own `.env` file, see the following table for details on each available option.
Note: If your `APP_URL` starts with `https://` you need to provide an `LE_EMAIL` as well so Certificates can be generated.
| Variable | Description | Required |
|-------------------| ------------------------------------------------------------------------------ | -------- |
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
| `DB_HOST` | The host of the mysql instance | yes |
| `DB_PORT` | The port of the mysql instance | yes |
| `DB_DATABASE` | The name of the mysql database | yes |
| `DB_USERNAME` | The mysql user | yes |
| `DB_PASSWORD` | The mysql password for the specified user | yes |
| `CACHE_STORE` | The cache driver (see [Cache drivers](#cache-drivers) for detais) | yes |
| `SESSION_DRIVER` | | yes |
| `QUEUE_DRIVER` | | yes |
| `REDIS_HOST` | The hostname or IP address of the redis database | yes |
| `REDIS_PASSWORD` | The password used to secure the redis database | maybe |
| `REDIS_PORT` | The port the redis database is using on the host | maybe |
| `MAIL_DRIVER` | The email driver (see [Mail drivers](#mail-drivers) for details) | yes |
| `MAIL_FROM` | The email that should be used as the sender email | yes |
| `MAIL_HOST` | The host of your mail driver instance | maybe |
| `MAIL_PORT` | The port of your mail driver instance | maybe |
| `MAIL_USERNAME` | The username for your mail driver | maybe |
| `MAIL_PASSWORD` | The password for your mail driver | maybe |
### Cache drivers
You can choose between different cache drivers depending on what you prefer.
We recommend redis when using docker as it can be started in a container easily.
| Driver | Description | Required variables |
| -------- | ------------------------------------ | ------------------------------------------------------ |
| redis | host where redis is running | `REDIS_HOST` |
| redis | port redis is running on | `REDIS_PORT` |
| redis | redis database password | `REDIS_PASSWORD` |
### Mail drivers
You can choose between different mail drivers according to your needs.
Every driver requires `MAIL_FROM` to be set.
| Driver | Description | Required variables |
| -------- | ------------------------------------ | ------------------------------------------------------------- |
| mail | uses the installed php mail | |
| mandrill | [Mandrill](http://www.mandrill.com/) | `MAIL_USERNAME` |
| postmark | [Postmark](https://postmarkapp.com/) | `MAIL_USERNAME` |
| mailgun | [Mailgun](https://www.mailgun.com/) | `MAIL_USERNAME`, `MAIL_HOST` |
| smtp | Any SMTP server can be configured | `MAIL_USERNAME`, `MAIL_HOST`, `MAIL_PASSWORD`, `MAIL_PORT` |

View File

@ -1,75 +0,0 @@
# If using Ubuntu this file should be placed in:
# /etc/nginx/sites-available/
#
# If using CentOS this file should be placed in:
# /etc/nginx/conf.d/
#
# The MIT License (MIT)
#
# Pterodactyl®
# Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
server {
listen 80;
server_name _;
root /app/public;
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
error_log /var/log/nginx/panel.app-error.log error;
# allow larger file uploads and longer script runtimes
client_max_body_size 100m;
client_body_timeout 120s;
sendfile off;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# the fastcgi_pass path needs to be changed accordingly when using CentOS
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_PROXY "";
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location ~ /\.ht {
deny all;
}
}

View File

@ -1,70 +0,0 @@
# If using Ubuntu this file should be placed in:
# /etc/nginx/sites-available/
#
server {
listen 80;
server_name <domain>;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name <domain>;
root /app/public;
index index.php;
access_log /var/log/nginx/panel.app-access.log;
error_log /var/log/nginx/panel.app-error.log error;
# allow larger file uploads and longer script runtimes
client_max_body_size 100m;
client_body_timeout 120s;
sendfile off;
# strengthen ssl security
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
# See the link below for more SSL information:
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
#
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
# Add headers to serve security related headers
add_header Strict-Transport-Security "max-age=15768000; preload;";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header Content-Security-Policy "frame-ancestors 'self'";
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_PROXY "";
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
include /etc/nginx/fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}

View File

@ -1,65 +0,0 @@
#!/bin/ash -e
#mkdir -p /var/log/supervisord/ /var/log/php8/ \
## check for .env file and generate app keys if missing
if [ -f /pelican-data/.env ]; then
echo "external vars exist."
rm -rf /var/www/html/.env
else
echo "external vars don't exist."
rm -rf /var/www/html/.env
touch /pelican-data/.env
## manually generate a key because key generate --force fails
if [ -z $APP_KEY ]; then
echo -e "Generating key."
APP_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
echo -e "Generated app key: $APP_KEY"
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
else
echo -e "APP_KEY exists in environment, using that."
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
fi
## enable installer
echo -e "APP_INSTALLED=false" >> /pelican-data/.env
fi
mkdir /pelican-data/database
ln -s /pelican-data/.env /var/www/html/
chown -h www-data:www-data /var/www/html/.env
ln -s /pelican-data/database/database.sqlite /var/www/html/database/
if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then
echo "Generating APP_KEY..."
php artisan key:generate --force
else
echo "APP_KEY is already set."
fi
## make sure the db is set up
echo -e "Migrating Database"
php artisan migrate --force
echo -e "Optimizing Filament"
php artisan filament:optimize
## start cronjobs for the queue
echo -e "Starting cron jobs."
crond -L /var/log/crond -l 5
export SUPERVISORD_CADDY=false
## disable caddy if SKIP_CADDY is set
if [[ "${SKIP_CADDY:-}" == "true" ]]; then
echo "Starting PHP-FPM only"
else
echo "Starting PHP-FPM and Caddy"
export SUPERVISORD_CADDY=true
fi
chown -R www-data:www-data /pelican-data/.env /pelican-data/database
echo "Starting Supervisord"
exec "$@"

View File

@ -1,16 +0,0 @@
[www]
user = nginx
group = nginx
listen = 127.0.0.1:9000
listen.owner = nginx
listen.group = nginx
listen.mode = 0750
pm = ondemand
pm.max_children = 9
pm.process_idle_timeout = 10s
pm.max_requests = 200
clear_env = no

View File

@ -3,31 +3,40 @@ name: Build
on: on:
push: push:
branches: branches:
- '**' - main
pull_request: pull_request:
branches:
- '**'
jobs: jobs:
ui: ui:
name: UI name: UI
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: true
matrix: matrix:
node-version: [18, 20] node-version: [20, 22]
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
tools: composer:v2
coverage: none
- name: Install PHP dependencies
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: "yarn" cache: "yarn"
- name: Install dependencies - name: Install JS dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
- name: Build - name: Build
run: yarn build:production run: yarn build

View File

@ -6,14 +6,77 @@ on:
- main - main
pull_request: pull_request:
env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
jobs: jobs:
sqlite:
name: SQLite
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [8.2, 8.3, 8.4]
env:
DB_CONNECTION: sqlite
DB_DATABASE: testing.sqlite
steps:
- name: Code Checkout
uses: actions/checkout@v4
- name: Get cache directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php }}-
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
tools: composer:v2
coverage: none
- name: Install dependencies
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: Create SQLite file
run: touch database/testing.sqlite
- name: Unit tests
run: vendor/bin/pest tests/Unit
env:
DB_HOST: UNIT_NO_DB
SKIP_MIGRATIONS: true
- name: Integration tests
run: vendor/bin/pest tests/Integration
mysql: mysql:
name: MySQL name: MySQL
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: true
matrix: matrix:
php: [8.2, 8.3] php: [8.2, 8.3, 8.4]
database: ["mysql:8"] database: ["mysql:8"]
services: services:
database: database:
@ -25,21 +88,10 @@ jobs:
- 3306 - 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
env: env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
DB_CONNECTION: mysql DB_CONNECTION: mysql
DB_HOST: 127.0.0.1 DB_HOST: 127.0.0.1
DB_DATABASE: testing DB_DATABASE: testing
DB_USERNAME: root DB_USERNAME: root
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -66,16 +118,16 @@ jobs:
coverage: none coverage: none
- name: Install dependencies - name: Install dependencies
run: composer install --no-interaction --no-suggest --prefer-dist run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: Unit tests - name: Unit tests
run: vendor/bin/phpunit tests/Unit run: vendor/bin/pest tests/Unit
env: env:
DB_HOST: UNIT_NO_DB DB_HOST: UNIT_NO_DB
SKIP_MIGRATIONS: true SKIP_MIGRATIONS: true
- name: Integration tests - name: Integration tests
run: vendor/bin/phpunit tests/Integration run: vendor/bin/pest tests/Integration
env: env:
DB_PORT: ${{ job.services.database.ports[3306] }} DB_PORT: ${{ job.services.database.ports[3306] }}
DB_USERNAME: root DB_USERNAME: root
@ -84,9 +136,9 @@ jobs:
name: MariaDB name: MariaDB
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: true
matrix: matrix:
php: [8.2, 8.3] php: [8.2, 8.3, 8.4]
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"] database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
services: services:
database: database:
@ -98,21 +150,10 @@ jobs:
- 3306 - 3306
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
env: env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
DB_CONNECTION: mariadb DB_CONNECTION: mariadb
DB_HOST: 127.0.0.1 DB_HOST: 127.0.0.1
DB_DATABASE: testing DB_DATABASE: testing
DB_USERNAME: root DB_USERNAME: root
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -139,41 +180,49 @@ jobs:
coverage: none coverage: none
- name: Install dependencies - name: Install dependencies
run: composer install --no-interaction --no-suggest --prefer-dist run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: Unit tests - name: Unit tests
run: vendor/bin/phpunit tests/Unit run: vendor/bin/pest tests/Unit
env: env:
DB_HOST: UNIT_NO_DB DB_HOST: UNIT_NO_DB
SKIP_MIGRATIONS: true SKIP_MIGRATIONS: true
- name: Integration tests - name: Integration tests
run: vendor/bin/phpunit tests/Integration run: vendor/bin/pest tests/Integration
env: env:
DB_PORT: ${{ job.services.database.ports[3306] }} DB_PORT: ${{ job.services.database.ports[3306] }}
DB_USERNAME: root DB_USERNAME: root
sqlite: postgresql:
name: SQLite name: PostgreSQL
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: true
matrix: matrix:
php: [8.2, 8.3] php: [8.2, 8.3, 8.4]
database: ["postgres:14"]
services:
database:
image: ${{ matrix.database }}
env: env:
APP_ENV: testing POSTGRES_DB: testing
APP_DEBUG: "false" POSTGRES_USER: postgres
APP_KEY: ThisIsARandomStringForTests12345 POSTGRES_PASSWORD: postgres
APP_TIMEZONE: UTC POSTGRES_HOST_AUTH_METHOD: trust
APP_URL: http://localhost/ ports:
CACHE_DRIVER: array - 5432:5432
MAIL_MAILER: array options: >-
SESSION_DRIVER: array --health-cmd pg_isready
QUEUE_CONNECTION: sync --health-interval 10s
DB_CONNECTION: sqlite --health-timeout 5s
DB_DATABASE: testing.sqlite --health-retries 5
GUZZLE_TIMEOUT: 60 env:
GUZZLE_CONNECT_TIMEOUT: 60 DB_CONNECTION: pgsql
DB_HOST: 127.0.0.1
DB_DATABASE: testing
DB_USERNAME: postgres
DB_PASSWORD: postgres
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -189,7 +238,6 @@ jobs:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-composer-${{ matrix.php }}-
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -200,16 +248,13 @@ jobs:
coverage: none coverage: none
- name: Install dependencies - name: Install dependencies
run: composer install --no-interaction --no-suggest --prefer-dist run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: Create SQLite file
run: touch database/testing.sqlite
- name: Unit tests - name: Unit tests
run: vendor/bin/phpunit tests/Unit run: vendor/bin/pest tests/Unit
env: env:
DB_HOST: UNIT_NO_DB DB_HOST: UNIT_NO_DB
SKIP_MIGRATIONS: true SKIP_MIGRATIONS: true
- name: Integration tests - name: Integration tests
run: vendor/bin/phpunit tests/Integration run: vendor/bin/pest tests/Integration

View File

@ -1,6 +1,5 @@
name: Docker name: Docker
on: on:
push: push:
branches: branches:
@ -14,12 +13,65 @@ env:
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
build-and-push: build-php-base:
name: Build and Push name: Build PHP base image on ${{ matrix.os }}
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
permissions: permissions:
contents: read contents: read
packages: write packages: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: amd64
platform: linux/amd64
- os: ubuntu-24.04-arm
arch: arm64
platform: linux/arm64
steps:
- name: Code checkout
uses: actions/checkout@v4
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v3
- name: Build the base PHP image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.base
push: false
load: true
platforms: ${{ matrix.platform }}
tags: base-php:${{ matrix.arch }}
cache-from: type=gha,scope=base-php${{ matrix.arch }}
cache-to: type=gha,scope=base-php${{ matrix.arch }}
- name: Export image to file
run: docker save -o base-php-${{ matrix.arch }}.tar base-php:${{ matrix.arch }}
- name: Push the docker build to the artifacts
uses: actions/upload-artifact@v4
with:
name: base-php-${{ matrix.arch }}.tar
path: base-php-${{ matrix.arch }}.tar
retention-days: 7
build-and-push:
name: Build and Push ubuntu-24.04
runs-on: ubuntu-24.04
needs: build-php-base
permissions:
contents: read
packages: write
# Start a temp local registry because workflow can not pull from localy loaded images
services:
registry:
image: registry:2
ports:
- 5000:5000
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message. # Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))" if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
steps: steps:
@ -41,8 +93,11 @@ jobs:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
# We Need to start it in host mode else it can't acces the local registry on port 5000
- name: Setup Docker buildx - name: Setup Docker buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@ -57,30 +112,57 @@ jobs:
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
# Download the base PHP image AMD64
- uses: actions/download-artifact@v4
with:
name: base-php-amd64.tar
# Download the base PHP image ARM64
- uses: actions/download-artifact@v4
with:
name: base-php-arm64.tar
- name: Load base images into local registry
run: |
docker load -i base-php-amd64.tar
docker load -i base-php-arm64.tar
docker tag base-php:amd64 localhost:5000/base-php:amd64
docker tag base-php:arm64 localhost:5000/base-php:arm64
docker push localhost:5000/base-php:amd64
docker push localhost:5000/base-php:arm64
rm base-php-arm64.tar base-php-amd64.tar
- name: Update version in config/app.php (tag)
if: "github.event_name == 'release' && github.event.action == 'published'"
run: |
sed -i "s/'version' => 'canary',/'version' => '${{ steps.build_info.outputs.version_tag }}',/" config/app.php
- name: Build and Push (tag) - name: Build and Push (tag)
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
if: "github.event_name == 'release' && github.event.action == 'published'" if: "github.event_name == 'release' && github.event.action == 'published'"
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: true push: true
platforms: linux/amd64,linux/arm64 platforms: 'linux/amd64,linux/arm64'
build-args: | build-args: |
VERSION=${{ steps.build_info.outputs.version_tag }} VERSION=${{ steps.build_info.outputs.version_tag }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.docker_meta.outputs.tags }}
cache-from: type=gha,scope=tagged${{ matrix.os }}
cache-to: type=gha,scope=tagged${{ matrix.os }},mode=max
- name: Build and Push (main) - name: Build and Push (main)
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
if: "github.event_name == 'push' && contains(github.ref, 'main')" if: "github.event_name == 'push' && contains(github.ref, 'main')"
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64 platforms: 'linux/amd64,linux/arm64'
build-args: | build-args: |
VERSION=dev-${{ steps.build_info.outputs.short_sha }} VERSION=dev-${{ steps.build_info.outputs.short_sha }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.docker_meta.outputs.tags }}
cache-from: type=gha cache-from: type=gha,scope=${{ matrix.os }}
cache-to: type=gha,mode=max cache-to: type=gha,scope=${{ matrix.os }},mode=max

View File

@ -25,21 +25,38 @@ jobs:
run: cp .env.example .env run: cp .env.example .env
- name: Install dependencies - name: Install dependencies
run: composer install --no-interaction --no-progress --prefer-dist run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts
- name: Pint - name: Pint
run: vendor/bin/pint --test run: vendor/bin/pint --test
phpstan: phpstan:
name: PHPStan name: PHPStan
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [ 8.2, 8.3, 8.4 ]
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Get cache directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php }}-
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: "8.3" php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
tools: composer:v2 tools: composer:v2
coverage: none coverage: none
@ -48,7 +65,7 @@ jobs:
run: cp .env.example .env run: cp .env.example .env
- name: Install dependencies - name: Install dependencies
run: composer install --no-interaction --no-progress --prefer-dist run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: PHPStan - name: PHPStan
run: vendor/bin/phpstan --memory-limit=-1 run: vendor/bin/phpstan --memory-limit=-1 --error-format=github

View File

@ -16,17 +16,28 @@ jobs:
- name: Code checkout - name: Code checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
tools: composer:v2
coverage: none
- name: Install PHP dependencies
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: "yarn" cache: "yarn"
- name: Install dependencies - name: Install JS dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
- name: Build - name: Build
run: yarn build:production run: yarn build
- name: Create release branch and bump version - name: Create release branch and bump version
env: env:
@ -44,8 +55,8 @@ jobs:
- name: Create release archive - name: Create release archive
run: | run: |
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpunit.xml shell.nix rm -rf node_modules vendor tests CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml shell.nix
tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json tar -czf panel.tar.gz * .env.example
- name: Create checksum - name: Create checksum
run: | run: |

7
.gitignore vendored
View File

@ -1,9 +1,9 @@
/.phpunit.cache /.phpunit.cache
/node_modules /node_modules
/public/build /public/build
/public/hot
/public/storage /public/storage
/storage/*.key /storage/*.key
/storage/pail
/storage/clockwork/* /storage/clockwork/*
/vendor /vendor
*.DS_Store* *.DS_Store*
@ -19,10 +19,11 @@ npm-debug.log
yarn-error.log yarn-error.log
/.fleet /.fleet
/.idea /.idea
/.nova
/.vscode /.vscode
/.ddev
public/assets/manifest.json public/assets/manifest.json
/database/*.sqlite /database/*.sqlite*
filament-monaco-editor/
_ide_helper* _ide_helper*
/.phpstorm.meta.php /.phpstorm.meta.php

View File

@ -1,52 +0,0 @@
<?php
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
$finder = (new Finder())
->in(__DIR__)
->exclude([
'vendor',
'node_modules',
'storage',
'bootstrap/cache',
])
->notName(['_ide_helper*']);
return (new Config())
->setRiskyAllowed(true)
->setFinder($finder)
->setRules([
'@Symfony' => true,
'@PSR1' => true,
'@PSR2' => true,
'@PSR12' => true,
'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],
'combine_consecutive_unsets' => true,
'concat_space' => ['spacing' => 'one'],
'heredoc_to_nowdoc' => true,
'no_alias_functions' => true,
'no_unreachable_default_argument_value' => true,
'no_useless_return' => true,
'ordered_imports' => [
'sort_algorithm' => 'length',
],
'phpdoc_align' => [
'align' => 'left',
'tags' => [
'param',
'property',
'return',
'throws',
'type',
'var',
],
],
'random_api_migration' => true,
'ternary_to_null_coalescing' => true,
'yoda_style' => [
'equal' => false,
'identical' => false,
'less_and_greater' => false,
],
]);

View File

@ -1,12 +0,0 @@
{
admin off
email {$ADMIN_EMAIL}
}
{$APP_URL} {
root * /var/www/html/public
encode gzip
php_fastcgi 127.0.0.1:9000
file_server
}

View File

@ -1,59 +1,110 @@
# syntax=docker.io/docker/dockerfile:1.13-labs
# Pelican Production Dockerfile # Pelican Production Dockerfile
FROM node:20-alpine AS yarn ##
#FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn # If you want to build this locally you want to run `docker build -f Dockerfile.dev`
##
# ================================
# Stage 1-1: Composer Install
# ================================
FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS composer
WORKDIR /build WORKDIR /build
COPY . ./ COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
# Copy bare minimum to install Composer dependencies
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
# ================================
# Stage 1-2: Yarn Install
# ================================
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
WORKDIR /build
# Copy bare minimum to install Yarn dependencies
COPY package.json yarn.lock ./
RUN yarn config set network-timeout 300000 \ RUN yarn config set network-timeout 300000 \
&& yarn install --frozen-lockfile \ && yarn install --frozen-lockfile
&& yarn run build:production
FROM php:8.3-fpm-alpine # ================================
# FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine # Stage 2-1: Composer Optimize
# ================================
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer # Copy full code to optimize autoload
COPY --exclude=Caddyfile --exclude=docker/ . ./
RUN composer dump-autoload --optimize
# ================================
# Stage 2-2: Build Frontend Assets
# ================================
FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
WORKDIR /build
# Copy full code
COPY --exclude=Caddyfile --exclude=docker/ . ./
COPY --from=composer /build .
RUN yarn run build
# ================================
# Stage 5: Build Final Application Image
# ================================
FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS final
WORKDIR /var/www/html WORKDIR /var/www/html
# Install dependencies # Install additional required libraries
RUN apk update && apk add --no-cache \ RUN apk add --no-cache \
libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \ caddy ca-certificates supervisor supercronic fcgi
zip unzip curl \
caddy ca-certificates supervisor \
&& docker-php-ext-install bcmath gd intl zip opcache pcntl posix pdo_mysql
# Copy the Caddyfile to the container COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
COPY Caddyfile /etc/caddy/Caddyfile COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
# Copy the application code to the container # Set permissions
COPY . . # First ensure all files are owned by root and restrict www-data to read access
RUN chown root:www-data ./ \
&& chmod 750 ./ \
# Files should not have execute set, but directories need it
&& find ./ -type d -exec chmod 750 {} \; \
# Create necessary directories
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
# Symlinks for env, database, and avatars
&& ln -s /pelican-data/.env ./.env \
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
# Allow www-data write permissions where necessary
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
&& chown -R www-data: /usr/local/etc/php/
COPY --from=yarn /build/public/assets ./public/assets # Configure Supervisor
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/Caddyfile /etc/caddy/Caddyfile
# Add Laravel scheduler to crontab
COPY docker/crontab /etc/supercronic/crontab
RUN touch .env COPY docker/entrypoint.sh /entrypoint.sh
COPY docker/healthcheck.sh /healthcheck.sh
RUN composer install --no-dev --optimize-autoloader
# Set file permissions
RUN chmod -R 755 storage bootstrap/cache \
&& chown -R www-data:www-data ./
# Add scheduler to cron
RUN echo "* * * * * php /var/www/html/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data -
## supervisord config and log dir
RUN cp .github/docker/supervisord.conf /etc/supervisord.conf && \
mkdir /var/log/supervisord/
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/up || exit 1 CMD /bin/ash /healthcheck.sh
EXPOSE 80 443 EXPOSE 80 443
VOLUME /pelican-data VOLUME /pelican-data
ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ] USER www-data
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ] CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]

10
Dockerfile.base Normal file
View File

@ -0,0 +1,10 @@
# ================================
# Stage 0: Build PHP Base Image
# ================================
FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
RUN rm /usr/local/bin/install-php-extensions

114
Dockerfile.dev Normal file
View File

@ -0,0 +1,114 @@
# syntax=docker.io/docker/dockerfile:1.13-labs
# Pelican Development Dockerfile
FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine AS base
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
RUN rm /usr/local/bin/install-php-extensions
# ================================
# Stage 1-1: Composer Install
# ================================
FROM --platform=$TARGETOS/$TARGETARCH base AS composer
WORKDIR /build
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
# Copy bare minimum to install Composer dependencies
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
# ================================
# Stage 1-2: Yarn Install
# ================================
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
WORKDIR /build
# Copy bare minimum to install Yarn dependencies
COPY package.json yarn.lock ./
RUN yarn config set network-timeout 300000 \
&& yarn install --frozen-lockfile
# ================================
# Stage 2-1: Composer Optimize
# ================================
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
# Copy full code to optimize autoload
COPY --exclude=Caddyfile --exclude=docker/ . ./
RUN composer dump-autoload --optimize
# ================================
# Stage 2-2: Build Frontend Assets
# ================================
FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
WORKDIR /build
# Copy full code
COPY --exclude=Caddyfile --exclude=docker/ . ./
COPY --from=composer /build .
RUN yarn run build
# ================================
# Stage 5: Build Final Application Image
# ================================
FROM --platform=$TARGETOS/$TARGETARCH base AS final
WORKDIR /var/www/html
# Install additional required libraries
RUN apk add --no-cache \
caddy ca-certificates supervisor supercronic fcgi coreutils
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
# Set permissions
# First ensure all files are owned by root and restrict www-data to read access
RUN chown root:www-data ./ \
&& chmod 750 ./ \
# Files should not have execute set, but directories need it
&& find ./ -type d -exec chmod 750 {} \; \
# Create necessary directories
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
# Symlinks for env, database, and avatars
&& ln -s /pelican-data/.env ./.env \
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
# Allow www-data write permissions where necessary
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
&& chown -R www-data: /usr/local/etc/php/
# Configure Supervisor
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/Caddyfile /etc/caddy/Caddyfile
# Add Laravel scheduler to crontab
COPY docker/crontab /etc/supercronic/crontab
COPY docker/entrypoint.sh /entrypoint.sh
COPY docker/healthcheck.sh /healthcheck.sh
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
CMD /bin/ash /healthcheck.sh
EXPOSE 80 443
VOLUME /pelican-data
USER www-data
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]

58
app/Checks/CacheCheck.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Checks;
use Exception;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
class CacheCheck extends Check
{
protected ?string $driver = null;
public function driver(string $driver): self
{
$this->driver = $driver;
return $this;
}
public function run(): Result
{
$driver = $this->driver ?? $this->defaultDriver();
$result = Result::make()->meta([
'driver' => $driver,
]);
try {
return $this->canWriteValuesToCache($driver)
? $result->ok(trans('admin/health.results.cache.ok'))
: $result->failed(trans('admin/health.results.cache.failed_retrieve'));
} catch (Exception $exception) {
return $result->failed(trans('admin/health.results.cache.failed', ['error' => $exception->getMessage()]));
}
}
protected function defaultDriver(): ?string
{
return config('cache.default', 'file');
}
protected function canWriteValuesToCache(?string $driver): bool
{
$expectedValue = Str::random(5);
$cacheName = "laravel-health:check-{$expectedValue}";
Cache::driver($driver)->put($cacheName, $expectedValue, 10);
$actualValue = Cache::driver($driver)->get($cacheName);
Cache::driver($driver)->forget($cacheName);
return $actualValue === $expectedValue;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Checks;
use Exception;
use Illuminate\Support\Facades\DB;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
class DatabaseCheck extends Check
{
protected ?string $connectionName = null;
public function connectionName(string $connectionName): self
{
$this->connectionName = $connectionName;
return $this;
}
public function run(): Result
{
$connectionName = $this->connectionName ?? $this->getDefaultConnectionName();
$result = Result::make()->meta([
'connection_name' => $connectionName,
]);
try {
DB::connection($connectionName)->getPdo();
return $result->ok(trans('admin/health.results.database.ok'));
} catch (Exception $exception) {
return $result->failed(trans('admin/health.results.database.failed', ['error' => $exception->getMessage()]));
}
}
protected function getDefaultConnectionName(): string
{
return config('database.default');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Checks;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
use function config;
class DebugModeCheck extends Check
{
protected bool $expected = false;
public function expectedToBe(bool $bool): self
{
$this->expected = $bool;
return $this;
}
public function run(): Result
{
$actual = config('app.debug');
$result = Result::make()
->meta([
'actual' => $actual,
'expected' => $this->expected,
])
->shortSummary($this->convertToWord($actual));
return $this->expected === $actual
? $result->ok()
: $result->failed(trans('admin/health.results.debugmode.failed', [
'actual' => $this->convertToWord($actual),
'expected' => $this->convertToWord($this->expected),
]));
}
protected function convertToWord(bool $boolean): string
{
return $boolean ? 'true' : 'false';
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Checks;
use Illuminate\Support\Facades\App;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
class EnvironmentCheck extends Check
{
protected string $expectedEnvironment = 'production';
public function expectEnvironment(string $expectedEnvironment): self
{
$this->expectedEnvironment = $expectedEnvironment;
return $this;
}
public function run(): Result
{
$actualEnvironment = (string) App::environment();
$result = Result::make()
->meta([
'actual' => $actualEnvironment,
'expected' => $this->expectedEnvironment,
])
->shortSummary($actualEnvironment);
return $this->expectedEnvironment === $actualEnvironment
? $result->ok(trans('admin/health.results.environment.ok'))
: $result->failed(trans('admin/health.results.environment.failed', [
'actual' => $actualEnvironment,
'expected' => $this->expectedEnvironment,
]));
}
}

View File

@ -14,30 +14,34 @@ class NodeVersionsCheck extends Check
public function run(): Result public function run(): Result
{ {
$all = Node::query()->count(); $all = Node::all();
if ($all === 0) { if ($all->isEmpty()) {
$result = Result::make()->notificationMessage('No Nodes created')->shortSummary('No Nodes'); $result = Result::make()
->notificationMessage(trans('admin/health.results.nodeversions.no_nodes_created'))
->shortSummary(trans('admin/health.results.nodeversions.no_nodes'));
$result->status = Status::skipped(); $result->status = Status::skipped();
return $result; return $result;
} }
$latestVersion = $this->versionService->latestWingsVersion(); $outdated = $all
->filter(fn (Node $node) => !isset($node->systemInformation()['exception']) && !$this->versionService->isLatestWings($node->systemInformation()['version']))
$outdated = Node::query()->get()
->filter(fn (Node $node) => !isset($node->systemInformation()['exception']) && $node->systemInformation()['version'] !== $latestVersion)
->count(); ->count();
$all = $all->count();
$latestVersion = $this->versionService->latestWingsVersion();
$result = Result::make() $result = Result::make()
->meta([ ->meta([
'all' => $all, 'all' => $all,
'outdated' => $outdated, 'outdated' => $outdated,
'latestVersion' => $latestVersion,
]) ])
->shortSummary($outdated === 0 ? 'All up-to-date' : "{$outdated}/{$all} outdated"); ->shortSummary($outdated === 0 ? trans('admin/health.results.nodeversions.all_up_to_date') : trans('admin/health.results.nodeversions.outdated', ['outdated' => $outdated, 'all' => $all]));
return $outdated === 0 return $outdated === 0
? $result->ok('All Nodes are up-to-date.') ? $result->ok(trans('admin/health.results.nodeversions.ok'))
: $result->failed(':outdated/:all Nodes are outdated.'); : $result->failed(trans('admin/health.results.nodeversions.failed', ['outdated' => $outdated, 'all' => $all]));
} }
} }

View File

@ -22,10 +22,13 @@ class PanelVersionCheck extends Check
'currentVersion' => $currentVersion, 'currentVersion' => $currentVersion,
'latestVersion' => $latestVersion, 'latestVersion' => $latestVersion,
]) ])
->shortSummary($isLatest ? 'up-to-date' : 'outdated'); ->shortSummary($isLatest ? trans('admin/health.results.panelversion.up_to_date') : trans('admin/health.results.panelversion.outdated'));
return $isLatest return $isLatest
? $result->ok('Panel is up-to-date.') ? $result->ok(trans('admin/health.results.panelversion.ok'))
: $result->failed('Installed version is `:currentVersion` but latest is `:latestVersion`.'); : $result->failed(trans('admin/health.results.panelversion.failed', [
'currentVersion' => $currentVersion,
'latestVersion' => $latestVersion,
]));
} }
} }

View File

@ -0,0 +1,41 @@
<?php
namespace App\Checks;
use Carbon\Carbon;
use Composer\InstalledVersions;
use Spatie\Health\Checks\Checks\ScheduleCheck as BaseCheck;
use Spatie\Health\Checks\Result;
class ScheduleCheck extends BaseCheck
{
public function run(): Result
{
$result = Result::make()->ok(trans('admin/health.results.schedule.ok'));
$lastHeartbeatTimestamp = cache()->store($this->cacheStoreName)->get($this->cacheKey);
if (!$lastHeartbeatTimestamp) {
return $result->failed(trans('admin/health.results.schedule.failed_not_ran'));
}
$latestHeartbeatAt = Carbon::createFromTimestamp($lastHeartbeatTimestamp);
$carbonVersion = InstalledVersions::getVersion('nesbot/carbon');
$minutesAgo = $latestHeartbeatAt->diffInMinutes();
if (version_compare($carbonVersion,
'3.0.0', '<')) {
$minutesAgo += 1;
}
if ($minutesAgo > $this->heartbeatMaxAgeInMinutes) {
return $result->failed(trans('admin/health.results.schedule.failed_last_ran', [
'time' => $minutesAgo,
]));
}
return $result;
}
}

View File

@ -2,10 +2,14 @@
namespace App\Console\Commands\Egg; namespace App\Console\Commands\Egg;
use App\Enums\EggFormat;
use App\Models\Egg; use App\Models\Egg;
use App\Services\Eggs\Sharing\EggExporterService; use App\Services\Eggs\Sharing\EggExporterService;
use Exception; use Exception;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use JsonException;
use Symfony\Component\Yaml\Yaml;
class CheckEggUpdatesCommand extends Command class CheckEggUpdatesCommand extends Command
{ {
@ -16,28 +20,42 @@ class CheckEggUpdatesCommand extends Command
$eggs = Egg::all(); $eggs = Egg::all();
foreach ($eggs as $egg) { foreach ($eggs as $egg) {
try { try {
if (is_null($egg->update_url)) { $this->check($egg, $exporterService);
$this->comment("{$egg->name}: Skipping (no update url set)");
continue;
}
$currentJson = json_decode($exporterService->handle($egg->id));
unset($currentJson->exported_at);
$updatedJson = json_decode(file_get_contents($egg->update_url));
unset($updatedJson->exported_at);
if (md5(json_encode($currentJson)) === md5(json_encode($updatedJson))) {
$this->info("{$egg->name}: Up-to-date");
cache()->put("eggs.{$egg->uuid}.update", false, now()->addHour());
} else {
$this->warn("{$egg->name}: Found update");
cache()->put("eggs.{$egg->uuid}.update", true, now()->addHour());
}
} catch (Exception $exception) { } catch (Exception $exception) {
$this->error("{$egg->name}: Error ({$exception->getMessage()})"); $this->error("{$egg->name}: Error ({$exception->getMessage()})");
} }
} }
} }
/**
* @throws JsonException
*/
private function check(Egg $egg, EggExporterService $exporterService): void
{
if (is_null($egg->update_url)) {
$this->comment("$egg->name: Skipping (no update url set)");
return;
}
$ext = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
$isYaml = in_array($ext, ['yaml', 'yml']);
$local = $isYaml
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url)->throw()->body();
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
unset($local['exported_at'], $remote['exported_at']);
$localHash = md5(json_encode($local, JSON_THROW_ON_ERROR));
$remoteHash = md5(json_encode($remote, JSON_THROW_ON_ERROR));
$status = $localHash === $remoteHash ? 'Up-to-date' : 'Found update';
$this->{($localHash === $remoteHash) ? 'info' : 'warn'}("$egg->name: $status");
cache()->put("eggs.$egg->uuid.update", $localHash !== $remoteHash, now()->addHour());
}
} }

View File

@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands\Egg;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class UpdateEggIndexCommand extends Command
{
protected $signature = 'p:egg:update-index';
public function handle(): int
{
try {
$data = Http::timeout(5)->connectTimeout(1)->get('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json')->throw()->json();
} catch (Exception $exception) {
$this->error($exception->getMessage());
return 1;
}
$index = [];
foreach ($data['nests'] as $nest) {
$nestName = $nest['nest_type'];
$this->info("Nest: $nestName");
$nestEggs = [];
foreach ($nest['Eggs'] as $egg) {
$eggName = $egg['egg']['name'];
$this->comment("Egg: $eggName");
$nestEggs[$egg['download_url']] = $eggName;
}
$index[$nestName] = $nestEggs;
$this->info('');
}
cache()->forever('eggs.index', $index);
return 0;
}
}

View File

@ -3,7 +3,6 @@
namespace App\Console\Commands\Environment; namespace App\Console\Commands\Environment;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class AppSettingsCommand extends Command class AppSettingsCommand extends Command
{ {
@ -21,9 +20,13 @@ class AppSettingsCommand extends Command
if (!config('app.key')) { if (!config('app.key')) {
$this->comment('Generating app key'); $this->comment('Generating app key');
Artisan::call('key:generate'); $this->call('key:generate');
} }
Artisan::call('filament:optimize'); $this->comment('Creating storage link');
$this->call('storage:link');
$this->comment('Caching components & icons');
$this->call('filament:optimize');
} }
} }

View File

@ -27,8 +27,6 @@ class CacheSettingsCommand extends Command
{--redis-pass= : Password used to connect to redis.} {--redis-pass= : Password used to connect to redis.}
{--redis-port= : Port to connect to redis over.}'; {--redis-port= : Port to connect to redis over.}';
protected array $variables = [];
/** /**
* CacheSettingsCommand constructor. * CacheSettingsCommand constructor.
*/ */

View File

@ -6,6 +6,7 @@ use App\Traits\EnvironmentWriterTrait;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel; use Illuminate\Contracts\Console\Kernel;
use Illuminate\Database\DatabaseManager; use Illuminate\Database\DatabaseManager;
use PDOException;
class DatabaseSettingsCommand extends Command class DatabaseSettingsCommand extends Command
{ {
@ -27,6 +28,7 @@ class DatabaseSettingsCommand extends Command
{--username= : Username to use when connecting to the MySQL/ MariaDB server.} {--username= : Username to use when connecting to the MySQL/ MariaDB server.}
{--password= : Password to use for the MySQL/ MariaDB database.}'; {--password= : Password to use for the MySQL/ MariaDB database.}';
/** @var array<array-key, mixed> */
protected array $variables = []; protected array $variables = [];
/** /**
@ -57,7 +59,7 @@ class DatabaseSettingsCommand extends Command
); );
if ($this->variables['DB_CONNECTION'] === 'mysql') { if ($this->variables['DB_CONNECTION'] === 'mysql') {
$this->output->note(__('commands.database_settings.DB_HOST_note')); $this->output->note(trans('commands.database_settings.DB_HOST_note'));
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask( $this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
'Database Host', 'Database Host',
config('database.connections.mysql.host', '127.0.0.1') config('database.connections.mysql.host', '127.0.0.1')
@ -73,7 +75,7 @@ class DatabaseSettingsCommand extends Command
config('database.connections.mysql.database', 'panel') config('database.connections.mysql.database', 'panel')
); );
$this->output->note(__('commands.database_settings.DB_USERNAME_note')); $this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask( $this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
'Database Username', 'Database Username',
config('database.connections.mysql.username', 'pelican') config('database.connections.mysql.username', 'pelican')
@ -82,7 +84,7 @@ class DatabaseSettingsCommand extends Command
$askForMySQLPassword = true; $askForMySQLPassword = true;
if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) { if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) {
$this->variables['DB_PASSWORD'] = config('database.connections.mysql.password'); $this->variables['DB_PASSWORD'] = config('database.connections.mysql.password');
$askForMySQLPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note')); $askForMySQLPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
} }
if ($askForMySQLPassword) { if ($askForMySQLPassword) {
@ -104,11 +106,11 @@ class DatabaseSettingsCommand extends Command
]); ]);
$this->database->connection('_panel_command_test')->getPdo(); $this->database->connection('_panel_command_test')->getPdo();
} catch (\PDOException $exception) { } catch (PDOException $exception) {
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage())); $this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
$this->output->error(__('commands.database_settings.DB_error_2')); $this->output->error(trans('commands.database_settings.DB_error_2'));
if ($this->confirm(__('commands.database_settings.go_back'))) { if ($this->confirm(trans('commands.database_settings.go_back'))) {
$this->database->disconnect('_panel_command_test'); $this->database->disconnect('_panel_command_test');
return $this->handle(); return $this->handle();
@ -117,7 +119,7 @@ class DatabaseSettingsCommand extends Command
return 1; return 1;
} }
} elseif ($this->variables['DB_CONNECTION'] === 'mariadb') { } elseif ($this->variables['DB_CONNECTION'] === 'mariadb') {
$this->output->note(__('commands.database_settings.DB_HOST_note')); $this->output->note(trans('commands.database_settings.DB_HOST_note'));
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask( $this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
'Database Host', 'Database Host',
config('database.connections.mariadb.host', '127.0.0.1') config('database.connections.mariadb.host', '127.0.0.1')
@ -133,7 +135,7 @@ class DatabaseSettingsCommand extends Command
config('database.connections.mariadb.database', 'panel') config('database.connections.mariadb.database', 'panel')
); );
$this->output->note(__('commands.database_settings.DB_USERNAME_note')); $this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask( $this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
'Database Username', 'Database Username',
config('database.connections.mariadb.username', 'pelican') config('database.connections.mariadb.username', 'pelican')
@ -142,7 +144,7 @@ class DatabaseSettingsCommand extends Command
$askForMariaDBPassword = true; $askForMariaDBPassword = true;
if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) { if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) {
$this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password'); $this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password');
$askForMariaDBPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note')); $askForMariaDBPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
} }
if ($askForMariaDBPassword) { if ($askForMariaDBPassword) {
@ -164,11 +166,11 @@ class DatabaseSettingsCommand extends Command
]); ]);
$this->database->connection('_panel_command_test')->getPdo(); $this->database->connection('_panel_command_test')->getPdo();
} catch (\PDOException $exception) { } catch (PDOException $exception) {
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage())); $this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
$this->output->error(__('commands.database_settings.DB_error_2')); $this->output->error(trans('commands.database_settings.DB_error_2'));
if ($this->confirm(__('commands.database_settings.go_back'))) { if ($this->confirm(trans('commands.database_settings.go_back'))) {
$this->database->disconnect('_panel_command_test'); $this->database->disconnect('_panel_command_test');
return $this->handle(); return $this->handle();
@ -179,7 +181,7 @@ class DatabaseSettingsCommand extends Command
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') { } elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask( $this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
'Database Path', 'Database Path',
env('DB_DATABASE', 'database.sqlite') (string) env('DB_DATABASE', 'database.sqlite')
); );
} }

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\Environment; namespace App\Console\Commands\Environment;
use App\Exceptions\PanelException;
use App\Traits\EnvironmentWriterTrait; use App\Traits\EnvironmentWriterTrait;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -22,12 +23,13 @@ class EmailSettingsCommand extends Command
{--username=} {--username=}
{--password=}'; {--password=}';
/** @var array<array-key, mixed> */
protected array $variables = []; protected array $variables = [];
/** /**
* Handle command execution. * Handle command execution.
* *
* @throws \App\Exceptions\PanelException * @throws PanelException
*/ */
public function handle(): void public function handle(): void
{ {
@ -91,7 +93,7 @@ class EmailSettingsCommand extends Command
trans('command/messages.environment.mail.ask_smtp_password') trans('command/messages.environment.mail.ask_smtp_password')
); );
$this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice( $this->variables['MAIL_SCHEME'] = $this->option('encryption') ?? $this->choice(
trans('command/messages.environment.mail.ask_encryption'), trans('command/messages.environment.mail.ask_encryption'),
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
config('mail.mailers.smtp.encryption', 'tls') config('mail.mailers.smtp.encryption', 'tls')

View File

@ -27,8 +27,6 @@ class QueueSettingsCommand extends Command
{--redis-pass= : Password used to connect to redis.} {--redis-pass= : Password used to connect to redis.}
{--redis-port= : Port to connect to redis over.}'; {--redis-port= : Port to connect to redis over.}';
protected array $variables = [];
/** /**
* QueueSettingsCommand constructor. * QueueSettingsCommand constructor.
*/ */

View File

@ -18,10 +18,21 @@ class QueueWorkerServiceCommand extends Command
public function handle(): void public function handle(): void
{ {
if (@file_exists('/.dockerenv')) {
$result = Process::run('supervisorctl restart queue-worker');
if ($result->failed()) {
$this->error('Error restarting service: ' . $result->errorOutput());
return;
}
$this->line('Queue worker service file updated successfully.');
return;
}
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue'); $serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
$path = '/etc/systemd/system/' . $serviceName . '.service'; $path = '/etc/systemd/system/' . $serviceName . '.service';
$fileExists = file_exists($path); $fileExists = @file_exists($path);
if ($fileExists && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) { if ($fileExists && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) {
$this->line('Creation of queue worker service file aborted because service file already exists.'); $this->line('Creation of queue worker service file aborted because service file already exists.');

View File

@ -20,8 +20,6 @@ class RedisSetupCommand extends Command
{--redis-pass= : Password used to connect to redis.} {--redis-pass= : Password used to connect to redis.}
{--redis-port= : Port to connect to redis over.}'; {--redis-port= : Port to connect to redis over.}';
protected array $variables = [];
/** /**
* RedisSetupCommand constructor. * RedisSetupCommand constructor.
*/ */
@ -37,7 +35,7 @@ class RedisSetupCommand extends Command
{ {
$this->variables['CACHE_STORE'] = 'redis'; $this->variables['CACHE_STORE'] = 'redis';
$this->variables['QUEUE_CONNECTION'] = 'redis'; $this->variables['QUEUE_CONNECTION'] = 'redis';
$this->variables['SESSION_DRIVERS'] = 'redis'; $this->variables['SESSION_DRIVER'] = 'redis';
$this->requestRedisSettings(); $this->requestRedisSettings();

View File

@ -28,8 +28,6 @@ class SessionSettingsCommand extends Command
{--redis-pass= : Password used to connect to redis.} {--redis-pass= : Password used to connect to redis.}
{--redis-port= : Port to connect to redis over.}'; {--redis-port= : Port to connect to redis over.}';
protected array $variables = [];
/** /**
* SessionSettingsCommand constructor. * SessionSettingsCommand constructor.
*/ */

View File

@ -4,8 +4,9 @@ namespace App\Console\Commands\Maintenance;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Illuminate\Contracts\Filesystem\Filesystem;
use SplFileInfo;
class CleanServiceBackupFilesCommand extends Command class CleanServiceBackupFilesCommand extends Command
{ {
@ -32,9 +33,10 @@ class CleanServiceBackupFilesCommand extends Command
*/ */
public function handle(): void public function handle(): void
{ {
/** @var SplFileInfo[] */
$files = $this->disk->files('services/.bak'); $files = $this->disk->files('services/.bak');
collect($files)->each(function (\SplFileInfo $file) { collect($files)->each(function ($file) {
$lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath())); $lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath()));
if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) { if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) {
$this->disk->delete($file->getPath()); $this->disk->delete($file->getPath());

View File

@ -5,6 +5,7 @@ namespace App\Console\Commands\Maintenance;
use App\Models\Backup; use App\Models\Backup;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use InvalidArgumentException;
class PruneOrphanedBackupsCommand extends Command class PruneOrphanedBackupsCommand extends Command
{ {
@ -16,7 +17,7 @@ class PruneOrphanedBackupsCommand extends Command
{ {
$since = $this->option('prune-age') ?? config('backups.prune_age', 360); $since = $this->option('prune-age') ?? config('backups.prune_age', 360);
if (!$since || !is_digit($since)) { if (!$since || !is_digit($since)) {
throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.'); throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
} }
$query = Backup::query() $query = Backup::query()

View File

@ -2,8 +2,9 @@
namespace App\Console\Commands\Node; namespace App\Console\Commands\Node;
use App\Exceptions\Model\DataValidationException;
use App\Models\Node;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Services\Nodes\NodeCreationService;
class MakeNodeCommand extends Command class MakeNodeCommand extends Command
{ {
@ -24,52 +25,46 @@ class MakeNodeCommand extends Command
{--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).} {--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).}
{--uploadSize= : Enter the maximum upload filesize.} {--uploadSize= : Enter the maximum upload filesize.}
{--daemonListeningPort= : Enter the daemon listening port.} {--daemonListeningPort= : Enter the daemon listening port.}
{--daemonConnectingPort= : Enter the daemon connecting port.}
{--daemonSFTPPort= : Enter the daemon SFTP listening port.} {--daemonSFTPPort= : Enter the daemon SFTP listening port.}
{--daemonSFTPAlias= : Enter the daemon SFTP alias.} {--daemonSFTPAlias= : Enter the daemon SFTP alias.}
{--daemonBase= : Enter the base folder.}'; {--daemonBase= : Enter the base folder.}';
protected $description = 'Creates a new node on the system via the CLI.'; protected $description = 'Creates a new node on the system via the CLI.';
/**
* MakeNodeCommand constructor.
*/
public function __construct(private NodeCreationService $creationService)
{
parent::__construct();
}
/** /**
* Handle the command execution process. * Handle the command execution process.
* *
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function handle(): void public function handle(): void
{ {
$data['name'] = $this->option('name') ?? $this->ask(__('commands.make_node.name')); $data['name'] = $this->option('name') ?? $this->ask(trans('commands.make_node.name'));
$data['description'] = $this->option('description') ?? $this->ask(__('commands.make_node.description')); $data['description'] = $this->option('description') ?? $this->ask(trans('commands.make_node.description'));
$data['scheme'] = $this->option('scheme') ?? $this->anticipate( $data['scheme'] = $this->option('scheme') ?? $this->anticipate(
__('commands.make_node.scheme'), trans('commands.make_node.scheme'),
['https', 'http'], ['https', 'http'],
'https' 'https'
); );
$data['fqdn'] = $this->option('fqdn') ?? $this->ask(__('commands.make_node.fqdn')); $data['fqdn'] = $this->option('fqdn') ?? $this->ask(trans('commands.make_node.fqdn'));
$data['public'] = $this->option('public') ?? $this->confirm(__('commands.make_node.public'), true); $data['public'] = $this->option('public') ?? $this->confirm(trans('commands.make_node.public'), true);
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(__('commands.make_node.behind_proxy')); $data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(trans('commands.make_node.behind_proxy'));
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(__('commands.make_node.maintenance_mode')); $data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(trans('commands.make_node.maintenance_mode'));
$data['memory'] = $this->option('maxMemory') ?? $this->ask(__('commands.make_node.memory'), '0'); $data['memory'] = $this->option('maxMemory') ?? $this->ask(trans('commands.make_node.memory'), '0');
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(__('commands.make_node.memory_overallocate'), '-1'); $data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(trans('commands.make_node.memory_overallocate'), '-1');
$data['disk'] = $this->option('maxDisk') ?? $this->ask(__('commands.make_node.disk'), '0'); $data['disk'] = $this->option('maxDisk') ?? $this->ask(trans('commands.make_node.disk'), '0');
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(__('commands.make_node.disk_overallocate'), '-1'); $data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(trans('commands.make_node.disk_overallocate'), '-1');
$data['cpu'] = $this->option('maxCpu') ?? $this->ask(__('commands.make_node.cpu'), '0'); $data['cpu'] = $this->option('maxCpu') ?? $this->ask(trans('commands.make_node.cpu'), '0');
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(__('commands.make_node.cpu_overallocate'), '-1'); $data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(trans('commands.make_node.cpu_overallocate'), '-1');
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(__('commands.make_node.upload_size'), '256'); $data['upload_size'] = $this->option('uploadSize') ?? $this->ask(trans('commands.make_node.upload_size'), '256');
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(__('commands.make_node.daemonListen'), '8080'); $data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(trans('commands.make_node.daemonListen'), '8080');
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(__('commands.make_node.daemonSFTP'), '2022'); $data['daemon_connect'] = $this->option('daemonConnectingPort') ?? $this->ask(trans('commands.make_node.daemonConnect'), '8080');
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(__('commands.make_node.daemonSFTPAlias'), ''); $data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(trans('commands.make_node.daemonSFTP'), '2022');
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(__('commands.make_node.daemonBase'), '/var/lib/pelican/volumes'); $data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(trans('commands.make_node.daemonSFTPAlias'), '');
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(trans('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
$node = $this->creationService->handle($data); $node = Node::create($data);
$this->line(__('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id])); $this->line(trans('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id]));
} }
} }

View File

@ -17,16 +17,16 @@ class NodeConfigurationCommand extends Command
{ {
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid'; $column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
/** @var \App\Models\Node $node */ /** @var Node $node */
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () { $node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
$this->error(__('commands.node_config.error_not_exist')); $this->error(trans('commands.node_config.error_not_exist'));
exit(1); exit(1);
}); });
$format = $this->option('format'); $format = $this->option('format');
if (!in_array($format, ['yaml', 'yml', 'json'])) { if (!in_array($format, ['yaml', 'yml', 'json'])) {
$this->error(__('commands.node_config.error_invalid_format')); $this->error(trans('commands.node_config.error_invalid_format'));
return 1; return 1;
} }

View File

@ -13,12 +13,12 @@ class KeyGenerateCommand extends BaseKeyGenerateCommand
public function handle(): void public function handle(): void
{ {
if (!empty(config('app.key')) && $this->input->isInteractive()) { if (!empty(config('app.key')) && $this->input->isInteractive()) {
$this->output->warning(__('commands.key_generate.error_already_exist')); $this->output->warning(trans('commands.key_generate.error_already_exist'));
if (!$this->confirm(__('commands.key_generate.understand'))) { if (!$this->confirm(trans('commands.key_generate.understand'))) {
return; return;
} }
if (!$this->confirm(__('commands.key_generate.continue'))) { if (!$this->confirm(trans('commands.key_generate.continue'))) {
return; return;
} }
} }

View File

@ -2,10 +2,11 @@
namespace App\Console\Commands\Schedule; namespace App\Console\Commands\Schedule;
use Illuminate\Console\Command;
use App\Models\Schedule; use App\Models\Schedule;
use Illuminate\Database\Eloquent\Builder;
use App\Services\Schedules\ProcessScheduleService; use App\Services\Schedules\ProcessScheduleService;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Throwable;
class ProcessRunnableCommand extends Command class ProcessRunnableCommand extends Command
{ {
@ -13,10 +14,7 @@ class ProcessRunnableCommand extends Command
protected $description = 'Process schedules in the database and determine which are ready to run.'; protected $description = 'Process schedules in the database and determine which are ready to run.';
/** public function handle(ProcessScheduleService $processScheduleService): int
* Handle command execution.
*/
public function handle(): int
{ {
$schedules = Schedule::query() $schedules = Schedule::query()
->with('tasks') ->with('tasks')
@ -27,7 +25,7 @@ class ProcessRunnableCommand extends Command
->get(); ->get();
if ($schedules->count() < 1) { if ($schedules->count() < 1) {
$this->line(__('commands.schedule.process.no_tasks')); $this->line(trans('commands.schedule.process.no_tasks'));
return 0; return 0;
} }
@ -35,7 +33,7 @@ class ProcessRunnableCommand extends Command
$bar = $this->output->createProgressBar(count($schedules)); $bar = $this->output->createProgressBar(count($schedules));
foreach ($schedules as $schedule) { foreach ($schedules as $schedule) {
$bar->clear(); $bar->clear();
$this->processSchedule($schedule); $this->processSchedule($processScheduleService, $schedule);
$bar->advance(); $bar->advance();
$bar->display(); $bar->display();
} }
@ -50,23 +48,23 @@ class ProcessRunnableCommand extends Command
* never throw an exception out, otherwise you'll end up killing the entire run group causing * never throw an exception out, otherwise you'll end up killing the entire run group causing
* any other schedules to not process correctly. * any other schedules to not process correctly.
*/ */
protected function processSchedule(Schedule $schedule): void protected function processSchedule(ProcessScheduleService $processScheduleService, Schedule $schedule): void
{ {
if ($schedule->tasks->isEmpty()) { if ($schedule->tasks->isEmpty()) {
return; return;
} }
try { try {
$this->getLaravel()->make(ProcessScheduleService::class)->handle($schedule); $processScheduleService->handle($schedule);
$this->line(trans('command/messages.schedule.output_line', [ $this->line(trans('command/messages.schedule.output_line', [
'schedule' => $schedule->name, 'schedule' => $schedule->name,
'id' => $schedule->id, 'id' => $schedule->id,
])); ]));
} catch (\Throwable|\Exception $exception) { } catch (Throwable $exception) {
logger()->error($exception, ['schedule_id' => $schedule->id]); logger()->error($exception, ['schedule_id' => $schedule->id]);
$this->error(__('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage()); $this->error(trans('commands.schedule.process.error_message', ['schedules' => " #$schedule->id: " . $exception->getMessage()]));
} }
} }
} }

View File

@ -3,12 +3,12 @@
namespace App\Console\Commands\Server; namespace App\Console\Commands\Server;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use Exception;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Factory as ValidatorFactory; use Illuminate\Validation\Factory as ValidatorFactory;
use App\Repositories\Daemon\DaemonPowerRepository; use Illuminate\Validation\ValidationException;
use App\Exceptions\Http\Connection\DaemonConnectionException;
class BulkPowerActionCommand extends Command class BulkPowerActionCommand extends Command
{ {
@ -19,26 +19,13 @@ class BulkPowerActionCommand extends Command
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.'; protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
/** public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
* BulkPowerActionCommand constructor.
*/
public function __construct(private DaemonPowerRepository $powerRepository, private ValidatorFactory $validator)
{
parent::__construct();
}
/**
* Handle the bulk power request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function handle(): void
{ {
$action = $this->argument('action'); $action = $this->argument('action');
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes')); $nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers')); $servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
$validator = $this->validator->make([ $validator = $validator->make([
'action' => $action, 'action' => $action,
'nodes' => $nodes, 'nodes' => $nodes,
'servers' => $servers, 'servers' => $servers,
@ -64,14 +51,17 @@ class BulkPowerActionCommand extends Command
} }
$bar = $this->output->createProgressBar($count); $bar = $this->output->createProgressBar($count);
$powerRepository = $this->powerRepository;
// @phpstan-ignore-next-line $this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
$bar->clear(); $bar->clear();
if (!$server instanceof Server) {
return null;
}
try { try {
$powerRepository->setServer($server)->send($action); $serverRepository->setServer($server)->power($action);
} catch (DaemonConnectionException $exception) { } catch (Exception $exception) {
$this->output->error(trans('command/messages.server.power.action_failed', [ $this->output->error(trans('command/messages.server.power.action_failed', [
'name' => $server->name, 'name' => $server->name,
'id' => $server->id, 'id' => $server->id,
@ -82,6 +72,8 @@ class BulkPowerActionCommand extends Command
$bar->advance(); $bar->advance();
$bar->display(); $bar->display();
return null;
}); });
$this->line(''); $this->line('');
@ -89,6 +81,9 @@ class BulkPowerActionCommand extends Command
/** /**
* Returns the query builder instance that will return the servers that should be affected. * Returns the query builder instance that will return the servers that should be affected.
*
* @param string[]|int[] $servers
* @param string[]|int[] $nodes
*/ */
protected function getQueryBuilder(array $servers, array $nodes): Builder protected function getQueryBuilder(array $servers, array $nodes): Builder
{ {

View File

@ -1,197 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Console\Kernel;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Helper\ProgressBar;
class UpgradeCommand extends Command
{
protected const DEFAULT_URL = 'https://github.com/pelican-dev/panel/releases/%s/panel.tar.gz';
protected $signature = 'p:upgrade
{--user= : The user that PHP runs under. All files will be owned by this user.}
{--group= : The group that PHP runs under. All files will be owned by this group.}
{--url= : The specific archive to download.}
{--release= : A specific version to download from GitHub. Leave blank to use latest.}
{--skip-download : If set no archive will be downloaded.}';
protected $description = 'Downloads a new archive from GitHub and then executes the normal upgrade commands.';
/**
* Executes an upgrade command which will run through all of our standard
* Panel commands and enable users to basically just download
* the archive and execute this and be done.
*
* This places the application in maintenance mode as well while the commands
* are being executed.
*
* @throws \Exception
*/
public function handle(): void
{
$skipDownload = $this->option('skip-download');
if (!$skipDownload) {
$this->output->warning(__('commands.upgrade.integrity'));
$this->output->comment(__('commands.upgrade.source_url'));
$this->line($this->getUrl());
}
if (version_compare(PHP_VERSION, '7.4.0') < 0) {
$this->error(__('commands.upgrade.php_version') . ' [' . PHP_VERSION . '].');
}
$user = 'www-data';
$group = 'www-data';
if ($this->input->isInteractive()) {
if (!$skipDownload) {
$skipDownload = !$this->confirm(__('commands.upgrade.skipDownload'), true);
}
if (is_null($this->option('user'))) {
$userDetails = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner('public')) : [];
$user = $userDetails['name'] ?? 'www-data';
$message = __('commands.upgrade.webserver_user', ['user' => $user]);
if (!$this->confirm($message, true)) {
$user = $this->anticipate(
__('commands.upgrade.name_webserver'),
[
'www-data',
'nginx',
'apache',
]
);
}
}
if (is_null($this->option('group'))) {
$groupDetails = function_exists('posix_getgrgid') ? posix_getgrgid(filegroup('public')) : [];
$group = $groupDetails['name'] ?? 'www-data';
$message = __('commands.upgrade.group_webserver', ['group' => $user]);
if (!$this->confirm($message, true)) {
$group = $this->anticipate(
__('commands.upgrade.group_webserver_question'),
[
'www-data',
'nginx',
'apache',
]
);
}
}
if (!$this->confirm(__('commands.upgrade.are_your_sure'))) {
$this->warn(__('commands.upgrade.terminated'));
return;
}
}
ini_set('output_buffering', '0');
$bar = $this->output->createProgressBar($skipDownload ? 9 : 10);
$bar->start();
if (!$skipDownload) {
$this->withProgress($bar, function () {
$this->line("\$upgrader> curl -L \"{$this->getUrl()}\" | tar -xzv");
$process = Process::fromShellCommandline("curl -L \"{$this->getUrl()}\" | tar -xzv");
$process->run(function ($type, $buffer) {
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
});
});
}
$this->withProgress($bar, function () {
$this->line('$upgrader> php artisan down');
$this->call('down');
});
$this->withProgress($bar, function () {
$this->line('$upgrader> chmod -R 755 storage bootstrap/cache');
$process = new Process(['chmod', '-R', '755', 'storage', 'bootstrap/cache']);
$process->run(function ($type, $buffer) {
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
});
});
$this->withProgress($bar, function () {
$command = ['composer', 'install', '--no-ansi'];
if (config('app.env') === 'production' && !config('app.debug')) {
$command[] = '--optimize-autoloader';
$command[] = '--no-dev';
}
$this->line('$upgrader> ' . implode(' ', $command));
$process = new Process($command);
$process->setTimeout(10 * 60);
$process->run(function ($type, $buffer) {
$this->line($buffer);
});
});
/** @var \Illuminate\Foundation\Application $app */
$app = require __DIR__ . '/../../../bootstrap/app.php';
/** @var \App\Console\Kernel $kernel */
$kernel = $app->make(Kernel::class);
$kernel->bootstrap();
$this->setLaravel($app);
$this->withProgress($bar, function () {
$this->line('$upgrader> php artisan view:clear');
$this->call('view:clear');
});
$this->withProgress($bar, function () {
$this->line('$upgrader> php artisan config:clear');
$this->call('config:clear');
});
$this->withProgress($bar, function () {
$this->line('$upgrader> php artisan migrate --force --seed');
$this->call('migrate', ['--force' => true, '--seed' => true]);
});
$this->withProgress($bar, function () use ($user, $group) {
$this->line("\$upgrader> chown -R {$user}:{$group} *");
$process = Process::fromShellCommandline("chown -R {$user}:{$group} *", $this->getLaravel()->basePath());
$process->setTimeout(10 * 60);
$process->run(function ($type, $buffer) {
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
});
});
$this->withProgress($bar, function () {
$this->line('$upgrader> php artisan queue:restart');
$this->call('queue:restart');
});
$this->withProgress($bar, function () {
$this->line('$upgrader> php artisan up');
$this->call('up');
});
$this->newLine(2);
$this->info(__('commands.upgrade.success'));
}
protected function withProgress(ProgressBar $bar, \Closure $callback): void
{
$bar->clear();
$callback();
$bar->advance();
$bar->display();
}
protected function getUrl(): string
{
if ($this->option('url')) {
return $this->option('url');
}
return sprintf(self::DEFAULT_URL, $this->option('release') ? 'download/v' . $this->option('release') : 'latest/download');
}
}

View File

@ -3,8 +3,8 @@
namespace App\Console\Commands\User; namespace App\Console\Commands\User;
use App\Models\User; use App\Models\User;
use Webmozart\Assert\Assert;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Webmozart\Assert\Assert;
class DeleteUserCommand extends Command class DeleteUserCommand extends Command
{ {
@ -35,7 +35,7 @@ class DeleteUserCommand extends Command
if ($this->input->isInteractive()) { if ($this->input->isInteractive()) {
$tableValues = []; $tableValues = [];
foreach ($results as $user) { foreach ($results as $user) {
$tableValues[] = [$user->id, $user->email, $user->name]; $tableValues[] = [$user->id, $user->email, $user->username];
} }
$this->table(['User ID', 'Email', 'Name'], $tableValues); $this->table(['User ID', 'Email', 'Name'], $tableValues);

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\User; namespace App\Console\Commands\User;
use App\Exceptions\Model\DataValidationException;
use App\Models\User; use App\Models\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -14,7 +15,7 @@ class DisableTwoFactorCommand extends Command
/** /**
* Handle command execution process. * Handle command execution process.
* *
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function handle(): void public function handle(): void
{ {
@ -24,10 +25,12 @@ class DisableTwoFactorCommand extends Command
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
$user = User::query()->where('email', $email)->firstOrFail(); $user = User::where('email', $email)->firstOrFail();
$user->use_totp = false; $user->update([
$user->totp_secret = null; 'mfa_app_secret' => null,
$user->save(); 'mfa_app_recovery_codes' => null,
'mfa_email_enabled' => false,
]);
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email])); $this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
} }

View File

@ -2,9 +2,10 @@
namespace App\Console\Commands\User; namespace App\Console\Commands\User;
use App\Exceptions\Model\DataValidationException;
use App\Services\Users\UserCreationService;
use Exception; use Exception;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Services\Users\UserCreationService;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class MakeUserCommand extends Command class MakeUserCommand extends Command
@ -25,7 +26,7 @@ class MakeUserCommand extends Command
* Handle command request to create a new user. * Handle command request to create a new user.
* *
* @throws Exception * @throws Exception
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function handle(): int public function handle(): int
{ {

View File

@ -3,11 +3,11 @@
namespace App\Console; namespace App\Console;
use App\Console\Commands\Egg\CheckEggUpdatesCommand; use App\Console\Commands\Egg\CheckEggUpdatesCommand;
use App\Console\Commands\Egg\UpdateEggIndexCommand;
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand; use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
use App\Console\Commands\Maintenance\PruneImagesCommand; use App\Console\Commands\Maintenance\PruneImagesCommand;
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand; use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
use App\Console\Commands\Schedule\ProcessRunnableCommand; use App\Console\Commands\Schedule\ProcessRunnableCommand;
use App\Jobs\NodeStatistics;
use App\Models\ActivityLog; use App\Models\ActivityLog;
use App\Models\Webhook; use App\Models\Webhook;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
@ -31,17 +31,20 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule): void protected function schedule(Schedule $schedule): void
{ {
if (config('cache.default') === 'redis') {
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags // https://laravel.com/docs/10.x/upgrade#redis-cache-tags
// This only needs to run when using redis. anything else throws an error.
$schedule->command('cache:prune-stale-tags')->hourly(); $schedule->command('cache:prune-stale-tags')->hourly();
}
// Execute scheduled commands for servers every minute, as if there was a normal cron running. // Execute scheduled commands for servers every minute, as if there was a normal cron running.
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping(); $schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
$schedule->command(CleanServiceBackupFilesCommand::class)->daily(); $schedule->command(CleanServiceBackupFilesCommand::class)->daily();
$schedule->command(PruneImagesCommand::class)->daily(); $schedule->command(PruneImagesCommand::class)->daily();
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
$schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping(); $schedule->command(CheckEggUpdatesCommand::class)->daily();
$schedule->command(UpdateEggIndexCommand::class)->daily();
if (config('backups.prune_age')) { if (config('backups.prune_age')) {
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted. // Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.

View File

@ -0,0 +1,22 @@
<?php
namespace App\Contracts;
use Illuminate\Validation\Validator;
interface Validatable
{
public function getValidator(): Validator;
/**
* @return array<string, mixed>
*/
public static function getRules(): array;
/**
* @return array<string, array<string, mixed>>
*/
public static function getRulesForField(string $field): array;
public function validate(): void;
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Eloquent;
use Illuminate\Database\Eloquent\Builder;
/**
* @template TModel of \Illuminate\Database\Eloquent\Model
*
* @extends Builder<TModel>
*/
class BackupQueryBuilder extends Builder
{
public function nonFailed(): self
{
$this->where(function (Builder $query) {
$query
->whereNull('completed_at')
->orWhere('is_successful', true);
});
return $this;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum BackupStatus: string implements HasColor, HasIcon, HasLabel
{
case InProgress = 'in_progress';
case Successful = 'successful';
case Failed = 'failed';
public function getIcon(): string
{
return match ($this) {
self::InProgress => 'tabler-circle-dashed',
self::Successful => 'tabler-circle-check',
self::Failed => 'tabler-circle-x',
};
}
public function getColor(): string
{
return match ($this) {
self::InProgress => 'primary',
self::Successful => 'success',
self::Failed => 'danger',
};
}
public function getLabel(): string
{
return trans('server/backup.backup_status.' . $this->value);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Enums;
enum ConsoleWidgetPosition: string
{
case Top = 'top';
case AboveConsole = 'above_console';
case BelowConsole = 'below_console';
case Bottom = 'bottom';
}

View File

@ -2,7 +2,11 @@
namespace App\Enums; namespace App\Enums;
enum ContainerStatus: string use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
{ {
// Docker Based // Docker Based
case Created = 'created'; case Created = 'created';
@ -19,7 +23,7 @@ enum ContainerStatus: string
// HTTP Based // HTTP Based
case Missing = 'missing'; case Missing = 'missing';
public function icon(): string public function getIcon(): string
{ {
return match ($this) { return match ($this) {
@ -36,8 +40,17 @@ enum ContainerStatus: string
}; };
} }
public function color(): string public function getColor(bool $hex = false): string
{ {
if ($hex) {
return match ($this) {
self::Created, self::Restarting => '#2563EB',
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
self::Running => '#22C55E',
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
};
}
return match ($this) { return match ($this) {
self::Created => 'primary', self::Created => 'primary',
self::Starting => 'warning', self::Starting => 'warning',
@ -49,18 +62,23 @@ enum ContainerStatus: string
self::Removing => 'warning', self::Removing => 'warning',
self::Missing => 'danger', self::Missing => 'danger',
self::Stopping => 'warning', self::Stopping => 'warning',
self::Offline => 'gray', self::Offline => 'danger',
}; };
} }
public function colorHex(): string public function getLabel(): string
{ {
return match ($this) { return trans('server/console.status.' . $this->value);
self::Created, self::Restarting => '#2563EB', }
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
self::Running => '#22C55E', public function isOffline(): bool
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444', {
}; return in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
}
public function isStartingOrRunning(): bool
{
return in_array($this, [ContainerStatus::Starting, ContainerStatus::Running]);
} }
public function isStartingOrStopping(): bool public function isStartingOrStopping(): bool
@ -70,7 +88,7 @@ enum ContainerStatus: string
public function isStartable(): bool public function isStartable(): bool
{ {
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]); return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Missing]);
} }
public function isRestartable(): bool public function isRestartable(): bool
@ -79,18 +97,16 @@ enum ContainerStatus: string
return true; return true;
} }
return !in_array($this, [ContainerStatus::Offline]); return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
} }
public function isStoppable(): bool public function isStoppable(): bool
{ {
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline]); return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline, ContainerStatus::Missing]);
} }
public function isKillable(): bool public function isKillable(): bool
{ {
// [ContainerStatus::Restarting, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Created] return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited, ContainerStatus::Missing]);
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited]);
} }
} }

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums;
enum CustomRenderHooks: string
{
case FooterStart = 'pelican::footer.start';
case FooterEnd = 'pelican::footer.end';
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Enums;
enum CustomizationKey: string
{
case ConsoleRows = 'console_rows';
case ConsoleFont = 'console_font';
case ConsoleFontSize = 'console_font_size';
case ConsoleGraphPeriod = 'console_graph_period';
case TopNavigation = 'top_navigation';
case DashboardLayout = 'dashboard_layout';
public function getDefaultValue(): string|int|bool
{
return match ($this) {
self::ConsoleRows => 30,
self::ConsoleFont => 'monospace',
self::ConsoleFontSize => 14,
self::ConsoleGraphPeriod => 30,
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
self::DashboardLayout => 'grid',
};
}
/** @return array<string, string|int|bool> */
public static function getDefaultCustomization(): array
{
$default = [];
foreach (self::cases() as $key) {
$default[$key->value] = $key->getDefaultValue();
}
return $default;
}
}

View File

@ -1,98 +0,0 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasLabel;
enum EditorLanguages: string implements HasLabel
{
case plaintext = 'plaintext';
case abap = 'abap';
case apex = 'apex';
case azcali = 'azcali';
case bat = 'bat';
case bicep = 'bicep';
case cameligo = 'cameligo';
case coljure = 'coljure';
case coffeescript = 'coffeescript';
case c = 'c';
case cpp = 'cpp';
case csharp = 'csharp';
case csp = 'csp';
case css = 'css';
case cypher = 'cypher';
case dart = 'dart';
case dockerfile = 'dockerfile';
case ecl = 'ecl';
case elixir = 'elixir';
case flow9 = 'flow9';
case fsharp = 'fsharp';
case go = 'go';
case graphql = 'graphql';
case handlebars = 'handlebars';
case hcl = 'hcl';
case html = 'html';
case ini = 'ini';
case java = 'java';
case javascript = 'javascript';
case julia = 'julia';
case kotlin = 'kotlin';
case less = 'less';
case lexon = 'lexon';
case lua = 'lua';
case liquid = 'liquid';
case m3 = 'm3';
case markdown = 'markdown';
case mdx = 'mdx';
case mips = 'mips';
case msdax = 'msdax';
case mysql = 'mysql';
case objectivec = 'objective-c';
case pascal = 'pascal';
case pascaligo = 'pascaligo';
case perl = 'perl';
case pgsql = 'pgsql';
case php = 'php';
case pla = 'pla';
case postiats = 'postiats';
case powerquery = 'powerquery';
case powershell = 'powershell';
case proto = 'proto';
case pug = 'pug';
case python = 'python';
case qsharp = 'qsharp';
case r = 'r';
case razor = 'razor';
case redis = 'redis';
case redshift = 'redshift';
case restructuredtext = 'restructuredtext';
case ruby = 'ruby';
case rust = 'rust';
case sb = 'sb';
case scala = 'scala';
case scheme = 'scheme';
case scss = 'scss';
case shell = 'shell';
case sol = 'sol';
case aes = 'aes';
case sparql = 'sparql';
case sql = 'sql';
case st = 'st';
case swift = 'swift';
case systemverilog = 'systemverilog';
case verilog = 'verilog';
case tcl = 'tcl';
case twig = 'twig';
case typescript = 'typescript';
case typespec = 'typespec';
case vb = 'vb';
case wgsl = 'wgsl';
case xml = 'xml';
case yaml = 'yaml';
case json = 'json';
public function getLabel(): ?string
{
return $this->name;
}
}

9
app/Enums/EggFormat.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums;
enum EggFormat: string
{
case YAML = 'yaml';
case JSON = 'json';
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums;
enum HeaderActionPosition: string
{
case Before = 'before';
case After = 'after';
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums;
enum HeaderWidgetPosition: string
{
case Before = 'before';
case After = 'after';
}

View File

@ -14,4 +14,24 @@ enum RolePermissionModels: string
case Server = 'server'; case Server = 'server';
case User = 'user'; case User = 'user';
case Webhook = 'webhook'; case Webhook = 'webhook';
public function viewAny(): string
{
return RolePermissionPrefixes::ViewAny->value . ' ' . $this->value;
}
public function view(): string
{
return RolePermissionPrefixes::View->value . ' ' . $this->value;
}
public function create(): string
{
return RolePermissionPrefixes::Create->value . ' ' . $this->value;
}
public function update(): string
{
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
}
} }

View File

@ -0,0 +1,27 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasLabel;
enum ScheduleStatus: string implements HasColor, HasLabel
{
case Inactive = 'inactive';
case Processing = 'processing';
case Active = 'active';
public function getColor(): string
{
return match ($this) {
self::Inactive => 'danger',
self::Processing => 'warning',
self::Active => 'success',
};
}
public function getLabel(): string
{
return trans('server/schedule.schedule_status.' . $this->value);
}
}

View File

@ -2,9 +2,50 @@
namespace App\Enums; namespace App\Enums;
enum ServerResourceType use App\Models\Server;
enum ServerResourceType: string
{ {
case Unit; case Uptime = 'uptime';
case Percentage; case CPU = 'cpu_absolute';
case Time; case Memory = 'memory_bytes';
case Disk = 'disk_bytes';
case CPULimit = 'cpu';
case MemoryLimit = 'memory';
case DiskLimit = 'disk';
/**
* @return int resource amount in bytes
*/
public function getResourceAmount(Server $server): int
{
if ($this->isLimit()) {
$resourceAmount = $server->{$this->value} ?? 0;
if (!$this->isPercentage()) {
// Our limits are entered as MiB/ MB so we need to convert them to bytes
$resourceAmount *= config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000;
}
return $resourceAmount;
}
return $server->retrieveResources()[$this->value] ?? 0;
}
public function isLimit(): bool
{
return $this === ServerResourceType::CPULimit || $this === ServerResourceType::MemoryLimit || $this === ServerResourceType::DiskLimit;
}
public function isTime(): bool
{
return $this === ServerResourceType::Uptime;
}
public function isPercentage(): bool
{
return $this === ServerResourceType::CPU || $this === ServerResourceType::CPULimit;
}
} }

View File

@ -2,19 +2,21 @@
namespace App\Enums; namespace App\Enums;
enum ServerState: string use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum ServerState: string implements HasColor, HasIcon, HasLabel
{ {
case Normal = 'normal';
case Installing = 'installing'; case Installing = 'installing';
case InstallFailed = 'install_failed'; case InstallFailed = 'install_failed';
case ReinstallFailed = 'reinstall_failed'; case ReinstallFailed = 'reinstall_failed';
case Suspended = 'suspended'; case Suspended = 'suspended';
case RestoringBackup = 'restoring_backup'; case RestoringBackup = 'restoring_backup';
public function icon(): string public function getIcon(): string
{ {
return match ($this) { return match ($this) {
self::Normal => 'tabler-heart',
self::Installing => 'tabler-heart-bolt', self::Installing => 'tabler-heart-bolt',
self::InstallFailed => 'tabler-heart-x', self::InstallFailed => 'tabler-heart-x',
self::ReinstallFailed => 'tabler-heart-x', self::ReinstallFailed => 'tabler-heart-x',
@ -23,10 +25,17 @@ enum ServerState: string
}; };
} }
public function color(): string public function getColor(bool $hex = false): string
{ {
if ($hex) {
return match ($this) {
self::Installing, self::RestoringBackup => '#2563EB',
self::Suspended => '#D97706',
self::InstallFailed, self::ReinstallFailed => '#EF4444',
};
}
return match ($this) { return match ($this) {
self::Normal => 'primary',
self::Installing => 'primary', self::Installing => 'primary',
self::InstallFailed => 'danger', self::InstallFailed => 'danger',
self::ReinstallFailed => 'danger', self::ReinstallFailed => 'danger',
@ -34,4 +43,9 @@ enum ServerState: string
self::RestoringBackup => 'primary', self::RestoringBackup => 'primary',
}; };
} }
public function getLabel(): string
{
return str($this->value)->headline();
}
} }

View File

@ -0,0 +1,11 @@
<?php
namespace App\Enums;
enum StartupVariableType: string
{
case Text = 'text';
case Number = 'number';
case Select = 'select';
case Toggle = 'toggle';
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums;
enum SuspendAction: string
{
case Suspend = 'suspend';
case Unsuspend = 'unsuspend';
}

34
app/Enums/WebhookType.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum WebhookType: string implements HasColor, HasIcon, HasLabel
{
case Regular = 'regular';
case Discord = 'discord';
public function getLabel(): string
{
return trans('admin/webhook.' . $this->value);
}
public function getColor(): ?string
{
return match ($this) {
self::Regular => null,
self::Discord => 'blurple',
};
}
public function getIcon(): string
{
return match ($this) {
self::Regular => 'tabler-world-www',
self::Discord => 'tabler-brand-discord',
};
}
}

View File

@ -2,9 +2,9 @@
namespace App\Events; namespace App\Events;
use Illuminate\Support\Str;
use App\Models\ActivityLog; use App\Models\ActivityLog;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class ActivityLogged extends Event class ActivityLogged extends Event
{ {

View File

@ -1,11 +0,0 @@
<?php
namespace App\Events\Auth;
use App\Models\User;
use App\Events\Event;
class DirectLogin extends Event
{
public function __construct(public User $user, public bool $remember) {}
}

View File

@ -1,16 +0,0 @@
<?php
namespace App\Events\Auth;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class FailedPasswordReset extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public string $ip, public string $email) {}
}

View File

@ -2,8 +2,8 @@
namespace App\Events\Auth; namespace App\Events\Auth;
use App\Models\User;
use App\Events\Event; use App\Events\Event;
use App\Models\User;
class ProvidedAuthenticationToken extends Event class ProvidedAuthenticationToken extends Event
{ {

View File

@ -4,14 +4,18 @@ namespace App\Exceptions;
use Exception; use Exception;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Illuminate\Container\Container;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Psr\Log\LoggerInterface;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Container\Container; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;
/**
* @deprecated
*/
class DisplayException extends PanelException implements HttpExceptionInterface class DisplayException extends PanelException implements HttpExceptionInterface
{ {
public const LEVEL_DEBUG = 'debug'; public const LEVEL_DEBUG = 'debug';
@ -25,7 +29,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
/** /**
* DisplayException constructor. * DisplayException constructor.
*/ */
public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0) public function __construct(string $message, ?Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
{ {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
} }
@ -40,6 +44,9 @@ class DisplayException extends PanelException implements HttpExceptionInterface
return Response::HTTP_BAD_REQUEST; return Response::HTTP_BAD_REQUEST;
} }
/**
* @return array<string, string>
*/
public function getHeaders(): array public function getHeaders(): array
{ {
return []; return [];
@ -73,11 +80,11 @@ class DisplayException extends PanelException implements HttpExceptionInterface
* Log the exception to the logs using the defined error level only if the previous * Log the exception to the logs using the defined error level only if the previous
* exception is set. * exception is set.
* *
* @throws \Throwable * @throws Throwable
*/ */
public function report(): void public function report(): void
{ {
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) { if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
return; return;
} }

View File

@ -2,24 +2,28 @@
namespace App\Exceptions; namespace App\Exceptions;
use Illuminate\Support\Arr; use Exception;
use Illuminate\Support\Str; use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse; use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Collection;
use Illuminate\Container\Container; use Illuminate\Container\Container;
use Illuminate\Database\Connection; use Illuminate\Database\Connection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Foundation\Application;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Illuminate\Foundation\Application;
use Symfony\Component\Mailer\Exception\TransportException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use PDOException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\Mailer\Exception\TransportException;
use Throwable;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@ -45,6 +49,8 @@ class Handler extends ExceptionHandler
/** /**
* Maps exceptions to a specific response code. This handles special exception * Maps exceptions to a specific response code. This handles special exception
* types that don't have a defined response code. * types that don't have a defined response code.
*
* @var array<class-string, int>
*/ */
protected static array $exceptionResponseCodes = [ protected static array $exceptionResponseCodes = [
AuthenticationException::class => 401, AuthenticationException::class => 401,
@ -76,7 +82,7 @@ class Handler extends ExceptionHandler
$this->dontReport = []; $this->dontReport = [];
} }
$this->reportable(function (\PDOException $ex) { $this->reportable(function (PDOException $ex) {
$ex = $this->generateCleanedExceptionStack($ex); $ex = $this->generateCleanedExceptionStack($ex);
}); });
@ -85,7 +91,7 @@ class Handler extends ExceptionHandler
}); });
} }
private function generateCleanedExceptionStack(\Throwable $exception): string private function generateCleanedExceptionStack(Throwable $exception): string
{ {
$cleanedStack = ''; $cleanedStack = '';
foreach ($exception->getTrace() as $index => $item) { foreach ($exception->getTrace() as $index => $item) {
@ -114,11 +120,11 @@ class Handler extends ExceptionHandler
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
* *
* @throws \Throwable * @throws Throwable
*/ */
public function render($request, \Throwable $e): Response public function render($request, Throwable $e): Response
{ {
$connections = $this->container->make(Connection::class); $connections = $this->container->make(Connection::class);
@ -140,7 +146,7 @@ class Handler extends ExceptionHandler
* Transform a validation exception into a consistent format to be returned for * Transform a validation exception into a consistent format to be returned for
* calls to the API. * calls to the API.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
*/ */
public function invalidJson($request, ValidationException $exception): JsonResponse public function invalidJson($request, ValidationException $exception): JsonResponse
{ {
@ -180,9 +186,16 @@ class Handler extends ExceptionHandler
} }
/** /**
* Return the exception as a JSONAPI representation for use on API requests. * @param array<string, mixed> $override
* @return array{errors: array{
* code: string,
* status: string,
* detail: string,
* source?: array{line: int, file: string},
* meta?: array{trace: string[], previous: string[]}
* }}|array{errors: array{non-empty-array<string, mixed>}}
*/ */
protected function convertExceptionToArray(\Throwable $e, array $override = []): array public static function exceptionToArray(Throwable $e, array $override = []): array
{ {
$match = self::$exceptionResponseCodes[get_class($e)] ?? null; $match = self::$exceptionResponseCodes[get_class($e)] ?? null;
@ -214,7 +227,7 @@ class Handler extends ExceptionHandler
'trace' => Collection::make($e->getTrace()) 'trace' => Collection::make($e->getTrace())
->map(fn ($trace) => Arr::except($trace, ['args'])) ->map(fn ($trace) => Arr::except($trace, ['args']))
->all(), ->all(),
'previous' => Collection::make($this->extractPrevious($e)) 'previous' => Collection::make(self::extractPrevious($e))
->map(fn ($exception) => $exception->getTrace()) ->map(fn ($exception) => $exception->getTrace())
->map(fn ($trace) => Arr::except($trace, ['args'])) ->map(fn ($trace) => Arr::except($trace, ['args']))
->all(), ->all(),
@ -225,10 +238,21 @@ class Handler extends ExceptionHandler
return ['errors' => [array_merge($error, $override)]]; return ['errors' => [array_merge($error, $override)]];
} }
/**
* Return the exception as a JSONAPI representation for use on API requests.
*
* @param array{detail?: mixed, source?: mixed, meta?: mixed} $override
* @return array{errors?: array<mixed>}
*/
protected function convertExceptionToArray(Throwable $e, array $override = []): array
{
return self::exceptionToArray($e, $override);
}
/** /**
* Return an array of exceptions that should not be reported. * Return an array of exceptions that should not be reported.
*/ */
public static function isReportable(\Exception $exception): bool public static function isReportable(Exception $exception): bool
{ {
return (new self(Container::getInstance()))->shouldReport($exception); return (new self(Container::getInstance()))->shouldReport($exception);
} }
@ -236,7 +260,7 @@ class Handler extends ExceptionHandler
/** /**
* Convert an authentication exception into an unauthenticated response. * Convert an authentication exception into an unauthenticated response.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
*/ */
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
{ {
@ -244,22 +268,19 @@ class Handler extends ExceptionHandler
return new JsonResponse($this->convertExceptionToArray($exception), JsonResponse::HTTP_UNAUTHORIZED); return new JsonResponse($this->convertExceptionToArray($exception), JsonResponse::HTTP_UNAUTHORIZED);
} }
return redirect()->guest('/auth/login'); return redirect()->guest(route('filament.app.auth.login'));
} }
/** /**
* Extracts all the previous exceptions that lead to the one passed into this * Extracts all the previous exceptions that lead to the one passed into this
* function being thrown. * function being thrown.
* *
* @return \Throwable[] * @return Throwable[]
*/ */
protected function extractPrevious(\Throwable $e): array public static function extractPrevious(Throwable $e): array
{ {
$previous = []; $previous = [];
while ($value = $e->getPrevious()) { while ($value = $e->getPrevious()) {
if (!$value instanceof \Throwable) {
break;
}
$previous[] = $value; $previous[] = $value;
$e = $value; $e = $value;
} }
@ -270,10 +291,11 @@ class Handler extends ExceptionHandler
/** /**
* Helper method to allow reaching into the handler to convert an exception * Helper method to allow reaching into the handler to convert an exception
* into the expected array response type. * into the expected array response type.
*
* @return array<mixed>
*/ */
public static function toArray(\Throwable $e): array public static function toArray(Throwable $e): array
{ {
// @phpstan-ignore-next-line return self::exceptionToArray($e);
return (new self(app()))->convertExceptionToArray($e);
} }
} }

View File

@ -1,73 +0,0 @@
<?php
namespace App\Exceptions\Http\Connection;
use Exception;
use Illuminate\Http\Response;
use App\Exceptions\DisplayException;
use Illuminate\Support\Facades\Context;
class DaemonConnectionException extends DisplayException
{
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
/**
* Every request to the daemon instance will return a unique X-Request-Id header
* which allows for all errors to be efficiently tied to a specific request that
* triggered them, and gives users a more direct method of informing hosts when
* something goes wrong.
*/
private ?string $requestId;
/**
* Throw a displayable exception caused by a daemon connection error.
*/
public function __construct(?Exception $previous, bool $useStatusCode = true)
{
/** @var \GuzzleHttp\Psr7\Response|null $response */
$response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null;
$this->requestId = $response?->getHeaderLine('X-Request-Id');
Context::add('request_id', $this->requestId);
if ($useStatusCode) {
$this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode();
// There are rare conditions where daemon encounters a panic condition and crashes the
// request being made after content has already been sent over the wire. In these cases
// you can end up with a "successful" response code that is actual an error.
//
// Handle those better here since we shouldn't ever end up in this exception state and
// be returning a 2XX level response.
if ($this->statusCode < 400) {
$this->statusCode = Response::HTTP_BAD_GATEWAY;
}
}
if (is_null($response)) {
$message = 'Could not establish a connection to the machine running this server. Please try again.';
} else {
$message = sprintf('There was an error while communicating with the machine running this server. This error has been logged, please try again. (code: %s) (request_id: %s)', $response->getStatusCode(), $this->requestId ?? '<nil>');
}
// Attempt to pull the actual error message off the response and return that if it is not
// a 500 level error.
if ($this->statusCode < 500 && !is_null($response)) {
$body = json_decode($response->getBody()->__toString(), true);
$message = sprintf('An error occurred on the remote host: %s. (request id: %s)', $body['error'] ?? $message, $this->requestId ?? '<nil>');
}
$level = $this->statusCode >= 500 && $this->statusCode !== 504
? DisplayException::LEVEL_ERROR
: DisplayException::LEVEL_WARNING;
parent::__construct($message, $previous, $level);
}
/**
* Return the HTTP status code for this exception.
*/
public function getStatusCode(): int
{
return $this->statusCode;
}
}

View File

@ -4,13 +4,14 @@ namespace App\Exceptions\Http;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class HttpForbiddenException extends HttpException class HttpForbiddenException extends HttpException
{ {
/** /**
* HttpForbiddenException constructor. * HttpForbiddenException constructor.
*/ */
public function __construct(?string $message = null, ?\Throwable $previous = null) public function __construct(?string $message = null, ?Throwable $previous = null)
{ {
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous); parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
} }

View File

@ -5,6 +5,7 @@ namespace App\Exceptions\Http\Server;
use App\Enums\ServerState; use App\Enums\ServerState;
use App\Models\Server; use App\Models\Server;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Throwable;
class ServerStateConflictException extends ConflictHttpException class ServerStateConflictException extends ConflictHttpException
{ {
@ -12,7 +13,7 @@ class ServerStateConflictException extends ConflictHttpException
* Exception thrown when the server is in an unsupported state for API access or * Exception thrown when the server is in an unsupported state for API access or
* certain operations within the codebase. * certain operations within the codebase.
*/ */
public function __construct(Server $server, ?\Throwable $previous = null) public function __construct(Server $server, ?Throwable $previous = null)
{ {
$message = 'This server is currently in an unsupported state, please try again later.'; $message = 'This server is currently in an unsupported state, please try again later.';
if ($server->isSuspended()) { if ($server->isSuspended()) {

View File

@ -2,13 +2,15 @@
namespace App\Exceptions; namespace App\Exceptions;
use Spatie\Ignition\Contracts\Solution; use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
use Exception;
use Spatie\Ignition\Contracts\ProvidesSolution; use Spatie\Ignition\Contracts\ProvidesSolution;
use Spatie\Ignition\Contracts\Solution;
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution class ManifestDoesNotExistException extends Exception implements ProvidesSolution
{ {
public function getSolution(): Solution public function getSolution(): Solution
{ {
return new Solutions\ManifestDoesNotExistSolution(); return new ManifestDoesNotExistSolution();
} }
} }

View File

@ -2,11 +2,11 @@
namespace App\Exceptions\Model; namespace App\Exceptions\Model;
use Illuminate\Support\MessageBag;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Validation\Validator;
use App\Exceptions\PanelException; use App\Exceptions\PanelException;
use Illuminate\Contracts\Support\MessageProvider; use Illuminate\Contracts\Support\MessageProvider;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\MessageBag;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class DataValidationException extends PanelException implements HttpExceptionInterface, MessageProvider class DataValidationException extends PanelException implements HttpExceptionInterface, MessageProvider
@ -42,6 +42,9 @@ class DataValidationException extends PanelException implements HttpExceptionInt
return 500; return 500;
} }
/**
* @return array<string, string>
*/
public function getHeaders(): array public function getHeaders(): array
{ {
return []; return [];

View File

@ -2,4 +2,6 @@
namespace App\Exceptions; namespace App\Exceptions;
class PanelException extends \Exception {} use Exception;
class PanelException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Repository;
use Exception;
class FileExistsException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Repository;
use Exception;
class FileNotEditableException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Service\Deployment;
use App\Exceptions\DisplayException;
class NoViableNodeException extends DisplayException {}

View File

@ -2,8 +2,8 @@
namespace App\Exceptions\Service; namespace App\Exceptions\Service;
use Illuminate\Http\Response;
use App\Exceptions\DisplayException; use App\Exceptions\DisplayException;
use Illuminate\Http\Response;
class HasActiveServersException extends DisplayException class HasActiveServersException extends DisplayException
{ {

View File

@ -3,6 +3,7 @@
namespace App\Exceptions\Service; namespace App\Exceptions\Service;
use App\Exceptions\DisplayException; use App\Exceptions\DisplayException;
use Throwable;
class ServiceLimitExceededException extends DisplayException class ServiceLimitExceededException extends DisplayException
{ {
@ -10,7 +11,7 @@ class ServiceLimitExceededException extends DisplayException
* Exception thrown when something goes over a defined limit, such as allocated * Exception thrown when something goes over a defined limit, such as allocated
* ports, tasks, databases, etc. * ports, tasks, databases, etc.
*/ */
public function __construct(string $message, ?\Throwable $previous = null) public function __construct(string $message, ?Throwable $previous = null)
{ {
parent::__construct($message, $previous, self::LEVEL_WARNING); parent::__construct($message, $previous, self::LEVEL_WARNING);
} }

View File

@ -1,17 +0,0 @@
<?php
namespace App\Exceptions\Service\User;
use App\Exceptions\DisplayException;
class TwoFactorAuthenticationTokenInvalid extends DisplayException
{
public string $title = 'Invalid 2FA Code';
public string $icon = 'tabler-2fa';
public function __construct()
{
parent::__construct('The provided two-factor authentication token was not valid.');
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Extensions\Avatar;
use App\Models\User;
interface AvatarSchemaInterface
{
public function getId(): string;
public function getName(): string;
public function get(User $user): ?string;
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Extensions\Avatar;
use App\Models\User;
use Illuminate\Support\Facades\Storage;
class AvatarService
{
/** @var AvatarSchemaInterface[] */
private array $schemas = [];
public function __construct(
private readonly bool $allowUploadedAvatars,
private readonly string $activeSchema,
) {}
public function get(string $id): ?AvatarSchemaInterface
{
return array_get($this->schemas, $id);
}
public function getActiveSchema(): ?AvatarSchemaInterface
{
return $this->get($this->activeSchema);
}
public function getAvatarUrl(User $user): ?string
{
if ($this->allowUploadedAvatars) {
$path = "avatars/$user->id.png";
if (Storage::disk('public')->exists($path)) {
return Storage::url($path);
}
}
return $this->getActiveSchema()?->get($user);
}
public function register(AvatarSchemaInterface $schema): void
{
if (array_key_exists($schema->getId(), $this->schemas)) {
return;
}
$this->schemas[$schema->getId()] = $schema;
}
/** @return array<string, string> */
public function getMappings(): array
{
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Extensions\Avatar\Schemas;
use App\Extensions\Avatar\AvatarSchemaInterface;
use App\Models\User;
class GravatarSchema implements AvatarSchemaInterface
{
public function getId(): string
{
return 'gravatar';
}
public function getName(): string
{
return 'Gravatar';
}
public function get(User $user): string
{
return 'https://gravatar.com/avatar/' . md5($user->email);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Extensions\Avatar\Schemas;
use App\Extensions\Avatar\AvatarSchemaInterface;
use App\Models\User;
class UiAvatarsSchema implements AvatarSchemaInterface
{
public function getId(): string
{
return 'uiavatars';
}
public function getName(): string
{
return 'UI Avatars';
}
public function get(User $user): ?string
{
// UI Avatars is the default of filament so just return null here
return null;
}
}

View File

@ -2,31 +2,33 @@
namespace App\Extensions\Backups; namespace App\Extensions\Backups;
use Closure; use App\Extensions\Filesystem\S3Filesystem;
use Aws\S3\S3Client; use Aws\S3\S3Client;
use Closure;
use Illuminate\Foundation\Application;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Webmozart\Assert\Assert; use InvalidArgumentException;
use Illuminate\Foundation\Application;
use League\Flysystem\FilesystemAdapter; use League\Flysystem\FilesystemAdapter;
use App\Extensions\Filesystem\S3Filesystem;
use League\Flysystem\InMemory\InMemoryFilesystemAdapter; use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
use Webmozart\Assert\Assert;
class BackupManager class BackupManager
{ {
/** /**
* The array of resolved backup drivers. * The array of resolved backup drivers.
*
* @var array<string, FilesystemAdapter>
*/ */
protected array $adapters = []; protected array $adapters = [];
/** /**
* The registered custom driver creators. * The registered custom driver creators.
*
* @var array<string, callable>
*/ */
protected array $customCreators; protected array $customCreators;
/**
* BackupManager constructor.
*/
public function __construct(protected Application $app) {} public function __construct(protected Application $app) {}
/** /**
@ -63,7 +65,7 @@ class BackupManager
$config = $this->getConfig($name); $config = $this->getConfig($name);
if (empty($config['adapter'])) { if (empty($config['adapter'])) {
throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter."); throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
} }
$adapter = $config['adapter']; $adapter = $config['adapter'];
@ -81,11 +83,13 @@ class BackupManager
return $instance; return $instance;
} }
throw new \InvalidArgumentException("Adapter [$adapter] is not supported."); throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
} }
/** /**
* Calls a custom creator for a given adapter type. * Calls a custom creator for a given adapter type.
*
* @param array{adapter: string} $config
*/ */
protected function callCustomCreator(array $config): mixed protected function callCustomCreator(array $config): mixed
{ {
@ -94,6 +98,8 @@ class BackupManager
/** /**
* Creates a new daemon adapter. * Creates a new daemon adapter.
*
* @param array<string, string> $config
*/ */
public function createWingsAdapter(array $config): FilesystemAdapter public function createWingsAdapter(array $config): FilesystemAdapter
{ {
@ -102,6 +108,8 @@ class BackupManager
/** /**
* Creates a new S3 adapter. * Creates a new S3 adapter.
*
* @param array<string, string> $config
*/ */
public function createS3Adapter(array $config): FilesystemAdapter public function createS3Adapter(array $config): FilesystemAdapter
{ {
@ -118,6 +126,8 @@ class BackupManager
/** /**
* Returns the configuration associated with a given backup type. * Returns the configuration associated with a given backup type.
*
* @return array<mixed>
*/ */
protected function getConfig(string $name): array protected function getConfig(string $name): array
{ {
@ -147,8 +157,9 @@ class BackupManager
*/ */
public function forget(array|string $adapter): self public function forget(array|string $adapter): self
{ {
$adapters = &$this->adapters;
foreach ((array) $adapter as $adapterName) { foreach ((array) $adapter as $adapterName) {
unset($this->adapters[$adapterName]); unset($adapters[$adapterName]);
} }
return $this; return $this;

View File

@ -0,0 +1,48 @@
<?php
namespace App\Extensions\Captcha;
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class CaptchaService
{
/** @var array<string, CaptchaSchemaInterface> */
private array $schemas = [];
/**
* @return CaptchaSchemaInterface[]
*/
public function getAll(): array
{
return $this->schemas;
}
public function get(string $id): ?CaptchaSchemaInterface
{
return array_get($this->schemas, $id);
}
public function register(CaptchaSchemaInterface $schema): void
{
if (array_key_exists($schema->getId(), $this->schemas)) {
return;
}
config()->set('captcha.' . Str::lower($schema->getId()), $schema->getConfig());
$this->schemas[$schema->getId()] = $schema;
}
/** @return Collection<CaptchaSchemaInterface> */
public function getActiveSchemas(): Collection
{
return collect($this->schemas)
->filter(fn (CaptchaSchemaInterface $schema) => $schema->isEnabled());
}
public function getActiveSchema(): ?CaptchaSchemaInterface
{
return $this->getActiveSchemas()->first();
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Extensions\Captcha\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Component;
use Illuminate\Support\Str;
abstract class BaseSchema
{
abstract public function getId(): string;
public function getName(): string
{
return Str::upper($this->getId());
}
/**
* @return array<string, string|string[]|bool|null>
*/
public function getConfig(): array
{
$id = Str::upper($this->getId());
return [
'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
];
}
/**
* @return Component[]
*/
public function getSettingsForm(): array
{
$id = Str::upper($this->getId());
return [
TextInput::make("CAPTCHA_{$id}_SITE_KEY")
->label('Site Key')
->placeholder('Site Key')
->columnSpan(2)
->required()
->password()
->revealable()
->autocomplete(false)
->default(env("CAPTCHA_{$id}_SITE_KEY")),
TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
->label('Secret Key')
->placeholder('Secret Key')
->columnSpan(2)
->required()
->password()
->revealable()
->autocomplete(false)
->default(env("CAPTCHA_{$id}_SECRET_KEY")),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Extensions\Captcha\Schemas;
use Filament\Schemas\Components\Component;
interface CaptchaSchemaInterface
{
public function getId(): string;
public function getName(): string;
/**
* @return array<string, string|string[]|bool|null>
*/
public function getConfig(): array;
public function isEnabled(): bool;
public function getFormComponent(): Component;
/**
* @return Component[]
*/
public function getSettingsForm(): array;
public function getIcon(): ?string;
public function validateResponse(?string $captchaResponse = null): void;
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Extensions\Captcha\Schemas\Turnstile;
use Filament\Forms\Components\Field;
class Component extends Field
{
protected string $viewIdentifier = 'turnstile';
protected string $view = 'filament.components.turnstile-captcha';
protected function setUp(): void
{
parent::setUp();
$this->hiddenLabel();
$this->required();
$this->rule(new Rule());
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Extensions\Captcha\Schemas\Turnstile;
use App\Extensions\Captcha\CaptchaService;
use Closure;
use Exception;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\App;
class Rule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
App::call(fn (CaptchaService $service) => $service->get('turnstile')->validateResponse($value));
} catch (Exception $exception) {
report($exception);
$fail('Captcha validation failed: ' . $exception->getMessage());
}
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace App\Extensions\Captcha\Schemas\Turnstile;
use App\Extensions\Captcha\Schemas\BaseSchema;
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
use Exception;
use Filament\Forms\Components\Toggle;
use Filament\Infolists\Components\TextEntry;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\HtmlString;
class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
{
public function getId(): string
{
return 'turnstile';
}
public function isEnabled(): bool
{
return env('CAPTCHA_TURNSTILE_ENABLED', false);
}
public function getFormComponent(): Component
{
return Component::make('turnstile');
}
/**
* @return array<string, string|string[]|bool|null>
*/
public function getConfig(): array
{
return array_merge(parent::getConfig(), [
'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
]);
}
/**
* @return \Filament\Support\Components\Component[]
*
* @throws Exception
*/
public function getSettingsForm(): array
{
return array_merge(parent::getSettingsForm(), [
Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
->label(trans('admin/setting.captcha.verify'))
->columnSpan(2)
->inline(false)
->onIcon('tabler-check')
->offIcon('tabler-x')
->onColor('success')
->offColor('danger')
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
TextEntry::make('info')
->label(trans('admin/setting.captcha.info_label'))
->columnSpan(2)
->state(new HtmlString(trans('admin/setting.captcha.info'))),
]);
}
public function getIcon(): ?string
{
return 'tabler-brand-cloudflare';
}
/**
* @throws Exception
*/
public function validateResponse(?string $captchaResponse = null): void
{
$captchaResponse ??= request()->get('cf-turnstile-response');
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
throw new Exception('Turnstile secret key is not defined.');
}
$response = Http::asJson()
->timeout(15)
->connectTimeout(5)
->throw()
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'secret' => $secret,
'response' => $captchaResponse,
])
->json();
if (!$response['success']) {
match ($response['error-codes'][0] ?? null) {
'missing-input-secret' => throw new Exception('The secret parameter was not passed.'),
'invalid-input-secret' => throw new Exception('The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.'),
'missing-input-response' => throw new Exception('The response parameter (token) was not passed.'),
'invalid-input-response' => throw new Exception('The response parameter (token) is invalid or has expired.'),
'bad-request' => throw new Exception('The request was rejected because it was malformed.'),
'timeout-or-duplicate' => throw new Exception('The response parameter (token) has already been validated before.'),
default => throw new Exception('An internal error happened while validating the response.'),
};
}
if (!$this->verifyDomain($response['hostname'] ?? '')) {
throw new Exception('Domain verification failed.');
}
}
private function verifyDomain(string $hostname): bool
{
if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
return true;
}
$requestUrl = parse_url(request()->url());
return $hostname === array_get($requestUrl, 'host');
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Extensions;
use App\Models\DatabaseHost;
class DynamicDatabaseConnection
{
public const DB_CHARSET = 'utf8';
public const DB_COLLATION = 'utf8_unicode_ci';
public const DB_DRIVER = 'mysql';
/**
* Adds a dynamic database connection entry to the runtime config.
*/
public function set(string $connection, DatabaseHost|int $host, string $database = 'mysql'): void
{
if (!$host instanceof DatabaseHost) {
$host = DatabaseHost::query()->findOrFail($host);
}
config()->set('database.connections.' . $connection, [
'driver' => self::DB_DRIVER,
'host' => $host->host,
'port' => $host->port,
'database' => $database,
'username' => $host->username,
'password' => $host->password,
'charset' => self::DB_CHARSET,
'collation' => self::DB_COLLATION,
]);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Extensions\Features;
use Filament\Actions\Action;
interface FeatureSchemaInterface
{
/** @return string[] */
public function getListeners(): array;
public function getId(): string;
public function getAction(): Action;
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Extensions\Features;
class FeatureService
{
/** @var FeatureSchemaInterface[] */
private array $schemas = [];
/**
* @return FeatureSchemaInterface[]
*/
public function getAll(): array
{
return $this->schemas;
}
public function get(string $id): ?FeatureSchemaInterface
{
return array_get($this->schemas, $id);
}
/**
* @param ?string[] $features
* @return FeatureSchemaInterface[]
*/
public function getActiveSchemas(?array $features = []): array
{
return collect($this->schemas)->only($features)->all();
}
public function register(FeatureSchemaInterface $schema): void
{
if (array_key_exists($schema->getId(), $this->schemas)) {
return;
}
$this->schemas[$schema->getId()] = $schema;
}
/**
* @param ?string[] $features
* @return array<string, array<string>>
*/
public function getMappings(?array $features = []): array
{
return collect($this->getActiveSchemas($features))
->mapWithKeys(fn (FeatureSchemaInterface $schema) => [
$schema->getId() => $schema->getListeners(),
])->all();
}
}

Some files were not shown because too many files have changed in this diff Show More