mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-25 03:14:39 +03:00
Compare commits
948 Commits
3.x
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ac03160c3 | ||
|
|
fe4668a517 | ||
|
|
6125b07afa | ||
|
|
9717aa4b5f | ||
|
|
9491322d8c | ||
|
|
8ed6bb4d8b | ||
|
|
a787af7a06 | ||
|
|
d9016702d6 | ||
|
|
d565441b6a | ||
|
|
cb522b24ef | ||
|
|
b85b17f080 | ||
|
|
47bd7289b1 | ||
|
|
a9b76a0f51 | ||
|
|
8eebb82eba | ||
|
|
b3501be6ec | ||
|
|
ac67656d82 | ||
|
|
968239beb3 | ||
|
|
7514206186 | ||
|
|
1a8321c937 | ||
|
|
340ae8099b | ||
|
|
9d02aeb130 | ||
|
|
cf57c28c40 | ||
|
|
382dcb3868 | ||
|
|
f793b49a81 | ||
|
|
41ddae1ba0 | ||
|
|
e717e20996 | ||
|
|
b5145b016b | ||
|
|
95a8f72058 | ||
|
|
19548338ee | ||
|
|
a8356fc5d2 | ||
|
|
7a447b04d5 | ||
|
|
45699e1614 | ||
|
|
cde3546889 | ||
|
|
3f9c1dbc3c | ||
|
|
bc2df22d78 | ||
|
|
1a3dc5c743 | ||
|
|
fdd1b3798c | ||
|
|
288cbee32f | ||
|
|
a70a060350 | ||
|
|
590569a131 | ||
|
|
7acc8782bb | ||
|
|
f3de185508 | ||
|
|
291b514e24 | ||
|
|
86c369d7ce | ||
|
|
5f77deb1fd | ||
|
|
5f4429e2c3 | ||
|
|
1df3e8d5b0 | ||
|
|
ecb195b2c4 | ||
|
|
86e8a6371e | ||
|
|
d653edb22e | ||
|
|
741252e395 | ||
|
|
308601e6fe | ||
|
|
3933222d98 | ||
|
|
c53ef78d89 | ||
|
|
60792c05c2 | ||
|
|
94420d06be | ||
|
|
6655ccca6e | ||
|
|
a193b4f5ab | ||
|
|
3d5c8d14bd | ||
|
|
de002324d7 | ||
|
|
bcbacb47cd | ||
|
|
e9f6fbadd4 | ||
|
|
c621d2dad5 | ||
|
|
64943aa50c | ||
|
|
020e41cbbc | ||
|
|
e162374e15 | ||
|
|
81c75f7966 | ||
|
|
2be8168468 | ||
|
|
465a372000 | ||
|
|
f0c536c045 | ||
|
|
6a8e630444 | ||
|
|
71aed151d9 | ||
|
|
bb5955cff4 | ||
|
|
38be89a71e | ||
|
|
deb6603840 | ||
|
|
c7a307af6e | ||
|
|
8740f0f645 | ||
|
|
466f9f7edc | ||
|
|
d21740d458 | ||
|
|
1bf6a880fb | ||
|
|
96acd268be | ||
|
|
c0a41acf1f | ||
|
|
75e89b2d4c | ||
|
|
54ea55d426 | ||
|
|
207d875df8 | ||
|
|
ff0215afed | ||
|
|
f357c9501f | ||
|
|
71116e81ba | ||
|
|
f2063d7506 | ||
|
|
c5c05150d8 | ||
|
|
214eb5874f | ||
|
|
b14f6e1645 | ||
|
|
04b251d125 | ||
|
|
5f9ee09ebd | ||
|
|
2fb85f8236 | ||
|
|
4eba5b3f7a | ||
|
|
f95ba6447c | ||
|
|
c0eedc16e0 | ||
|
|
3c5da1cd70 | ||
|
|
8638e53f2b | ||
|
|
3ec90264bd | ||
|
|
e23a4a667a | ||
|
|
a946669dc8 | ||
|
|
6a8ff1a186 | ||
|
|
b003404aea | ||
|
|
45b73debc2 | ||
|
|
329a3993c1 | ||
|
|
da7cba3203 | ||
|
|
6c205a744d | ||
|
|
e78f7bc054 | ||
|
|
12a189f585 | ||
|
|
af4cba341a | ||
|
|
aafe17174f | ||
|
|
a067419d6e | ||
|
|
6117282909 | ||
|
|
967d02612d | ||
|
|
0cd20eb444 | ||
|
|
4dba73163b | ||
|
|
aab3817244 | ||
|
|
1785883c55 | ||
|
|
4c19144640 | ||
|
|
a8a2668754 | ||
|
|
6734fe3be6 | ||
|
|
ff0cde5152 | ||
|
|
b098d20afb | ||
|
|
3ca77765e6 | ||
|
|
476eccca53 | ||
|
|
f686eda718 | ||
|
|
0f58643cf2 | ||
|
|
83ba05d7fb | ||
|
|
66841f5fab | ||
|
|
c03ef43767 | ||
|
|
805461aaf0 | ||
|
|
6f15537d77 | ||
|
|
4fc8d98a0f | ||
|
|
9779365432 | ||
|
|
6e998498e3 | ||
|
|
7d0b9af21a | ||
|
|
116175ba60 | ||
|
|
1e841ac40d | ||
|
|
3401703ccd | ||
|
|
f7cb42e008 | ||
|
|
b6e55795c1 | ||
|
|
17c0041bfd | ||
|
|
478948c81b | ||
|
|
6b706de23d | ||
|
|
508e1c9645 | ||
|
|
3e7c29d264 | ||
|
|
fc643f57f9 | ||
|
|
68a0cbbf10 | ||
|
|
8497e8b009 | ||
|
|
8c64a4ad55 | ||
|
|
49e93c1379 | ||
|
|
d7b5966e1b | ||
|
|
e152efc5f9 | ||
|
|
58307c15a3 | ||
|
|
40810877e0 | ||
|
|
818781ca66 | ||
|
|
05477c711f | ||
|
|
20b06b7b39 | ||
|
|
c2b1a98d29 | ||
|
|
0ff429215d | ||
|
|
d1ca21de9f | ||
|
|
d0c89b0729 | ||
|
|
ffadf9ac16 | ||
|
|
bf23389dba | ||
|
|
68e24896ae | ||
|
|
1864fff04f | ||
|
|
155f2d6476 | ||
|
|
bad5409d9c | ||
|
|
3158bdfef8 | ||
|
|
1fba700096 | ||
|
|
7f8fb3f650 | ||
|
|
d6e0421aaf | ||
|
|
e8e1958969 | ||
|
|
2e094605e9 | ||
|
|
953ee940aa | ||
|
|
496eaaaf83 | ||
|
|
18cf6e9338 | ||
|
|
525a106e81 | ||
|
|
d22f975684 | ||
|
|
c4864feaa5 | ||
|
|
b7b72d7336 | ||
|
|
686c4375bc | ||
|
|
3f40256f8b | ||
|
|
a58e159478 | ||
|
|
d89af243a8 | ||
|
|
bddd6af8af | ||
|
|
e1bdf95971 | ||
|
|
465a03bf0e | ||
|
|
2c2e52b18a | ||
|
|
fcef8d69ae | ||
|
|
8662806dfd | ||
|
|
acf43f2826 | ||
|
|
dfba8e3993 | ||
|
|
56484a2282 | ||
|
|
56b4938dc2 | ||
|
|
10806d6d6b | ||
|
|
a04937d698 | ||
|
|
8a3d67ada0 | ||
|
|
833ae30e59 | ||
|
|
1fdff43ae7 | ||
|
|
bb7c0e0e66 | ||
|
|
447e889a4f | ||
|
|
1c1c8c0cc6 | ||
|
|
7dad2d0e42 | ||
|
|
212c93c2ba | ||
|
|
7557dc1c8d | ||
|
|
07735464c7 | ||
|
|
8ba15538a9 | ||
|
|
c115c6ddf5 | ||
|
|
160ea1ed50 | ||
|
|
7164951085 | ||
|
|
40721a2cb8 | ||
|
|
c464b321dd | ||
|
|
0f8c27a297 | ||
|
|
40819cf171 | ||
|
|
133b94ab08 | ||
|
|
82c0568129 | ||
|
|
75d35e6ee8 | ||
|
|
2a740b43e6 | ||
|
|
818a8a42ad | ||
|
|
67dbf772d5 | ||
|
|
efb834c8f7 | ||
|
|
cf37994c3b | ||
|
|
fc92a87993 | ||
|
|
f459987458 | ||
|
|
5290b8f8bb | ||
|
|
e08cbdecd4 | ||
|
|
70c31eef8f | ||
|
|
5409532ca1 | ||
|
|
a1190c12e0 | ||
|
|
42ca4e7fba | ||
|
|
d6b71885ec | ||
|
|
7b0a15e746 | ||
|
|
7813b6060c | ||
|
|
c431775b7e | ||
|
|
6692942f6f | ||
|
|
276b51f477 | ||
|
|
d4eecdd53d | ||
|
|
d7316c4dfe | ||
|
|
011579451d | ||
|
|
6b5b480902 | ||
|
|
87dc8066c9 | ||
|
|
aa08e774a1 | ||
|
|
482e8ed6b2 | ||
|
|
59bbb63739 | ||
|
|
f4c3c89c17 | ||
|
|
fe4e6271fb | ||
|
|
8ee5d6aabd | ||
|
|
42ecd2951d | ||
|
|
7a6edab79a | ||
|
|
4f43e9171a | ||
|
|
5a3c606627 | ||
|
|
6916b89638 | ||
|
|
0da184c56e | ||
|
|
ce1163d387 | ||
|
|
cd4fc1a95d | ||
|
|
0c0b468525 | ||
|
|
12518bc5d6 | ||
|
|
7c829fb9cf | ||
|
|
61f3e965ba | ||
|
|
10796f8916 | ||
|
|
1d66d4c320 | ||
|
|
e95cd0cd98 | ||
|
|
46a24a087b | ||
|
|
f216376265 | ||
|
|
6d6b50c27d | ||
|
|
58bfa12280 | ||
|
|
8e5660a1b9 | ||
|
|
beac4cd3f6 | ||
|
|
9184441763 | ||
|
|
3ac23d1514 | ||
|
|
6295ea34de | ||
|
|
3cadbbc60c | ||
|
|
60c5f826d6 | ||
|
|
1047e8f948 | ||
|
|
f3501d8b14 | ||
|
|
9114685680 | ||
|
|
8080435eca | ||
|
|
c5824ff26c | ||
|
|
dd7a01aa04 | ||
|
|
55badb5644 | ||
|
|
93f059025c | ||
|
|
7be0cd6928 | ||
|
|
0156456919 | ||
|
|
b9d1ce4438 | ||
|
|
9ce262bf56 | ||
|
|
7ee52affb2 | ||
|
|
93bfe925b9 | ||
|
|
cc1ac1eba1 | ||
|
|
02d24b8a36 | ||
|
|
16fac3b5c6 | ||
|
|
6b249b9545 | ||
|
|
70fc84309f | ||
|
|
f43fb985a2 | ||
|
|
eb99f53d87 | ||
|
|
643e4168b9 | ||
|
|
51cd7a8e81 | ||
|
|
91bf38b63d | ||
|
|
e3699f34d8 | ||
|
|
dc3da2dc98 | ||
|
|
d245751c97 | ||
|
|
e0d7a094ab | ||
|
|
3010e3d61e | ||
|
|
d68e7218a8 | ||
|
|
a4435a7454 | ||
|
|
df26c4f9f5 | ||
|
|
6f1de67523 | ||
|
|
6f009ee126 | ||
|
|
328e159c6b | ||
|
|
f9fd426aca | ||
|
|
6166fac929 | ||
|
|
4bd1070025 | ||
|
|
2d6e30b646 | ||
|
|
f61c6b9dc2 | ||
|
|
5e29737dc5 | ||
|
|
d996019204 | ||
|
|
91d8dbd084 | ||
|
|
bb03ddda50 | ||
|
|
1c66681c0e | ||
|
|
0728266826 | ||
|
|
d81c9faac6 | ||
|
|
cff54f1969 | ||
|
|
201563a13b | ||
|
|
8f2261f6cd | ||
|
|
29cc92f0dc | ||
|
|
33f10cbcb9 | ||
|
|
b538532e34 | ||
|
|
a892821b4f | ||
|
|
5a3b50b31f | ||
|
|
51b217571b | ||
|
|
6e75c76c60 | ||
|
|
e22c5c3e0a | ||
|
|
f3171939a4 | ||
|
|
189d564f87 | ||
|
|
7926f97c8e | ||
|
|
f4d39c1c68 | ||
|
|
6c2d0a2d50 | ||
|
|
f6899301fd | ||
|
|
cbb4ef1da2 | ||
|
|
f6ef76d98e | ||
|
|
65a697d8f7 | ||
|
|
9515a82a75 | ||
|
|
44f5ea567f | ||
|
|
88f910f3e7 | ||
|
|
020f028008 | ||
|
|
0cb7f737b0 | ||
|
|
53aa52f519 | ||
|
|
e884eda5a7 | ||
|
|
58d1fd3917 | ||
|
|
0b0952650e | ||
|
|
aa55a7ed83 | ||
|
|
c7fa7a1bad | ||
|
|
4a3bdd78ef | ||
|
|
a1067fd4aa | ||
|
|
110cc1248b | ||
|
|
04a1ccc97e | ||
|
|
5e7f5c2a4c | ||
|
|
b804878d7b | ||
|
|
118977c8c5 | ||
|
|
c31b7b8c6a | ||
|
|
eefe59b153 | ||
|
|
cd4b7cbf9e | ||
|
|
67cb3d4816 | ||
|
|
7762e68a6c | ||
|
|
7a327ea378 | ||
|
|
b3ca7b7ac9 | ||
|
|
abc99cd928 | ||
|
|
cb638369cf | ||
|
|
9174de2d8c | ||
|
|
7cda358b66 | ||
|
|
33f6551b21 | ||
|
|
b1928e89b4 | ||
|
|
c956cd0106 | ||
|
|
5081cc3f63 | ||
|
|
8eb2c23420 | ||
|
|
cfe385f53a | ||
|
|
264d3498a6 | ||
|
|
065f3f2468 | ||
|
|
957638d4ac | ||
|
|
7d0ce1627b | ||
|
|
8cec7368ab | ||
|
|
5519931ee5 | ||
|
|
97ac0fe54b | ||
|
|
7657364208 | ||
|
|
ef1a208b95 | ||
|
|
aa82c6dd04 | ||
|
|
8ecabef6b5 | ||
|
|
a6d07ede5a | ||
|
|
f6325c07c4 | ||
|
|
7674ee0e2b | ||
|
|
5760e72b8f | ||
|
|
b6e46f758d | ||
|
|
e980877bbc | ||
|
|
dd223b47c0 | ||
|
|
639fa3399d | ||
|
|
82fd547484 | ||
|
|
d461242f08 | ||
|
|
dec1cf8e74 | ||
|
|
15caac51fb | ||
|
|
183c274a0d | ||
|
|
a8b2fb440f | ||
|
|
f8e4514998 | ||
|
|
deeebf73d3 | ||
|
|
422fc102c9 | ||
|
|
e715e92f9d | ||
|
|
73babfa2b3 | ||
|
|
e0a92d733b | ||
|
|
1e67cd9944 | ||
|
|
3946116dff | ||
|
|
b77fd3d653 | ||
|
|
f4672c6cb1 | ||
|
|
5b9e4b1729 | ||
|
|
48f715ae69 | ||
|
|
51460782cc | ||
|
|
b007e63937 | ||
|
|
4dd833562b | ||
|
|
b579f14f3f | ||
|
|
eadaec1b30 | ||
|
|
a9e58bb493 | ||
|
|
5c33c7495a | ||
|
|
f9aa8cf218 | ||
|
|
da698a3666 | ||
|
|
2808a3dd35 | ||
|
|
7ea365e8de | ||
|
|
ae399f9bad | ||
|
|
53a5ff6e6d | ||
|
|
54ae4b3dc1 | ||
|
|
859a721e17 | ||
|
|
03cbdd5bdd | ||
|
|
4c43fd1683 | ||
|
|
0c61a63191 | ||
|
|
b1f99ca8a3 | ||
|
|
0a5810358a | ||
|
|
1bae239971 | ||
|
|
597f74f105 | ||
|
|
5344d99a40 | ||
|
|
1db1a1a3e0 | ||
|
|
712b6a285b | ||
|
|
38b92ae21d | ||
|
|
c18d291f8f | ||
|
|
45c0cfe4d8 | ||
|
|
89d555f308 | ||
|
|
9b2e00ead2 | ||
|
|
d0997dca1a | ||
|
|
cca723d21e | ||
|
|
68dbd6e329 | ||
|
|
544364f061 | ||
|
|
649f319776 | ||
|
|
6ffe800e0d | ||
|
|
12af8fe51e | ||
|
|
9f5d27896a | ||
|
|
15969da41e | ||
|
|
628a6e54e3 | ||
|
|
edcd406b82 | ||
|
|
1742061807 | ||
|
|
289eda64ad | ||
|
|
cc46c463ac | ||
|
|
e6dded61a4 | ||
|
|
921b76f1e1 | ||
|
|
91a3bb969e | ||
|
|
8b62df6c53 | ||
|
|
3c14e1ffa4 | ||
|
|
095fff7ad6 | ||
|
|
0cd03e95f4 | ||
|
|
00dda7dbe4 | ||
|
|
eee1260fcc | ||
|
|
71c91e086f | ||
|
|
94bbf46659 | ||
|
|
558b8b2ea7 | ||
|
|
4bb16887c4 | ||
|
|
0c72833af7 | ||
|
|
ee3f8cd3ec | ||
|
|
b513366f35 | ||
|
|
b1d056a301 | ||
|
|
c72d3f1338 | ||
|
|
7e6231e6de | ||
|
|
4ad837ff14 | ||
|
|
d7ebde5f5f | ||
|
|
6bdd1b3ccb | ||
|
|
afd9f2eb0e | ||
|
|
8a617d416e | ||
|
|
7f4cc64d11 | ||
|
|
38e117e304 | ||
|
|
4d272a1234 | ||
|
|
e9dc6cd32c | ||
|
|
98ba2c1b8b | ||
|
|
2d643ec79f | ||
|
|
2b4625e9b9 | ||
|
|
9f40ed2f84 | ||
|
|
af797b3018 | ||
|
|
e5146e0dbb | ||
|
|
586d1b413c | ||
|
|
7d239de7f6 | ||
|
|
5f13c15c70 | ||
|
|
4e3f919d8e | ||
|
|
7efa04f1ef | ||
|
|
35ac1f863a | ||
|
|
68195ab0b7 | ||
|
|
f79dac2d13 | ||
|
|
4f176c47d2 | ||
|
|
f7e7864dfe | ||
|
|
2f8a15facd | ||
|
|
7bf175190b | ||
|
|
8866ca3d96 | ||
|
|
703e5480ff | ||
|
|
45a6c37594 | ||
|
|
340085cc9b | ||
|
|
ef9d1ab614 | ||
|
|
55a0bfdf7e | ||
|
|
d6f3934f80 | ||
|
|
f9e8adad30 | ||
|
|
08dc5753d4 | ||
|
|
0c851ea075 | ||
|
|
cf3ea38e65 | ||
|
|
b813de0467 | ||
|
|
399bed7576 | ||
|
|
5aa7128b9c | ||
|
|
92d167eb10 | ||
|
|
f02eb5bfba | ||
|
|
f348ac9f0a | ||
|
|
893b2dca89 | ||
|
|
1f5217a9d9 | ||
|
|
e84b47410a | ||
|
|
76cf4391ae | ||
|
|
7688006574 | ||
|
|
ed36041a7e | ||
|
|
bb52485606 | ||
|
|
615b70f9a2 | ||
|
|
3609873c4c | ||
|
|
7f6e4a18c1 | ||
|
|
c5b441e54a | ||
|
|
30452890f5 | ||
|
|
af18a3704f | ||
|
|
2c98693bd2 | ||
|
|
259599b441 | ||
|
|
2da058be49 | ||
|
|
8d33e20f6c | ||
|
|
b4e8f0586a | ||
|
|
6f8f5e2746 | ||
|
|
37cc3ae20d | ||
|
|
9fc46b9ae5 | ||
|
|
d409ff037c | ||
|
|
000363cd17 | ||
|
|
2beb12c04f | ||
|
|
373ede8548 | ||
|
|
e286100197 | ||
|
|
082163389a | ||
|
|
f9247c9318 | ||
|
|
5a00b8690d | ||
|
|
c0ca189536 | ||
|
|
84a3ceeae3 | ||
|
|
1c499d84cf | ||
|
|
871e93a38c | ||
|
|
1481338eb9 | ||
|
|
1e4fadda24 | ||
|
|
585fe8d1a1 | ||
|
|
f58c697d28 | ||
|
|
b32f8966e1 | ||
|
|
b18ebeefdc | ||
|
|
5fd7e419d9 | ||
|
|
602c1ed9a6 | ||
|
|
39bc87c2e2 | ||
|
|
44cc5a8132 | ||
|
|
0c7ae26313 | ||
|
|
102955bf6a | ||
|
|
72eb3ce467 | ||
|
|
d656f21cd9 | ||
|
|
8da5afb35e | ||
|
|
44e9da93b6 | ||
|
|
29e1bd4757 | ||
|
|
f1493c5139 | ||
|
|
30a668c84a | ||
|
|
9f4bf8777e | ||
|
|
dfe2e9d629 | ||
|
|
607e186082 | ||
|
|
e60c86a87e | ||
|
|
577479edaf | ||
|
|
4423baa1e7 | ||
|
|
d967681227 | ||
|
|
f391edda27 | ||
|
|
255030136f | ||
|
|
f79d304586 | ||
|
|
60772b1775 | ||
|
|
bf3ef435ae | ||
|
|
30411ccd13 | ||
|
|
bd8ca0abcf | ||
|
|
6621ece3a1 | ||
|
|
8f4b68617a | ||
|
|
1b3017222e | ||
|
|
a050fbd2d3 | ||
|
|
fe3bf88ea4 | ||
|
|
94f583fef0 | ||
|
|
8da0017eaf | ||
|
|
4e838201c6 | ||
|
|
111b8b3cda | ||
|
|
d0d388534b | ||
|
|
939b7354e4 | ||
|
|
7077693da2 | ||
|
|
9c047d0a45 | ||
|
|
f13dbfa766 | ||
|
|
d4dd1349da | ||
|
|
1db8c209fb | ||
|
|
852d2b7431 | ||
|
|
6468f85cb0 | ||
|
|
5c61865dfb | ||
|
|
3feb9d2304 | ||
|
|
f17ac6ffac | ||
|
|
04675a73fd | ||
|
|
e2353af0d8 | ||
|
|
8eaf64b5fd | ||
|
|
e5c5bc40d9 | ||
|
|
5286f446dc | ||
|
|
abbf2038a7 | ||
|
|
52026ca9e4 | ||
|
|
0b0c4bb434 | ||
|
|
22aa56d306 | ||
|
|
3b935a1eea | ||
|
|
146421ee52 | ||
|
|
9875942191 | ||
|
|
3d2b18140a | ||
|
|
ec0882cd14 | ||
|
|
48c97ee1cc | ||
|
|
17787fee18 | ||
|
|
f2a59002bc | ||
|
|
98419bc625 | ||
|
|
fb596fa4f9 | ||
|
|
7a4289cee1 | ||
|
|
488acce564 | ||
|
|
3f3b500a14 | ||
|
|
29803bbaf2 | ||
|
|
e07eabc579 | ||
|
|
e82a3b838c | ||
|
|
9761c3762d | ||
|
|
1ecfcc611f | ||
|
|
ad60b437ce | ||
|
|
cc5208cc6b | ||
|
|
a9212d9e7d | ||
|
|
efcf22d837 | ||
|
|
d4a02336aa | ||
|
|
386eba28e6 | ||
|
|
651b887a0e | ||
|
|
2982757649 | ||
|
|
04a8999e0b | ||
|
|
41ee9e563c | ||
|
|
16a16dc390 | ||
|
|
80155a17e5 | ||
|
|
26da0c5e74 | ||
|
|
27059e7b99 | ||
|
|
fdc51e03ac | ||
|
|
549ab12048 | ||
|
|
fb95a3d923 | ||
|
|
70da7c0f51 | ||
|
|
d7051bb7ed | ||
|
|
dd8f28b864 | ||
|
|
8b060e8834 | ||
|
|
7b66e1ce33 | ||
|
|
5d2248ab1f | ||
|
|
7df5f12c75 | ||
|
|
b0dadc60f2 | ||
|
|
36b7998714 | ||
|
|
d42fc88535 | ||
|
|
4ebc67aab0 | ||
|
|
5bef99611b | ||
|
|
191f1456ee | ||
|
|
a839078c7d | ||
|
|
2d7804311d | ||
|
|
6b0c1d136b | ||
|
|
383845ca62 | ||
|
|
a9b755ae2d | ||
|
|
6ed85cfdc6 | ||
|
|
137b6040ab | ||
|
|
da08b60f20 | ||
|
|
267952d750 | ||
|
|
38aea0edbe | ||
|
|
51bb60c3b1 | ||
|
|
8f2413dc7e | ||
|
|
1bdef318f0 | ||
|
|
2efb807f0b | ||
|
|
be35692125 | ||
|
|
076125485d | ||
|
|
1800f105d7 | ||
|
|
933e693897 | ||
|
|
97ff693e5c | ||
|
|
7853cdc9ed | ||
|
|
cba00d822c | ||
|
|
00502f6d4d | ||
|
|
7ffd7019a2 | ||
|
|
ea146f4715 | ||
|
|
85b250d016 | ||
|
|
7bbbba37f5 | ||
|
|
07244c38eb | ||
|
|
50f9dde280 | ||
|
|
76a3197022 | ||
|
|
f26628a546 | ||
|
|
ceb365b95c | ||
|
|
b0a2bae0b5 | ||
|
|
426b82754d | ||
|
|
2f82229048 | ||
|
|
65bfda1034 | ||
|
|
2328f07473 | ||
|
|
ad2e48cfc1 | ||
|
|
cca5e4a4c0 | ||
|
|
17ec5c7acf | ||
|
|
4708105104 | ||
|
|
19d2066a1a | ||
|
|
ae3a355a99 | ||
|
|
1996ffe724 | ||
|
|
edf9bc6f4d | ||
|
|
c31eafaf4f | ||
|
|
1a884c0cdf | ||
|
|
e343de00c0 | ||
|
|
0f360fcdd1 | ||
|
|
a9a18464dd | ||
|
|
e47beb59d2 | ||
|
|
d4ff502e08 | ||
|
|
8c9c2c080a | ||
|
|
aaf7429298 | ||
|
|
329268697b | ||
|
|
0b950832c2 | ||
|
|
788056d55d | ||
|
|
dfaff50ca1 | ||
|
|
145568237c | ||
|
|
9baaff53cd | ||
|
|
3ad622dd69 | ||
|
|
906a1d7f3e | ||
|
|
ba7a5d5126 | ||
|
|
693c65995d | ||
|
|
85f7bf30b9 | ||
|
|
53ad87a349 | ||
|
|
7a034c1abf | ||
|
|
eeee5779ba | ||
|
|
6ff9568760 | ||
|
|
7de4cf1417 | ||
|
|
db67c64da0 | ||
|
|
8476f89f19 | ||
|
|
01f89d7855 | ||
|
|
fa46f78fd5 | ||
|
|
3e239f9caa | ||
|
|
204734914d | ||
|
|
67edf4f472 | ||
|
|
7693106a44 | ||
|
|
d8f5e1506c | ||
|
|
330b3bb496 | ||
|
|
ac3a36e489 | ||
|
|
22c03c8075 | ||
|
|
bc972da982 | ||
|
|
d9738949c1 | ||
|
|
1c0e91a301 | ||
|
|
3db6593b0e | ||
|
|
2bb1caf308 | ||
|
|
807a6f02fd | ||
|
|
367b9bd154 | ||
|
|
d30accbc71 | ||
|
|
716d298b75 | ||
|
|
05c4610654 | ||
|
|
82c294ab63 | ||
|
|
ee142a26b0 | ||
|
|
dea310e9ab | ||
|
|
daf2cb0ebc | ||
|
|
2812129d00 | ||
|
|
89b6f70cde | ||
|
|
ad372a754e | ||
|
|
c0b1345e90 | ||
|
|
11e6430d42 | ||
|
|
1e10f250b4 | ||
|
|
4b23703f99 | ||
|
|
bab4315bb7 | ||
|
|
c4839708ce | ||
|
|
fa379be99b | ||
|
|
12eee6f6a2 | ||
|
|
50240933a0 | ||
|
|
69b70bf649 | ||
|
|
3e01e483fb | ||
|
|
ae189748f1 | ||
|
|
679c72d70e | ||
|
|
49e02a2574 | ||
|
|
ee735c9b77 | ||
|
|
e25ca5dfc1 | ||
|
|
422fc1a6b2 | ||
|
|
0949362da5 | ||
|
|
65f59c446e | ||
|
|
556ab76fc5 | ||
|
|
4c5072b5c0 | ||
|
|
25177d1685 | ||
|
|
5469dce6ca | ||
|
|
d659bf4349 | ||
|
|
c5008a43e7 | ||
|
|
256a961e1b | ||
|
|
d642987df4 | ||
|
|
7d0fc80a80 | ||
|
|
fa0bc96611 | ||
|
|
c492fa285f | ||
|
|
11494bbad6 | ||
|
|
c83cc073f0 | ||
|
|
b7c0829af9 | ||
|
|
e899acbdbe | ||
|
|
1f95430507 | ||
|
|
b70ab0e6cc | ||
|
|
8ec4dc1b6e | ||
|
|
9b9875a31b | ||
|
|
56e2cac85f | ||
|
|
65359f87d0 | ||
|
|
058371ba7d | ||
|
|
dc8d7aa3da | ||
|
|
e1c6545507 | ||
|
|
5c8097d9b7 | ||
|
|
6118ed91fa | ||
|
|
0814b82b7e | ||
|
|
288d3a2cff | ||
|
|
e2399eb4e2 | ||
|
|
bfe8fc66ce | ||
|
|
dd1b25604a | ||
|
|
b488253c76 | ||
|
|
56f96348f4 | ||
|
|
18cbaf7458 | ||
|
|
9add408b6b | ||
|
|
54eaf8ab0f | ||
|
|
32a3b8dd9b | ||
|
|
06c773c3b1 | ||
|
|
18e5c17ebe | ||
|
|
287c657e60 | ||
|
|
f69d0823f4 | ||
|
|
f26373dfd5 | ||
|
|
c54217f236 | ||
|
|
25c1b251a3 | ||
|
|
9fafe2f42c | ||
|
|
f25bd33f06 | ||
|
|
de02e8853d | ||
|
|
4c09905503 | ||
|
|
a9a39ae502 | ||
|
|
99693367d3 | ||
|
|
05e9f12dc4 | ||
|
|
0b1712f653 | ||
|
|
8575f1b036 | ||
|
|
4c2278a7f2 | ||
|
|
af4d1d1fee | ||
|
|
d52cb4c7d7 | ||
|
|
65acb3fd94 | ||
|
|
ecf54a3025 | ||
|
|
180bfc30a8 | ||
|
|
cbc255ddf8 | ||
|
|
15971aaa94 | ||
|
|
695ebd35a4 | ||
|
|
21247e91c7 | ||
|
|
d30471ae6a | ||
|
|
03292bb02d | ||
|
|
297e292e06 | ||
|
|
f32aa8609d | ||
|
|
f8550334dd | ||
|
|
da1bf320dd | ||
|
|
27f05b5f95 | ||
|
|
203289fd38 | ||
|
|
f9b93f284c | ||
|
|
24f9a8aeb1 | ||
|
|
c6eb6dc054 | ||
|
|
a16ef9743b | ||
|
|
6c8816c289 | ||
|
|
d4f325e6c5 | ||
|
|
93ec3bdfc4 | ||
|
|
a4fb9eea40 | ||
|
|
f201a5eaf6 | ||
|
|
6689e796a7 | ||
|
|
b2e5b4862d | ||
|
|
546dc5c449 | ||
|
|
f660611ed3 | ||
|
|
89a507de69 | ||
|
|
3cc29ba7b6 | ||
|
|
1660af94a5 | ||
|
|
c51687246e | ||
|
|
bc72d6103c | ||
|
|
cf44e46490 | ||
|
|
8311669e6c | ||
|
|
195557373c | ||
|
|
e7055242e1 | ||
|
|
6020b8d6a8 | ||
|
|
7c14a2edff | ||
|
|
6307919546 | ||
|
|
2dd53eee27 | ||
|
|
4e0aaedc86 | ||
|
|
970d2b0f0f | ||
|
|
b1cc4ef45c | ||
|
|
17c20d6b91 | ||
|
|
d05332662b | ||
|
|
42728fd9b9 | ||
|
|
732db9a5b4 | ||
|
|
a19c8e72b3 | ||
|
|
b0067c4e4b | ||
|
|
a133503256 | ||
|
|
3465d2fc64 | ||
|
|
05ae2b2ecf | ||
|
|
e00d9ed273 | ||
|
|
bf8f90f479 | ||
|
|
978a4ac0a2 | ||
|
|
aac522232f | ||
|
|
95570724e6 | ||
|
|
a96c53f407 | ||
|
|
f79ec37f5e | ||
|
|
5d38f2ece6 | ||
|
|
cf13bfb1e4 | ||
|
|
051b5d6bea | ||
|
|
a8532d1cd2 | ||
|
|
a7d8f3b79f | ||
|
|
e634dd81b1 | ||
|
|
c6f4ee3d57 | ||
|
|
f47b420785 | ||
|
|
1add3ca605 | ||
|
|
2b172e6d8b | ||
|
|
1728cbf28b | ||
|
|
766c6c08f4 | ||
|
|
400e4d783b | ||
|
|
a8b3e2bfa4 | ||
|
|
57649d1c08 | ||
|
|
dc794c64ce | ||
|
|
eda3959748 | ||
|
|
f5a0a0f8ba | ||
|
|
6e4f3f7191 | ||
|
|
e276a07f1b | ||
|
|
db9b3e9b67 | ||
|
|
d32d0692f8 | ||
|
|
ebdca47fbc | ||
|
|
957a335817 | ||
|
|
71a27862bd | ||
|
|
a2b03895d7 | ||
|
|
a93ebfd7bf | ||
|
|
3c43f3aa18 | ||
|
|
eb5c304f69 | ||
|
|
0925e141b4 | ||
|
|
ad8d087fd9 | ||
|
|
e50e3509bd | ||
|
|
090b2e6f1b | ||
|
|
0af0bea90d | ||
|
|
f452280cdb | ||
|
|
8c892ac05d | ||
|
|
65384250d6 | ||
|
|
3e7bff2446 | ||
|
|
b6d39c66d1 | ||
|
|
03e1733b7d | ||
|
|
039ac40cf7 | ||
|
|
2664ba0774 | ||
|
|
90efb4e827 | ||
|
|
946d597a13 | ||
|
|
0961d6314c | ||
|
|
0ef015bb0e | ||
|
|
36ca708850 | ||
|
|
1c539ff50c | ||
|
|
49d0865010 |
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.git
|
||||
node_modules
|
||||
vendor
|
||||
database/database.sqlite
|
||||
storage/debugbar/*.json
|
||||
storage/logs/*.log
|
||||
storage/framework/cache/data/*
|
||||
storage/framework/sessions/*
|
||||
storage/framework/testing
|
||||
storage/framework/views/*.php
|
||||
41
.env.example
41
.env.example
@@ -1,44 +1,7 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_KEY=
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://panel.test
|
||||
APP_INSTALLED=false
|
||||
APP_TIMEZONE=UTC
|
||||
APP_LOCALE=en
|
||||
APP_ENVIRONMENT_ONLY=true
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=panel
|
||||
DB_USERNAME=panel
|
||||
DB_PASSWORD=
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
CACHE_STORE=file
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
|
||||
HASHIDS_SALT=
|
||||
HASHIDS_LENGTH=8
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=smtp.example.com
|
||||
MAIL_PORT=25
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS=no-reply@example.com
|
||||
MAIL_FROM_NAME="Pelican Admin"
|
||||
# Set this to your domain to prevent it defaulting to 'localhost', causing mail servers such as Gmail to reject your mail
|
||||
# MAIL_EHLO_DOMAIN=panel.example.com
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
@@ -39,6 +39,7 @@ module.exports = {
|
||||
'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.
|
||||
//
|
||||
|
||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU]
|
||||
github: pelican-dev
|
||||
custom: [https://hub.pelican.dev/donors]
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -33,7 +33,6 @@ body:
|
||||
attributes:
|
||||
label: Panel Version
|
||||
description: Version number of your Panel (latest is not a version)
|
||||
placeholder: 1.4.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -42,7 +41,6 @@ body:
|
||||
attributes:
|
||||
label: Wings Version
|
||||
description: Version number of your Wings (latest is not a version)
|
||||
placeholder: 1.4.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -68,7 +66,7 @@ body:
|
||||
Run the following command to collect logs on your system.
|
||||
|
||||
Wings: `sudo wings diagnostics`
|
||||
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | nc pelipaste.com 99`
|
||||
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl -X POST -F 'c=@-' paste.pelistuff.com`
|
||||
placeholder: "https://pelipaste.com/a1h6z"
|
||||
render: bash
|
||||
validations:
|
||||
|
||||
24
.github/docker/default.conf
vendored
24
.github/docker/default.conf
vendored
@@ -4,6 +4,30 @@
|
||||
# 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 _;
|
||||
|
||||
88
.github/docker/entrypoint.sh
vendored
88
.github/docker/entrypoint.sh
vendored
@@ -1,81 +1,65 @@
|
||||
#!/bin/ash -e
|
||||
cd /app
|
||||
|
||||
mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \
|
||||
&& chmod 777 /var/log/panel/logs/ \
|
||||
&& ln -s /app/storage/logs/ /var/log/panel/
|
||||
#mkdir -p /var/log/supervisord/ /var/log/php8/ \
|
||||
|
||||
## check for .env file and generate app keys if missing
|
||||
if [ -f /app/var/.env ]; then
|
||||
if [ -f /pelican-data/.env ]; then
|
||||
echo "external vars exist."
|
||||
rm -rf /app/.env
|
||||
ln -s /app/var/.env /app/
|
||||
rm -rf /var/www/html/.env
|
||||
else
|
||||
echo "external vars don't exist."
|
||||
rm -rf /app/.env
|
||||
touch /app/var/.env
|
||||
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" > /app/var/.env
|
||||
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" > /app/var/.env
|
||||
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
|
||||
fi
|
||||
|
||||
ln -s /app/var/.env /app/
|
||||
## enable installer
|
||||
echo -e "APP_INSTALLED=false" >> /pelican-data/.env
|
||||
fi
|
||||
|
||||
echo "Checking if https is required."
|
||||
if [ -f /etc/nginx/http.d/panel.conf ]; then
|
||||
echo "Using nginx config already in place."
|
||||
if [ $LE_EMAIL ]; then
|
||||
echo "Checking for cert update"
|
||||
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
|
||||
else
|
||||
echo "No letsencrypt email is set"
|
||||
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 "Checking if letsencrypt email is set."
|
||||
if [ -z $LE_EMAIL ]; then
|
||||
echo "No letsencrypt email is set using http config."
|
||||
cp .github/docker/default.conf /etc/nginx/http.d/panel.conf
|
||||
else
|
||||
echo "writing ssl config"
|
||||
cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf
|
||||
echo "updating ssl config for domain"
|
||||
sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf
|
||||
echo "generating certs"
|
||||
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
|
||||
fi
|
||||
echo "Removing the default nginx config"
|
||||
rm -rf /etc/nginx/http.d/default.conf
|
||||
echo "APP_KEY is already set."
|
||||
fi
|
||||
|
||||
if [[ -z $DB_PORT ]]; then
|
||||
echo -e "DB_PORT not specified, defaulting to 3306"
|
||||
DB_PORT=3306
|
||||
fi
|
||||
|
||||
## check for DB up before starting the panel
|
||||
echo "Checking database status."
|
||||
until nc -z -v -w30 $DB_HOST $DB_PORT
|
||||
do
|
||||
echo "Waiting for database connection..."
|
||||
# wait for 1 seconds before check again
|
||||
sleep 1
|
||||
done
|
||||
|
||||
## make sure the db is set up
|
||||
echo -e "Migrating and Seeding D.B"
|
||||
php artisan migrate --seed --force
|
||||
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
|
||||
|
||||
echo -e "Starting supervisord."
|
||||
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 "$@"
|
||||
|
||||
18
.github/docker/supervisord.conf
vendored
18
.github/docker/supervisord.conf
vendored
@@ -1,5 +1,7 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
username=dummy
|
||||
password=dummy
|
||||
|
||||
[supervisord]
|
||||
logfile=/var/log/supervisord/supervisord.log ; supervisord log file
|
||||
@@ -18,6 +20,8 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
username=dummy
|
||||
password=dummy
|
||||
|
||||
[program:php-fpm]
|
||||
command=/usr/local/sbin/php-fpm -F
|
||||
@@ -25,15 +29,15 @@ autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:queue-worker]
|
||||
command=/usr/local/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
|
||||
user=nginx
|
||||
command=/usr/local/bin/php /var/www/html/artisan queue:work --tries=3
|
||||
user=www-data
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx -g 'daemon off;'
|
||||
autostart=true
|
||||
autorestart=true
|
||||
[program:caddy]
|
||||
command=caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
autostart=%(ENV_SUPERVISORD_CADDY)s
|
||||
autorestart=%(ENV_SUPERVISORD_CADDY)s
|
||||
priority=10
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
|
||||
5
.github/workflows/build.yaml
vendored
5
.github/workflows/build.yaml
vendored
@@ -1,6 +1,9 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
@@ -12,7 +15,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [18]
|
||||
node-version: [18, 20]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
170
.github/workflows/ci.yaml
vendored
170
.github/workflows/ci.yaml
vendored
@@ -1,19 +1,20 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests
|
||||
mysql:
|
||||
name: MySQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.2", "mysql:8"]
|
||||
database: ["mysql:8"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -23,6 +24,22 @@ jobs:
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
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_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -44,22 +61,15 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Setup .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
||||
|
||||
- name: Generate App Key
|
||||
run: php artisan key:generate
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
@@ -69,3 +79,137 @@ jobs:
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
mariadb:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
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_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
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 --prefer-dist
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/phpunit tests/Integration
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
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: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
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 --prefer-dist
|
||||
|
||||
- name: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/phpunit tests/Integration
|
||||
|
||||
8
.github/workflows/cla.yaml
vendored
8
.github/workflows/cla.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||
uses: contributor-assistant/github-action@v2.3.0
|
||||
uses: contributor-assistant/github-action@v2.4.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PERSONAL_ACCESS_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: 'version1/cla.json'
|
||||
path-to-document: 'https://github.com/pelican-dev/panel/blob/3.x/contributor_license_agreement.md'
|
||||
path-to-document: 'https://github.com/pelican-dev/panel/blob/main/contributor_license_agreement.md'
|
||||
branch: 'main'
|
||||
allowlist: dependabot[bot]
|
||||
remote-organization-name: pelican-dev
|
||||
|
||||
86
.github/workflows/docker-publish.yml
vendored
Normal file
86
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: Docker
|
||||
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# 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'))"
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }}
|
||||
type=ref,event=tag
|
||||
type=ref,event=branch
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get Build Information
|
||||
id: build_info
|
||||
run: |
|
||||
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v5
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=${{ steps.build_info.outputs.version_tag }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
|
||||
- name: Build and Push (main)
|
||||
uses: docker/build-push-action@v5
|
||||
if: "github.event_name == 'push' && contains(github.ref, 'main')"
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=dev-${{ steps.build_info.outputs.short_sha }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
29
.github/workflows/lint.yaml
vendored
29
.github/workflows/lint.yaml
vendored
@@ -6,8 +6,8 @@ on:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
pint:
|
||||
name: Pint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -29,3 +29,26 @@ jobs:
|
||||
|
||||
- name: Pint
|
||||
run: vendor/bin/pint --test
|
||||
phpstan:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Setup .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-progress --prefer-dist
|
||||
|
||||
- name: PHPStan
|
||||
run: vendor/bin/phpstan --memory-limit=-1
|
||||
|
||||
52
.github/workflows/release.yaml
vendored
52
.github/workflows/release.yaml
vendored
@@ -8,15 +8,18 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -30,8 +33,8 @@ jobs:
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
BRANCH=release/${REF:10}
|
||||
git config --local user.email "ci@pelican.dev"
|
||||
git config --local user.name "Pelican CI"
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git checkout -b $BRANCH
|
||||
git push -u origin $BRANCH
|
||||
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php
|
||||
@@ -44,46 +47,19 @@ jobs:
|
||||
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpunit.xml shell.nix
|
||||
tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json
|
||||
|
||||
- name: Extract changelog
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG
|
||||
|
||||
- name: Create checksum and add to changelog
|
||||
- name: Create checksum
|
||||
run: |
|
||||
SUM=`sha256sum panel.tar.gz`
|
||||
echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
||||
echo $SUM > checksum.txt
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||
body_path: ./RELEASE_CHANGELOG
|
||||
|
||||
- name: Upload release archive
|
||||
id: upload-release-archive
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: panel.tar.gz
|
||||
asset_name: panel.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: Upload release checksum
|
||||
id: upload-release-checksum
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./checksum.txt
|
||||
asset_name: checksum.txt
|
||||
asset_content_type: text/plain
|
||||
files: |
|
||||
panel.tar.gz
|
||||
checksum.txt
|
||||
|
||||
60
.gitignore
vendored
60
.gitignore
vendored
@@ -1,36 +1,28 @@
|
||||
/vendor
|
||||
*.DS_Store*
|
||||
!.env.ci
|
||||
!.env.example
|
||||
.env*
|
||||
.vagrant/*
|
||||
.vscode/*
|
||||
storage/framework/*
|
||||
/.idea
|
||||
/nbproject
|
||||
/.direnv
|
||||
|
||||
node_modules
|
||||
*.log
|
||||
_ide_helper.php
|
||||
_ide_helper_models.php
|
||||
.phpstorm.meta.php
|
||||
.yarn
|
||||
public/assets/manifest.json
|
||||
|
||||
# For local development with docker
|
||||
# Remove if we ever put the Dockerfile in the repo
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
|
||||
# for image related files
|
||||
misc
|
||||
.php-cs-fixer.cache
|
||||
coverage.xml
|
||||
resources/lang/locales.js
|
||||
.phpunit.result.cache
|
||||
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
result
|
||||
docker-compose.yaml
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/clockwork/*
|
||||
/vendor
|
||||
*.DS_Store*
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
|
||||
public/assets/manifest.json
|
||||
/database/*.sqlite
|
||||
filament-monaco-editor/
|
||||
_ide_helper*
|
||||
/.phpstorm.meta.php
|
||||
|
||||
12
Caddyfile
Normal file
12
Caddyfile
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
admin off
|
||||
email {$ADMIN_EMAIL}
|
||||
}
|
||||
|
||||
{$APP_URL} {
|
||||
root * /var/www/html/public
|
||||
encode gzip
|
||||
|
||||
php_fastcgi 127.0.0.1:9000
|
||||
file_server
|
||||
}
|
||||
82
Dockerfile
82
Dockerfile
@@ -1,41 +1,59 @@
|
||||
# Stage 0:
|
||||
# Build the assets that are needed for the frontend. This build stage is then discarded
|
||||
# since we won't need NodeJS anymore in the future. This Docker image ships a final production
|
||||
# level distribution
|
||||
FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14
|
||||
WORKDIR /app
|
||||
# Pelican Production Dockerfile
|
||||
|
||||
FROM node:20-alpine AS yarn
|
||||
#FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY . ./
|
||||
RUN yarn install --frozen-lockfile \
|
||||
|
||||
RUN yarn config set network-timeout 300000 \
|
||||
&& yarn install --frozen-lockfile \
|
||||
&& yarn run build:production
|
||||
|
||||
# Stage 1:
|
||||
# Build the actual container with all of the needed PHP dependencies that will run the application.
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.1-fpm-alpine
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
COPY --from=0 /app/public/assets ./public/assets
|
||||
RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot certbot-nginx \
|
||||
&& docker-php-ext-configure zip \
|
||||
&& docker-php-ext-install bcmath gd pdo_mysql zip \
|
||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||
&& cp .env.example .env \
|
||||
&& mkdir -p bootstrap/cache/ storage/logs storage/framework/sessions storage/framework/views storage/framework/cache \
|
||||
&& chmod 777 -R bootstrap storage \
|
||||
&& composer install --no-dev --optimize-autoloader \
|
||||
&& rm -rf .env bootstrap/cache/*.php \
|
||||
&& mkdir -p /app/storage/logs/ \
|
||||
&& chown -R nginx:nginx .
|
||||
FROM php:8.3-fpm-alpine
|
||||
# FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine
|
||||
|
||||
RUN rm /usr/local/etc/php-fpm.conf \
|
||||
&& echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \
|
||||
&& echo "0 23 * * * certbot renew --nginx --quiet" >> /var/spool/cron/crontabs/root \
|
||||
&& sed -i s/ssl_session_cache/#ssl_session_cache/g /etc/nginx/nginx.conf \
|
||||
&& mkdir -p /var/run/php /var/run/nginx
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
COPY .github/docker/default.conf /etc/nginx/http.d/default.conf
|
||||
COPY .github/docker/www.conf /usr/local/etc/php-fpm.conf
|
||||
COPY .github/docker/supervisord.conf /etc/supervisord.conf
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Install dependencies
|
||||
RUN apk update && apk add --no-cache \
|
||||
libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \
|
||||
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 Caddyfile /etc/caddy/Caddyfile
|
||||
|
||||
# Copy the application code to the container
|
||||
COPY . .
|
||||
|
||||
COPY --from=yarn /build/public/assets ./public/assets
|
||||
|
||||
RUN touch .env
|
||||
|
||||
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 \
|
||||
CMD curl -f http://localhost/up || exit 1
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
VOLUME /pelican-data
|
||||
|
||||
ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ]
|
||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||
|
||||
43
app/Console/Commands/Egg/CheckEggUpdatesCommand.php
Normal file
43
app/Console/Commands/Egg/CheckEggUpdatesCommand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CheckEggUpdatesCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:egg:check-updates';
|
||||
|
||||
public function handle(EggExporterService $exporterService): void
|
||||
{
|
||||
$eggs = Egg::all();
|
||||
foreach ($eggs as $egg) {
|
||||
try {
|
||||
if (is_null($egg->update_url)) {
|
||||
$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) {
|
||||
$this->error("{$egg->name}: Error ({$exception->getMessage()})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,174 +3,27 @@
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class AppSettingsCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
public const CACHE_DRIVERS = [
|
||||
'redis' => 'Redis',
|
||||
'memcached' => 'Memcached',
|
||||
'file' => 'Filesystem (recommended)',
|
||||
];
|
||||
|
||||
public const SESSION_DRIVERS = [
|
||||
'redis' => 'Redis',
|
||||
'memcached' => 'Memcached',
|
||||
'database' => 'MySQL Database',
|
||||
'file' => 'Filesystem (recommended)',
|
||||
'cookie' => 'Cookie',
|
||||
];
|
||||
|
||||
public const QUEUE_DRIVERS = [
|
||||
'redis' => 'Redis',
|
||||
'database' => 'MySQL Database',
|
||||
'sync' => 'Sync (recommended)',
|
||||
];
|
||||
|
||||
protected $description = 'Configure basic environment settings for the Panel.';
|
||||
|
||||
protected $signature = 'p:environment:setup
|
||||
{--new-salt : Whether or not to generate a new salt for Hashids.}
|
||||
{--author= : The email that services created on this instance should be linked to.}
|
||||
{--url= : The URL that this Panel is running on.}
|
||||
{--timezone= : The timezone to use for Panel times.}
|
||||
{--cache= : The cache driver backend to use.}
|
||||
{--session= : The session driver backend to use.}
|
||||
{--queue= : The queue driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}
|
||||
{--settings-ui= : Enable or disable the settings UI.}';
|
||||
protected $signature = 'p:environment:setup';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* AppSettingsCommand constructor.
|
||||
*/
|
||||
public function __construct(private Kernel $console)
|
||||
public function handle(): void
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*
|
||||
* @throws \App\Exceptions\PanelException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
if (empty(config('hashids.salt')) || $this->option('new-salt')) {
|
||||
$this->variables['HASHIDS_SALT'] = str_random(20);
|
||||
$path = base_path('.env');
|
||||
if (!file_exists($path)) {
|
||||
$this->comment('Copying example .env file');
|
||||
copy($path . '.example', $path);
|
||||
}
|
||||
|
||||
$this->output->comment('Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.');
|
||||
$this->variables['APP_SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask(
|
||||
'Egg Author Email',
|
||||
config('panel.service.author', 'unknown@unknown.com')
|
||||
);
|
||||
|
||||
if (!filter_var($this->variables['APP_SERVICE_AUTHOR'], FILTER_VALIDATE_EMAIL)) {
|
||||
$this->output->error('The service author email provided is invalid.');
|
||||
|
||||
return 1;
|
||||
if (!config('app.key')) {
|
||||
$this->comment('Generating app key');
|
||||
Artisan::call('key:generate');
|
||||
}
|
||||
|
||||
$this->output->comment('The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.');
|
||||
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
||||
'Application URL',
|
||||
config('app.url', 'https://example.com')
|
||||
);
|
||||
|
||||
$this->output->comment('The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.');
|
||||
$this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate(
|
||||
'Application Timezone',
|
||||
\DateTimeZone::listIdentifiers(),
|
||||
config('app.timezone')
|
||||
);
|
||||
|
||||
$selected = config('cache.default', 'file');
|
||||
$this->variables['CACHE_STORE'] = $this->option('cache') ?? $this->choice(
|
||||
'Cache Driver',
|
||||
self::CACHE_DRIVERS,
|
||||
array_key_exists($selected, self::CACHE_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
$selected = config('session.driver', 'file');
|
||||
$this->variables['SESSION_DRIVER'] = $this->option('session') ?? $this->choice(
|
||||
'Session Driver',
|
||||
self::SESSION_DRIVERS,
|
||||
array_key_exists($selected, self::SESSION_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
$selected = config('queue.default', 'sync');
|
||||
$this->variables['QUEUE_CONNECTION'] = $this->option('queue') ?? $this->choice(
|
||||
'Queue Driver',
|
||||
self::QUEUE_DRIVERS,
|
||||
array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
if (!is_null($this->option('settings-ui'))) {
|
||||
$this->variables['APP_ENVIRONMENT_ONLY'] = $this->option('settings-ui') == 'true' ? 'false' : 'true';
|
||||
} else {
|
||||
$this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm('Enable UI based settings editor?', true) ? 'false' : 'true';
|
||||
}
|
||||
|
||||
// Make sure session cookies are set as "secure" when using HTTPS
|
||||
if (str_starts_with($this->variables['APP_URL'], 'https://')) {
|
||||
$this->variables['SESSION_SECURE_COOKIE'] = 'true';
|
||||
}
|
||||
|
||||
$this->checkForRedis();
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info($this->console->output());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if redis is selected, if so, request connection details and verify them.
|
||||
*/
|
||||
private function checkForRedis()
|
||||
{
|
||||
$items = collect($this->variables)->filter(function ($item) {
|
||||
return $item === 'redis';
|
||||
});
|
||||
|
||||
// Redis was not selected, no need to continue.
|
||||
if (count($items) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output->note('You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.');
|
||||
$this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask(
|
||||
'Redis Host',
|
||||
config('database.redis.default.host')
|
||||
);
|
||||
|
||||
$askForRedisPassword = true;
|
||||
if (!empty(config('database.redis.default.password'))) {
|
||||
$this->variables['REDIS_PASSWORD'] = config('database.redis.default.password');
|
||||
$askForRedisPassword = $this->confirm('It seems a password is already defined for Redis, would you like to change it?');
|
||||
}
|
||||
|
||||
if ($askForRedisPassword) {
|
||||
$this->output->comment('By default a Redis server instance has no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.');
|
||||
$this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden(
|
||||
'Redis Password'
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->variables['REDIS_PASSWORD'])) {
|
||||
$this->variables['REDIS_PASSWORD'] = 'null';
|
||||
}
|
||||
|
||||
$this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask(
|
||||
'Redis Port',
|
||||
config('database.redis.default.port')
|
||||
);
|
||||
Artisan::call('filament:optimize');
|
||||
}
|
||||
}
|
||||
|
||||
68
app/Console/Commands/Environment/CacheSettingsCommand.php
Normal file
68
app/Console/Commands/Environment/CacheSettingsCommand.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
class CacheSettingsCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use RequestRedisSettingsTrait;
|
||||
|
||||
public const CACHE_DRIVERS = [
|
||||
'file' => 'Filesystem (default)',
|
||||
'database' => 'Database',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
protected $description = 'Configure cache settings for the Panel.';
|
||||
|
||||
protected $signature = 'p:environment:cache
|
||||
{--driver= : The cache driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* CacheSettingsCommand constructor.
|
||||
*/
|
||||
public function __construct(private Kernel $console)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$selected = config('cache.default', 'file');
|
||||
$this->variables['CACHE_STORE'] = $this->option('driver') ?? $this->choice(
|
||||
'Cache Driver',
|
||||
self::CACHE_DRIVERS,
|
||||
array_key_exists($selected, self::CACHE_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
if ($this->variables['CACHE_STORE'] === 'redis') {
|
||||
$this->requestRedisSettings();
|
||||
|
||||
if (config('queue.default') !== 'sync') {
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info($this->console->output());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,30 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
|
||||
class DatabaseSettingsCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite (recommended)',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
protected $description = 'Configure database settings for the Panel.';
|
||||
|
||||
protected $signature = 'p:environment:database
|
||||
{--host= : The connection address for the MySQL server.}
|
||||
{--port= : The connection port for the MySQL server.}
|
||||
{--driver= : The database driver backend to use.}
|
||||
{--database= : The database to use.}
|
||||
{--username= : Username to use when connecting.}
|
||||
{--password= : Password to use for this database.}';
|
||||
{--host= : The connection address for the MySQL/ MariaDB server.}
|
||||
{--port= : The connection port for the MySQL/ MariaDB server.}
|
||||
{--username= : Username to use when connecting to the MySQL/ MariaDB server.}
|
||||
{--password= : Password to use for the MySQL/ MariaDB database.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
@@ -35,51 +42,145 @@ class DatabaseSettingsCommand extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->output->note('It is highly recommended to not use "localhost" as your database host as we have seen frequent socket connection issues. If you want to use a local connection you should be using "127.0.0.1".');
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.mysql.host', '127.0.0.1')
|
||||
);
|
||||
|
||||
$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
|
||||
'Database Port',
|
||||
config('database.connections.mysql.port', 3306)
|
||||
);
|
||||
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Name',
|
||||
config('database.connections.mysql.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note('Using the "root" account for MySQL connections is not only highly frowned upon, it is also not allowed by this application. You\'ll need to have created a MySQL user for this software.');
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.mysql.username', 'panel')
|
||||
);
|
||||
|
||||
$askForMySQLPassword = true;
|
||||
if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.mysql.password');
|
||||
$askForMySQLPassword = $this->confirm('It appears you already have a MySQL connection password defined, would you like to change it?');
|
||||
$this->error('Changing the database driver will NOT move any database data!');
|
||||
$this->error('Please make sure you made a database backup first!');
|
||||
$this->error('After changing the driver you will have to manually move the old data to the new database.');
|
||||
if (!$this->confirm('Do you want to continue?')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($askForMySQLPassword) {
|
||||
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
|
||||
}
|
||||
$selected = config('database.default', 'sqlite');
|
||||
$this->variables['DB_CONNECTION'] = $this->option('driver') ?? $this->choice(
|
||||
'Database Driver',
|
||||
self::DATABASE_DRIVERS,
|
||||
array_key_exists($selected, self::DATABASE_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
try {
|
||||
$this->testMySQLConnection();
|
||||
} 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('Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.');
|
||||
if ($this->variables['DB_CONNECTION'] === 'mysql') {
|
||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.mysql.host', '127.0.0.1')
|
||||
);
|
||||
|
||||
if ($this->confirm('Go back and try again?')) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
|
||||
'Database Port',
|
||||
config('database.connections.mysql.port', 3306)
|
||||
);
|
||||
|
||||
return $this->handle();
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Name',
|
||||
config('database.connections.mysql.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.mysql.username', 'pelican')
|
||||
);
|
||||
|
||||
$askForMySQLPassword = true;
|
||||
if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.mysql.password');
|
||||
$askForMySQLPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
return 1;
|
||||
if ($askForMySQLPassword) {
|
||||
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
|
||||
}
|
||||
|
||||
try {
|
||||
// Test connection
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'mysql',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} 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(__('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'mariadb') {
|
||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.mariadb.host', '127.0.0.1')
|
||||
);
|
||||
|
||||
$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
|
||||
'Database Port',
|
||||
config('database.connections.mariadb.port', 3306)
|
||||
);
|
||||
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Name',
|
||||
config('database.connections.mariadb.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.mariadb.username', 'pelican')
|
||||
);
|
||||
|
||||
$askForMariaDBPassword = true;
|
||||
if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password');
|
||||
$askForMariaDBPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
if ($askForMariaDBPassword) {
|
||||
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
|
||||
}
|
||||
|
||||
try {
|
||||
// Test connection
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'mariadb',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} 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(__('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Path',
|
||||
env('DB_DATABASE', 'database.sqlite')
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
@@ -88,24 +189,4 @@ class DatabaseSettingsCommand extends Command
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can connect to the provided MySQL instance and perform a selection.
|
||||
*/
|
||||
private function testMySQLConnection()
|
||||
{
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'mysql',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
|
||||
class EmailSettingsCommand extends Command
|
||||
{
|
||||
@@ -31,19 +31,20 @@ class EmailSettingsCommand extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->variables['MAIL_DRIVER'] = $this->option('driver') ?? $this->choice(
|
||||
$this->variables['MAIL_MAILER'] = $this->option('driver') ?? $this->choice(
|
||||
trans('command/messages.environment.mail.ask_driver'),
|
||||
[
|
||||
'log' => 'Log',
|
||||
'smtp' => 'SMTP Server',
|
||||
'sendmail' => 'sendmail Binary',
|
||||
'mailgun' => 'Mailgun Transactional Email',
|
||||
'mandrill' => 'Mandrill Transactional Email',
|
||||
'postmark' => 'Postmark Transactional Email',
|
||||
'mailgun' => 'Mailgun',
|
||||
'mandrill' => 'Mandrill',
|
||||
'postmark' => 'Postmark',
|
||||
],
|
||||
config('mail.default', 'smtp')
|
||||
env('MAIL_MAILER', env('MAIL_DRIVER', 'smtp')),
|
||||
);
|
||||
|
||||
$method = 'setup' . studly_case($this->variables['MAIL_DRIVER']) . 'DriverVariables';
|
||||
$method = 'setup' . studly_case($this->variables['MAIL_MAILER']) . 'DriverVariables';
|
||||
if (method_exists($this, $method)) {
|
||||
$this->{$method}();
|
||||
}
|
||||
@@ -60,6 +61,8 @@ class EmailSettingsCommand extends Command
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->call('queue:restart');
|
||||
|
||||
$this->line('Updating stored environment configuration file.');
|
||||
$this->line('');
|
||||
}
|
||||
@@ -67,7 +70,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for SMTP driver.
|
||||
*/
|
||||
private function setupSmtpDriverVariables()
|
||||
private function setupSmtpDriverVariables(): void
|
||||
{
|
||||
$this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask(
|
||||
trans('command/messages.environment.mail.ask_smtp_host'),
|
||||
@@ -98,7 +101,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for mailgun driver.
|
||||
*/
|
||||
private function setupMailgunDriverVariables()
|
||||
private function setupMailgunDriverVariables(): void
|
||||
{
|
||||
$this->variables['MAILGUN_DOMAIN'] = $this->option('host') ?? $this->ask(
|
||||
trans('command/messages.environment.mail.ask_mailgun_domain'),
|
||||
@@ -119,7 +122,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for mandrill driver.
|
||||
*/
|
||||
private function setupMandrillDriverVariables()
|
||||
private function setupMandrillDriverVariables(): void
|
||||
{
|
||||
$this->variables['MANDRILL_SECRET'] = $this->option('password') ?? $this->ask(
|
||||
trans('command/messages.environment.mail.ask_mandrill_secret'),
|
||||
@@ -130,7 +133,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for postmark driver.
|
||||
*/
|
||||
private function setupPostmarkDriverVariables()
|
||||
private function setupPostmarkDriverVariables(): void
|
||||
{
|
||||
$this->variables['MAIL_DRIVER'] = 'smtp';
|
||||
$this->variables['MAIL_HOST'] = 'smtp.postmarkapp.com';
|
||||
|
||||
66
app/Console/Commands/Environment/QueueSettingsCommand.php
Normal file
66
app/Console/Commands/Environment/QueueSettingsCommand.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
class QueueSettingsCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use RequestRedisSettingsTrait;
|
||||
|
||||
public const QUEUE_DRIVERS = [
|
||||
'database' => 'Database (default)',
|
||||
'redis' => 'Redis',
|
||||
'sync' => 'Synchronous',
|
||||
];
|
||||
|
||||
protected $description = 'Configure queue settings for the Panel.';
|
||||
|
||||
protected $signature = 'p:environment:queue
|
||||
{--driver= : The queue driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* QueueSettingsCommand constructor.
|
||||
*/
|
||||
public function __construct(private Kernel $console)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$selected = config('queue.default', 'database');
|
||||
$this->variables['QUEUE_CONNECTION'] = $this->option('driver') ?? $this->choice(
|
||||
'Queue Driver',
|
||||
self::QUEUE_DRIVERS,
|
||||
array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
if ($this->variables['QUEUE_CONNECTION'] === 'redis') {
|
||||
$this->requestRedisSettings();
|
||||
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info($this->console->output());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
class QueueWorkerServiceCommand extends Command
|
||||
{
|
||||
protected $description = 'Create the service for the queue worker.';
|
||||
|
||||
protected $signature = 'p:environment:queue-service
|
||||
{--service-name= : Name of the queue worker service.}
|
||||
{--user= : The user that PHP runs under.}
|
||||
{--group= : The group that PHP runs under.}
|
||||
{--overwrite : Force overwrite if the service file already exists.}';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
|
||||
$path = '/etc/systemd/system/' . $serviceName . '.service';
|
||||
|
||||
$fileExists = file_exists($path);
|
||||
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.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->option('user') ?? $this->ask('Webserver User', 'www-data');
|
||||
$group = $this->option('group') ?? $this->ask('Webserver Group', 'www-data');
|
||||
|
||||
$redisUsed = config('queue.default') === 'redis' || config('session.driver') === 'redis' || config('cache.default') === 'redis';
|
||||
$afterRedis = $redisUsed ? '
|
||||
After=redis-server.service' : '';
|
||||
|
||||
$basePath = base_path();
|
||||
|
||||
$success = File::put($path, "# Pelican Queue File
|
||||
# ----------------------------------
|
||||
|
||||
[Unit]
|
||||
Description=Pelican Queue Service$afterRedis
|
||||
|
||||
[Service]
|
||||
User=$user
|
||||
Group=$group
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/php $basePath/artisan queue:work --tries=3
|
||||
StartLimitInterval=180
|
||||
StartLimitBurst=30
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
");
|
||||
|
||||
if (!$success) {
|
||||
$this->error('Error creating service file');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($fileExists) {
|
||||
$result = Process::run("systemctl restart $serviceName.service");
|
||||
if ($result->failed()) {
|
||||
$this->error('Error restarting service: ' . $result->errorOutput());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('Queue worker service file updated successfully.');
|
||||
} else {
|
||||
$result = Process::run("systemctl enable --now $serviceName.service");
|
||||
if ($result->failed()) {
|
||||
$this->error('Error enabling service: ' . $result->errorOutput());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('Queue worker service file created successfully.');
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/Console/Commands/Environment/RedisSetupCommand.php
Normal file
54
app/Console/Commands/Environment/RedisSetupCommand.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
class RedisSetupCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use RequestRedisSettingsTrait;
|
||||
|
||||
protected $description = 'Configure the Panel to use Redis as cache, queue and session driver.';
|
||||
|
||||
protected $signature = 'p:redis:setup
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* RedisSetupCommand constructor.
|
||||
*/
|
||||
public function __construct(private Kernel $console)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->variables['CACHE_STORE'] = 'redis';
|
||||
$this->variables['QUEUE_CONNECTION'] = 'redis';
|
||||
$this->variables['SESSION_DRIVERS'] = 'redis';
|
||||
|
||||
$this->requestRedisSettings();
|
||||
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info($this->console->output());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
69
app/Console/Commands/Environment/SessionSettingsCommand.php
Normal file
69
app/Console/Commands/Environment/SessionSettingsCommand.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
class SessionSettingsCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use RequestRedisSettingsTrait;
|
||||
|
||||
public const SESSION_DRIVERS = [
|
||||
'file' => 'Filesystem (default)',
|
||||
'redis' => 'Redis',
|
||||
'database' => 'Database',
|
||||
'cookie' => 'Cookie',
|
||||
];
|
||||
|
||||
protected $description = 'Configure session settings for the Panel.';
|
||||
|
||||
protected $signature = 'p:environment:session
|
||||
{--driver= : The session driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* SessionSettingsCommand constructor.
|
||||
*/
|
||||
public function __construct(private Kernel $console)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$selected = config('session.driver', 'file');
|
||||
$this->variables['SESSION_DRIVER'] = $this->option('driver') ?? $this->choice(
|
||||
'Session Driver',
|
||||
self::SESSION_DRIVERS,
|
||||
array_key_exists($selected, self::SESSION_DRIVERS) ? $selected : null
|
||||
);
|
||||
|
||||
if ($this->variables['SESSION_DRIVER'] === 'redis') {
|
||||
$this->requestRedisSettings();
|
||||
|
||||
if (config('queue.default') !== 'sync') {
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info($this->console->output());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@ use App\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class InfoCommand extends Command
|
||||
{
|
||||
protected $description = 'Displays the application, database, and email configurations along with the panel version.';
|
||||
protected $description = 'Displays the application, database, email and backup configurations along with the panel version.';
|
||||
|
||||
protected $signature = 'p:info';
|
||||
|
||||
/**
|
||||
* VersionCommand constructor.
|
||||
* InfoCommand constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $versionService)
|
||||
{
|
||||
@@ -26,48 +26,76 @@ class InfoCommand extends Command
|
||||
{
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', config('app.version')],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Panel Version', $this->versionService->currentPanelVersion()],
|
||||
['Latest Version', $this->versionService->latestPanelVersion()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
['Unique Identifier', config('panel.service.author')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Application Configuration');
|
||||
$this->table([], [
|
||||
['Environment', $this->formatText(config('app.env'), config('app.env') === 'production' ?: 'bg=red')],
|
||||
['Debug Mode', $this->formatText(config('app.debug') ? 'Yes' : 'No', !config('app.debug') ?: 'bg=red')],
|
||||
['Installation URL', config('app.url')],
|
||||
['Environment', config('app.env') === 'production' ? config('app.env') : $this->formatText(config('app.env'), 'bg=red')],
|
||||
['Debug Mode', config('app.debug') ? $this->formatText('Yes', 'bg=red') : 'No'],
|
||||
['Application Name', config('app.name')],
|
||||
['Application URL', config('app.url')],
|
||||
['Installation Directory', base_path()],
|
||||
['Timezone', config('app.timezone')],
|
||||
['Cache Driver', config('cache.default')],
|
||||
['Queue Driver', config('queue.default')],
|
||||
['Queue Driver', config('queue.default') === 'sync' ? $this->formatText(config('queue.default'), 'bg=red') : config('queue.default')],
|
||||
['Session Driver', config('session.driver')],
|
||||
['Filesystem Driver', config('filesystems.default')],
|
||||
['Default Theme', config('themes.active')],
|
||||
['Proxies', config('trustedproxies.proxies')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Database Configuration');
|
||||
$driver = config('database.default');
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("database.connections.$driver.host")],
|
||||
['Port', config("database.connections.$driver.port")],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
['Username', config("database.connections.$driver.username")],
|
||||
], 'compact');
|
||||
if ($driver === 'sqlite') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("database.connections.$driver.host")],
|
||||
['Port', config("database.connections.$driver.port")],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
['Username', config("database.connections.$driver.username")],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
// TODO: Update this to handle other mail drivers
|
||||
$this->output->title('Email Configuration');
|
||||
$this->table([], [
|
||||
['Driver', config('mail.default')],
|
||||
['Host', config('mail.mailers.smtp.host')],
|
||||
['Port', config('mail.mailers.smtp.port')],
|
||||
['Username', config('mail.mailers.smtp.username')],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
['Encryption', config('mail.mailers.smtp.encryption')],
|
||||
], 'compact');
|
||||
$driver = config('mail.default');
|
||||
if ($driver === 'smtp') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("mail.mailers.$driver.host")],
|
||||
['Port', config("mail.mailers.$driver.port")],
|
||||
['Username', config("mail.mailers.$driver.username")],
|
||||
['Encryption', config("mail.mailers.$driver.encryption")],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
$this->output->title('Backup Configuration');
|
||||
$driver = config('backups.default');
|
||||
if ($driver === 's3') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Region', config("backups.disks.$driver.region")],
|
||||
['Bucket', config("backups.disks.$driver.bucket")],
|
||||
['Endpoint', config("backups.disks.$driver.endpoint")],
|
||||
['Use path style endpoint', config("backups.disks.$driver.use_path_style_endpoint") ? 'Yes' : 'No'],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
], 'compact');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
60
app/Console/Commands/Maintenance/PruneImagesCommand.php
Normal file
60
app/Console/Commands/Maintenance/PruneImagesCommand.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Maintenance;
|
||||
|
||||
use App\Models\Node;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class PruneImagesCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:maintenance:prune-images {node?}';
|
||||
|
||||
protected $description = 'Clean up all dangling docker images to clear up disk space.';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$node = $this->argument('node');
|
||||
|
||||
if (empty($node)) {
|
||||
$nodes = Node::all();
|
||||
/** @var Node $node */
|
||||
foreach ($nodes as $node) {
|
||||
$this->cleanupImages($node);
|
||||
}
|
||||
} else {
|
||||
$this->cleanupImages((int) $node);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupImages(int|Node $node): void
|
||||
{
|
||||
if (!$node instanceof Node) {
|
||||
$node = Node::query()->findOrFail($node);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::daemon($node)
|
||||
->connectTimeout(5)
|
||||
->timeout(30)
|
||||
->delete('/api/system/docker/image/prune')
|
||||
->json() ?? [];
|
||||
|
||||
if (empty($response) || $response['ImagesDeleted'] === null) {
|
||||
$this->warn("Node {$node->id}: No images to clean up.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$count = count($response['ImagesDeleted']);
|
||||
|
||||
$useBinaryPrefix = config('panel.use_binary_prefix');
|
||||
$space = round($useBinaryPrefix ? $response['SpaceReclaimed'] / 1024 / 1024 : $response['SpaceReclaimed'] / 1000 / 1000, 2) . ($useBinaryPrefix ? ' MiB' : ' MB');
|
||||
|
||||
$this->info("Node {$node->id}: Cleaned up {$count} dangling docker images. ({$space})");
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,12 @@ class MakeNodeCommand extends Command
|
||||
{--overallocateMemory= : Enter the amount of ram to overallocate (% or -1 to overallocate the maximum).}
|
||||
{--maxDisk= : Set the max disk amount.}
|
||||
{--overallocateDisk= : Enter the amount of disk to overallocate (% or -1 to overallocate the maximum).}
|
||||
{--maxCpu= : Set the max cpu amount.}
|
||||
{--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).}
|
||||
{--uploadSize= : Enter the maximum upload filesize.}
|
||||
{--daemonListeningPort= : Enter the daemon listening port.}
|
||||
{--daemonSFTPPort= : Enter the daemon SFTP listening port.}
|
||||
{--daemonSFTPAlias= : Enter the daemon SFTP alias.}
|
||||
{--daemonBase= : Enter the base folder.}';
|
||||
|
||||
protected $description = 'Creates a new node on the system via the CLI.';
|
||||
@@ -42,27 +45,31 @@ class MakeNodeCommand extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$data['name'] = $this->option('name') ?? $this->ask('Enter a short identifier used to distinguish this node from others');
|
||||
$data['description'] = $this->option('description') ?? $this->ask('Enter a description to identify the node');
|
||||
$data['name'] = $this->option('name') ?? $this->ask(__('commands.make_node.name'));
|
||||
$data['description'] = $this->option('description') ?? $this->ask(__('commands.make_node.description'));
|
||||
$data['scheme'] = $this->option('scheme') ?? $this->anticipate(
|
||||
'Please either enter https for SSL or http for a non-ssl connection',
|
||||
__('commands.make_node.scheme'),
|
||||
['https', 'http'],
|
||||
'https'
|
||||
);
|
||||
$data['fqdn'] = $this->option('fqdn') ?? $this->ask('Enter a domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node');
|
||||
$data['public'] = $this->option('public') ?? $this->confirm('Should this node be public? As a note, setting a node to private you will be denying the ability to auto-deploy to this node.', true);
|
||||
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm('Is your FQDN behind a proxy?');
|
||||
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm('Should maintenance mode be enabled?');
|
||||
$data['memory'] = $this->option('maxMemory') ?? $this->ask('Enter the maximum amount of memory');
|
||||
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask('Enter the amount of memory to over allocate by, -1 will disable checking and 0 will prevent creating new servers');
|
||||
$data['disk'] = $this->option('maxDisk') ?? $this->ask('Enter the maximum amount of disk space');
|
||||
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask('Enter the amount of memory to over allocate by, -1 will disable checking and 0 will prevent creating new server');
|
||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask('Enter the maximum filesize upload', '100');
|
||||
$data['daemonListen'] = $this->option('daemonListeningPort') ?? $this->ask('Enter the daemon listening port', '8080');
|
||||
$data['daemonSFTP'] = $this->option('daemonSFTPPort') ?? $this->ask('Enter the daemon SFTP listening port', '2022');
|
||||
$data['daemonBase'] = $this->option('daemonBase') ?? $this->ask('Enter the base folder', '/var/lib/panel/volumes');
|
||||
|
||||
$data['fqdn'] = $this->option('fqdn') ?? $this->ask(__('commands.make_node.fqdn'));
|
||||
$data['public'] = $this->option('public') ?? $this->confirm(__('commands.make_node.public'), true);
|
||||
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(__('commands.make_node.behind_proxy'));
|
||||
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(__('commands.make_node.maintenance_mode'));
|
||||
$data['memory'] = $this->option('maxMemory') ?? $this->ask(__('commands.make_node.memory'), '0');
|
||||
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(__('commands.make_node.memory_overallocate'), '-1');
|
||||
$data['disk'] = $this->option('maxDisk') ?? $this->ask(__('commands.make_node.disk'), '0');
|
||||
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(__('commands.make_node.disk_overallocate'), '-1');
|
||||
$data['cpu'] = $this->option('maxCpu') ?? $this->ask(__('commands.make_node.cpu'), '0');
|
||||
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(__('commands.make_node.cpu_overallocate'), '-1');
|
||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(__('commands.make_node.upload_size'), '256');
|
||||
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(__('commands.make_node.daemonListen'), '8080');
|
||||
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(__('commands.make_node.daemonSFTP'), '2022');
|
||||
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(__('commands.make_node.daemonSFTPAlias'), '');
|
||||
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(__('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
||||
|
||||
$node = $this->creationService->handle($data);
|
||||
$this->line('Successfully created a new node with the name ' . $data['name'] . ' and has an id of ' . $node->id . '.');
|
||||
$this->line(__('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ class NodeConfigurationCommand extends Command
|
||||
|
||||
/** @var \App\Models\Node $node */
|
||||
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
||||
$this->error('The selected node does not exist.');
|
||||
$this->error(__('commands.node_config.error_not_exist'));
|
||||
|
||||
exit(1);
|
||||
});
|
||||
|
||||
$format = $this->option('format');
|
||||
if (!in_array($format, ['yaml', 'yml', 'json'])) {
|
||||
$this->error('Invalid format specified. Valid options are "yaml" and "json".');
|
||||
$this->error(__('commands.node_config.error_invalid_format'));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ class KeyGenerateCommand extends BaseKeyGenerateCommand
|
||||
public function handle(): void
|
||||
{
|
||||
if (!empty(config('app.key')) && $this->input->isInteractive()) {
|
||||
$this->output->warning('It appears you have already configured an application encryption key. Continuing with this process with overwrite that key and cause data corruption for any existing encrypted data. DO NOT CONTINUE UNLESS YOU KNOW WHAT YOU ARE DOING.');
|
||||
if (!$this->confirm('I understand the consequences of performing this command and accept all responsibility for the loss of encrypted data.')) {
|
||||
$this->output->warning(__('commands.key_generate.error_already_exist'));
|
||||
if (!$this->confirm(__('commands.key_generate.understand'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->confirm('Are you sure you wish to continue? Changing the application encryption key WILL CAUSE DATA LOSS.')) {
|
||||
if (!$this->confirm(__('commands.key_generate.continue'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use App\Console\RequiresDatabaseMigrations;
|
||||
use App\Traits\Commands\RequiresDatabaseMigrations;
|
||||
use Illuminate\Database\Console\Seeds\SeedCommand as BaseSeedCommand;
|
||||
|
||||
class SeedCommand extends BaseSeedCommand
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use App\Console\RequiresDatabaseMigrations;
|
||||
use App\Traits\Commands\RequiresDatabaseMigrations;
|
||||
use Illuminate\Foundation\Console\UpCommand as BaseUpCommand;
|
||||
|
||||
class UpCommand extends BaseUpCommand
|
||||
|
||||
@@ -23,11 +23,11 @@ class ProcessRunnableCommand extends Command
|
||||
->whereRelation('server', fn (Builder $builder) => $builder->whereNull('status'))
|
||||
->where('is_active', true)
|
||||
->where('is_processing', false)
|
||||
->whereRaw('next_run_at <= NOW()')
|
||||
->where('next_run_at', '<=', now('UTC')->toDateTimeString())
|
||||
->get();
|
||||
|
||||
if ($schedules->count() < 1) {
|
||||
$this->line('There are no scheduled tasks for servers that need to be run.');
|
||||
$this->line(__('commands.schedule.process.no_tasks'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class ProcessRunnableCommand extends Command
|
||||
* never throw an exception out, otherwise you'll end up killing the entire run group causing
|
||||
* any other schedules to not process correctly.
|
||||
*/
|
||||
protected function processSchedule(Schedule $schedule)
|
||||
protected function processSchedule(Schedule $schedule): void
|
||||
{
|
||||
if ($schedule->tasks->isEmpty()) {
|
||||
return;
|
||||
@@ -61,12 +61,12 @@ class ProcessRunnableCommand extends Command
|
||||
|
||||
$this->line(trans('command/messages.schedule.output_line', [
|
||||
'schedule' => $schedule->name,
|
||||
'hash' => $schedule->hashid,
|
||||
'id' => $schedule->id,
|
||||
]));
|
||||
} catch (\Throwable|\Exception $exception) {
|
||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||
|
||||
$this->error("An error was encountered while processing Schedule #$schedule->id: " . $exception->getMessage());
|
||||
$this->error(__('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,29 +34,30 @@ class UpgradeCommand extends Command
|
||||
{
|
||||
$skipDownload = $this->option('skip-download');
|
||||
if (!$skipDownload) {
|
||||
$this->output->warning('This command does not verify the integrity of downloaded assets. Please ensure that you trust the download source before continuing. If you do not wish to download an archive, please indicate that using the --skip-download flag, or answering "no" to the question below.');
|
||||
$this->output->comment('Download Source (set with --url=):');
|
||||
$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('Cannot execute self-upgrade process. The minimum required PHP version required is 7.4.0, you have [' . PHP_VERSION . '].');
|
||||
$this->error(__('commands.upgrade.php_version') . ' [' . PHP_VERSION . '].');
|
||||
}
|
||||
|
||||
$user = 'www-data';
|
||||
$group = 'www-data';
|
||||
if ($this->input->isInteractive()) {
|
||||
if (!$skipDownload) {
|
||||
$skipDownload = !$this->confirm('Would you like to download and unpack the archive files for the latest version?', true);
|
||||
$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';
|
||||
|
||||
if (!$this->confirm("Your webserver user has been detected as <fg=blue>[{$user}]:</> is this correct?", true)) {
|
||||
$message = __('commands.upgrade.webserver_user', ['user' => $user]);
|
||||
if (!$this->confirm($message, true)) {
|
||||
$user = $this->anticipate(
|
||||
'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data", "nginx", or "apache".',
|
||||
__('commands.upgrade.name_webserver'),
|
||||
[
|
||||
'www-data',
|
||||
'nginx',
|
||||
@@ -70,9 +71,10 @@ class UpgradeCommand extends Command
|
||||
$groupDetails = function_exists('posix_getgrgid') ? posix_getgrgid(filegroup('public')) : [];
|
||||
$group = $groupDetails['name'] ?? 'www-data';
|
||||
|
||||
if (!$this->confirm("Your webserver group has been detected as <fg=blue>[{$group}]:</> is this correct?", true)) {
|
||||
$message = __('commands.upgrade.group_webserver', ['group' => $user]);
|
||||
if (!$this->confirm($message, true)) {
|
||||
$group = $this->anticipate(
|
||||
'Please enter the name of the group running your webserver process. Normally this is the same as your user.',
|
||||
__('commands.upgrade.group_webserver_question'),
|
||||
[
|
||||
'www-data',
|
||||
'nginx',
|
||||
@@ -82,8 +84,8 @@ class UpgradeCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) {
|
||||
$this->warn('Upgrade process terminated by user.');
|
||||
if (!$this->confirm(__('commands.upgrade.are_your_sure'))) {
|
||||
$this->warn(__('commands.upgrade.terminated'));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -173,10 +175,10 @@ class UpgradeCommand extends Command
|
||||
});
|
||||
|
||||
$this->newLine(2);
|
||||
$this->info('Panel has been successfully upgraded. Please ensure you also update any Daemon instances');
|
||||
$this->info(__('commands.upgrade.success'));
|
||||
}
|
||||
|
||||
protected function withProgress(ProgressBar $bar, \Closure $callback)
|
||||
protected function withProgress(ProgressBar $bar, \Closure $callback): void
|
||||
{
|
||||
$bar->clear();
|
||||
$callback();
|
||||
|
||||
@@ -15,7 +15,7 @@ class DeleteUserCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
$search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users'));
|
||||
Assert::notEmpty($search, 'Search term should be an email address, got: %s.');
|
||||
Assert::notEmpty($search, 'Search term should not be empty.');
|
||||
|
||||
$results = User::query()
|
||||
->where('id', 'LIKE', "$search%")
|
||||
@@ -42,6 +42,8 @@ class DeleteUserCommand extends Command
|
||||
if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) {
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
$deleteUser = User::query()->findOrFail($deleteUser);
|
||||
} else {
|
||||
if (count($results) > 1) {
|
||||
$this->error(trans('command/messages.user.multiple_found'));
|
||||
@@ -53,8 +55,7 @@ class DeleteUserCommand extends Command
|
||||
}
|
||||
|
||||
if ($this->confirm(trans('command/messages.user.confirm_delete')) || !$this->input->isInteractive()) {
|
||||
$user = User::query()->findOrFail($deleteUser);
|
||||
$user->delete();
|
||||
$deleteUser->delete();
|
||||
|
||||
$this->info(trans('command/messages.user.deleted'));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class MakeUserCommand extends Command
|
||||
{
|
||||
protected $description = 'Creates a user on the system via the CLI.';
|
||||
|
||||
protected $signature = 'p:user:make {--email=} {--username=} {--name-first=} {--name-last=} {--password=} {--admin=} {--no-password}';
|
||||
protected $signature = 'p:user:make {--email=} {--username=} {--password=} {--admin=} {--no-password}';
|
||||
|
||||
/**
|
||||
* MakeUserCommand constructor.
|
||||
@@ -30,7 +30,7 @@ class MakeUserCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
DB::select('select 1 where 1');
|
||||
DB::connection()->getPdo();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
@@ -40,8 +40,6 @@ class MakeUserCommand extends Command
|
||||
$root_admin = $this->option('admin') ?? $this->confirm(trans('command/messages.user.ask_admin'));
|
||||
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
||||
$username = $this->option('username') ?? $this->ask(trans('command/messages.user.ask_username'));
|
||||
$name_first = $this->option('name-first') ?? $this->ask(trans('command/messages.user.ask_name_first'));
|
||||
$name_last = $this->option('name-last') ?? $this->ask(trans('command/messages.user.ask_name_last'));
|
||||
|
||||
if (is_null($password = $this->option('password')) && !$this->option('no-password')) {
|
||||
$this->warn(trans('command/messages.user.ask_password_help'));
|
||||
@@ -49,13 +47,12 @@ class MakeUserCommand extends Command
|
||||
$password = $this->secret(trans('command/messages.user.ask_password'));
|
||||
}
|
||||
|
||||
$user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin'));
|
||||
$user = $this->creationService->handle(compact('email', 'username', 'password', 'root_admin'));
|
||||
$this->table(['Field', 'Value'], [
|
||||
['UUID', $user->uuid],
|
||||
['Email', $user->email],
|
||||
['Username', $user->username],
|
||||
['Name', $user->name],
|
||||
['Admin', $user->root_admin ? 'Yes' : 'No'],
|
||||
['Admin', $user->isRootAdmin() ? 'Yes' : 'No'],
|
||||
]);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Jobs\NodeStatistics;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Database\Console\PruneCommand;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
@@ -30,7 +34,12 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
||||
|
||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||
$schedule->command(PruneImagesCommand::class)->daily();
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
|
||||
|
||||
$schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping();
|
||||
|
||||
if (config('backups.prune_age')) {
|
||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||
@@ -40,5 +49,9 @@ class Kernel extends ConsoleKernel
|
||||
if (config('activity.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
|
||||
}
|
||||
|
||||
if (config('panel.webhook.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [Webhook::class]])->daily();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts\Extensions;
|
||||
|
||||
use Hashids\HashidsInterface as VendorHashidsInterface;
|
||||
|
||||
interface HashidsInterface extends VendorHashidsInterface
|
||||
{
|
||||
/**
|
||||
* Decode an encoded hashid and return the first result.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function decodeFirst(string $encoded, string $default = null): mixed;
|
||||
}
|
||||
55
app/Enums/ContainerStatus.php
Normal file
55
app/Enums/ContainerStatus.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ContainerStatus: string
|
||||
{
|
||||
// Docker Based
|
||||
case Created = 'created';
|
||||
case Starting = 'starting';
|
||||
case Running = 'running';
|
||||
case Restarting = 'restarting';
|
||||
case Exited = 'exited';
|
||||
case Paused = 'paused';
|
||||
case Dead = 'dead';
|
||||
case Removing = 'removing';
|
||||
case Stopping = 'stopping';
|
||||
case Offline = 'offline';
|
||||
|
||||
// HTTP Based
|
||||
case Missing = 'missing';
|
||||
|
||||
public function icon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
|
||||
self::Created => 'tabler-heart-plus',
|
||||
self::Starting => 'tabler-heart-up',
|
||||
self::Running => 'tabler-heartbeat',
|
||||
self::Restarting => 'tabler-heart-bolt',
|
||||
self::Exited => 'tabler-heart-exclamation',
|
||||
self::Paused => 'tabler-heart-pause',
|
||||
self::Dead, self::Offline => 'tabler-heart-x',
|
||||
self::Removing => 'tabler-heart-down',
|
||||
self::Missing => 'tabler-heart-search',
|
||||
self::Stopping => 'tabler-heart-minus',
|
||||
};
|
||||
}
|
||||
|
||||
public function color(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Created => 'primary',
|
||||
self::Starting => 'warning',
|
||||
self::Running => 'success',
|
||||
self::Restarting => 'info',
|
||||
self::Exited => 'danger',
|
||||
self::Paused => 'warning',
|
||||
self::Dead => 'danger',
|
||||
self::Removing => 'warning',
|
||||
self::Missing => 'danger',
|
||||
self::Stopping => 'warning',
|
||||
self::Offline => 'gray',
|
||||
};
|
||||
}
|
||||
}
|
||||
31
app/Enums/HttpStatusCode.php
Normal file
31
app/Enums/HttpStatusCode.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HttpStatusCode: int
|
||||
{
|
||||
// Client
|
||||
case BadRequest = 400;
|
||||
case Unauthorized = 401;
|
||||
case Forbidden = 403;
|
||||
case NotFound = 404;
|
||||
case MethodNotAllowed = 405;
|
||||
case NotAcceptable = 406;
|
||||
case ProxyAuthenticationRequired = 407;
|
||||
case RequestTimeout = 408;
|
||||
case Conflict = 409;
|
||||
case Gone = 410;
|
||||
case LengthRequired = 411;
|
||||
case PreconditionFailed = 412;
|
||||
case PayloadTooLarge = 413;
|
||||
case UriTooLong = 414;
|
||||
case UnsupportedMediaType = 415;
|
||||
|
||||
// Server
|
||||
case InternalServerError = 500;
|
||||
case NotImplemented = 501;
|
||||
case BadGateway = 502;
|
||||
case ServiceUnavailable = 503;
|
||||
case GatewayTimeout = 504;
|
||||
case HTTPVersionNotSupported = 505;
|
||||
}
|
||||
16
app/Enums/RolePermissionModels.php
Normal file
16
app/Enums/RolePermissionModels.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum RolePermissionModels: string
|
||||
{
|
||||
case ApiKey = 'apiKey';
|
||||
case DatabaseHost = 'databaseHost';
|
||||
case Database = 'database';
|
||||
case Egg = 'egg';
|
||||
case Mount = 'mount';
|
||||
case Node = 'node';
|
||||
case Role = 'role';
|
||||
case Server = 'server';
|
||||
case User = 'user';
|
||||
}
|
||||
12
app/Enums/RolePermissionPrefixes.php
Normal file
12
app/Enums/RolePermissionPrefixes.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum RolePermissionPrefixes: string
|
||||
{
|
||||
case ViewAny = 'viewList';
|
||||
case View = 'view';
|
||||
case Create = 'create';
|
||||
case Update = 'update';
|
||||
case Delete = 'delete';
|
||||
}
|
||||
37
app/Enums/ServerState.php
Normal file
37
app/Enums/ServerState.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ServerState: string
|
||||
{
|
||||
case Normal = 'normal';
|
||||
case Installing = 'installing';
|
||||
case InstallFailed = 'install_failed';
|
||||
case ReinstallFailed = 'reinstall_failed';
|
||||
case Suspended = 'suspended';
|
||||
case RestoringBackup = 'restoring_backup';
|
||||
|
||||
public function icon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Normal => 'tabler-heart',
|
||||
self::Installing => 'tabler-heart-bolt',
|
||||
self::InstallFailed => 'tabler-heart-x',
|
||||
self::ReinstallFailed => 'tabler-heart-x',
|
||||
self::Suspended => 'tabler-heart-cancel',
|
||||
self::RestoringBackup => 'tabler-heart-up',
|
||||
};
|
||||
}
|
||||
|
||||
public function color(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Normal => 'primary',
|
||||
self::Installing => 'primary',
|
||||
self::InstallFailed => 'danger',
|
||||
self::ReinstallFailed => 'danger',
|
||||
self::Suspended => 'warning',
|
||||
self::RestoringBackup => 'primary',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class FailedCaptcha extends Event
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public string $ip, public string $domain)
|
||||
public function __construct(public string $ip, public ?string $message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Saved extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Saving extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class AccountNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class AutoDeploymentException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -3,20 +3,23 @@
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
{
|
||||
public const LEVEL_DEBUG = 'debug';
|
||||
|
||||
public const LEVEL_INFO = 'info';
|
||||
|
||||
public const LEVEL_WARNING = 'warning';
|
||||
|
||||
public const LEVEL_ERROR = 'error';
|
||||
|
||||
/**
|
||||
@@ -47,14 +50,22 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
* and then redirecting them back to the page that they came from. If the
|
||||
* request originated from an API hit, return the error in JSONAPI spec format.
|
||||
*/
|
||||
public function render(Request $request): JsonResponse|RedirectResponse
|
||||
public function render(Request $request): bool|RedirectResponse|JsonResponse
|
||||
{
|
||||
if ($request->is('livewire/update')) {
|
||||
Notification::make()
|
||||
->title(static::class)
|
||||
->body($this->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
|
||||
}
|
||||
|
||||
app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
|
||||
|
||||
return redirect()->back()->withInput();
|
||||
}
|
||||
|
||||
@@ -64,10 +75,10 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function report()
|
||||
public function report(): void
|
||||
{
|
||||
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -76,6 +87,6 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
throw $this->getPrevious();
|
||||
}
|
||||
|
||||
return $logger->{$this->getErrorLevel()}($this->getPrevious());
|
||||
$logger->{$this->getErrorLevel()}($this->getPrevious());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
@@ -140,7 +140,7 @@ class Handler extends ExceptionHandler
|
||||
* Transform a validation exception into a consistent format to be returned for
|
||||
* calls to the API.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*/
|
||||
public function invalidJson($request, ValidationException $exception): JsonResponse
|
||||
{
|
||||
@@ -215,7 +215,7 @@ class Handler extends ExceptionHandler
|
||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||
->all(),
|
||||
'previous' => Collection::make($this->extractPrevious($e))
|
||||
->map(fn ($exception) => $e->getTrace())
|
||||
->map(fn ($exception) => $exception->getTrace())
|
||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||
->all(),
|
||||
],
|
||||
@@ -236,7 +236,7 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
|
||||
{
|
||||
@@ -273,6 +273,7 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public static function toArray(\Throwable $e): array
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return (new self(app()))->convertExceptionToArray($e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@ use GuzzleHttp\Exception\GuzzleException;
|
||||
use App\Exceptions\DisplayException;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
|
||||
/**
|
||||
* @method \GuzzleHttp\Exception\GuzzleException getPrevious()
|
||||
*/
|
||||
class DaemonConnectionException extends DisplayException
|
||||
{
|
||||
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
|
||||
|
||||
@@ -10,7 +10,7 @@ class HttpForbiddenException extends HttpException
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Http\Server;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class FileTypeNotEditableException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Exceptions\Http\Server;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Models\Server;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
|
||||
@@ -11,7 +12,7 @@ class ServerStateConflictException extends ConflictHttpException
|
||||
* Exception thrown when the server is in an unsupported state for API access or
|
||||
* 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.';
|
||||
if ($server->isSuspended()) {
|
||||
@@ -20,7 +21,7 @@ class ServerStateConflictException extends ConflictHttpException
|
||||
$message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
|
||||
} elseif (!$server->isInstalled()) {
|
||||
$message = 'This server has not yet completed its installation process, please try again later.';
|
||||
} elseif ($server->status === Server::STATUS_RESTORING_BACKUP) {
|
||||
} elseif ($server->status === ServerState::RestoringBackup) {
|
||||
$message = 'This server is currently restoring from a backup, please try again later.';
|
||||
} elseif (!is_null($server->transfer)) {
|
||||
$message = 'This server is currently being transferred to a new machine, please try again later.';
|
||||
|
||||
@@ -11,7 +11,7 @@ class TwoFactorAuthRequiredException extends HttpException implements HttpExcept
|
||||
/**
|
||||
* TwoFactorAuthRequiredException constructor.
|
||||
*/
|
||||
public function __construct(\Throwable $previous = null)
|
||||
public function __construct(?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository\Daemon;
|
||||
|
||||
use App\Exceptions\Repository\RepositoryException;
|
||||
|
||||
class InvalidPowerSignalException extends RepositoryException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class RepositoryException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Allocation;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class AllocationDoesNotBelongToServerException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Deployment;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoViableNodeException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class BadJsonFormatException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Egg;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoParentConfigurationFoundException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Helper;
|
||||
|
||||
class CdnVersionFetchingException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Schedule\Task;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class TaskIntervalTooLongException extends DisplayException
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Server;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class RequiredVariableMissingException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class ServiceLimitExceededException extends DisplayException
|
||||
* Exception thrown when something goes over a defined limit, such as allocated
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ use App\Exceptions\DisplayException;
|
||||
|
||||
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
||||
{
|
||||
/**
|
||||
* TwoFactorAuthenticationTokenInvalid constructor.
|
||||
*/
|
||||
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.');
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Transformer;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
|
||||
class InvalidTransformerLevelException extends PanelException
|
||||
{
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class BackupManager
|
||||
/**
|
||||
* Returns a backup adapter instance.
|
||||
*/
|
||||
public function adapter(string $name = null): FilesystemAdapter
|
||||
public function adapter(?string $name = null): FilesystemAdapter
|
||||
{
|
||||
return $this->get($name ?: $this->getDefaultAdapter());
|
||||
}
|
||||
@@ -97,7 +97,7 @@ class BackupManager
|
||||
/**
|
||||
* Creates a new daemon adapter.
|
||||
*/
|
||||
public function createDaemonAdapter(array $config): FilesystemAdapter
|
||||
public function createWingsAdapter(array $config): FilesystemAdapter
|
||||
{
|
||||
return new InMemoryFilesystemAdapter();
|
||||
}
|
||||
@@ -145,7 +145,7 @@ class BackupManager
|
||||
/**
|
||||
* Unset the given adapter instances.
|
||||
*
|
||||
* @param string|string[] $adapter
|
||||
* @param string|string[] $adapter
|
||||
*/
|
||||
public function forget(array|string $adapter): self
|
||||
{
|
||||
|
||||
@@ -7,7 +7,9 @@ use App\Models\DatabaseHost;
|
||||
class DynamicDatabaseConnection
|
||||
{
|
||||
public const DB_CHARSET = 'utf8';
|
||||
|
||||
public const DB_COLLATION = 'utf8_unicode_ci';
|
||||
|
||||
public const DB_DRIVER = 'mysql';
|
||||
|
||||
/**
|
||||
@@ -25,7 +27,7 @@ class DynamicDatabaseConnection
|
||||
'port' => $host->port,
|
||||
'database' => $database,
|
||||
'username' => $host->username,
|
||||
'password' => decrypt($host->password),
|
||||
'password' => $host->password,
|
||||
'charset' => self::DB_CHARSET,
|
||||
'collation' => self::DB_COLLATION,
|
||||
]);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Theme extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'extensions.themes';
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,28 @@
|
||||
<?php
|
||||
|
||||
/* 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. */
|
||||
|
||||
namespace App\Extensions\Filesystem;
|
||||
|
||||
use Aws\S3\S3ClientInterface;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions;
|
||||
|
||||
use Hashids\Hashids as VendorHashids;
|
||||
use App\Contracts\Extensions\HashidsInterface;
|
||||
|
||||
class Hashids extends VendorHashids implements HashidsInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decodeFirst(string $encoded, string $default = null): mixed
|
||||
{
|
||||
$result = $this->decode($encoded);
|
||||
if (!is_array($result)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return array_first($result, null, $default);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Themes;
|
||||
|
||||
class Theme
|
||||
{
|
||||
public function js($path): string
|
||||
{
|
||||
return sprintf('<script src="%s"></script>' . PHP_EOL, $this->getUrl($path));
|
||||
}
|
||||
|
||||
public function css($path): string
|
||||
{
|
||||
return sprintf('<link media="all" type="text/css" rel="stylesheet" href="%s"/>' . PHP_EOL, $this->getUrl($path));
|
||||
}
|
||||
|
||||
protected function getUrl($path): string
|
||||
{
|
||||
return '/themes/panel/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use App\Services\Activity\ActivityLogBatchService;
|
||||
|
||||
class LogBatch extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return ActivityLogBatchService::class;
|
||||
}
|
||||
}
|
||||
36
app/Filament/Pages/Auth/Login.php
Normal file
36
app/Filament/Pages/Auth/Login.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Auth;
|
||||
|
||||
use Coderflex\FilamentTurnstile\Forms\Components\Turnstile;
|
||||
use Filament\Pages\Auth\Login as BaseLogin;
|
||||
|
||||
class Login extends BaseLogin
|
||||
{
|
||||
protected function getForms(): array
|
||||
{
|
||||
return [
|
||||
'form' => $this->form(
|
||||
$this->makeForm()
|
||||
->schema([
|
||||
$this->getEmailFormComponent(),
|
||||
$this->getPasswordFormComponent(),
|
||||
$this->getRememberFormComponent(),
|
||||
Turnstile::make('captcha')
|
||||
->hidden(!config('turnstile.turnstile_enabled'))
|
||||
->validationMessages([
|
||||
'required' => config('turnstile.error_messages.turnstile_check_message'),
|
||||
]),
|
||||
])
|
||||
->statePath('data'),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function throwFailureValidationException(): never
|
||||
{
|
||||
$this->dispatch('reset-captcha');
|
||||
|
||||
parent::throwFailureValidationException();
|
||||
}
|
||||
}
|
||||
86
app/Filament/Pages/Dashboard.php
Normal file
86
app/Filament/Pages/Dashboard.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Filament\Resources\NodeResource\Pages\CreateNode;
|
||||
use App\Filament\Resources\NodeResource\Pages\ListNodes;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Pages\Page;
|
||||
|
||||
class Dashboard extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'tabler-layout-dashboard';
|
||||
|
||||
protected static string $view = 'filament.pages.dashboard';
|
||||
|
||||
protected ?string $heading = '';
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('strings.dashboard');
|
||||
}
|
||||
|
||||
protected static ?string $slug = '/';
|
||||
|
||||
public string $activeTab = 'nodes';
|
||||
|
||||
private SoftwareVersionService $softwareVersionService;
|
||||
|
||||
public function mount(SoftwareVersionService $softwareVersionService): void
|
||||
{
|
||||
$this->softwareVersionService = $softwareVersionService;
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'inDevelopment' => config('app.version') === 'canary',
|
||||
'version' => $this->softwareVersionService->currentPanelVersion(),
|
||||
'latestVersion' => $this->softwareVersionService->latestPanelVersion(),
|
||||
'isLatest' => $this->softwareVersionService->isLatestPanel(),
|
||||
'eggsCount' => Egg::query()->count(),
|
||||
'nodesList' => ListNodes::getUrl(),
|
||||
'nodesCount' => Node::query()->count(),
|
||||
'serversCount' => Server::query()->count(),
|
||||
'usersCount' => User::query()->count(),
|
||||
|
||||
'devActions' => [
|
||||
CreateAction::make()
|
||||
->label('Bugs & Features')
|
||||
->icon('tabler-brand-github')
|
||||
->url('https://github.com/pelican-dev/panel/discussions', true),
|
||||
],
|
||||
'updateActions' => [
|
||||
CreateAction::make()
|
||||
->label('Read Documentation')
|
||||
->icon('tabler-clipboard-text')
|
||||
->url('https://pelican.dev/docs/panel/update', true)
|
||||
->color('warning'),
|
||||
],
|
||||
'nodeActions' => [
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-first-node.button_label'))
|
||||
->icon('tabler-server-2')
|
||||
->url(CreateNode::getUrl()),
|
||||
],
|
||||
'supportActions' => [
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-support.button_donate'))
|
||||
->icon('tabler-cash')
|
||||
->url('https://pelican.dev/donate', true)
|
||||
->color('success'),
|
||||
],
|
||||
'helpActions' => [
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-help.button_docs'))
|
||||
->icon('tabler-speedboat')
|
||||
->url('https://pelican.dev/docs', true),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
184
app/Filament/Pages/Installer/PanelInstaller.php
Normal file
184
app/Filament/Pages/Installer/PanelInstaller.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer;
|
||||
|
||||
use App\Filament\Pages\Dashboard;
|
||||
use App\Filament\Pages\Installer\Steps\CacheStep;
|
||||
use App\Filament\Pages\Installer\Steps\DatabaseStep;
|
||||
use App\Filament\Pages\Installer\Steps\EnvironmentStep;
|
||||
use App\Filament\Pages\Installer\Steps\QueueStep;
|
||||
use App\Filament\Pages\Installer\Steps\RequirementsStep;
|
||||
use App\Filament\Pages\Installer\Steps\SessionStep;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Traits\CheckMigrationsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Wizard;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\SimplePage;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
/**
|
||||
* @property Form $form
|
||||
*/
|
||||
class PanelInstaller extends SimplePage implements HasForms
|
||||
{
|
||||
use CheckMigrationsTrait;
|
||||
use EnvironmentWriterTrait;
|
||||
use InteractsWithForms;
|
||||
|
||||
public array $data = [];
|
||||
|
||||
protected static string $view = 'filament.pages.installer';
|
||||
|
||||
public function getMaxWidth(): MaxWidth|string
|
||||
{
|
||||
return MaxWidth::SevenExtraLarge;
|
||||
}
|
||||
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
// This defaults to true so existing panels count as "installed"
|
||||
return env('APP_INSTALLED', true);
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
abort_if(self::isInstalled(), 404);
|
||||
|
||||
$this->form->fill();
|
||||
}
|
||||
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
Wizard::make([
|
||||
RequirementsStep::make(),
|
||||
EnvironmentStep::make($this),
|
||||
DatabaseStep::make($this),
|
||||
CacheStep::make($this),
|
||||
QueueStep::make($this),
|
||||
SessionStep::make(),
|
||||
])
|
||||
->persistStepInQueryString()
|
||||
->nextAction(fn (Action $action) => $action->keyBindings('enter'))
|
||||
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
size="sm"
|
||||
wire:loading.attr="disabled"
|
||||
>
|
||||
Finish
|
||||
<span wire:loading><x-filament::loading-indicator class="h-4 w-4" /></span>
|
||||
</x-filament::button>
|
||||
BLADE))),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormStatePath(): ?string
|
||||
{
|
||||
return 'data';
|
||||
}
|
||||
|
||||
public function submit(UserCreationService $userCreationService): Redirector|RedirectResponse
|
||||
{
|
||||
// Disable installer
|
||||
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
|
||||
|
||||
// Create admin user & login
|
||||
$user = $this->createAdminUser($userCreationService);
|
||||
auth()->guard()->login($user, true);
|
||||
|
||||
// Write session data at the very end to avoid "page expired" errors
|
||||
$this->writeToEnv('env_session');
|
||||
|
||||
// Redirect to admin panel
|
||||
return redirect(Dashboard::getUrl());
|
||||
}
|
||||
|
||||
public function writeToEnv(string $key): void
|
||||
{
|
||||
try {
|
||||
$variables = array_get($this->data, $key);
|
||||
$variables = array_filter($variables); // Filter array to remove NULL values
|
||||
$this->writeToEnvironment($variables);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->title('Could not write to .env file')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
throw new Halt('Error while writing .env file');
|
||||
}
|
||||
|
||||
Artisan::call('config:clear');
|
||||
}
|
||||
|
||||
public function runMigrations(string $driver): void
|
||||
{
|
||||
try {
|
||||
Artisan::call('migrate', [
|
||||
'--force' => true,
|
||||
'--seed' => true,
|
||||
'--database' => $driver,
|
||||
]);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->title('Migrations failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
throw new Halt('Error while running migrations');
|
||||
}
|
||||
|
||||
if (!$this->hasCompletedMigrations()) {
|
||||
Notification::make()
|
||||
->title('Migrations failed')
|
||||
->danger()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
throw new Halt('Migrations failed');
|
||||
}
|
||||
}
|
||||
|
||||
public function createAdminUser(UserCreationService $userCreationService): User
|
||||
{
|
||||
try {
|
||||
$userData = array_get($this->data, 'user');
|
||||
$userData['root_admin'] = true;
|
||||
|
||||
return $userCreationService->handle($userData);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->title('Could not create admin user')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
throw new Halt('Error while creating admin user');
|
||||
}
|
||||
}
|
||||
}
|
||||
123
app/Filament/Pages/Installer/Steps/CacheStep.php
Normal file
123
app/Filament/Pages/Installer/Steps/CacheStep.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Redis\RedisManager;
|
||||
|
||||
class CacheStep
|
||||
{
|
||||
public const CACHE_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('cache')
|
||||
->label('Cache')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_cache.CACHE_STORE')
|
||||
->label('Cache Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for caching. We recommend "Filesystem".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::CACHE_DRIVERS)
|
||||
->default(config('cache.default'))
|
||||
->columnSpanFull()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
if ($state !== 'redis') {
|
||||
$set('env_cache.REDIS_HOST', null);
|
||||
$set('env_cache.REDIS_PORT', null);
|
||||
$set('env_cache.REDIS_USERNAME', null);
|
||||
$set('env_cache.REDIS_PASSWORD', null);
|
||||
} else {
|
||||
$set('env_cache.REDIS_HOST', $get('env_cache.REDIS_HOST') ?? '127.0.0.1');
|
||||
$set('env_cache.REDIS_PORT', $get('env_cache.REDIS_PORT') ?? '6379');
|
||||
$set('env_cache.REDIS_USERNAME', null);
|
||||
}
|
||||
}),
|
||||
TextInput::make('env_cache.REDIS_HOST')
|
||||
->label('Redis Host')
|
||||
->placeholder('127.0.0.1')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your redis server. Make sure it is reachable.')
|
||||
->required(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.host') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_PORT')
|
||||
->label('Redis Port')
|
||||
->placeholder('6379')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your redis server.')
|
||||
->required(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.port') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_USERNAME')
|
||||
->label('Redis Username')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your redis user. Can be empty')
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.username') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
TextInput::make('env_cache.REDIS_PASSWORD')
|
||||
->label('Redis Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password for your redis user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis' ? config('database.redis.default.password') : null)
|
||||
->visible(fn (Get $get) => $get('env_cache.CACHE_STORE') === 'redis'),
|
||||
])
|
||||
->afterValidation(function (Get $get, Application $app) use ($installer) {
|
||||
$driver = $get('env_cache.CACHE_STORE');
|
||||
|
||||
if (!self::testConnection($app, $driver, $get('env_cache.REDIS_HOST'), $get('env_cache.REDIS_PORT'), $get('env_cache.REDIS_USERNAME'), $get('env_cache.REDIS_PASSWORD'))) {
|
||||
throw new Halt('Redis connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_cache');
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(Application $app, string $driver, ?string $host, null|string|int $port, ?string $username, ?string $password): bool
|
||||
{
|
||||
if ($driver !== 'redis') {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$redis = new RedisManager($app, 'predis', [
|
||||
'default' => [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
],
|
||||
]);
|
||||
|
||||
$redis->connection()->command('ping');
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Redis connection failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
139
app/Filament/Pages/Installer/Steps/DatabaseStep.php
Normal file
139
app/Filament/Pages/Installer/Steps/DatabaseStep.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DatabaseStep
|
||||
{
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('database')
|
||||
->label('Database')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_database.DB_CONNECTION')
|
||||
->label('Database Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::DATABASE_DRIVERS)
|
||||
->default(config('database.default'))
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
$set('env_database.DB_DATABASE', $state === 'sqlite' ? 'database.sqlite' : 'panel');
|
||||
|
||||
if ($state === 'sqlite') {
|
||||
$set('env_database.DB_HOST', null);
|
||||
$set('env_database.DB_PORT', null);
|
||||
$set('env_database.DB_USERNAME', null);
|
||||
$set('env_database.DB_PASSWORD', null);
|
||||
} else {
|
||||
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
|
||||
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '3306');
|
||||
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
|
||||
}
|
||||
}),
|
||||
TextInput::make('env_database.DB_DATABASE')
|
||||
->label(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
|
||||
->placeholder(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
|
||||
->required()
|
||||
->default('database.sqlite'),
|
||||
TextInput::make('env_database.DB_HOST')
|
||||
->label('Database Host')
|
||||
->placeholder('127.0.0.1')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The host of your database. Make sure it is reachable.')
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_PORT')
|
||||
->label('Database Port')
|
||||
->placeholder('3306')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The port of your database.')
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_USERNAME')
|
||||
->label('Database Username')
|
||||
->placeholder('pelican')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The name of your database user.')
|
||||
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
TextInput::make('env_database.DB_PASSWORD')
|
||||
->label('Database Password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The password of your database user. Can be empty.')
|
||||
->password()
|
||||
->revealable()
|
||||
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
|
||||
])
|
||||
->afterValidation(function (Get $get) use ($installer) {
|
||||
$driver = $get('env_database.DB_CONNECTION');
|
||||
|
||||
if (!self::testConnection($driver, $get('env_database.DB_HOST'), $get('env_database.DB_PORT'), $get('env_database.DB_DATABASE'), $get('env_database.DB_USERNAME'), $get('env_database.DB_PASSWORD'))) {
|
||||
throw new Halt('Database connection failed');
|
||||
}
|
||||
|
||||
$installer->writeToEnv('env_database');
|
||||
|
||||
$installer->runMigrations($driver);
|
||||
});
|
||||
}
|
||||
|
||||
private static function testConnection(string $driver, ?string $host, null|string|int $port, ?string $database, ?string $username, ?string $password): bool
|
||||
{
|
||||
if ($driver === 'sqlite') {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
config()->set('database.connections._panel_install_test', [
|
||||
'driver' => $driver,
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'database' => $database,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
DB::connection('_panel_install_test')->getPdo();
|
||||
} catch (Exception $exception) {
|
||||
DB::disconnect('_panel_install_test');
|
||||
|
||||
Notification::make()
|
||||
->title('Database connection failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
52
app/Filament/Pages/Installer/Steps/EnvironmentStep.php
Normal file
52
app/Filament/Pages/Installer/Steps/EnvironmentStep.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
|
||||
class EnvironmentStep
|
||||
{
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('environment')
|
||||
->label('Environment')
|
||||
->columns()
|
||||
->schema([
|
||||
TextInput::make('env_general.APP_NAME')
|
||||
->label('App Name')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('This will be the Name of your Panel.')
|
||||
->required()
|
||||
->default(config('app.name')),
|
||||
TextInput::make('env_general.APP_URL')
|
||||
->label('App URL')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('This will be the URL you access your Panel from.')
|
||||
->required()
|
||||
->default(url('')),
|
||||
Fieldset::make('adminuser')
|
||||
->label('Admin User')
|
||||
->columns(3)
|
||||
->schema([
|
||||
TextInput::make('user.email')
|
||||
->label('E-Mail')
|
||||
->required()
|
||||
->email()
|
||||
->placeholder('admin@example.com'),
|
||||
TextInput::make('user.username')
|
||||
->label('Username')
|
||||
->required()
|
||||
->placeholder('admin'),
|
||||
TextInput::make('user.password')
|
||||
->label('Password')
|
||||
->required()
|
||||
->password()
|
||||
->revealable(),
|
||||
]),
|
||||
])
|
||||
->afterValidation(fn () => $installer->writeToEnv('env_general'));
|
||||
}
|
||||
}
|
||||
64
app/Filament/Pages/Installer/Steps/QueueStep.php
Normal file
64
app/Filament/Pages/Installer/Steps/QueueStep.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class QueueStep
|
||||
{
|
||||
public const QUEUE_DRIVERS = [
|
||||
'database' => 'Database',
|
||||
'redis' => 'Redis',
|
||||
'sync' => 'Sync',
|
||||
];
|
||||
|
||||
public static function make(PanelInstaller $installer): Step
|
||||
{
|
||||
return Step::make('queue')
|
||||
->label('Queue')
|
||||
->columns()
|
||||
->schema([
|
||||
ToggleButtons::make('env_queue.QUEUE_CONNECTION')
|
||||
->label('Queue Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::QUEUE_DRIVERS)
|
||||
->disableOptionWhen(fn ($value, Get $get) => $value === 'redis' && $get('env_cache.CACHE_STORE') !== 'redis')
|
||||
->default(config('queue.default')),
|
||||
Toggle::make('done')
|
||||
->label('I have done both steps below.')
|
||||
->accepted(fn () => !file_exists('/.dockerenv'))
|
||||
->inline(false)
|
||||
->validationMessages([
|
||||
'accepted' => 'You need to do both steps before continuing!',
|
||||
])
|
||||
->hidden(fn () => file_exists('/.dockerenv')),
|
||||
TextInput::make('crontab')
|
||||
->label(new HtmlString('Run the following command to set up your crontab. Note that <code>www-data</code> is your webserver user. On some systems this username might be different!'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -')
|
||||
->hidden(fn () => file_exists('/.dockerenv'))
|
||||
->columnSpanFull(),
|
||||
TextInput::make('queueService')
|
||||
->label(new HtmlString('To setup the queue worker service you simply have to run the following command.'))
|
||||
->disabled()
|
||||
->hintAction(CopyAction::make())
|
||||
->default('sudo php ' . base_path() . '/artisan p:environment:queue-service')
|
||||
->hidden(fn () => file_exists('/.dockerenv'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->afterValidation(function () use ($installer) {
|
||||
$installer->writeToEnv('env_queue');
|
||||
});
|
||||
}
|
||||
}
|
||||
89
app/Filament/Pages/Installer/Steps/RequirementsStep.php
Normal file
89
app/Filament/Pages/Installer/Steps/RequirementsStep.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
|
||||
class RequirementsStep
|
||||
{
|
||||
public const MIN_PHP_VERSION = '8.2';
|
||||
|
||||
public static function make(): Step
|
||||
{
|
||||
$correctPhpVersion = version_compare(PHP_VERSION, self::MIN_PHP_VERSION) >= 0;
|
||||
|
||||
$fields = [
|
||||
Section::make('PHP Version')
|
||||
->description(self::MIN_PHP_VERSION . ' or newer')
|
||||
->icon($correctPhpVersion ? 'tabler-check' : 'tabler-x')
|
||||
->iconColor($correctPhpVersion ? 'success' : 'danger')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content('Your PHP Version is ' . PHP_VERSION . '.'),
|
||||
]),
|
||||
];
|
||||
|
||||
$phpExtensions = [
|
||||
'BCMath' => extension_loaded('bcmath'),
|
||||
'cURL' => extension_loaded('curl'),
|
||||
'GD' => extension_loaded('gd'),
|
||||
'intl' => extension_loaded('intl'),
|
||||
'mbstring' => extension_loaded('mbstring'),
|
||||
'MySQL' => extension_loaded('pdo_mysql'),
|
||||
'SQLite3' => extension_loaded('pdo_sqlite'),
|
||||
'XML' => extension_loaded('xml'),
|
||||
'Zip' => extension_loaded('zip'),
|
||||
];
|
||||
$allExtensionsInstalled = !in_array(false, $phpExtensions);
|
||||
|
||||
$fields[] = Section::make('PHP Extensions')
|
||||
->description(implode(', ', array_keys($phpExtensions)))
|
||||
->icon($allExtensionsInstalled ? 'tabler-check' : 'tabler-x')
|
||||
->iconColor($allExtensionsInstalled ? 'success' : 'danger')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content('All needed PHP Extensions are installed.')
|
||||
->visible($allExtensionsInstalled),
|
||||
Placeholder::make('')
|
||||
->content('The following PHP Extensions are missing: ' . implode(', ', array_keys($phpExtensions, false)))
|
||||
->visible(!$allExtensionsInstalled),
|
||||
]);
|
||||
|
||||
$folderPermissions = [
|
||||
'Storage' => substr(sprintf('%o', fileperms(base_path('storage/'))), -4) >= 755,
|
||||
'Cache' => substr(sprintf('%o', fileperms(base_path('bootstrap/cache/'))), -4) >= 755,
|
||||
];
|
||||
$correctFolderPermissions = !in_array(false, $folderPermissions);
|
||||
|
||||
$fields[] = Section::make('Folder Permissions')
|
||||
->description(implode(', ', array_keys($folderPermissions)))
|
||||
->icon($correctFolderPermissions ? 'tabler-check' : 'tabler-x')
|
||||
->iconColor($correctFolderPermissions ? 'success' : 'danger')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content('All Folders have the correct permissions.')
|
||||
->visible($correctFolderPermissions),
|
||||
Placeholder::make('')
|
||||
->content('The following Folders have wrong permissions: ' . implode(', ', array_keys($folderPermissions, false)))
|
||||
->visible(!$correctFolderPermissions),
|
||||
]);
|
||||
|
||||
return Step::make('requirements')
|
||||
->label('Server Requirements')
|
||||
->schema($fields)
|
||||
->afterValidation(function () use ($correctPhpVersion, $allExtensionsInstalled, $correctFolderPermissions) {
|
||||
if (!$correctPhpVersion || !$allExtensionsInstalled || !$correctFolderPermissions) {
|
||||
Notification::make()
|
||||
->title('Some requirements are missing!')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
throw new Halt('Some requirements are missing');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
38
app/Filament/Pages/Installer/Steps/SessionStep.php
Normal file
38
app/Filament/Pages/Installer/Steps/SessionStep.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
|
||||
class SessionStep
|
||||
{
|
||||
public const SESSION_DRIVERS = [
|
||||
'file' => 'Filesystem',
|
||||
'database' => 'Database',
|
||||
'cookie' => 'Cookie',
|
||||
'redis' => 'Redis',
|
||||
];
|
||||
|
||||
public static function make(): Step
|
||||
{
|
||||
return Step::make('session')
|
||||
->label('Session')
|
||||
->schema([
|
||||
ToggleButtons::make('env_session.SESSION_DRIVER')
|
||||
->label('Session Driver')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".')
|
||||
->required()
|
||||
->inline()
|
||||
->options(self::SESSION_DRIVERS)
|
||||
->disableOptionWhen(fn ($value, Get $get) => $value === 'redis' && $get('env_cache.CACHE_STORE') !== 'redis')
|
||||
->default(config('session.driver')),
|
||||
TextInput::make('env_session.SESSION_SECURE_COOKIE')
|
||||
->hidden()
|
||||
->default(request()->isSecure()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
631
app/Filament/Pages/Settings.php
Normal file
631
app/Filament/Pages/Settings.php
Normal file
@@ -0,0 +1,631 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Actions\Action as FormAction;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Concerns\HasUnsavedDataChangesAlert;
|
||||
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||
use Filament\Pages\Page;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
/**
|
||||
* @property Form $form
|
||||
*/
|
||||
class Settings extends Page implements HasForms
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use HasUnsavedDataChangesAlert;
|
||||
use InteractsWithForms;
|
||||
use InteractsWithHeaderActions;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-settings';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static string $view = 'filament.pages.settings';
|
||||
|
||||
public ?array $data = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->form->fill();
|
||||
}
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->can('view settings');
|
||||
}
|
||||
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
Tabs::make('Tabs')
|
||||
->columns()
|
||||
->persistTabInQueryString()
|
||||
->disabled(fn () => !auth()->user()->can('update settings'))
|
||||
->tabs([
|
||||
Tab::make('general')
|
||||
->label('General')
|
||||
->icon('tabler-home')
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('captcha')
|
||||
->label('Captcha')
|
||||
->icon('tabler-shield')
|
||||
->schema($this->captchaSettings())
|
||||
->columns(3),
|
||||
Tab::make('mail')
|
||||
->label('Mail')
|
||||
->icon('tabler-mail')
|
||||
->schema($this->mailSettings()),
|
||||
Tab::make('backup')
|
||||
->label('Backup')
|
||||
->icon('tabler-box')
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('misc')
|
||||
->label('Misc')
|
||||
->icon('tabler-tool')
|
||||
->schema($this->miscSettings()),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function generalSettings(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('APP_NAME')
|
||||
->label('App Name')
|
||||
->required()
|
||||
->default(env('APP_NAME', 'Pelican')),
|
||||
TextInput::make('APP_FAVICON')
|
||||
->label('App Favicon')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('Favicons should be placed in the public folder, located in the root panel directory.')
|
||||
->required()
|
||||
->default(env('APP_FAVICON', '/pelican.ico')),
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label('Enable Debug Mode?')
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
|
||||
->default(env('APP_DEBUG', config('app.debug'))),
|
||||
ToggleButtons::make('FILAMENT_TOP_NAVIGATION')
|
||||
->label('Navigation')
|
||||
->inline()
|
||||
->options([
|
||||
false => 'Sidebar',
|
||||
true => 'Topbar',
|
||||
])
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
||||
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
||||
ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
|
||||
->label('Unit prefix')
|
||||
->inline()
|
||||
->options([
|
||||
false => 'Decimal Prefix (MB/ GB)',
|
||||
true => 'Binary Prefix (MiB/ GiB)',
|
||||
])
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_USE_BINARY_PREFIX', (bool) $state))
|
||||
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
|
||||
ToggleButtons::make('APP_2FA_REQUIRED')
|
||||
->label('2FA Requirement')
|
||||
->inline()
|
||||
->options([
|
||||
0 => 'Not required',
|
||||
1 => 'Required for only Admins',
|
||||
2 => 'Required for all Users',
|
||||
])
|
||||
->formatStateUsing(fn ($state): int => (int) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_2FA_REQUIRED', (int) $state))
|
||||
->default(env('APP_2FA_REQUIRED', config('panel.auth.2fa_required'))),
|
||||
TagsInput::make('TRUSTED_PROXIES')
|
||||
->label('Trusted Proxies')
|
||||
->separator()
|
||||
->splitKeys(['Tab', ' '])
|
||||
->placeholder('New IP or IP Range')
|
||||
->default(env('TRUSTED_PROXIES', implode(',', config('trustedproxy.proxies'))))
|
||||
->hintActions([
|
||||
FormAction::make('clear')
|
||||
->label('Clear')
|
||||
->color('danger')
|
||||
->icon('tabler-trash')
|
||||
->requiresConfirmation()
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||
FormAction::make('cloudflare')
|
||||
->label('Set to Cloudflare IPs')
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->action(function (Client $client, Set $set) {
|
||||
$ips = collect();
|
||||
try {
|
||||
$response = $client->request(
|
||||
'GET',
|
||||
'https://api.cloudflare.com/client/v4/ips',
|
||||
config('panel.guzzle')
|
||||
);
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$result = json_decode($response->getBody(), true)['result'];
|
||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||
$ips->push(...data_get($result, $value));
|
||||
}
|
||||
$ips->unique();
|
||||
}
|
||||
} catch (GuzzleException $e) {
|
||||
}
|
||||
|
||||
$set('TRUSTED_PROXIES', $ips->values()->all());
|
||||
}),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function captchaSettings(): array
|
||||
{
|
||||
return [
|
||||
Toggle::make('TURNSTILE_ENABLED')
|
||||
->label('Enable Turnstile Captcha?')
|
||||
->inline(false)
|
||||
->columnSpan(1)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_ENABLED', (bool) $state))
|
||||
->default(env('TURNSTILE_ENABLED', config('turnstile.turnstile_enabled'))),
|
||||
Placeholder::make('info')
|
||||
->columnSpan(2)
|
||||
->content(new HtmlString('<p>You can generate the keys on your <u><a href="https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key" target="_blank">Cloudflare Dashboard</a></u>. A Cloudflare account is required.</p>')),
|
||||
TextInput::make('TURNSTILE_SITE_KEY')
|
||||
->label('Site Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->default(env('TURNSTILE_SITE_KEY', config('turnstile.turnstile_site_key')))
|
||||
->placeholder('1x00000000000000000000AA'),
|
||||
TextInput::make('TURNSTILE_SECRET_KEY')
|
||||
->label('Secret Key')
|
||||
->required()
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->default(env('TURNSTILE_SECRET_KEY', config('turnstile.secret_key')))
|
||||
->placeholder('1x0000000000000000000000000000000AA'),
|
||||
Toggle::make('TURNSTILE_VERIFY_DOMAIN')
|
||||
->label('Verify domain?')
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_VERIFY_DOMAIN', (bool) $state))
|
||||
->default(env('TURNSTILE_VERIFY_DOMAIN', config('turnstile.turnstile_verify_domain'))),
|
||||
];
|
||||
}
|
||||
|
||||
private function mailSettings(): array
|
||||
{
|
||||
return [
|
||||
ToggleButtons::make('MAIL_MAILER')
|
||||
->label('Mail Driver')
|
||||
->columnSpanFull()
|
||||
->inline()
|
||||
->options([
|
||||
'log' => 'Print mails to Log',
|
||||
'smtp' => 'SMTP Server',
|
||||
'sendmail' => 'sendmail Binary',
|
||||
'mailgun' => 'Mailgun',
|
||||
'mandrill' => 'Mandrill',
|
||||
'postmark' => 'Postmark',
|
||||
])
|
||||
->live()
|
||||
->default(env('MAIL_MAILER', config('mail.default')))
|
||||
->hintAction(
|
||||
FormAction::make('test')
|
||||
->label('Send Test Mail')
|
||||
->icon('tabler-send')
|
||||
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->action(function () {
|
||||
try {
|
||||
MailNotification::route('mail', auth()->user()->email)
|
||||
->notify(new MailTested(auth()->user()));
|
||||
|
||||
Notification::make()
|
||||
->title('Test Mail sent')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Test Mail failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
})
|
||||
),
|
||||
Section::make('"From" Settings')
|
||||
->description('Set the Address and Name used as "From" in mails.')
|
||||
->columns()
|
||||
->schema([
|
||||
TextInput::make('MAIL_FROM_ADDRESS')
|
||||
->label('From Address')
|
||||
->required()
|
||||
->email()
|
||||
->default(env('MAIL_FROM_ADDRESS', config('mail.from.address'))),
|
||||
TextInput::make('MAIL_FROM_NAME')
|
||||
->label('From Name')
|
||||
->required()
|
||||
->default(env('MAIL_FROM_NAME', config('mail.from.name'))),
|
||||
]),
|
||||
Section::make('SMTP Configuration')
|
||||
->columns()
|
||||
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp')
|
||||
->schema([
|
||||
TextInput::make('MAIL_HOST')
|
||||
->label('Host')
|
||||
->required()
|
||||
->default(env('MAIL_HOST', config('mail.mailers.smtp.host'))),
|
||||
TextInput::make('MAIL_PORT')
|
||||
->label('Port')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(env('MAIL_PORT', config('mail.mailers.smtp.port'))),
|
||||
TextInput::make('MAIL_USERNAME')
|
||||
->label('Username')
|
||||
->default(env('MAIL_USERNAME', config('mail.mailers.smtp.username'))),
|
||||
TextInput::make('MAIL_PASSWORD')
|
||||
->label('Password')
|
||||
->password()
|
||||
->revealable()
|
||||
->default(env('MAIL_PASSWORD')),
|
||||
ToggleButtons::make('MAIL_ENCRYPTION')
|
||||
->label('Encryption')
|
||||
->inline()
|
||||
->options(['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'])
|
||||
->default(env('MAIL_ENCRYPTION', config('mail.mailers.smtp.encryption', 'tls'))),
|
||||
]),
|
||||
Section::make('Mailgun Configuration')
|
||||
->columns()
|
||||
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun')
|
||||
->schema([
|
||||
TextInput::make('MAILGUN_DOMAIN')
|
||||
->label('Domain')
|
||||
->required()
|
||||
->default(env('MAILGUN_DOMAIN', config('services.mailgun.domain'))),
|
||||
TextInput::make('MAILGUN_SECRET')
|
||||
->label('Secret')
|
||||
->required()
|
||||
->default(env('MAILGUN_SECRET', config('services.mailgun.secret'))),
|
||||
TextInput::make('MAILGUN_ENDPOINT')
|
||||
->label('Endpoint')
|
||||
->required()
|
||||
->default(env('MAILGUN_ENDPOINT', config('services.mailgun.endpoint'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function backupSettings(): array
|
||||
{
|
||||
return [
|
||||
ToggleButtons::make('APP_BACKUP_DRIVER')
|
||||
->label('Backup Driver')
|
||||
->columnSpanFull()
|
||||
->inline()
|
||||
->options([
|
||||
Backup::ADAPTER_DAEMON => 'Wings',
|
||||
Backup::ADAPTER_AWS_S3 => 'S3',
|
||||
])
|
||||
->live()
|
||||
->default(env('APP_BACKUP_DRIVER', config('backups.default'))),
|
||||
Section::make('Throttles')
|
||||
->description('Configure how many backups can be created in a period. Set period to 0 to disable this throttle.')
|
||||
->columns()
|
||||
->schema([
|
||||
TextInput::make('BACKUP_THROTTLE_LIMIT')
|
||||
->label('Limit')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->default(config('backups.throttles.limit')),
|
||||
TextInput::make('BACKUP_THROTTLE_PERIOD')
|
||||
->label('Period')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->suffix('Seconds')
|
||||
->default(config('backups.throttles.period')),
|
||||
]),
|
||||
Section::make('S3 Configuration')
|
||||
->columns()
|
||||
->visible(fn (Get $get) => $get('APP_BACKUP_DRIVER') === Backup::ADAPTER_AWS_S3)
|
||||
->schema([
|
||||
TextInput::make('AWS_DEFAULT_REGION')
|
||||
->label('Default Region')
|
||||
->required()
|
||||
->default(config('backups.disks.s3.region')),
|
||||
TextInput::make('AWS_ACCESS_KEY_ID')
|
||||
->label('Access Key ID')
|
||||
->required()
|
||||
->default(config('backups.disks.s3.key')),
|
||||
TextInput::make('AWS_SECRET_ACCESS_KEY')
|
||||
->label('Secret Access Key')
|
||||
->required()
|
||||
->default(config('backups.disks.s3.secret')),
|
||||
TextInput::make('AWS_BACKUPS_BUCKET')
|
||||
->label('Bucket')
|
||||
->required()
|
||||
->default(config('backups.disks.s3.bucket')),
|
||||
TextInput::make('AWS_ENDPOINT')
|
||||
->label('Endpoint')
|
||||
->required()
|
||||
->default(config('backups.disks.s3.endpoint')),
|
||||
Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT')
|
||||
->label('Use path style endpoint?')
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('AWS_USE_PATH_STYLE_ENDPOINT', (bool) $state))
|
||||
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function miscSettings(): array
|
||||
{
|
||||
return [
|
||||
Section::make('Automatic Allocation Creation')
|
||||
->description('Toggle if Users can create allocations via the client area.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED')
|
||||
->label('Allow Users to create allocations?')
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_CLIENT_ALLOCATIONS_ENABLED', (bool) $state))
|
||||
->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))),
|
||||
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START')
|
||||
->label('Starting Port')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1024)
|
||||
->maxValue(65535)
|
||||
->visible(fn (Get $get) => $get('PANEL_CLIENT_ALLOCATIONS_ENABLED'))
|
||||
->default(env('PANEL_CLIENT_ALLOCATIONS_RANGE_START')),
|
||||
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_END')
|
||||
->label('Ending Port')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1024)
|
||||
->maxValue(65535)
|
||||
->visible(fn (Get $get) => $get('PANEL_CLIENT_ALLOCATIONS_ENABLED'))
|
||||
->default(env('PANEL_CLIENT_ALLOCATIONS_RANGE_END')),
|
||||
]),
|
||||
Section::make('Mail Notifications')
|
||||
->description('Toggle which mail notifications should be sent to Users.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION')
|
||||
->label('Server Installed')
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_INSTALL_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
||||
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
||||
->label('Server Reinstalled')
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))),
|
||||
]),
|
||||
Section::make('Connections')
|
||||
->description('Timeouts used when making requests.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('GUZZLE_TIMEOUT')
|
||||
->label('Request Timeout')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(15)
|
||||
->maxValue(60)
|
||||
->suffix('Seconds')
|
||||
->default(env('GUZZLE_TIMEOUT', config('panel.guzzle.timeout'))),
|
||||
TextInput::make('GUZZLE_CONNECT_TIMEOUT')
|
||||
->label('Connect Timeout')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(5)
|
||||
->maxValue(60)
|
||||
->suffix('Seconds')
|
||||
->default(env('GUZZLE_CONNECT_TIMEOUT', config('panel.guzzle.connect_timeout'))),
|
||||
]),
|
||||
Section::make('Activity Logs')
|
||||
->description('Configure how often old activity logs should be pruned and whether admin activities should be logged.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('APP_ACTIVITY_PRUNE_DAYS')
|
||||
->label('Prune age')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(365)
|
||||
->suffix('Days')
|
||||
->default(env('APP_ACTIVITY_PRUNE_DAYS', config('activity.prune_days'))),
|
||||
Toggle::make('APP_ACTIVITY_HIDE_ADMIN')
|
||||
->label('Hide admin activities?')
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_ACTIVITY_HIDE_ADMIN', (bool) $state))
|
||||
->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))),
|
||||
]),
|
||||
Section::make('API')
|
||||
->description('Defines the rate limit for the number of requests per minute that can be executed.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('APP_API_CLIENT_RATELIMIT')
|
||||
->label('Client API Rate Limit')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix('Requests Per Minute')
|
||||
->default(env('APP_API_CLIENT_RATELIMIT', config('http.rate_limit.client'))),
|
||||
TextInput::make('APP_API_APPLICATION_RATELIMIT')
|
||||
->label('Application API Rate Limit')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix('Requests Per Minute')
|
||||
->default(env('APP_API_APPLICATION_RATELIMIT', config('http.rate_limit.application'))),
|
||||
]),
|
||||
Section::make('Server')
|
||||
->description('Settings for Servers.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
Toggle::make('PANEL_EDITABLE_SERVER_DESCRIPTIONS')
|
||||
->label('Allow Users to edit Server Descriptions?')
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_EDITABLE_SERVER_DESCRIPTIONS', (bool) $state))
|
||||
->default(env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', config('panel.editable_server_descriptions'))),
|
||||
]),
|
||||
Section::make('Webhook')
|
||||
->description('Configure how often old webhook logs should be pruned.')
|
||||
->columns()
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('APP_WEBHOOK_PRUNE_DAYS')
|
||||
->label('Prune age')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(365)
|
||||
->suffix('Days')
|
||||
->default(env('APP_WEBHOOK_PRUNE_DAYS', config('panel.webhook.prune_days'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormStatePath(): ?string
|
||||
{
|
||||
return 'data';
|
||||
}
|
||||
|
||||
protected function hasUnsavedDataChangesAlert(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
$data = $this->form->getState();
|
||||
|
||||
// Convert bools to a string, so they are correctly written to the .env file
|
||||
$data = array_map(fn ($value) => is_bool($value) ? ($value ? 'true' : 'false') : $value, $data);
|
||||
|
||||
$this->writeToEnvironment($data);
|
||||
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('queue:restart');
|
||||
|
||||
$this->rememberData();
|
||||
|
||||
$this->redirect($this->getUrl());
|
||||
|
||||
Notification::make()
|
||||
->title('Settings saved')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Save failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('save')
|
||||
->action('save')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->keyBindings(['mod+s']),
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
37
app/Filament/Resources/ApiKeyResource.php
Normal file
37
app/Filament/Resources/ApiKeyResource.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ApiKeyResource\Pages;
|
||||
use App\Models\ApiKey;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ApiKeyResource extends Resource
|
||||
{
|
||||
protected static ?string $model = ApiKey::class;
|
||||
|
||||
protected static ?string $label = 'API Key';
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-key';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::where('key_type', ApiKey::TYPE_APPLICATION)->count() ?: null;
|
||||
}
|
||||
|
||||
public static function canEdit(Model $record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListApiKeys::route('/'),
|
||||
'create' => Pages\CreateApiKey::route('/create'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user