mirror of
https://github.com/pelican-dev/panel.git
synced 2025-12-09 01:00:15 +01:00
Compare commits
577 Commits
v1.0.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a95712ed0 | ||
|
|
b6aeb954c4 | ||
|
|
7c0d53c796 | ||
|
|
71bd267166 | ||
|
|
25d8adbcc6 | ||
|
|
27b896c6d2 | ||
|
|
bda2f9a699 | ||
|
|
04375439d7 | ||
|
|
0fe8917668 | ||
|
|
c312ef493f | ||
|
|
6c02f9a663 | ||
|
|
2dd6e3d4fc | ||
|
|
575e5bdb0d | ||
|
|
efa8eef57c | ||
|
|
d16e7dd876 | ||
|
|
897b95ec13 | ||
|
|
97f5a0f20b | ||
|
|
d0af45a0c7 | ||
|
|
78ab098d02 | ||
|
|
cdccca8fa2 | ||
|
|
bb33bcca4f | ||
|
|
611b8649e0 | ||
|
|
b1b723485f | ||
|
|
25c8ff3f1f | ||
|
|
07763d912b | ||
|
|
65bb99e2b0 | ||
|
|
a195b56f93 | ||
|
|
d78c977d75 | ||
|
|
5e25ea4a43 | ||
|
|
886836c60a | ||
|
|
f575e3edfa | ||
|
|
1a66b3fab4 | ||
|
|
0f1efcfd15 | ||
|
|
3f89c6ddd8 | ||
|
|
20cb7850ef | ||
|
|
108dad09fb | ||
|
|
445c9364bc | ||
|
|
acec117b1e | ||
|
|
89199dfbe5 | ||
|
|
216a3484f1 | ||
|
|
5c3b0919aa | ||
|
|
f4ee33fa4f | ||
|
|
d8368c4cec | ||
|
|
aa35d7d001 | ||
|
|
3c25b43b46 | ||
|
|
0891db5342 | ||
|
|
172436e012 | ||
|
|
2b5403a4da | ||
|
|
a30c45fbbe | ||
|
|
b06df23823 | ||
|
|
1ff965611e | ||
|
|
cec141889a | ||
|
|
6ed84b5584 | ||
|
|
49f24e37b6 | ||
|
|
e0c4e47a6c | ||
|
|
4bda7cba75 | ||
|
|
852f7beb39 | ||
|
|
d61583cd7b | ||
|
|
21f9f259d0 | ||
|
|
b2aff5445b | ||
|
|
1f26750a2a | ||
|
|
6d83c6d908 | ||
|
|
574a391e73 | ||
|
|
605fcbe61a | ||
|
|
0214b127e4 | ||
|
|
e6aa76ef2c | ||
|
|
d38075e3cb | ||
|
|
0fec6adc3e | ||
|
|
5e3c22ea5e | ||
|
|
d1a808a746 | ||
|
|
3bcdeea800 | ||
|
|
e6bd6e416f | ||
|
|
8e006ac32d | ||
|
|
430f28a847 | ||
|
|
1a4fa5e67a | ||
|
|
a65469b33b | ||
|
|
d587cf3ee5 | ||
|
|
2cd9fa2cde | ||
|
|
d735e858a2 | ||
|
|
317fa46894 | ||
|
|
e589f972fb | ||
|
|
266e3779d5 | ||
|
|
4652680a7b | ||
|
|
e99f7179c6 | ||
|
|
1f56b8e114 | ||
|
|
574e03a986 | ||
|
|
05f3422dda | ||
|
|
dbe4bdd62d | ||
|
|
f6710dbbe4 | ||
|
|
e4f807b297 | ||
|
|
cd965678b7 | ||
|
|
a58ae874f3 | ||
|
|
432fb8a514 | ||
|
|
bb02ec4c6c | ||
|
|
69b669e345 | ||
|
|
80993f38a9 | ||
|
|
19103b16b8 | ||
|
|
246997754e | ||
|
|
df75dbe2ad | ||
|
|
f02b58c320 | ||
|
|
8aa0fc7fc2 | ||
|
|
2fc30e14fd | ||
|
|
ec5fd3262a | ||
|
|
81178f81b4 | ||
|
|
5373f1e30a | ||
|
|
9f35f1c3ee | ||
|
|
a5858a6d9b | ||
|
|
e3b3c92dcb | ||
|
|
42c84c2df5 | ||
|
|
4792542f20 | ||
|
|
bb40a5273f | ||
|
|
e5c24fe8b6 | ||
|
|
c10280af4b | ||
|
|
6db1d82738 | ||
|
|
68f8244298 | ||
|
|
ce393af7a6 | ||
|
|
932809fec5 | ||
|
|
3d2390dbcc | ||
|
|
d5d50d4150 | ||
|
|
cba8717188 | ||
|
|
df4543a079 | ||
|
|
8dc99e6390 | ||
|
|
8f1ec20e96 | ||
|
|
61dcb9a3ba | ||
|
|
0e34886d7e | ||
|
|
806820592f | ||
|
|
1900c04b71 | ||
|
|
32eb1abd4a | ||
|
|
47557021fd | ||
|
|
2ef81eae1a | ||
|
|
420730ba1f | ||
|
|
925ab26fb4 | ||
|
|
2952e22619 | ||
|
|
079eaed010 | ||
|
|
6671d45651 | ||
|
|
3543b4773a | ||
|
|
02f788a659 | ||
|
|
7ace3978d8 | ||
|
|
8f277aaca0 | ||
|
|
76451fa0ad | ||
|
|
0104a08ba4 | ||
|
|
5eff006843 | ||
|
|
a8241bf9f3 | ||
|
|
4aae2562ea | ||
|
|
42db5b328a | ||
|
|
bc4dfb3e92 | ||
|
|
3b9c81534f | ||
|
|
f31aa78f6f | ||
|
|
b5ebd544f4 | ||
|
|
c77a37ec89 | ||
|
|
4d78e5dcd1 | ||
|
|
15075b6ab8 | ||
|
|
a8f233e204 | ||
|
|
795cad43b9 | ||
|
|
46934d7a85 | ||
|
|
06067f375c | ||
|
|
d1df53c683 | ||
|
|
b03d2cf919 | ||
|
|
27a8423f55 | ||
|
|
ad70934430 | ||
|
|
900f8d0fe1 | ||
|
|
6a4ac515a7 | ||
|
|
7c315ac995 | ||
|
|
49e9440e0f | ||
|
|
02e3e43f1e | ||
|
|
8eddef6f04 | ||
|
|
d2f1936bbf | ||
|
|
36863f94c0 | ||
|
|
75863c50d1 | ||
|
|
ec0727b406 | ||
|
|
5b2e9d94ca | ||
|
|
8840d109ef | ||
|
|
71225bd2dc | ||
|
|
bab8ec6e18 | ||
|
|
d307a2095b | ||
|
|
a777f4e0ff | ||
|
|
86a71afc6c | ||
|
|
88943563c7 | ||
|
|
20071a64fa | ||
|
|
d0d3418e03 | ||
|
|
083e3dc62a | ||
|
|
d7e60f2456 | ||
|
|
38e746240d | ||
|
|
986063dce4 | ||
|
|
71d0326cb2 | ||
|
|
62ca53eeaf | ||
|
|
9f2305f351 | ||
|
|
340d1b543c | ||
|
|
61098b11f2 | ||
|
|
4d03d6b948 | ||
|
|
1f67054777 | ||
|
|
4a9814f16c | ||
|
|
e0697d3288 | ||
|
|
d165da20ec | ||
|
|
ae27b179fe | ||
|
|
1113ffe0f7 | ||
|
|
5531bc0ba1 | ||
|
|
a3819122db | ||
|
|
c5528a61f3 | ||
|
|
5a7c6ac6e5 | ||
|
|
5e8cccef19 | ||
|
|
0ccb248d91 | ||
|
|
514d961c24 | ||
|
|
f8e802afcd | ||
|
|
556551b4f3 | ||
|
|
23ddded61e | ||
|
|
c5aa8a3980 | ||
|
|
21ac75efae | ||
|
|
9655700cde | ||
|
|
c9b7e979c0 | ||
|
|
77a3b0640d | ||
|
|
de4cb38766 | ||
|
|
74bd7f9991 | ||
|
|
ba7f814300 | ||
|
|
cdcd1c521e | ||
|
|
4d0aabe91e | ||
|
|
68f72b9b4d | ||
|
|
dca37ccc95 | ||
|
|
6a088d0c4f | ||
|
|
7731f16b0f | ||
|
|
9a1e7de4ae | ||
|
|
c61b6920b9 | ||
|
|
6107524522 | ||
|
|
57a13a2701 | ||
|
|
4dd414ad87 | ||
|
|
0156ac1509 | ||
|
|
387471716b | ||
|
|
1dc5ec027e | ||
|
|
b05eabfdb0 | ||
|
|
3039c1c698 | ||
|
|
de166bca03 | ||
|
|
af609994b6 | ||
|
|
bd2a00760d | ||
|
|
65deffc6e6 | ||
|
|
34865d4288 | ||
|
|
2961c3e88b | ||
|
|
e7a950ffcb | ||
|
|
ece732d9e5 | ||
|
|
456c4f46bc | ||
|
|
0ba497a2eb | ||
|
|
3b744f37dd | ||
|
|
b34778f736 | ||
|
|
84c351d0ae | ||
|
|
520cea7f09 | ||
|
|
35ce1d34ab | ||
|
|
17555a1d09 | ||
|
|
837121b1fb | ||
|
|
af9f2c653e | ||
|
|
c22e7456b5 | ||
|
|
97fb66f5d6 | ||
|
|
51037c5c20 | ||
|
|
23d13d9e83 | ||
|
|
6c20426757 | ||
|
|
1224210668 | ||
|
|
258c97bf14 | ||
|
|
7034c4d013 | ||
|
|
e5cba893e4 | ||
|
|
fd49f472c3 | ||
|
|
c8556a4c56 | ||
|
|
6de6306a19 | ||
|
|
1f8a5cdd1d | ||
|
|
30ae860d69 | ||
|
|
f400e2db76 | ||
|
|
1f7562563a | ||
|
|
2296e41a8b | ||
|
|
7971dc13fc | ||
|
|
8406f4686c | ||
|
|
67705b14b4 | ||
|
|
bc115af5fd | ||
|
|
da35703f75 | ||
|
|
c54bfd714b | ||
|
|
b83e3657d6 | ||
|
|
e2c87a8206 | ||
|
|
e38a736b61 | ||
|
|
26e20453bf | ||
|
|
292523d153 | ||
|
|
85d625d118 | ||
|
|
c8230771ec | ||
|
|
79691ba663 | ||
|
|
a6326f64fb | ||
|
|
03745eb4be | ||
|
|
c0fda71e20 | ||
|
|
f2f1026a97 | ||
|
|
e1eaf805ea | ||
|
|
03ec20e3a0 | ||
|
|
a5ffff8c8c | ||
|
|
82ef6c1408 | ||
|
|
2d581c7cbd | ||
|
|
7f0266be5e | ||
|
|
1ae9490b8f | ||
|
|
a53b3fda10 | ||
|
|
e9ddf80d10 | ||
|
|
3f1e99f1df | ||
|
|
435c615ff1 | ||
|
|
3effd98013 | ||
|
|
e354bc9be7 | ||
|
|
14d351103c | ||
|
|
92c23451af | ||
|
|
2046fa453a | ||
|
|
b39a8186ae | ||
|
|
8ae3c88c91 | ||
|
|
329a29f7da | ||
|
|
98a2cab5ca | ||
|
|
8407547574 | ||
|
|
fccd7e5e75 | ||
|
|
c0225b9e10 | ||
|
|
544aaab960 | ||
|
|
914e215bc0 | ||
|
|
90fd73f6a4 | ||
|
|
0037b4a1d4 | ||
|
|
3deada57c6 | ||
|
|
6427903f9f | ||
|
|
b16e19b4fb | ||
|
|
7e99d5cd8e | ||
|
|
05b1a44a34 | ||
|
|
058b613c98 | ||
|
|
0e2ab4b711 | ||
|
|
ee838316e6 | ||
|
|
ffd94b8892 | ||
|
|
a186900262 | ||
|
|
bf14755287 | ||
|
|
038504fbec | ||
|
|
22a0a52f7b | ||
|
|
862afaa0e9 | ||
|
|
a4dd8cca4c | ||
|
|
e67e0830eb | ||
|
|
b444112085 | ||
|
|
f23d4d6971 | ||
|
|
2a3781f5a8 | ||
|
|
cb245dc722 | ||
|
|
3ffbf9e46a | ||
|
|
8221c80ec2 | ||
|
|
702a6bb750 | ||
|
|
02d7ad04ad | ||
|
|
7409f020ba | ||
|
|
98d8510f11 | ||
|
|
6c6d458445 | ||
|
|
51fda2eaf4 | ||
|
|
92fbd75772 | ||
|
|
fa8ae0aea5 | ||
|
|
377b3f170d | ||
|
|
566e7c1b24 | ||
|
|
b9d4773bd7 | ||
|
|
49638e75e5 | ||
|
|
80c404a48c | ||
|
|
befe6be80b | ||
|
|
3639d7ccec | ||
|
|
20f271041a | ||
|
|
c3b8b71f9c | ||
|
|
c73d0544d9 | ||
|
|
484a3b445a | ||
|
|
c0fa8c1cd8 | ||
|
|
e562a35057 | ||
|
|
636279c6eb | ||
|
|
ed88ce9ae3 | ||
|
|
0cce716e2c | ||
|
|
3639f0cb50 | ||
|
|
9c3f47590c | ||
|
|
630031e1c2 | ||
|
|
2c00f90ba6 | ||
|
|
875dca54f5 | ||
|
|
a03b604f2d | ||
|
|
8261184b57 | ||
|
|
bca02ced86 | ||
|
|
a768fadaea | ||
|
|
7471347b55 | ||
|
|
1457c4bd06 | ||
|
|
8b943fa160 | ||
|
|
5c5c9654b4 | ||
|
|
dd20cb0f11 | ||
|
|
88deb35dc8 | ||
|
|
0f92632c06 | ||
|
|
a85fc5c88e | ||
|
|
8d7eff13fb | ||
|
|
c39c29e50b | ||
|
|
db3b16e609 | ||
|
|
72b9c309d3 | ||
|
|
68a6dc45cb | ||
|
|
9a258efe53 | ||
|
|
3310746107 | ||
|
|
42706dba14 | ||
|
|
ec6529ac4c | ||
|
|
bced93c5be | ||
|
|
cb1c953540 | ||
|
|
c689f6860b | ||
|
|
a73404c1b4 | ||
|
|
61cbe5465f | ||
|
|
5bea1ea80a | ||
|
|
b69136d7a4 | ||
|
|
a8c3082b79 | ||
|
|
a47ad071c9 | ||
|
|
ab953b2f4d | ||
|
|
03d6c88f65 | ||
|
|
b4eab02254 | ||
|
|
23f39acd4e | ||
|
|
82b0aff105 | ||
|
|
adca50a372 | ||
|
|
c5230efad6 | ||
|
|
e5d9d53aa3 | ||
|
|
29f3defc73 | ||
|
|
2dbb9a5f9b | ||
|
|
a05e330b19 | ||
|
|
4a7951995e | ||
|
|
3d29243cf0 | ||
|
|
c52439132d | ||
|
|
517f17cbcc | ||
|
|
f8d119b458 | ||
|
|
fbeb747fc3 | ||
|
|
f563128237 | ||
|
|
f2f3ee548f | ||
|
|
0b3dce132f | ||
|
|
5bf23b972d | ||
|
|
22d02c0df5 | ||
|
|
253abf65b1 | ||
|
|
d452e3d2f2 | ||
|
|
0051370f24 | ||
|
|
4e85180b3d | ||
|
|
9f4a3b1c0d | ||
|
|
45db06a1bd | ||
|
|
3e26a1cf09 | ||
|
|
44111696df | ||
|
|
e04abcbcf9 | ||
|
|
ea5914f362 | ||
|
|
98c36c4cc3 | ||
|
|
6bc55b1039 | ||
|
|
11b153d23c | ||
|
|
998ad2ee31 | ||
|
|
7f0c7da37f | ||
|
|
e93d122a27 | ||
|
|
9aaf6b3798 | ||
|
|
fd6e7eb314 | ||
|
|
4e694b50ca | ||
|
|
3a24edfe1d | ||
|
|
0179ade557 | ||
|
|
05d74232af | ||
|
|
a2b2e373be | ||
|
|
0a17e78f33 | ||
|
|
c3a65aed07 | ||
|
|
d438e29154 | ||
|
|
1fdc428f3e | ||
|
|
a9e4495c91 | ||
|
|
98ddb65509 | ||
|
|
6caa741798 | ||
|
|
5512c10ee1 | ||
|
|
5331c5abfa | ||
|
|
36a38ab947 | ||
|
|
da195fd2fe | ||
|
|
82409f2fba | ||
|
|
839be53231 | ||
|
|
d79d461e7c | ||
|
|
d8e8240756 | ||
|
|
0b84b0c08c | ||
|
|
e2045e334f | ||
|
|
5e2d106bb9 | ||
|
|
40c138f086 | ||
|
|
ab543a399b | ||
|
|
0308045738 | ||
|
|
cd9cbf20ce | ||
|
|
e1308cb04d | ||
|
|
2d937229fb | ||
|
|
3d764a89f7 | ||
|
|
2f56ca5ed5 | ||
|
|
fe8e6fcfda | ||
|
|
1e7a901371 | ||
|
|
d53820bbdc | ||
|
|
d03366cf3d | ||
|
|
7d68da41f4 | ||
|
|
599d53b4f2 | ||
|
|
f0f04fd86a | ||
|
|
324fc4b7d5 | ||
|
|
5be4e22a0c | ||
|
|
75aae3e45b | ||
|
|
c1704eef3b | ||
|
|
09abec6ee6 | ||
|
|
206cc76a8b | ||
|
|
b355830db4 | ||
|
|
09375df8a7 | ||
|
|
96ec2eb3c2 | ||
|
|
b464bb4d25 | ||
|
|
c561035c75 | ||
|
|
48d1ef5d26 | ||
|
|
1f6b659546 | ||
|
|
8f47ccfbf7 | ||
|
|
35d25d216e | ||
|
|
a6963ad802 | ||
|
|
d48cf6b722 | ||
|
|
cba4cf11aa | ||
|
|
96c09acc52 | ||
|
|
7f697017a7 | ||
|
|
f8ad720f52 | ||
|
|
513117cc42 | ||
|
|
5797b790fd | ||
|
|
9ec2f6eae1 | ||
|
|
77bf70b063 | ||
|
|
b8c1b68328 | ||
|
|
431c1977e3 | ||
|
|
f8ad9a1805 | ||
|
|
635cc6a029 | ||
|
|
20125dbc6f | ||
|
|
d5b8a4c501 | ||
|
|
dde5305b3f | ||
|
|
e352754e6f | ||
|
|
7cde90a39a | ||
|
|
3202a59b07 | ||
|
|
71f3abe464 | ||
|
|
401026efa1 | ||
|
|
654143addc | ||
|
|
37f9725f27 | ||
|
|
98c915490d | ||
|
|
6fb54e32f1 | ||
|
|
fef19b9fdd | ||
|
|
6a4963200c | ||
|
|
37ba62410f | ||
|
|
262e2fd09a | ||
|
|
9e8b9cd599 | ||
|
|
3411e5e65c | ||
|
|
7e6769c96e | ||
|
|
03eaddb126 | ||
|
|
61bdf0dcd7 | ||
|
|
cbacc18e56 | ||
|
|
ad1a9cd33f | ||
|
|
02c4eb19f0 | ||
|
|
3a25d0f976 | ||
|
|
634b8dec55 | ||
|
|
43d0b78742 | ||
|
|
6b77e69e43 | ||
|
|
efbf4df2a2 | ||
|
|
4ec9171017 | ||
|
|
885e03ee06 | ||
|
|
7c6b3a03db | ||
|
|
fe43539ea7 | ||
|
|
e145fcdc56 | ||
|
|
8078f2ca4e | ||
|
|
d1007ad2fe | ||
|
|
7f3b1fd758 | ||
|
|
d088e79e5e | ||
|
|
9cfd87090f | ||
|
|
a7a7c5ba4d | ||
|
|
b14e8fd724 | ||
|
|
c93a836ad8 | ||
|
|
6fcf4173d3 | ||
|
|
7449b82f41 | ||
|
|
af4ac1db92 | ||
|
|
6707d1ccf6 | ||
|
|
b197e73173 | ||
|
|
e5418491c8 | ||
|
|
98ebc75965 | ||
|
|
121ebe6017 | ||
|
|
fc27b24783 | ||
|
|
8049ef462e | ||
|
|
17bb23b5b8 | ||
|
|
8926f9712f | ||
|
|
e4849d89d7 | ||
|
|
af11888b82 | ||
|
|
1845f2955f | ||
|
|
a2b315ba74 | ||
|
|
76c3632d14 | ||
|
|
4facaecea0 | ||
|
|
a55a2cce6e | ||
|
|
448fe41e78 | ||
|
|
7f37b3b099 | ||
|
|
ef54d52866 | ||
|
|
7bd66c3d85 | ||
|
|
74efc6e8c1 | ||
|
|
a7b767ae78 | ||
|
|
a3ecf3994b | ||
|
|
158fa24fff | ||
|
|
e5069e754d | ||
|
|
cdd46de274 | ||
|
|
ff5812e87b | ||
|
|
20ce0ca8e6 | ||
|
|
66ec86694f | ||
|
|
295134fb6c | ||
|
|
ae445840f7 | ||
|
|
77fd54fdc2 | ||
|
|
18fe4f1123 | ||
|
|
2525af8f02 |
@ -1,10 +1,29 @@
|
|||||||
|
**.DS_Store
|
||||||
|
.env
|
||||||
|
.devcontainer
|
||||||
|
.dockerignore
|
||||||
|
.editorconfig
|
||||||
.git
|
.git
|
||||||
node_modules
|
.github
|
||||||
vendor
|
**.gitignore
|
||||||
|
.php-cs-fixer.dist.php
|
||||||
|
.prettierrc.json
|
||||||
|
.vscode
|
||||||
|
Dockerfile
|
||||||
|
bounties.md
|
||||||
|
compose.yml
|
||||||
|
contributing.md
|
||||||
|
contributor_license_agreement.md
|
||||||
database/database.sqlite
|
database/database.sqlite
|
||||||
|
docker/README.md
|
||||||
|
node_modules
|
||||||
|
phpstan.neon
|
||||||
|
phpunit.xml
|
||||||
|
readme.md
|
||||||
storage/debugbar/*.json
|
storage/debugbar/*.json
|
||||||
storage/logs/*.log
|
|
||||||
storage/framework/cache/data/*
|
storage/framework/cache/data/*
|
||||||
storage/framework/sessions/*
|
storage/framework/sessions/*
|
||||||
storage/framework/testing
|
storage/framework/testing
|
||||||
storage/framework/views/*.php
|
storage/framework/views/*.php
|
||||||
|
storage/logs/*.log
|
||||||
|
vendor
|
||||||
|
|||||||
@ -3,5 +3,4 @@ APP_DEBUG=false
|
|||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_URL=http://panel.test
|
APP_URL=http://panel.test
|
||||||
APP_INSTALLED=false
|
APP_INSTALLED=false
|
||||||
APP_TIMEZONE=UTC
|
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
public
|
|
||||||
node_modules
|
|
||||||
resources/views
|
|
||||||
babel.config.js
|
|
||||||
tailwind.config.js
|
|
||||||
webpack.config.js
|
|
||||||
52
.eslintrc.js
52
.eslintrc.js
@ -1,52 +0,0 @@
|
|||||||
/** @type {import('eslint').Linter.Config} */
|
|
||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 6,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
project: './tsconfig.json',
|
|
||||||
tsconfigRootDir: './',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
pragma: 'React',
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
linkComponents: [
|
|
||||||
{ name: 'Link', linkAttribute: 'to' },
|
|
||||||
{ name: 'NavLink', linkAttribute: 'to' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
plugins: ['react', 'react-hooks', 'prettier', '@typescript-eslint'],
|
|
||||||
extends: [
|
|
||||||
// 'standard',
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:jest-dom/recommended',
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
eqeqeq: 'error',
|
|
||||||
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
|
|
||||||
// TypeScript can infer this significantly better than eslint ever can.
|
|
||||||
'react/prop-types': 0,
|
|
||||||
'react/display-name': 0,
|
|
||||||
'@typescript-eslint/no-explicit-any': 0,
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 0,
|
|
||||||
// 'react/no-unknown-property': ['error', { ignore: ['css'] }],
|
|
||||||
// This setup is required to avoid a spam of errors when running eslint about React being
|
|
||||||
// used before it is defined.
|
|
||||||
//
|
|
||||||
// @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use
|
|
||||||
'no-use-before-define': 0,
|
|
||||||
'@typescript-eslint/no-use-before-define': 'warn',
|
|
||||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
||||||
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
7
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -64,10 +64,9 @@ body:
|
|||||||
label: Error Logs
|
label: Error Logs
|
||||||
description: |
|
description: |
|
||||||
Run the following command to collect logs on your system.
|
Run the following command to collect logs on your system.
|
||||||
|
Wings: `sudo wings diagnostics --hastebin-url=https://logs.pelican.dev`
|
||||||
Wings: `sudo wings diagnostics`
|
Panel: `tail -n 300 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl --data-binary @- https://logs.pelican.dev`
|
||||||
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl -X POST -F 'c=@-' paste.pelistuff.com`
|
placeholder: "https://logs.pelican.dev/c17f750e"
|
||||||
placeholder: "https://pelipaste.com/a1h6z"
|
|
||||||
render: bash
|
render: bash
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
76
.github/docker/README.md
vendored
76
.github/docker/README.md
vendored
@ -1,76 +0,0 @@
|
|||||||
# Pelican Panel - Docker Image
|
|
||||||
This is a ready to use docker image for the panel.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
This docker image requires some additional software to function. The software can either be provided in other containers (see the [docker-compose.yml](https://github.com/pelican-dev/panel/blob/develop/docker-compose.example.yml) as an example) or as existing instances.
|
|
||||||
|
|
||||||
A mysql database is required. We recommend the stock [MariaDB Image](https://hub.docker.com/_/mariadb/) image if you prefer to run it in a docker container. As a non-containerized option we recommend mariadb.
|
|
||||||
|
|
||||||
A caching software is required as well. We recommend the stock [Redis Image](https://hub.docker.com/_/redis/) image. You can choose any of the [supported options](#cache-drivers).
|
|
||||||
|
|
||||||
You can provide additional settings using a custom `.env` file or by setting the appropriate environment variables in the docker-compose file.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Start the docker container and the required dependencies (either provide existing ones or start containers as well, see the [docker-compose.yml](https://github.com/pelican-dev/panel/blob/develop/docker-compose.example.yml) file as an example.
|
|
||||||
|
|
||||||
After the startup is complete you'll need to create a user.
|
|
||||||
If you are running the docker container without docker-compose, use:
|
|
||||||
```
|
|
||||||
docker exec -it <container id> php artisan p:user:make
|
|
||||||
```
|
|
||||||
If you are using docker compose use
|
|
||||||
```
|
|
||||||
docker-compose exec panel php artisan p:user:make
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
There are multiple environment variables to configure the panel when not providing your own `.env` file, see the following table for details on each available option.
|
|
||||||
|
|
||||||
Note: If your `APP_URL` starts with `https://` you need to provide an `LE_EMAIL` as well so Certificates can be generated.
|
|
||||||
|
|
||||||
| Variable | Description | Required |
|
|
||||||
|-------------------| ------------------------------------------------------------------------------ | -------- |
|
|
||||||
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
|
|
||||||
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
|
|
||||||
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
|
||||||
| `DB_HOST` | The host of the mysql instance | yes |
|
|
||||||
| `DB_PORT` | The port of the mysql instance | yes |
|
|
||||||
| `DB_DATABASE` | The name of the mysql database | yes |
|
|
||||||
| `DB_USERNAME` | The mysql user | yes |
|
|
||||||
| `DB_PASSWORD` | The mysql password for the specified user | yes |
|
|
||||||
| `CACHE_STORE` | The cache driver (see [Cache drivers](#cache-drivers) for detais) | yes |
|
|
||||||
| `SESSION_DRIVER` | | yes |
|
|
||||||
| `QUEUE_DRIVER` | | yes |
|
|
||||||
| `REDIS_HOST` | The hostname or IP address of the redis database | yes |
|
|
||||||
| `REDIS_PASSWORD` | The password used to secure the redis database | maybe |
|
|
||||||
| `REDIS_PORT` | The port the redis database is using on the host | maybe |
|
|
||||||
| `MAIL_DRIVER` | The email driver (see [Mail drivers](#mail-drivers) for details) | yes |
|
|
||||||
| `MAIL_FROM` | The email that should be used as the sender email | yes |
|
|
||||||
| `MAIL_HOST` | The host of your mail driver instance | maybe |
|
|
||||||
| `MAIL_PORT` | The port of your mail driver instance | maybe |
|
|
||||||
| `MAIL_USERNAME` | The username for your mail driver | maybe |
|
|
||||||
| `MAIL_PASSWORD` | The password for your mail driver | maybe |
|
|
||||||
|
|
||||||
|
|
||||||
### Cache drivers
|
|
||||||
You can choose between different cache drivers depending on what you prefer.
|
|
||||||
We recommend redis when using docker as it can be started in a container easily.
|
|
||||||
|
|
||||||
| Driver | Description | Required variables |
|
|
||||||
| -------- | ------------------------------------ | ------------------------------------------------------ |
|
|
||||||
| redis | host where redis is running | `REDIS_HOST` |
|
|
||||||
| redis | port redis is running on | `REDIS_PORT` |
|
|
||||||
| redis | redis database password | `REDIS_PASSWORD` |
|
|
||||||
|
|
||||||
### Mail drivers
|
|
||||||
You can choose between different mail drivers according to your needs.
|
|
||||||
Every driver requires `MAIL_FROM` to be set.
|
|
||||||
|
|
||||||
| Driver | Description | Required variables |
|
|
||||||
| -------- | ------------------------------------ | ------------------------------------------------------------- |
|
|
||||||
| mail | uses the installed php mail | |
|
|
||||||
| mandrill | [Mandrill](http://www.mandrill.com/) | `MAIL_USERNAME` |
|
|
||||||
| postmark | [Postmark](https://postmarkapp.com/) | `MAIL_USERNAME` |
|
|
||||||
| mailgun | [Mailgun](https://www.mailgun.com/) | `MAIL_USERNAME`, `MAIL_HOST` |
|
|
||||||
| smtp | Any SMTP server can be configured | `MAIL_USERNAME`, `MAIL_HOST`, `MAIL_PASSWORD`, `MAIL_PORT` |
|
|
||||||
75
.github/docker/default.conf
vendored
75
.github/docker/default.conf
vendored
@ -1,75 +0,0 @@
|
|||||||
# If using Ubuntu this file should be placed in:
|
|
||||||
# /etc/nginx/sites-available/
|
|
||||||
#
|
|
||||||
# If using CentOS this file should be placed in:
|
|
||||||
# /etc/nginx/conf.d/
|
|
||||||
#
|
|
||||||
|
|
||||||
# The MIT License (MIT)
|
|
||||||
#
|
|
||||||
# Pterodactyl®
|
|
||||||
# Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
|
||||||
# in the Software without restriction, including without limitation the rights
|
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
|
||||||
# furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
|
||||||
# copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
root /app/public;
|
|
||||||
index index.html index.htm index.php;
|
|
||||||
charset utf-8;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.php?$query_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /favicon.ico { access_log off; log_not_found off; }
|
|
||||||
location = /robots.txt { access_log off; log_not_found off; }
|
|
||||||
|
|
||||||
access_log off;
|
|
||||||
error_log /var/log/nginx/panel.app-error.log error;
|
|
||||||
|
|
||||||
# allow larger file uploads and longer script runtimes
|
|
||||||
client_max_body_size 100m;
|
|
||||||
client_body_timeout 120s;
|
|
||||||
|
|
||||||
sendfile off;
|
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
# the fastcgi_pass path needs to be changed accordingly when using CentOS
|
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
||||||
fastcgi_param HTTP_PROXY "";
|
|
||||||
fastcgi_intercept_errors off;
|
|
||||||
fastcgi_buffer_size 16k;
|
|
||||||
fastcgi_buffers 4 16k;
|
|
||||||
fastcgi_connect_timeout 300;
|
|
||||||
fastcgi_send_timeout 300;
|
|
||||||
fastcgi_read_timeout 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
70
.github/docker/default_ssl.conf
vendored
70
.github/docker/default_ssl.conf
vendored
@ -1,70 +0,0 @@
|
|||||||
# If using Ubuntu this file should be placed in:
|
|
||||||
# /etc/nginx/sites-available/
|
|
||||||
#
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name <domain>;
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name <domain>;
|
|
||||||
|
|
||||||
root /app/public;
|
|
||||||
index index.php;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/panel.app-access.log;
|
|
||||||
error_log /var/log/nginx/panel.app-error.log error;
|
|
||||||
|
|
||||||
# allow larger file uploads and longer script runtimes
|
|
||||||
client_max_body_size 100m;
|
|
||||||
client_body_timeout 120s;
|
|
||||||
|
|
||||||
sendfile off;
|
|
||||||
|
|
||||||
# strengthen ssl security
|
|
||||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
|
||||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
|
||||||
|
|
||||||
# See the link below for more SSL information:
|
|
||||||
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
|
||||||
#
|
|
||||||
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
|
||||||
|
|
||||||
# Add headers to serve security related headers
|
|
||||||
add_header Strict-Transport-Security "max-age=15768000; preload;";
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
add_header X-Robots-Tag none;
|
|
||||||
add_header Content-Security-Policy "frame-ancestors 'self'";
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.php?$query_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
||||||
fastcgi_param HTTP_PROXY "";
|
|
||||||
fastcgi_intercept_errors off;
|
|
||||||
fastcgi_buffer_size 16k;
|
|
||||||
fastcgi_buffers 4 16k;
|
|
||||||
fastcgi_connect_timeout 300;
|
|
||||||
fastcgi_send_timeout 300;
|
|
||||||
fastcgi_read_timeout 300;
|
|
||||||
include /etc/nginx/fastcgi_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
65
.github/docker/entrypoint.sh
vendored
65
.github/docker/entrypoint.sh
vendored
@ -1,65 +0,0 @@
|
|||||||
#!/bin/ash -e
|
|
||||||
|
|
||||||
#mkdir -p /var/log/supervisord/ /var/log/php8/ \
|
|
||||||
|
|
||||||
## check for .env file and generate app keys if missing
|
|
||||||
if [ -f /pelican-data/.env ]; then
|
|
||||||
echo "external vars exist."
|
|
||||||
rm -rf /var/www/html/.env
|
|
||||||
else
|
|
||||||
echo "external vars don't exist."
|
|
||||||
rm -rf /var/www/html/.env
|
|
||||||
touch /pelican-data/.env
|
|
||||||
|
|
||||||
## manually generate a key because key generate --force fails
|
|
||||||
if [ -z $APP_KEY ]; then
|
|
||||||
echo -e "Generating key."
|
|
||||||
APP_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
|
|
||||||
echo -e "Generated app key: $APP_KEY"
|
|
||||||
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
|
|
||||||
else
|
|
||||||
echo -e "APP_KEY exists in environment, using that."
|
|
||||||
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
## enable installer
|
|
||||||
echo -e "APP_INSTALLED=false" >> /pelican-data/.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir /pelican-data/database
|
|
||||||
ln -s /pelican-data/.env /var/www/html/
|
|
||||||
chown -h www-data:www-data /var/www/html/.env
|
|
||||||
ln -s /pelican-data/database/database.sqlite /var/www/html/database/
|
|
||||||
|
|
||||||
if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then
|
|
||||||
echo "Generating APP_KEY..."
|
|
||||||
php artisan key:generate --force
|
|
||||||
else
|
|
||||||
echo "APP_KEY is already set."
|
|
||||||
fi
|
|
||||||
|
|
||||||
## make sure the db is set up
|
|
||||||
echo -e "Migrating Database"
|
|
||||||
php artisan migrate --force
|
|
||||||
|
|
||||||
echo -e "Optimizing Filament"
|
|
||||||
php artisan filament:optimize
|
|
||||||
|
|
||||||
## start cronjobs for the queue
|
|
||||||
echo -e "Starting cron jobs."
|
|
||||||
crond -L /var/log/crond -l 5
|
|
||||||
|
|
||||||
export SUPERVISORD_CADDY=false
|
|
||||||
|
|
||||||
## disable caddy if SKIP_CADDY is set
|
|
||||||
if [[ "${SKIP_CADDY:-}" == "true" ]]; then
|
|
||||||
echo "Starting PHP-FPM only"
|
|
||||||
else
|
|
||||||
echo "Starting PHP-FPM and Caddy"
|
|
||||||
export SUPERVISORD_CADDY=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
chown -R www-data:www-data /pelican-data/.env /pelican-data/database
|
|
||||||
|
|
||||||
echo "Starting Supervisord"
|
|
||||||
exec "$@"
|
|
||||||
16
.github/docker/www.conf
vendored
16
.github/docker/www.conf
vendored
@ -1,16 +0,0 @@
|
|||||||
[www]
|
|
||||||
|
|
||||||
user = nginx
|
|
||||||
group = nginx
|
|
||||||
|
|
||||||
listen = 127.0.0.1:9000
|
|
||||||
listen.owner = nginx
|
|
||||||
listen.group = nginx
|
|
||||||
listen.mode = 0750
|
|
||||||
|
|
||||||
pm = ondemand
|
|
||||||
pm.max_children = 9
|
|
||||||
pm.process_idle_timeout = 10s
|
|
||||||
pm.max_requests = 200
|
|
||||||
|
|
||||||
clear_env = no
|
|
||||||
23
.github/workflows/build.yaml
vendored
23
.github/workflows/build.yaml
vendored
@ -3,31 +3,40 @@ name: Build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ui:
|
ui:
|
||||||
name: UI
|
name: UI
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18, 20]
|
node-version: [20, 22]
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install PHP dependencies
|
||||||
|
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build:production
|
run: yarn build
|
||||||
|
|||||||
157
.github/workflows/ci.yaml
vendored
157
.github/workflows/ci.yaml
vendored
@ -6,14 +6,77 @@ on:
|
|||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
APP_ENV: testing
|
||||||
|
APP_DEBUG: "false"
|
||||||
|
APP_KEY: ThisIsARandomStringForTests12345
|
||||||
|
APP_TIMEZONE: UTC
|
||||||
|
APP_URL: http://localhost/
|
||||||
|
CACHE_DRIVER: array
|
||||||
|
MAIL_MAILER: array
|
||||||
|
SESSION_DRIVER: array
|
||||||
|
QUEUE_CONNECTION: sync
|
||||||
|
GUZZLE_TIMEOUT: 60
|
||||||
|
GUZZLE_CONNECT_TIMEOUT: 60
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
sqlite:
|
||||||
|
name: SQLite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
php: [8.2, 8.3, 8.4]
|
||||||
|
env:
|
||||||
|
DB_CONNECTION: sqlite
|
||||||
|
DB_DATABASE: testing.sqlite
|
||||||
|
steps:
|
||||||
|
- name: Code Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: |
|
||||||
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||||
|
|
||||||
|
- name: Create SQLite file
|
||||||
|
run: touch database/testing.sqlite
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: vendor/bin/pest tests/Unit
|
||||||
|
env:
|
||||||
|
DB_HOST: UNIT_NO_DB
|
||||||
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
|
- name: Integration tests
|
||||||
|
run: vendor/bin/pest tests/Integration
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
name: MySQL
|
name: MySQL
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3]
|
php: [8.2, 8.3, 8.4]
|
||||||
database: ["mysql:8"]
|
database: ["mysql:8"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
@ -25,21 +88,10 @@ jobs:
|
|||||||
- 3306
|
- 3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
env:
|
env:
|
||||||
APP_ENV: testing
|
|
||||||
APP_DEBUG: "false"
|
|
||||||
APP_KEY: ThisIsARandomStringForTests12345
|
|
||||||
APP_TIMEZONE: UTC
|
|
||||||
APP_URL: http://localhost/
|
|
||||||
CACHE_DRIVER: array
|
|
||||||
MAIL_MAILER: array
|
|
||||||
SESSION_DRIVER: array
|
|
||||||
QUEUE_CONNECTION: sync
|
|
||||||
DB_CONNECTION: mysql
|
DB_CONNECTION: mysql
|
||||||
DB_HOST: 127.0.0.1
|
DB_HOST: 127.0.0.1
|
||||||
DB_DATABASE: testing
|
DB_DATABASE: testing
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
GUZZLE_TIMEOUT: 60
|
|
||||||
GUZZLE_CONNECT_TIMEOUT: 60
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -66,16 +118,16 @@ jobs:
|
|||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||||
|
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: vendor/bin/phpunit tests/Unit
|
run: vendor/bin/pest tests/Unit
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
run: vendor/bin/phpunit tests/Integration
|
run: vendor/bin/pest tests/Integration
|
||||||
env:
|
env:
|
||||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
@ -84,9 +136,9 @@ jobs:
|
|||||||
name: MariaDB
|
name: MariaDB
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3]
|
php: [8.2, 8.3, 8.4]
|
||||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
@ -98,21 +150,10 @@ jobs:
|
|||||||
- 3306
|
- 3306
|
||||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
env:
|
env:
|
||||||
APP_ENV: testing
|
|
||||||
APP_DEBUG: "false"
|
|
||||||
APP_KEY: ThisIsARandomStringForTests12345
|
|
||||||
APP_TIMEZONE: UTC
|
|
||||||
APP_URL: http://localhost/
|
|
||||||
CACHE_DRIVER: array
|
|
||||||
MAIL_MAILER: array
|
|
||||||
SESSION_DRIVER: array
|
|
||||||
QUEUE_CONNECTION: sync
|
|
||||||
DB_CONNECTION: mariadb
|
DB_CONNECTION: mariadb
|
||||||
DB_HOST: 127.0.0.1
|
DB_HOST: 127.0.0.1
|
||||||
DB_DATABASE: testing
|
DB_DATABASE: testing
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
GUZZLE_TIMEOUT: 60
|
|
||||||
GUZZLE_CONNECT_TIMEOUT: 60
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -139,41 +180,49 @@ jobs:
|
|||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||||
|
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: vendor/bin/phpunit tests/Unit
|
run: vendor/bin/pest tests/Unit
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
run: vendor/bin/phpunit tests/Integration
|
run: vendor/bin/pest tests/Integration
|
||||||
env:
|
env:
|
||||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
|
|
||||||
sqlite:
|
postgresql:
|
||||||
name: SQLite
|
name: PostgreSQL
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3]
|
php: [8.2, 8.3, 8.4]
|
||||||
|
database: ["postgres:14"]
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: ${{ matrix.database }}
|
||||||
env:
|
env:
|
||||||
APP_ENV: testing
|
POSTGRES_DB: testing
|
||||||
APP_DEBUG: "false"
|
POSTGRES_USER: postgres
|
||||||
APP_KEY: ThisIsARandomStringForTests12345
|
POSTGRES_PASSWORD: postgres
|
||||||
APP_TIMEZONE: UTC
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
APP_URL: http://localhost/
|
ports:
|
||||||
CACHE_DRIVER: array
|
- 5432:5432
|
||||||
MAIL_MAILER: array
|
options: >-
|
||||||
SESSION_DRIVER: array
|
--health-cmd pg_isready
|
||||||
QUEUE_CONNECTION: sync
|
--health-interval 10s
|
||||||
DB_CONNECTION: sqlite
|
--health-timeout 5s
|
||||||
DB_DATABASE: testing.sqlite
|
--health-retries 5
|
||||||
GUZZLE_TIMEOUT: 60
|
env:
|
||||||
GUZZLE_CONNECT_TIMEOUT: 60
|
DB_CONNECTION: pgsql
|
||||||
|
DB_HOST: 127.0.0.1
|
||||||
|
DB_DATABASE: testing
|
||||||
|
DB_USERNAME: postgres
|
||||||
|
DB_PASSWORD: postgres
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -189,7 +238,6 @@ jobs:
|
|||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@ -200,16 +248,13 @@ jobs:
|
|||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||||
|
|
||||||
- name: Create SQLite file
|
|
||||||
run: touch database/testing.sqlite
|
|
||||||
|
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: vendor/bin/phpunit tests/Unit
|
run: vendor/bin/pest tests/Unit
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
run: vendor/bin/phpunit tests/Integration
|
run: vendor/bin/pest tests/Integration
|
||||||
|
|||||||
102
.github/workflows/docker-publish.yml
vendored
102
.github/workflows/docker-publish.yml
vendored
@ -1,6 +1,5 @@
|
|||||||
name: Docker
|
name: Docker
|
||||||
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -14,12 +13,65 @@ env:
|
|||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-php-base:
|
||||||
name: Build and Push
|
name: Build PHP base image on ${{ matrix.os }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-24.04
|
||||||
|
arch: amd64
|
||||||
|
platform: linux/amd64
|
||||||
|
- os: ubuntu-24.04-arm
|
||||||
|
arch: arm64
|
||||||
|
platform: linux/arm64
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build the base PHP image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile.base
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
tags: base-php:${{ matrix.arch }}
|
||||||
|
cache-from: type=gha,scope=base-php${{ matrix.arch }}
|
||||||
|
cache-to: type=gha,scope=base-php${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Export image to file
|
||||||
|
run: docker save -o base-php-${{ matrix.arch }}.tar base-php:${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Push the docker build to the artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: base-php-${{ matrix.arch }}.tar
|
||||||
|
path: base-php-${{ matrix.arch }}.tar
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
|
||||||
|
build-and-push:
|
||||||
|
name: Build and Push ubuntu-24.04
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: build-php-base
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
# Start a temp local registry because workflow can not pull from localy loaded images
|
||||||
|
services:
|
||||||
|
registry:
|
||||||
|
image: registry:2
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
||||||
if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||||
steps:
|
steps:
|
||||||
@ -41,8 +93,11 @@ jobs:
|
|||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
# We Need to start it in host mode else it can't acces the local registry on port 5000
|
||||||
- name: Setup Docker buildx
|
- name: Setup Docker buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
driver-opts: network=host
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@ -57,30 +112,57 @@ jobs:
|
|||||||
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
|
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
|
||||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Download the base PHP image AMD64
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: base-php-amd64.tar
|
||||||
|
|
||||||
|
# Download the base PHP image ARM64
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: base-php-arm64.tar
|
||||||
|
|
||||||
|
- name: Load base images into local registry
|
||||||
|
run: |
|
||||||
|
docker load -i base-php-amd64.tar
|
||||||
|
docker load -i base-php-arm64.tar
|
||||||
|
docker tag base-php:amd64 localhost:5000/base-php:amd64
|
||||||
|
docker tag base-php:arm64 localhost:5000/base-php:arm64
|
||||||
|
docker push localhost:5000/base-php:amd64
|
||||||
|
docker push localhost:5000/base-php:arm64
|
||||||
|
rm base-php-arm64.tar base-php-amd64.tar
|
||||||
|
|
||||||
|
- name: Update version in config/app.php (tag)
|
||||||
|
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||||
|
run: |
|
||||||
|
sed -i "s/'version' => 'canary',/'version' => '${{ steps.build_info.outputs.version_tag }}',/" config/app.php
|
||||||
|
|
||||||
- name: Build and Push (tag)
|
- name: Build and Push (tag)
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: 'linux/amd64,linux/arm64'
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ steps.build_info.outputs.version_tag }}
|
VERSION=${{ steps.build_info.outputs.version_tag }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
|
cache-from: type=gha,scope=tagged${{ matrix.os }}
|
||||||
|
cache-to: type=gha,scope=tagged${{ matrix.os }},mode=max
|
||||||
|
|
||||||
- name: Build and Push (main)
|
- name: Build and Push (main)
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
if: "github.event_name == 'push' && contains(github.ref, 'main')"
|
if: "github.event_name == 'push' && contains(github.ref, 'main')"
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: 'linux/amd64,linux/arm64'
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=dev-${{ steps.build_info.outputs.short_sha }}
|
VERSION=dev-${{ steps.build_info.outputs.short_sha }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha,scope=${{ matrix.os }}
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,scope=${{ matrix.os }},mode=max
|
||||||
|
|||||||
25
.github/workflows/lint.yaml
vendored
25
.github/workflows/lint.yaml
vendored
@ -25,21 +25,38 @@ jobs:
|
|||||||
run: cp .env.example .env
|
run: cp .env.example .env
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-progress --prefer-dist
|
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts
|
||||||
|
|
||||||
- name: Pint
|
- name: Pint
|
||||||
run: vendor/bin/pint --test
|
run: vendor/bin/pint --test
|
||||||
phpstan:
|
phpstan:
|
||||||
name: PHPStan
|
name: PHPStan
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
php: [ 8.2, 8.3, 8.4 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: |
|
||||||
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: "8.3"
|
php-version: ${{ matrix.php }}
|
||||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
tools: composer:v2
|
tools: composer:v2
|
||||||
coverage: none
|
coverage: none
|
||||||
@ -48,7 +65,7 @@ jobs:
|
|||||||
run: cp .env.example .env
|
run: cp .env.example .env
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-progress --prefer-dist
|
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||||
|
|
||||||
- name: PHPStan
|
- name: PHPStan
|
||||||
run: vendor/bin/phpstan --memory-limit=-1
|
run: vendor/bin/phpstan --memory-limit=-1 --error-format=github
|
||||||
|
|||||||
19
.github/workflows/release.yaml
vendored
19
.github/workflows/release.yaml
vendored
@ -16,17 +16,28 @@ jobs:
|
|||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install PHP dependencies
|
||||||
|
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build:production
|
run: yarn build
|
||||||
|
|
||||||
- name: Create release branch and bump version
|
- name: Create release branch and bump version
|
||||||
env:
|
env:
|
||||||
@ -44,8 +55,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release archive
|
- name: Create release archive
|
||||||
run: |
|
run: |
|
||||||
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpunit.xml shell.nix
|
rm -rf node_modules vendor tests CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml shell.nix
|
||||||
tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json
|
tar -czf panel.tar.gz * .env.example
|
||||||
|
|
||||||
- name: Create checksum
|
- name: Create checksum
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,9 +1,9 @@
|
|||||||
/.phpunit.cache
|
/.phpunit.cache
|
||||||
/node_modules
|
/node_modules
|
||||||
/public/build
|
/public/build
|
||||||
/public/hot
|
|
||||||
/public/storage
|
/public/storage
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
|
/storage/pail
|
||||||
/storage/clockwork/*
|
/storage/clockwork/*
|
||||||
/vendor
|
/vendor
|
||||||
*.DS_Store*
|
*.DS_Store*
|
||||||
@ -19,10 +19,11 @@ npm-debug.log
|
|||||||
yarn-error.log
|
yarn-error.log
|
||||||
/.fleet
|
/.fleet
|
||||||
/.idea
|
/.idea
|
||||||
|
/.nova
|
||||||
/.vscode
|
/.vscode
|
||||||
|
/.ddev
|
||||||
|
|
||||||
public/assets/manifest.json
|
public/assets/manifest.json
|
||||||
/database/*.sqlite
|
/database/*.sqlite*
|
||||||
filament-monaco-editor/
|
|
||||||
_ide_helper*
|
_ide_helper*
|
||||||
/.phpstorm.meta.php
|
/.phpstorm.meta.php
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use PhpCsFixer\Config;
|
|
||||||
use PhpCsFixer\Finder;
|
|
||||||
|
|
||||||
$finder = (new Finder())
|
|
||||||
->in(__DIR__)
|
|
||||||
->exclude([
|
|
||||||
'vendor',
|
|
||||||
'node_modules',
|
|
||||||
'storage',
|
|
||||||
'bootstrap/cache',
|
|
||||||
])
|
|
||||||
->notName(['_ide_helper*']);
|
|
||||||
|
|
||||||
return (new Config())
|
|
||||||
->setRiskyAllowed(true)
|
|
||||||
->setFinder($finder)
|
|
||||||
->setRules([
|
|
||||||
'@Symfony' => true,
|
|
||||||
'@PSR1' => true,
|
|
||||||
'@PSR2' => true,
|
|
||||||
'@PSR12' => true,
|
|
||||||
'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],
|
|
||||||
'combine_consecutive_unsets' => true,
|
|
||||||
'concat_space' => ['spacing' => 'one'],
|
|
||||||
'heredoc_to_nowdoc' => true,
|
|
||||||
'no_alias_functions' => true,
|
|
||||||
'no_unreachable_default_argument_value' => true,
|
|
||||||
'no_useless_return' => true,
|
|
||||||
'ordered_imports' => [
|
|
||||||
'sort_algorithm' => 'length',
|
|
||||||
],
|
|
||||||
'phpdoc_align' => [
|
|
||||||
'align' => 'left',
|
|
||||||
'tags' => [
|
|
||||||
'param',
|
|
||||||
'property',
|
|
||||||
'return',
|
|
||||||
'throws',
|
|
||||||
'type',
|
|
||||||
'var',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'random_api_migration' => true,
|
|
||||||
'ternary_to_null_coalescing' => true,
|
|
||||||
'yoda_style' => [
|
|
||||||
'equal' => false,
|
|
||||||
'identical' => false,
|
|
||||||
'less_and_greater' => false,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
12
Caddyfile
12
Caddyfile
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
admin off
|
|
||||||
email {$ADMIN_EMAIL}
|
|
||||||
}
|
|
||||||
|
|
||||||
{$APP_URL} {
|
|
||||||
root * /var/www/html/public
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
php_fastcgi 127.0.0.1:9000
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
121
Dockerfile
121
Dockerfile
@ -1,59 +1,110 @@
|
|||||||
|
# syntax=docker.io/docker/dockerfile:1.13-labs
|
||||||
# Pelican Production Dockerfile
|
# Pelican Production Dockerfile
|
||||||
|
|
||||||
FROM node:20-alpine AS yarn
|
##
|
||||||
#FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
|
# If you want to build this locally you want to run `docker build -f Dockerfile.dev`
|
||||||
|
##
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 1-1: Composer Install
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS composer
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
COPY . ./
|
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Copy bare minimum to install Composer dependencies
|
||||||
|
COPY composer.json composer.lock ./
|
||||||
|
|
||||||
|
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 1-2: Yarn Install
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy bare minimum to install Yarn dependencies
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
RUN yarn config set network-timeout 300000 \
|
RUN yarn config set network-timeout 300000 \
|
||||||
&& yarn install --frozen-lockfile \
|
&& yarn install --frozen-lockfile
|
||||||
&& yarn run build:production
|
|
||||||
|
|
||||||
FROM php:8.3-fpm-alpine
|
# ================================
|
||||||
# FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine
|
# Stage 2-1: Composer Optimize
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||||
|
|
||||||
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
# Copy full code to optimize autoload
|
||||||
|
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||||
|
|
||||||
|
RUN composer dump-autoload --optimize
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 2-2: Build Frontend Assets
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy full code
|
||||||
|
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||||
|
COPY --from=composer /build .
|
||||||
|
|
||||||
|
RUN yarn run build
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 5: Build Final Application Image
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS final
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
# Install dependencies
|
# Install additional required libraries
|
||||||
RUN apk update && apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \
|
caddy ca-certificates supervisor supercronic fcgi
|
||||||
zip unzip curl \
|
|
||||||
caddy ca-certificates supervisor \
|
|
||||||
&& docker-php-ext-install bcmath gd intl zip opcache pcntl posix pdo_mysql
|
|
||||||
|
|
||||||
# Copy the Caddyfile to the container
|
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||||
COPY Caddyfile /etc/caddy/Caddyfile
|
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||||
|
|
||||||
# Copy the application code to the container
|
# Set permissions
|
||||||
COPY . .
|
# First ensure all files are owned by root and restrict www-data to read access
|
||||||
|
RUN chown root:www-data ./ \
|
||||||
|
&& chmod 750 ./ \
|
||||||
|
# Files should not have execute set, but directories need it
|
||||||
|
&& find ./ -type d -exec chmod 750 {} \; \
|
||||||
|
# Create necessary directories
|
||||||
|
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||||
|
# Symlinks for env, database, and avatars
|
||||||
|
&& ln -s /pelican-data/.env ./.env \
|
||||||
|
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||||
|
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||||
|
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||||
|
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||||
|
# Allow www-data write permissions where necessary
|
||||||
|
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||||
|
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||||
|
&& chown -R www-data: /usr/local/etc/php/
|
||||||
|
|
||||||
COPY --from=yarn /build/public/assets ./public/assets
|
# Configure Supervisor
|
||||||
|
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||||
|
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
# Add Laravel scheduler to crontab
|
||||||
|
COPY docker/crontab /etc/supercronic/crontab
|
||||||
|
|
||||||
RUN touch .env
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
|
COPY docker/healthcheck.sh /healthcheck.sh
|
||||||
RUN composer install --no-dev --optimize-autoloader
|
|
||||||
|
|
||||||
# Set file permissions
|
|
||||||
RUN chmod -R 755 storage bootstrap/cache \
|
|
||||||
&& chown -R www-data:www-data ./
|
|
||||||
|
|
||||||
# Add scheduler to cron
|
|
||||||
RUN echo "* * * * * php /var/www/html/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data -
|
|
||||||
|
|
||||||
## supervisord config and log dir
|
|
||||||
RUN cp .github/docker/supervisord.conf /etc/supervisord.conf && \
|
|
||||||
mkdir /var/log/supervisord/
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD curl -f http://localhost/up || exit 1
|
CMD /bin/ash /healthcheck.sh
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
|
|
||||||
VOLUME /pelican-data
|
VOLUME /pelican-data
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ]
|
USER www-data
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||||
|
|||||||
10
Dockerfile.base
Normal file
10
Dockerfile.base
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# ================================
|
||||||
|
# Stage 0: Build PHP Base Image
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine
|
||||||
|
|
||||||
|
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||||
|
|
||||||
|
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
|
||||||
|
|
||||||
|
RUN rm /usr/local/bin/install-php-extensions
|
||||||
114
Dockerfile.dev
Normal file
114
Dockerfile.dev
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# syntax=docker.io/docker/dockerfile:1.13-labs
|
||||||
|
# Pelican Development Dockerfile
|
||||||
|
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine AS base
|
||||||
|
|
||||||
|
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||||
|
|
||||||
|
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
|
||||||
|
|
||||||
|
RUN rm /usr/local/bin/install-php-extensions
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 1-1: Composer Install
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH base AS composer
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Copy bare minimum to install Composer dependencies
|
||||||
|
COPY composer.json composer.lock ./
|
||||||
|
|
||||||
|
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 1-2: Yarn Install
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy bare minimum to install Yarn dependencies
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
|
RUN yarn config set network-timeout 300000 \
|
||||||
|
&& yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 2-1: Composer Optimize
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||||
|
|
||||||
|
# Copy full code to optimize autoload
|
||||||
|
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||||
|
|
||||||
|
RUN composer dump-autoload --optimize
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 2-2: Build Frontend Assets
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy full code
|
||||||
|
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||||
|
COPY --from=composer /build .
|
||||||
|
|
||||||
|
RUN yarn run build
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Stage 5: Build Final Application Image
|
||||||
|
# ================================
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH base AS final
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
# Install additional required libraries
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
caddy ca-certificates supervisor supercronic fcgi coreutils
|
||||||
|
|
||||||
|
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||||
|
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
# First ensure all files are owned by root and restrict www-data to read access
|
||||||
|
RUN chown root:www-data ./ \
|
||||||
|
&& chmod 750 ./ \
|
||||||
|
# Files should not have execute set, but directories need it
|
||||||
|
&& find ./ -type d -exec chmod 750 {} \; \
|
||||||
|
# Create necessary directories
|
||||||
|
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||||
|
# Symlinks for env, database, and avatars
|
||||||
|
&& ln -s /pelican-data/.env ./.env \
|
||||||
|
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||||
|
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||||
|
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||||
|
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||||
|
# Allow www-data write permissions where necessary
|
||||||
|
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||||
|
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||||
|
&& chown -R www-data: /usr/local/etc/php/
|
||||||
|
|
||||||
|
# Configure Supervisor
|
||||||
|
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||||
|
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
# Add Laravel scheduler to crontab
|
||||||
|
COPY docker/crontab /etc/supercronic/crontab
|
||||||
|
|
||||||
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
|
COPY docker/healthcheck.sh /healthcheck.sh
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD /bin/ash /healthcheck.sh
|
||||||
|
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
|
VOLUME /pelican-data
|
||||||
|
|
||||||
|
USER www-data
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||||
|
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||||
58
app/Checks/CacheCheck.php
Normal file
58
app/Checks/CacheCheck.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Checks;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Spatie\Health\Checks\Check;
|
||||||
|
use Spatie\Health\Checks\Result;
|
||||||
|
|
||||||
|
class CacheCheck extends Check
|
||||||
|
{
|
||||||
|
protected ?string $driver = null;
|
||||||
|
|
||||||
|
public function driver(string $driver): self
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): Result
|
||||||
|
{
|
||||||
|
$driver = $this->driver ?? $this->defaultDriver();
|
||||||
|
|
||||||
|
$result = Result::make()->meta([
|
||||||
|
'driver' => $driver,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $this->canWriteValuesToCache($driver)
|
||||||
|
? $result->ok(trans('admin/health.results.cache.ok'))
|
||||||
|
: $result->failed(trans('admin/health.results.cache.failed_retrieve'));
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
return $result->failed(trans('admin/health.results.cache.failed', ['error' => $exception->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defaultDriver(): ?string
|
||||||
|
{
|
||||||
|
return config('cache.default', 'file');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canWriteValuesToCache(?string $driver): bool
|
||||||
|
{
|
||||||
|
$expectedValue = Str::random(5);
|
||||||
|
|
||||||
|
$cacheName = "laravel-health:check-{$expectedValue}";
|
||||||
|
|
||||||
|
Cache::driver($driver)->put($cacheName, $expectedValue, 10);
|
||||||
|
|
||||||
|
$actualValue = Cache::driver($driver)->get($cacheName);
|
||||||
|
|
||||||
|
Cache::driver($driver)->forget($cacheName);
|
||||||
|
|
||||||
|
return $actualValue === $expectedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Checks/DatabaseCheck.php
Normal file
42
app/Checks/DatabaseCheck.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Checks;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Spatie\Health\Checks\Check;
|
||||||
|
use Spatie\Health\Checks\Result;
|
||||||
|
|
||||||
|
class DatabaseCheck extends Check
|
||||||
|
{
|
||||||
|
protected ?string $connectionName = null;
|
||||||
|
|
||||||
|
public function connectionName(string $connectionName): self
|
||||||
|
{
|
||||||
|
$this->connectionName = $connectionName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): Result
|
||||||
|
{
|
||||||
|
$connectionName = $this->connectionName ?? $this->getDefaultConnectionName();
|
||||||
|
|
||||||
|
$result = Result::make()->meta([
|
||||||
|
'connection_name' => $connectionName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::connection($connectionName)->getPdo();
|
||||||
|
|
||||||
|
return $result->ok(trans('admin/health.results.database.ok'));
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
return $result->failed(trans('admin/health.results.database.failed', ['error' => $exception->getMessage()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDefaultConnectionName(): string
|
||||||
|
{
|
||||||
|
return config('database.default');
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Checks/DebugModeCheck.php
Normal file
44
app/Checks/DebugModeCheck.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Checks;
|
||||||
|
|
||||||
|
use Spatie\Health\Checks\Check;
|
||||||
|
use Spatie\Health\Checks\Result;
|
||||||
|
|
||||||
|
use function config;
|
||||||
|
|
||||||
|
class DebugModeCheck extends Check
|
||||||
|
{
|
||||||
|
protected bool $expected = false;
|
||||||
|
|
||||||
|
public function expectedToBe(bool $bool): self
|
||||||
|
{
|
||||||
|
$this->expected = $bool;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): Result
|
||||||
|
{
|
||||||
|
$actual = config('app.debug');
|
||||||
|
|
||||||
|
$result = Result::make()
|
||||||
|
->meta([
|
||||||
|
'actual' => $actual,
|
||||||
|
'expected' => $this->expected,
|
||||||
|
])
|
||||||
|
->shortSummary($this->convertToWord($actual));
|
||||||
|
|
||||||
|
return $this->expected === $actual
|
||||||
|
? $result->ok()
|
||||||
|
: $result->failed(trans('admin/health.results.debugmode.failed', [
|
||||||
|
'actual' => $this->convertToWord($actual),
|
||||||
|
'expected' => $this->convertToWord($this->expected),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function convertToWord(bool $boolean): string
|
||||||
|
{
|
||||||
|
return $boolean ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Checks/EnvironmentCheck.php
Normal file
38
app/Checks/EnvironmentCheck.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Checks;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Spatie\Health\Checks\Check;
|
||||||
|
use Spatie\Health\Checks\Result;
|
||||||
|
|
||||||
|
class EnvironmentCheck extends Check
|
||||||
|
{
|
||||||
|
protected string $expectedEnvironment = 'production';
|
||||||
|
|
||||||
|
public function expectEnvironment(string $expectedEnvironment): self
|
||||||
|
{
|
||||||
|
$this->expectedEnvironment = $expectedEnvironment;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): Result
|
||||||
|
{
|
||||||
|
$actualEnvironment = (string) App::environment();
|
||||||
|
|
||||||
|
$result = Result::make()
|
||||||
|
->meta([
|
||||||
|
'actual' => $actualEnvironment,
|
||||||
|
'expected' => $this->expectedEnvironment,
|
||||||
|
])
|
||||||
|
->shortSummary($actualEnvironment);
|
||||||
|
|
||||||
|
return $this->expectedEnvironment === $actualEnvironment
|
||||||
|
? $result->ok(trans('admin/health.results.environment.ok'))
|
||||||
|
: $result->failed(trans('admin/health.results.environment.failed', [
|
||||||
|
'actual' => $actualEnvironment,
|
||||||
|
'expected' => $this->expectedEnvironment,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,30 +14,34 @@ class NodeVersionsCheck extends Check
|
|||||||
|
|
||||||
public function run(): Result
|
public function run(): Result
|
||||||
{
|
{
|
||||||
$all = Node::query()->count();
|
$all = Node::all();
|
||||||
|
|
||||||
if ($all === 0) {
|
if ($all->isEmpty()) {
|
||||||
$result = Result::make()->notificationMessage('No Nodes created')->shortSummary('No Nodes');
|
$result = Result::make()
|
||||||
|
->notificationMessage(trans('admin/health.results.nodeversions.no_nodes_created'))
|
||||||
|
->shortSummary(trans('admin/health.results.nodeversions.no_nodes'));
|
||||||
$result->status = Status::skipped();
|
$result->status = Status::skipped();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$latestVersion = $this->versionService->latestWingsVersion();
|
$outdated = $all
|
||||||
|
->filter(fn (Node $node) => !isset($node->systemInformation()['exception']) && !$this->versionService->isLatestWings($node->systemInformation()['version']))
|
||||||
$outdated = Node::query()->get()
|
|
||||||
->filter(fn (Node $node) => !isset($node->systemInformation()['exception']) && $node->systemInformation()['version'] !== $latestVersion)
|
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
|
$all = $all->count();
|
||||||
|
$latestVersion = $this->versionService->latestWingsVersion();
|
||||||
|
|
||||||
$result = Result::make()
|
$result = Result::make()
|
||||||
->meta([
|
->meta([
|
||||||
'all' => $all,
|
'all' => $all,
|
||||||
'outdated' => $outdated,
|
'outdated' => $outdated,
|
||||||
|
'latestVersion' => $latestVersion,
|
||||||
])
|
])
|
||||||
->shortSummary($outdated === 0 ? 'All up-to-date' : "{$outdated}/{$all} outdated");
|
->shortSummary($outdated === 0 ? trans('admin/health.results.nodeversions.all_up_to_date') : trans('admin/health.results.nodeversions.outdated', ['outdated' => $outdated, 'all' => $all]));
|
||||||
|
|
||||||
return $outdated === 0
|
return $outdated === 0
|
||||||
? $result->ok('All Nodes are up-to-date.')
|
? $result->ok(trans('admin/health.results.nodeversions.ok'))
|
||||||
: $result->failed(':outdated/:all Nodes are outdated.');
|
: $result->failed(trans('admin/health.results.nodeversions.failed', ['outdated' => $outdated, 'all' => $all]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,10 +22,13 @@ class PanelVersionCheck extends Check
|
|||||||
'currentVersion' => $currentVersion,
|
'currentVersion' => $currentVersion,
|
||||||
'latestVersion' => $latestVersion,
|
'latestVersion' => $latestVersion,
|
||||||
])
|
])
|
||||||
->shortSummary($isLatest ? 'up-to-date' : 'outdated');
|
->shortSummary($isLatest ? trans('admin/health.results.panelversion.up_to_date') : trans('admin/health.results.panelversion.outdated'));
|
||||||
|
|
||||||
return $isLatest
|
return $isLatest
|
||||||
? $result->ok('Panel is up-to-date.')
|
? $result->ok(trans('admin/health.results.panelversion.ok'))
|
||||||
: $result->failed('Installed version is `:currentVersion` but latest is `:latestVersion`.');
|
: $result->failed(trans('admin/health.results.panelversion.failed', [
|
||||||
|
'currentVersion' => $currentVersion,
|
||||||
|
'latestVersion' => $latestVersion,
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
app/Checks/ScheduleCheck.php
Normal file
41
app/Checks/ScheduleCheck.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Checks;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Composer\InstalledVersions;
|
||||||
|
use Spatie\Health\Checks\Checks\ScheduleCheck as BaseCheck;
|
||||||
|
use Spatie\Health\Checks\Result;
|
||||||
|
|
||||||
|
class ScheduleCheck extends BaseCheck
|
||||||
|
{
|
||||||
|
public function run(): Result
|
||||||
|
{
|
||||||
|
$result = Result::make()->ok(trans('admin/health.results.schedule.ok'));
|
||||||
|
|
||||||
|
$lastHeartbeatTimestamp = cache()->store($this->cacheStoreName)->get($this->cacheKey);
|
||||||
|
|
||||||
|
if (!$lastHeartbeatTimestamp) {
|
||||||
|
return $result->failed(trans('admin/health.results.schedule.failed_not_ran'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$latestHeartbeatAt = Carbon::createFromTimestamp($lastHeartbeatTimestamp);
|
||||||
|
|
||||||
|
$carbonVersion = InstalledVersions::getVersion('nesbot/carbon');
|
||||||
|
|
||||||
|
$minutesAgo = $latestHeartbeatAt->diffInMinutes();
|
||||||
|
|
||||||
|
if (version_compare($carbonVersion,
|
||||||
|
'3.0.0', '<')) {
|
||||||
|
$minutesAgo += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($minutesAgo > $this->heartbeatMaxAgeInMinutes) {
|
||||||
|
return $result->failed(trans('admin/health.results.schedule.failed_last_ran', [
|
||||||
|
'time' => $minutesAgo,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Egg;
|
namespace App\Console\Commands\Egg;
|
||||||
|
|
||||||
|
use App\Enums\EggFormat;
|
||||||
use App\Models\Egg;
|
use App\Models\Egg;
|
||||||
use App\Services\Eggs\Sharing\EggExporterService;
|
use App\Services\Eggs\Sharing\EggExporterService;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use JsonException;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class CheckEggUpdatesCommand extends Command
|
class CheckEggUpdatesCommand extends Command
|
||||||
{
|
{
|
||||||
@ -16,28 +20,42 @@ class CheckEggUpdatesCommand extends Command
|
|||||||
$eggs = Egg::all();
|
$eggs = Egg::all();
|
||||||
foreach ($eggs as $egg) {
|
foreach ($eggs as $egg) {
|
||||||
try {
|
try {
|
||||||
if (is_null($egg->update_url)) {
|
$this->check($egg, $exporterService);
|
||||||
$this->comment("{$egg->name}: Skipping (no update url set)");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentJson = json_decode($exporterService->handle($egg->id));
|
|
||||||
unset($currentJson->exported_at);
|
|
||||||
|
|
||||||
$updatedJson = json_decode(file_get_contents($egg->update_url));
|
|
||||||
unset($updatedJson->exported_at);
|
|
||||||
|
|
||||||
if (md5(json_encode($currentJson)) === md5(json_encode($updatedJson))) {
|
|
||||||
$this->info("{$egg->name}: Up-to-date");
|
|
||||||
cache()->put("eggs.{$egg->uuid}.update", false, now()->addHour());
|
|
||||||
} else {
|
|
||||||
$this->warn("{$egg->name}: Found update");
|
|
||||||
cache()->put("eggs.{$egg->uuid}.update", true, now()->addHour());
|
|
||||||
}
|
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
$this->error("{$egg->name}: Error ({$exception->getMessage()})");
|
$this->error("{$egg->name}: Error ({$exception->getMessage()})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws JsonException
|
||||||
|
*/
|
||||||
|
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||||
|
{
|
||||||
|
if (is_null($egg->update_url)) {
|
||||||
|
$this->comment("$egg->name: Skipping (no update url set)");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ext = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||||
|
$isYaml = in_array($ext, ['yaml', 'yml']);
|
||||||
|
|
||||||
|
$local = $isYaml
|
||||||
|
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||||
|
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
||||||
|
|
||||||
|
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url)->throw()->body();
|
||||||
|
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||||
|
|
||||||
|
unset($local['exported_at'], $remote['exported_at']);
|
||||||
|
|
||||||
|
$localHash = md5(json_encode($local, JSON_THROW_ON_ERROR));
|
||||||
|
$remoteHash = md5(json_encode($remote, JSON_THROW_ON_ERROR));
|
||||||
|
|
||||||
|
$status = $localHash === $remoteHash ? 'Up-to-date' : 'Found update';
|
||||||
|
$this->{($localHash === $remoteHash) ? 'info' : 'warn'}("$egg->name: $status");
|
||||||
|
|
||||||
|
cache()->put("eggs.$egg->uuid.update", $localHash !== $remoteHash, now()->addHour());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Egg;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class UpdateEggIndexCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:egg:update-index';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = Http::timeout(5)->connectTimeout(1)->get('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json')->throw()->json();
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->error($exception->getMessage());
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = [];
|
||||||
|
foreach ($data['nests'] as $nest) {
|
||||||
|
$nestName = $nest['nest_type'];
|
||||||
|
|
||||||
|
$this->info("Nest: $nestName");
|
||||||
|
|
||||||
|
$nestEggs = [];
|
||||||
|
foreach ($nest['Eggs'] as $egg) {
|
||||||
|
$eggName = $egg['egg']['name'];
|
||||||
|
|
||||||
|
$this->comment("Egg: $eggName");
|
||||||
|
|
||||||
|
$nestEggs[$egg['download_url']] = $eggName;
|
||||||
|
}
|
||||||
|
$index[$nestName] = $nestEggs;
|
||||||
|
|
||||||
|
$this->info('');
|
||||||
|
}
|
||||||
|
|
||||||
|
cache()->forever('eggs.index', $index);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@
|
|||||||
namespace App\Console\Commands\Environment;
|
namespace App\Console\Commands\Environment;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
|
|
||||||
class AppSettingsCommand extends Command
|
class AppSettingsCommand extends Command
|
||||||
{
|
{
|
||||||
@ -21,9 +20,13 @@ class AppSettingsCommand extends Command
|
|||||||
|
|
||||||
if (!config('app.key')) {
|
if (!config('app.key')) {
|
||||||
$this->comment('Generating app key');
|
$this->comment('Generating app key');
|
||||||
Artisan::call('key:generate');
|
$this->call('key:generate');
|
||||||
}
|
}
|
||||||
|
|
||||||
Artisan::call('filament:optimize');
|
$this->comment('Creating storage link');
|
||||||
|
$this->call('storage:link');
|
||||||
|
|
||||||
|
$this->comment('Caching components & icons');
|
||||||
|
$this->call('filament:optimize');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,6 @@ class CacheSettingsCommand extends Command
|
|||||||
{--redis-pass= : Password used to connect to redis.}
|
{--redis-pass= : Password used to connect to redis.}
|
||||||
{--redis-port= : Port to connect to redis over.}';
|
{--redis-port= : Port to connect to redis over.}';
|
||||||
|
|
||||||
protected array $variables = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CacheSettingsCommand constructor.
|
* CacheSettingsCommand constructor.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use App\Traits\EnvironmentWriterTrait;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
use Illuminate\Contracts\Console\Kernel;
|
||||||
use Illuminate\Database\DatabaseManager;
|
use Illuminate\Database\DatabaseManager;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
class DatabaseSettingsCommand extends Command
|
class DatabaseSettingsCommand extends Command
|
||||||
{
|
{
|
||||||
@ -27,6 +28,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
{--username= : Username to use when connecting to the MySQL/ MariaDB server.}
|
{--username= : Username to use when connecting to the MySQL/ MariaDB server.}
|
||||||
{--password= : Password to use for the MySQL/ MariaDB database.}';
|
{--password= : Password to use for the MySQL/ MariaDB database.}';
|
||||||
|
|
||||||
|
/** @var array<array-key, mixed> */
|
||||||
protected array $variables = [];
|
protected array $variables = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +59,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($this->variables['DB_CONNECTION'] === 'mysql') {
|
if ($this->variables['DB_CONNECTION'] === 'mysql') {
|
||||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
$this->output->note(trans('commands.database_settings.DB_HOST_note'));
|
||||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||||
'Database Host',
|
'Database Host',
|
||||||
config('database.connections.mysql.host', '127.0.0.1')
|
config('database.connections.mysql.host', '127.0.0.1')
|
||||||
@ -73,7 +75,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
config('database.connections.mysql.database', 'panel')
|
config('database.connections.mysql.database', 'panel')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
$this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
|
||||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||||
'Database Username',
|
'Database Username',
|
||||||
config('database.connections.mysql.username', 'pelican')
|
config('database.connections.mysql.username', 'pelican')
|
||||||
@ -82,7 +84,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
$askForMySQLPassword = true;
|
$askForMySQLPassword = true;
|
||||||
if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) {
|
if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) {
|
||||||
$this->variables['DB_PASSWORD'] = config('database.connections.mysql.password');
|
$this->variables['DB_PASSWORD'] = config('database.connections.mysql.password');
|
||||||
$askForMySQLPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
$askForMySQLPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($askForMySQLPassword) {
|
if ($askForMySQLPassword) {
|
||||||
@ -104,11 +106,11 @@ class DatabaseSettingsCommand extends Command
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->database->connection('_panel_command_test')->getPdo();
|
$this->database->connection('_panel_command_test')->getPdo();
|
||||||
} catch (\PDOException $exception) {
|
} catch (PDOException $exception) {
|
||||||
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||||
$this->output->error(__('commands.database_settings.DB_error_2'));
|
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||||
|
|
||||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
if ($this->confirm(trans('commands.database_settings.go_back'))) {
|
||||||
$this->database->disconnect('_panel_command_test');
|
$this->database->disconnect('_panel_command_test');
|
||||||
|
|
||||||
return $this->handle();
|
return $this->handle();
|
||||||
@ -117,7 +119,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} elseif ($this->variables['DB_CONNECTION'] === 'mariadb') {
|
} elseif ($this->variables['DB_CONNECTION'] === 'mariadb') {
|
||||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
$this->output->note(trans('commands.database_settings.DB_HOST_note'));
|
||||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||||
'Database Host',
|
'Database Host',
|
||||||
config('database.connections.mariadb.host', '127.0.0.1')
|
config('database.connections.mariadb.host', '127.0.0.1')
|
||||||
@ -133,7 +135,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
config('database.connections.mariadb.database', 'panel')
|
config('database.connections.mariadb.database', 'panel')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
$this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
|
||||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||||
'Database Username',
|
'Database Username',
|
||||||
config('database.connections.mariadb.username', 'pelican')
|
config('database.connections.mariadb.username', 'pelican')
|
||||||
@ -142,7 +144,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
$askForMariaDBPassword = true;
|
$askForMariaDBPassword = true;
|
||||||
if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) {
|
if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) {
|
||||||
$this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password');
|
$this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password');
|
||||||
$askForMariaDBPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
$askForMariaDBPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($askForMariaDBPassword) {
|
if ($askForMariaDBPassword) {
|
||||||
@ -164,11 +166,11 @@ class DatabaseSettingsCommand extends Command
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->database->connection('_panel_command_test')->getPdo();
|
$this->database->connection('_panel_command_test')->getPdo();
|
||||||
} catch (\PDOException $exception) {
|
} catch (PDOException $exception) {
|
||||||
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||||
$this->output->error(__('commands.database_settings.DB_error_2'));
|
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||||
|
|
||||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
if ($this->confirm(trans('commands.database_settings.go_back'))) {
|
||||||
$this->database->disconnect('_panel_command_test');
|
$this->database->disconnect('_panel_command_test');
|
||||||
|
|
||||||
return $this->handle();
|
return $this->handle();
|
||||||
@ -179,7 +181,7 @@ class DatabaseSettingsCommand extends Command
|
|||||||
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
||||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||||
'Database Path',
|
'Database Path',
|
||||||
env('DB_DATABASE', 'database.sqlite')
|
(string) env('DB_DATABASE', 'database.sqlite')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Environment;
|
namespace App\Console\Commands\Environment;
|
||||||
|
|
||||||
|
use App\Exceptions\PanelException;
|
||||||
use App\Traits\EnvironmentWriterTrait;
|
use App\Traits\EnvironmentWriterTrait;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
@ -22,12 +23,13 @@ class EmailSettingsCommand extends Command
|
|||||||
{--username=}
|
{--username=}
|
||||||
{--password=}';
|
{--password=}';
|
||||||
|
|
||||||
|
/** @var array<array-key, mixed> */
|
||||||
protected array $variables = [];
|
protected array $variables = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle command execution.
|
* Handle command execution.
|
||||||
*
|
*
|
||||||
* @throws \App\Exceptions\PanelException
|
* @throws PanelException
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@ -91,7 +93,7 @@ class EmailSettingsCommand extends Command
|
|||||||
trans('command/messages.environment.mail.ask_smtp_password')
|
trans('command/messages.environment.mail.ask_smtp_password')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice(
|
$this->variables['MAIL_SCHEME'] = $this->option('encryption') ?? $this->choice(
|
||||||
trans('command/messages.environment.mail.ask_encryption'),
|
trans('command/messages.environment.mail.ask_encryption'),
|
||||||
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
|
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
|
||||||
config('mail.mailers.smtp.encryption', 'tls')
|
config('mail.mailers.smtp.encryption', 'tls')
|
||||||
|
|||||||
@ -27,8 +27,6 @@ class QueueSettingsCommand extends Command
|
|||||||
{--redis-pass= : Password used to connect to redis.}
|
{--redis-pass= : Password used to connect to redis.}
|
||||||
{--redis-port= : Port to connect to redis over.}';
|
{--redis-port= : Port to connect to redis over.}';
|
||||||
|
|
||||||
protected array $variables = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QueueSettingsCommand constructor.
|
* QueueSettingsCommand constructor.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -18,10 +18,21 @@ class QueueWorkerServiceCommand extends Command
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
if (@file_exists('/.dockerenv')) {
|
||||||
|
$result = Process::run('supervisorctl restart queue-worker');
|
||||||
|
if ($result->failed()) {
|
||||||
|
$this->error('Error restarting service: ' . $result->errorOutput());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->line('Queue worker service file updated successfully.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
|
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
|
||||||
$path = '/etc/systemd/system/' . $serviceName . '.service';
|
$path = '/etc/systemd/system/' . $serviceName . '.service';
|
||||||
|
|
||||||
$fileExists = file_exists($path);
|
$fileExists = @file_exists($path);
|
||||||
if ($fileExists && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) {
|
if ($fileExists && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) {
|
||||||
$this->line('Creation of queue worker service file aborted because service file already exists.');
|
$this->line('Creation of queue worker service file aborted because service file already exists.');
|
||||||
|
|
||||||
|
|||||||
@ -20,8 +20,6 @@ class RedisSetupCommand extends Command
|
|||||||
{--redis-pass= : Password used to connect to redis.}
|
{--redis-pass= : Password used to connect to redis.}
|
||||||
{--redis-port= : Port to connect to redis over.}';
|
{--redis-port= : Port to connect to redis over.}';
|
||||||
|
|
||||||
protected array $variables = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RedisSetupCommand constructor.
|
* RedisSetupCommand constructor.
|
||||||
*/
|
*/
|
||||||
@ -37,7 +35,7 @@ class RedisSetupCommand extends Command
|
|||||||
{
|
{
|
||||||
$this->variables['CACHE_STORE'] = 'redis';
|
$this->variables['CACHE_STORE'] = 'redis';
|
||||||
$this->variables['QUEUE_CONNECTION'] = 'redis';
|
$this->variables['QUEUE_CONNECTION'] = 'redis';
|
||||||
$this->variables['SESSION_DRIVERS'] = 'redis';
|
$this->variables['SESSION_DRIVER'] = 'redis';
|
||||||
|
|
||||||
$this->requestRedisSettings();
|
$this->requestRedisSettings();
|
||||||
|
|
||||||
|
|||||||
@ -28,8 +28,6 @@ class SessionSettingsCommand extends Command
|
|||||||
{--redis-pass= : Password used to connect to redis.}
|
{--redis-pass= : Password used to connect to redis.}
|
||||||
{--redis-port= : Port to connect to redis over.}';
|
{--redis-port= : Port to connect to redis over.}';
|
||||||
|
|
||||||
protected array $variables = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SessionSettingsCommand constructor.
|
* SessionSettingsCommand constructor.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -4,8 +4,9 @@ namespace App\Console\Commands\Maintenance;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
|
||||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||||
|
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||||
|
use SplFileInfo;
|
||||||
|
|
||||||
class CleanServiceBackupFilesCommand extends Command
|
class CleanServiceBackupFilesCommand extends Command
|
||||||
{
|
{
|
||||||
@ -32,9 +33,10 @@ class CleanServiceBackupFilesCommand extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
/** @var SplFileInfo[] */
|
||||||
$files = $this->disk->files('services/.bak');
|
$files = $this->disk->files('services/.bak');
|
||||||
|
|
||||||
collect($files)->each(function (\SplFileInfo $file) {
|
collect($files)->each(function ($file) {
|
||||||
$lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath()));
|
$lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath()));
|
||||||
if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) {
|
if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) {
|
||||||
$this->disk->delete($file->getPath());
|
$this->disk->delete($file->getPath());
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace App\Console\Commands\Maintenance;
|
|||||||
use App\Models\Backup;
|
use App\Models\Backup;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class PruneOrphanedBackupsCommand extends Command
|
class PruneOrphanedBackupsCommand extends Command
|
||||||
{
|
{
|
||||||
@ -16,7 +17,7 @@ class PruneOrphanedBackupsCommand extends Command
|
|||||||
{
|
{
|
||||||
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
||||||
if (!$since || !is_digit($since)) {
|
if (!$since || !is_digit($since)) {
|
||||||
throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = Backup::query()
|
$query = Backup::query()
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Node;
|
namespace App\Console\Commands\Node;
|
||||||
|
|
||||||
|
use App\Exceptions\Model\DataValidationException;
|
||||||
|
use App\Models\Node;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use App\Services\Nodes\NodeCreationService;
|
|
||||||
|
|
||||||
class MakeNodeCommand extends Command
|
class MakeNodeCommand extends Command
|
||||||
{
|
{
|
||||||
@ -24,52 +25,46 @@ class MakeNodeCommand extends Command
|
|||||||
{--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).}
|
{--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).}
|
||||||
{--uploadSize= : Enter the maximum upload filesize.}
|
{--uploadSize= : Enter the maximum upload filesize.}
|
||||||
{--daemonListeningPort= : Enter the daemon listening port.}
|
{--daemonListeningPort= : Enter the daemon listening port.}
|
||||||
|
{--daemonConnectingPort= : Enter the daemon connecting port.}
|
||||||
{--daemonSFTPPort= : Enter the daemon SFTP listening port.}
|
{--daemonSFTPPort= : Enter the daemon SFTP listening port.}
|
||||||
{--daemonSFTPAlias= : Enter the daemon SFTP alias.}
|
{--daemonSFTPAlias= : Enter the daemon SFTP alias.}
|
||||||
{--daemonBase= : Enter the base folder.}';
|
{--daemonBase= : Enter the base folder.}';
|
||||||
|
|
||||||
protected $description = 'Creates a new node on the system via the CLI.';
|
protected $description = 'Creates a new node on the system via the CLI.';
|
||||||
|
|
||||||
/**
|
|
||||||
* MakeNodeCommand constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(private NodeCreationService $creationService)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the command execution process.
|
* Handle the command execution process.
|
||||||
*
|
*
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
* @throws DataValidationException
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$data['name'] = $this->option('name') ?? $this->ask(__('commands.make_node.name'));
|
$data['name'] = $this->option('name') ?? $this->ask(trans('commands.make_node.name'));
|
||||||
$data['description'] = $this->option('description') ?? $this->ask(__('commands.make_node.description'));
|
$data['description'] = $this->option('description') ?? $this->ask(trans('commands.make_node.description'));
|
||||||
$data['scheme'] = $this->option('scheme') ?? $this->anticipate(
|
$data['scheme'] = $this->option('scheme') ?? $this->anticipate(
|
||||||
__('commands.make_node.scheme'),
|
trans('commands.make_node.scheme'),
|
||||||
['https', 'http'],
|
['https', 'http'],
|
||||||
'https'
|
'https'
|
||||||
);
|
);
|
||||||
|
|
||||||
$data['fqdn'] = $this->option('fqdn') ?? $this->ask(__('commands.make_node.fqdn'));
|
$data['fqdn'] = $this->option('fqdn') ?? $this->ask(trans('commands.make_node.fqdn'));
|
||||||
$data['public'] = $this->option('public') ?? $this->confirm(__('commands.make_node.public'), true);
|
$data['public'] = $this->option('public') ?? $this->confirm(trans('commands.make_node.public'), true);
|
||||||
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(__('commands.make_node.behind_proxy'));
|
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(trans('commands.make_node.behind_proxy'));
|
||||||
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(__('commands.make_node.maintenance_mode'));
|
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(trans('commands.make_node.maintenance_mode'));
|
||||||
$data['memory'] = $this->option('maxMemory') ?? $this->ask(__('commands.make_node.memory'), '0');
|
$data['memory'] = $this->option('maxMemory') ?? $this->ask(trans('commands.make_node.memory'), '0');
|
||||||
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(__('commands.make_node.memory_overallocate'), '-1');
|
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(trans('commands.make_node.memory_overallocate'), '-1');
|
||||||
$data['disk'] = $this->option('maxDisk') ?? $this->ask(__('commands.make_node.disk'), '0');
|
$data['disk'] = $this->option('maxDisk') ?? $this->ask(trans('commands.make_node.disk'), '0');
|
||||||
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(__('commands.make_node.disk_overallocate'), '-1');
|
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(trans('commands.make_node.disk_overallocate'), '-1');
|
||||||
$data['cpu'] = $this->option('maxCpu') ?? $this->ask(__('commands.make_node.cpu'), '0');
|
$data['cpu'] = $this->option('maxCpu') ?? $this->ask(trans('commands.make_node.cpu'), '0');
|
||||||
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(__('commands.make_node.cpu_overallocate'), '-1');
|
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(trans('commands.make_node.cpu_overallocate'), '-1');
|
||||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(__('commands.make_node.upload_size'), '256');
|
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(trans('commands.make_node.upload_size'), '256');
|
||||||
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(__('commands.make_node.daemonListen'), '8080');
|
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(trans('commands.make_node.daemonListen'), '8080');
|
||||||
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(__('commands.make_node.daemonSFTP'), '2022');
|
$data['daemon_connect'] = $this->option('daemonConnectingPort') ?? $this->ask(trans('commands.make_node.daemonConnect'), '8080');
|
||||||
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(__('commands.make_node.daemonSFTPAlias'), '');
|
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(trans('commands.make_node.daemonSFTP'), '2022');
|
||||||
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(__('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(trans('commands.make_node.daemonSFTPAlias'), '');
|
||||||
|
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(trans('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
||||||
|
|
||||||
$node = $this->creationService->handle($data);
|
$node = Node::create($data);
|
||||||
$this->line(__('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id]));
|
$this->line(trans('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,16 +17,16 @@ class NodeConfigurationCommand extends Command
|
|||||||
{
|
{
|
||||||
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
|
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
|
||||||
|
|
||||||
/** @var \App\Models\Node $node */
|
/** @var Node $node */
|
||||||
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
||||||
$this->error(__('commands.node_config.error_not_exist'));
|
$this->error(trans('commands.node_config.error_not_exist'));
|
||||||
|
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
$format = $this->option('format');
|
$format = $this->option('format');
|
||||||
if (!in_array($format, ['yaml', 'yml', 'json'])) {
|
if (!in_array($format, ['yaml', 'yml', 'json'])) {
|
||||||
$this->error(__('commands.node_config.error_invalid_format'));
|
$this->error(trans('commands.node_config.error_invalid_format'));
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,12 +13,12 @@ class KeyGenerateCommand extends BaseKeyGenerateCommand
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
if (!empty(config('app.key')) && $this->input->isInteractive()) {
|
if (!empty(config('app.key')) && $this->input->isInteractive()) {
|
||||||
$this->output->warning(__('commands.key_generate.error_already_exist'));
|
$this->output->warning(trans('commands.key_generate.error_already_exist'));
|
||||||
if (!$this->confirm(__('commands.key_generate.understand'))) {
|
if (!$this->confirm(trans('commands.key_generate.understand'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->confirm(__('commands.key_generate.continue'))) {
|
if (!$this->confirm(trans('commands.key_generate.continue'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Schedule;
|
namespace App\Console\Commands\Schedule;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use App\Models\Schedule;
|
use App\Models\Schedule;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use App\Services\Schedules\ProcessScheduleService;
|
use App\Services\Schedules\ProcessScheduleService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class ProcessRunnableCommand extends Command
|
class ProcessRunnableCommand extends Command
|
||||||
{
|
{
|
||||||
@ -13,10 +14,7 @@ class ProcessRunnableCommand extends Command
|
|||||||
|
|
||||||
protected $description = 'Process schedules in the database and determine which are ready to run.';
|
protected $description = 'Process schedules in the database and determine which are ready to run.';
|
||||||
|
|
||||||
/**
|
public function handle(ProcessScheduleService $processScheduleService): int
|
||||||
* Handle command execution.
|
|
||||||
*/
|
|
||||||
public function handle(): int
|
|
||||||
{
|
{
|
||||||
$schedules = Schedule::query()
|
$schedules = Schedule::query()
|
||||||
->with('tasks')
|
->with('tasks')
|
||||||
@ -27,7 +25,7 @@ class ProcessRunnableCommand extends Command
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
if ($schedules->count() < 1) {
|
if ($schedules->count() < 1) {
|
||||||
$this->line(__('commands.schedule.process.no_tasks'));
|
$this->line(trans('commands.schedule.process.no_tasks'));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -35,7 +33,7 @@ class ProcessRunnableCommand extends Command
|
|||||||
$bar = $this->output->createProgressBar(count($schedules));
|
$bar = $this->output->createProgressBar(count($schedules));
|
||||||
foreach ($schedules as $schedule) {
|
foreach ($schedules as $schedule) {
|
||||||
$bar->clear();
|
$bar->clear();
|
||||||
$this->processSchedule($schedule);
|
$this->processSchedule($processScheduleService, $schedule);
|
||||||
$bar->advance();
|
$bar->advance();
|
||||||
$bar->display();
|
$bar->display();
|
||||||
}
|
}
|
||||||
@ -50,23 +48,23 @@ class ProcessRunnableCommand extends Command
|
|||||||
* never throw an exception out, otherwise you'll end up killing the entire run group causing
|
* never throw an exception out, otherwise you'll end up killing the entire run group causing
|
||||||
* any other schedules to not process correctly.
|
* any other schedules to not process correctly.
|
||||||
*/
|
*/
|
||||||
protected function processSchedule(Schedule $schedule): void
|
protected function processSchedule(ProcessScheduleService $processScheduleService, Schedule $schedule): void
|
||||||
{
|
{
|
||||||
if ($schedule->tasks->isEmpty()) {
|
if ($schedule->tasks->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->getLaravel()->make(ProcessScheduleService::class)->handle($schedule);
|
$processScheduleService->handle($schedule);
|
||||||
|
|
||||||
$this->line(trans('command/messages.schedule.output_line', [
|
$this->line(trans('command/messages.schedule.output_line', [
|
||||||
'schedule' => $schedule->name,
|
'schedule' => $schedule->name,
|
||||||
'id' => $schedule->id,
|
'id' => $schedule->id,
|
||||||
]));
|
]));
|
||||||
} catch (\Throwable|\Exception $exception) {
|
} catch (Throwable $exception) {
|
||||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||||
|
|
||||||
$this->error(__('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage());
|
$this->error(trans('commands.schedule.process.error_message', ['schedules' => " #$schedule->id: " . $exception->getMessage()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
namespace App\Console\Commands\Server;
|
namespace App\Console\Commands\Server;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
use Illuminate\Validation\ValidationException;
|
||||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
|
||||||
|
|
||||||
class BulkPowerActionCommand extends Command
|
class BulkPowerActionCommand extends Command
|
||||||
{
|
{
|
||||||
@ -19,26 +19,13 @@ class BulkPowerActionCommand extends Command
|
|||||||
|
|
||||||
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
||||||
|
|
||||||
/**
|
public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
|
||||||
* BulkPowerActionCommand constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(private DaemonPowerRepository $powerRepository, private ValidatorFactory $validator)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the bulk power request.
|
|
||||||
*
|
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
|
||||||
{
|
{
|
||||||
$action = $this->argument('action');
|
$action = $this->argument('action');
|
||||||
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
||||||
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
|
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
|
||||||
|
|
||||||
$validator = $this->validator->make([
|
$validator = $validator->make([
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'nodes' => $nodes,
|
'nodes' => $nodes,
|
||||||
'servers' => $servers,
|
'servers' => $servers,
|
||||||
@ -64,14 +51,17 @@ class BulkPowerActionCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$bar = $this->output->createProgressBar($count);
|
$bar = $this->output->createProgressBar($count);
|
||||||
$powerRepository = $this->powerRepository;
|
|
||||||
// @phpstan-ignore-next-line
|
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
|
||||||
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
|
|
||||||
$bar->clear();
|
$bar->clear();
|
||||||
|
|
||||||
|
if (!$server instanceof Server) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$powerRepository->setServer($server)->send($action);
|
$serverRepository->setServer($server)->power($action);
|
||||||
} catch (DaemonConnectionException $exception) {
|
} catch (Exception $exception) {
|
||||||
$this->output->error(trans('command/messages.server.power.action_failed', [
|
$this->output->error(trans('command/messages.server.power.action_failed', [
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
'id' => $server->id,
|
'id' => $server->id,
|
||||||
@ -82,6 +72,8 @@ class BulkPowerActionCommand extends Command
|
|||||||
|
|
||||||
$bar->advance();
|
$bar->advance();
|
||||||
$bar->display();
|
$bar->display();
|
||||||
|
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->line('');
|
$this->line('');
|
||||||
@ -89,6 +81,9 @@ class BulkPowerActionCommand extends Command
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the query builder instance that will return the servers that should be affected.
|
* Returns the query builder instance that will return the servers that should be affected.
|
||||||
|
*
|
||||||
|
* @param string[]|int[] $servers
|
||||||
|
* @param string[]|int[] $nodes
|
||||||
*/
|
*/
|
||||||
protected function getQueryBuilder(array $servers, array $nodes): Builder
|
protected function getQueryBuilder(array $servers, array $nodes): Builder
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,197 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use App\Console\Kernel;
|
|
||||||
use Symfony\Component\Process\Process;
|
|
||||||
use Symfony\Component\Console\Helper\ProgressBar;
|
|
||||||
|
|
||||||
class UpgradeCommand extends Command
|
|
||||||
{
|
|
||||||
protected const DEFAULT_URL = 'https://github.com/pelican-dev/panel/releases/%s/panel.tar.gz';
|
|
||||||
|
|
||||||
protected $signature = 'p:upgrade
|
|
||||||
{--user= : The user that PHP runs under. All files will be owned by this user.}
|
|
||||||
{--group= : The group that PHP runs under. All files will be owned by this group.}
|
|
||||||
{--url= : The specific archive to download.}
|
|
||||||
{--release= : A specific version to download from GitHub. Leave blank to use latest.}
|
|
||||||
{--skip-download : If set no archive will be downloaded.}';
|
|
||||||
|
|
||||||
protected $description = 'Downloads a new archive from GitHub and then executes the normal upgrade commands.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes an upgrade command which will run through all of our standard
|
|
||||||
* Panel commands and enable users to basically just download
|
|
||||||
* the archive and execute this and be done.
|
|
||||||
*
|
|
||||||
* This places the application in maintenance mode as well while the commands
|
|
||||||
* are being executed.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
$skipDownload = $this->option('skip-download');
|
|
||||||
if (!$skipDownload) {
|
|
||||||
$this->output->warning(__('commands.upgrade.integrity'));
|
|
||||||
$this->output->comment(__('commands.upgrade.source_url'));
|
|
||||||
$this->line($this->getUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version_compare(PHP_VERSION, '7.4.0') < 0) {
|
|
||||||
$this->error(__('commands.upgrade.php_version') . ' [' . PHP_VERSION . '].');
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = 'www-data';
|
|
||||||
$group = 'www-data';
|
|
||||||
if ($this->input->isInteractive()) {
|
|
||||||
if (!$skipDownload) {
|
|
||||||
$skipDownload = !$this->confirm(__('commands.upgrade.skipDownload'), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($this->option('user'))) {
|
|
||||||
$userDetails = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner('public')) : [];
|
|
||||||
$user = $userDetails['name'] ?? 'www-data';
|
|
||||||
|
|
||||||
$message = __('commands.upgrade.webserver_user', ['user' => $user]);
|
|
||||||
if (!$this->confirm($message, true)) {
|
|
||||||
$user = $this->anticipate(
|
|
||||||
__('commands.upgrade.name_webserver'),
|
|
||||||
[
|
|
||||||
'www-data',
|
|
||||||
'nginx',
|
|
||||||
'apache',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($this->option('group'))) {
|
|
||||||
$groupDetails = function_exists('posix_getgrgid') ? posix_getgrgid(filegroup('public')) : [];
|
|
||||||
$group = $groupDetails['name'] ?? 'www-data';
|
|
||||||
|
|
||||||
$message = __('commands.upgrade.group_webserver', ['group' => $user]);
|
|
||||||
if (!$this->confirm($message, true)) {
|
|
||||||
$group = $this->anticipate(
|
|
||||||
__('commands.upgrade.group_webserver_question'),
|
|
||||||
[
|
|
||||||
'www-data',
|
|
||||||
'nginx',
|
|
||||||
'apache',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->confirm(__('commands.upgrade.are_your_sure'))) {
|
|
||||||
$this->warn(__('commands.upgrade.terminated'));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ini_set('output_buffering', '0');
|
|
||||||
$bar = $this->output->createProgressBar($skipDownload ? 9 : 10);
|
|
||||||
$bar->start();
|
|
||||||
|
|
||||||
if (!$skipDownload) {
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line("\$upgrader> curl -L \"{$this->getUrl()}\" | tar -xzv");
|
|
||||||
$process = Process::fromShellCommandline("curl -L \"{$this->getUrl()}\" | tar -xzv");
|
|
||||||
$process->run(function ($type, $buffer) {
|
|
||||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> php artisan down');
|
|
||||||
$this->call('down');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> chmod -R 755 storage bootstrap/cache');
|
|
||||||
$process = new Process(['chmod', '-R', '755', 'storage', 'bootstrap/cache']);
|
|
||||||
$process->run(function ($type, $buffer) {
|
|
||||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$command = ['composer', 'install', '--no-ansi'];
|
|
||||||
if (config('app.env') === 'production' && !config('app.debug')) {
|
|
||||||
$command[] = '--optimize-autoloader';
|
|
||||||
$command[] = '--no-dev';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->line('$upgrader> ' . implode(' ', $command));
|
|
||||||
$process = new Process($command);
|
|
||||||
$process->setTimeout(10 * 60);
|
|
||||||
$process->run(function ($type, $buffer) {
|
|
||||||
$this->line($buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @var \Illuminate\Foundation\Application $app */
|
|
||||||
$app = require __DIR__ . '/../../../bootstrap/app.php';
|
|
||||||
/** @var \App\Console\Kernel $kernel */
|
|
||||||
$kernel = $app->make(Kernel::class);
|
|
||||||
$kernel->bootstrap();
|
|
||||||
$this->setLaravel($app);
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> php artisan view:clear');
|
|
||||||
$this->call('view:clear');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> php artisan config:clear');
|
|
||||||
$this->call('config:clear');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> php artisan migrate --force --seed');
|
|
||||||
$this->call('migrate', ['--force' => true, '--seed' => true]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () use ($user, $group) {
|
|
||||||
$this->line("\$upgrader> chown -R {$user}:{$group} *");
|
|
||||||
$process = Process::fromShellCommandline("chown -R {$user}:{$group} *", $this->getLaravel()->basePath());
|
|
||||||
$process->setTimeout(10 * 60);
|
|
||||||
$process->run(function ($type, $buffer) {
|
|
||||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> php artisan queue:restart');
|
|
||||||
$this->call('queue:restart');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->withProgress($bar, function () {
|
|
||||||
$this->line('$upgrader> php artisan up');
|
|
||||||
$this->call('up');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->newLine(2);
|
|
||||||
$this->info(__('commands.upgrade.success'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function withProgress(ProgressBar $bar, \Closure $callback): void
|
|
||||||
{
|
|
||||||
$bar->clear();
|
|
||||||
$callback();
|
|
||||||
$bar->advance();
|
|
||||||
$bar->display();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getUrl(): string
|
|
||||||
{
|
|
||||||
if ($this->option('url')) {
|
|
||||||
return $this->option('url');
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf(self::DEFAULT_URL, $this->option('release') ? 'download/v' . $this->option('release') : 'latest/download');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,8 +3,8 @@
|
|||||||
namespace App\Console\Commands\User;
|
namespace App\Console\Commands\User;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Webmozart\Assert\Assert;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
class DeleteUserCommand extends Command
|
class DeleteUserCommand extends Command
|
||||||
{
|
{
|
||||||
@ -35,7 +35,7 @@ class DeleteUserCommand extends Command
|
|||||||
if ($this->input->isInteractive()) {
|
if ($this->input->isInteractive()) {
|
||||||
$tableValues = [];
|
$tableValues = [];
|
||||||
foreach ($results as $user) {
|
foreach ($results as $user) {
|
||||||
$tableValues[] = [$user->id, $user->email, $user->name];
|
$tableValues[] = [$user->id, $user->email, $user->username];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\User;
|
namespace App\Console\Commands\User;
|
||||||
|
|
||||||
|
use App\Exceptions\Model\DataValidationException;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ class DisableTwoFactorCommand extends Command
|
|||||||
/**
|
/**
|
||||||
* Handle command execution process.
|
* Handle command execution process.
|
||||||
*
|
*
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
* @throws DataValidationException
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@ -24,10 +25,12 @@ class DisableTwoFactorCommand extends Command
|
|||||||
|
|
||||||
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
||||||
|
|
||||||
$user = User::query()->where('email', $email)->firstOrFail();
|
$user = User::where('email', $email)->firstOrFail();
|
||||||
$user->use_totp = false;
|
$user->update([
|
||||||
$user->totp_secret = null;
|
'mfa_app_secret' => null,
|
||||||
$user->save();
|
'mfa_app_recovery_codes' => null,
|
||||||
|
'mfa_email_enabled' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
|
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\User;
|
namespace App\Console\Commands\User;
|
||||||
|
|
||||||
|
use App\Exceptions\Model\DataValidationException;
|
||||||
|
use App\Services\Users\UserCreationService;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use App\Services\Users\UserCreationService;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class MakeUserCommand extends Command
|
class MakeUserCommand extends Command
|
||||||
@ -25,7 +26,7 @@ class MakeUserCommand extends Command
|
|||||||
* Handle command request to create a new user.
|
* Handle command request to create a new user.
|
||||||
*
|
*
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws \App\Exceptions\Model\DataValidationException
|
* @throws DataValidationException
|
||||||
*/
|
*/
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
||||||
|
use App\Console\Commands\Egg\UpdateEggIndexCommand;
|
||||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||||
use App\Jobs\NodeStatistics;
|
|
||||||
use App\Models\ActivityLog;
|
use App\Models\ActivityLog;
|
||||||
use App\Models\Webhook;
|
use App\Models\Webhook;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
@ -31,17 +31,20 @@ class Kernel extends ConsoleKernel
|
|||||||
*/
|
*/
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
|
if (config('cache.default') === 'redis') {
|
||||||
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
||||||
|
// This only needs to run when using redis. anything else throws an error.
|
||||||
$schedule->command('cache:prune-stale-tags')->hourly();
|
$schedule->command('cache:prune-stale-tags')->hourly();
|
||||||
|
}
|
||||||
|
|
||||||
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||||
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
||||||
|
|
||||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||||
$schedule->command(PruneImagesCommand::class)->daily();
|
$schedule->command(PruneImagesCommand::class)->daily();
|
||||||
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
|
|
||||||
|
|
||||||
$schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping();
|
$schedule->command(CheckEggUpdatesCommand::class)->daily();
|
||||||
|
$schedule->command(UpdateEggIndexCommand::class)->daily();
|
||||||
|
|
||||||
if (config('backups.prune_age')) {
|
if (config('backups.prune_age')) {
|
||||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||||
|
|||||||
22
app/Contracts/Validatable.php
Normal file
22
app/Contracts/Validatable.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Validator;
|
||||||
|
|
||||||
|
interface Validatable
|
||||||
|
{
|
||||||
|
public function getValidator(): Validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function getRules(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public static function getRulesForField(string $field): array;
|
||||||
|
|
||||||
|
public function validate(): void;
|
||||||
|
}
|
||||||
24
app/Eloquent/BackupQueryBuilder.php
Normal file
24
app/Eloquent/BackupQueryBuilder.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Eloquent;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||||
|
*
|
||||||
|
* @extends Builder<TModel>
|
||||||
|
*/
|
||||||
|
class BackupQueryBuilder extends Builder
|
||||||
|
{
|
||||||
|
public function nonFailed(): self
|
||||||
|
{
|
||||||
|
$this->where(function (Builder $query) {
|
||||||
|
$query
|
||||||
|
->whereNull('completed_at')
|
||||||
|
->orWhere('is_successful', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/Enums/BackupStatus.php
Normal file
37
app/Enums/BackupStatus.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
||||||
|
{
|
||||||
|
case InProgress = 'in_progress';
|
||||||
|
case Successful = 'successful';
|
||||||
|
case Failed = 'failed';
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::InProgress => 'tabler-circle-dashed',
|
||||||
|
self::Successful => 'tabler-circle-check',
|
||||||
|
self::Failed => 'tabler-circle-x',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColor(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::InProgress => 'primary',
|
||||||
|
self::Successful => 'success',
|
||||||
|
self::Failed => 'danger',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('server/backup.backup_status.' . $this->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Enums/ConsoleWidgetPosition.php
Normal file
11
app/Enums/ConsoleWidgetPosition.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ConsoleWidgetPosition: string
|
||||||
|
{
|
||||||
|
case Top = 'top';
|
||||||
|
case AboveConsole = 'above_console';
|
||||||
|
case BelowConsole = 'below_console';
|
||||||
|
case Bottom = 'bottom';
|
||||||
|
}
|
||||||
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
enum ContainerStatus: string
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||||
{
|
{
|
||||||
// Docker Based
|
// Docker Based
|
||||||
case Created = 'created';
|
case Created = 'created';
|
||||||
@ -19,7 +23,7 @@ enum ContainerStatus: string
|
|||||||
// HTTP Based
|
// HTTP Based
|
||||||
case Missing = 'missing';
|
case Missing = 'missing';
|
||||||
|
|
||||||
public function icon(): string
|
public function getIcon(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
|
|
||||||
@ -36,8 +40,17 @@ enum ContainerStatus: string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function color(): string
|
public function getColor(bool $hex = false): string
|
||||||
{
|
{
|
||||||
|
if ($hex) {
|
||||||
|
return match ($this) {
|
||||||
|
self::Created, self::Restarting => '#2563EB',
|
||||||
|
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
|
||||||
|
self::Running => '#22C55E',
|
||||||
|
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Created => 'primary',
|
self::Created => 'primary',
|
||||||
self::Starting => 'warning',
|
self::Starting => 'warning',
|
||||||
@ -49,18 +62,23 @@ enum ContainerStatus: string
|
|||||||
self::Removing => 'warning',
|
self::Removing => 'warning',
|
||||||
self::Missing => 'danger',
|
self::Missing => 'danger',
|
||||||
self::Stopping => 'warning',
|
self::Stopping => 'warning',
|
||||||
self::Offline => 'gray',
|
self::Offline => 'danger',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function colorHex(): string
|
public function getLabel(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return trans('server/console.status.' . $this->value);
|
||||||
self::Created, self::Restarting => '#2563EB',
|
}
|
||||||
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
|
|
||||||
self::Running => '#22C55E',
|
public function isOffline(): bool
|
||||||
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
|
{
|
||||||
};
|
return in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isStartingOrRunning(): bool
|
||||||
|
{
|
||||||
|
return in_array($this, [ContainerStatus::Starting, ContainerStatus::Running]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isStartingOrStopping(): bool
|
public function isStartingOrStopping(): bool
|
||||||
@ -70,7 +88,7 @@ enum ContainerStatus: string
|
|||||||
|
|
||||||
public function isStartable(): bool
|
public function isStartable(): bool
|
||||||
{
|
{
|
||||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
|
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Missing]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isRestartable(): bool
|
public function isRestartable(): bool
|
||||||
@ -79,18 +97,16 @@ enum ContainerStatus: string
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !in_array($this, [ContainerStatus::Offline]);
|
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isStoppable(): bool
|
public function isStoppable(): bool
|
||||||
{
|
{
|
||||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline]);
|
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isKillable(): bool
|
public function isKillable(): bool
|
||||||
{
|
{
|
||||||
// [ContainerStatus::Restarting, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Created]
|
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited, ContainerStatus::Missing]);
|
||||||
|
|
||||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
app/Enums/CustomRenderHooks.php
Normal file
9
app/Enums/CustomRenderHooks.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum CustomRenderHooks: string
|
||||||
|
{
|
||||||
|
case FooterStart = 'pelican::footer.start';
|
||||||
|
case FooterEnd = 'pelican::footer.end';
|
||||||
|
}
|
||||||
37
app/Enums/CustomizationKey.php
Normal file
37
app/Enums/CustomizationKey.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum CustomizationKey: string
|
||||||
|
{
|
||||||
|
case ConsoleRows = 'console_rows';
|
||||||
|
case ConsoleFont = 'console_font';
|
||||||
|
case ConsoleFontSize = 'console_font_size';
|
||||||
|
case ConsoleGraphPeriod = 'console_graph_period';
|
||||||
|
case TopNavigation = 'top_navigation';
|
||||||
|
case DashboardLayout = 'dashboard_layout';
|
||||||
|
|
||||||
|
public function getDefaultValue(): string|int|bool
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::ConsoleRows => 30,
|
||||||
|
self::ConsoleFont => 'monospace',
|
||||||
|
self::ConsoleFontSize => 14,
|
||||||
|
self::ConsoleGraphPeriod => 30,
|
||||||
|
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
||||||
|
self::DashboardLayout => 'grid',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, string|int|bool> */
|
||||||
|
public static function getDefaultCustomization(): array
|
||||||
|
{
|
||||||
|
$default = [];
|
||||||
|
|
||||||
|
foreach (self::cases() as $key) {
|
||||||
|
$default[$key->value] = $key->getDefaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,98 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Enums;
|
|
||||||
|
|
||||||
use Filament\Support\Contracts\HasLabel;
|
|
||||||
|
|
||||||
enum EditorLanguages: string implements HasLabel
|
|
||||||
{
|
|
||||||
case plaintext = 'plaintext';
|
|
||||||
case abap = 'abap';
|
|
||||||
case apex = 'apex';
|
|
||||||
case azcali = 'azcali';
|
|
||||||
case bat = 'bat';
|
|
||||||
case bicep = 'bicep';
|
|
||||||
case cameligo = 'cameligo';
|
|
||||||
case coljure = 'coljure';
|
|
||||||
case coffeescript = 'coffeescript';
|
|
||||||
case c = 'c';
|
|
||||||
case cpp = 'cpp';
|
|
||||||
case csharp = 'csharp';
|
|
||||||
case csp = 'csp';
|
|
||||||
case css = 'css';
|
|
||||||
case cypher = 'cypher';
|
|
||||||
case dart = 'dart';
|
|
||||||
case dockerfile = 'dockerfile';
|
|
||||||
case ecl = 'ecl';
|
|
||||||
case elixir = 'elixir';
|
|
||||||
case flow9 = 'flow9';
|
|
||||||
case fsharp = 'fsharp';
|
|
||||||
case go = 'go';
|
|
||||||
case graphql = 'graphql';
|
|
||||||
case handlebars = 'handlebars';
|
|
||||||
case hcl = 'hcl';
|
|
||||||
case html = 'html';
|
|
||||||
case ini = 'ini';
|
|
||||||
case java = 'java';
|
|
||||||
case javascript = 'javascript';
|
|
||||||
case julia = 'julia';
|
|
||||||
case kotlin = 'kotlin';
|
|
||||||
case less = 'less';
|
|
||||||
case lexon = 'lexon';
|
|
||||||
case lua = 'lua';
|
|
||||||
case liquid = 'liquid';
|
|
||||||
case m3 = 'm3';
|
|
||||||
case markdown = 'markdown';
|
|
||||||
case mdx = 'mdx';
|
|
||||||
case mips = 'mips';
|
|
||||||
case msdax = 'msdax';
|
|
||||||
case mysql = 'mysql';
|
|
||||||
case objectivec = 'objective-c';
|
|
||||||
case pascal = 'pascal';
|
|
||||||
case pascaligo = 'pascaligo';
|
|
||||||
case perl = 'perl';
|
|
||||||
case pgsql = 'pgsql';
|
|
||||||
case php = 'php';
|
|
||||||
case pla = 'pla';
|
|
||||||
case postiats = 'postiats';
|
|
||||||
case powerquery = 'powerquery';
|
|
||||||
case powershell = 'powershell';
|
|
||||||
case proto = 'proto';
|
|
||||||
case pug = 'pug';
|
|
||||||
case python = 'python';
|
|
||||||
case qsharp = 'qsharp';
|
|
||||||
case r = 'r';
|
|
||||||
case razor = 'razor';
|
|
||||||
case redis = 'redis';
|
|
||||||
case redshift = 'redshift';
|
|
||||||
case restructuredtext = 'restructuredtext';
|
|
||||||
case ruby = 'ruby';
|
|
||||||
case rust = 'rust';
|
|
||||||
case sb = 'sb';
|
|
||||||
case scala = 'scala';
|
|
||||||
case scheme = 'scheme';
|
|
||||||
case scss = 'scss';
|
|
||||||
case shell = 'shell';
|
|
||||||
case sol = 'sol';
|
|
||||||
case aes = 'aes';
|
|
||||||
case sparql = 'sparql';
|
|
||||||
case sql = 'sql';
|
|
||||||
case st = 'st';
|
|
||||||
case swift = 'swift';
|
|
||||||
case systemverilog = 'systemverilog';
|
|
||||||
case verilog = 'verilog';
|
|
||||||
case tcl = 'tcl';
|
|
||||||
case twig = 'twig';
|
|
||||||
case typescript = 'typescript';
|
|
||||||
case typespec = 'typespec';
|
|
||||||
case vb = 'vb';
|
|
||||||
case wgsl = 'wgsl';
|
|
||||||
case xml = 'xml';
|
|
||||||
case yaml = 'yaml';
|
|
||||||
case json = 'json';
|
|
||||||
|
|
||||||
public function getLabel(): ?string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
app/Enums/EggFormat.php
Normal file
9
app/Enums/EggFormat.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum EggFormat: string
|
||||||
|
{
|
||||||
|
case YAML = 'yaml';
|
||||||
|
case JSON = 'json';
|
||||||
|
}
|
||||||
9
app/Enums/HeaderActionPosition.php
Normal file
9
app/Enums/HeaderActionPosition.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum HeaderActionPosition: string
|
||||||
|
{
|
||||||
|
case Before = 'before';
|
||||||
|
case After = 'after';
|
||||||
|
}
|
||||||
9
app/Enums/HeaderWidgetPosition.php
Normal file
9
app/Enums/HeaderWidgetPosition.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum HeaderWidgetPosition: string
|
||||||
|
{
|
||||||
|
case Before = 'before';
|
||||||
|
case After = 'after';
|
||||||
|
}
|
||||||
@ -14,4 +14,24 @@ enum RolePermissionModels: string
|
|||||||
case Server = 'server';
|
case Server = 'server';
|
||||||
case User = 'user';
|
case User = 'user';
|
||||||
case Webhook = 'webhook';
|
case Webhook = 'webhook';
|
||||||
|
|
||||||
|
public function viewAny(): string
|
||||||
|
{
|
||||||
|
return RolePermissionPrefixes::ViewAny->value . ' ' . $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
return RolePermissionPrefixes::View->value . ' ' . $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): string
|
||||||
|
{
|
||||||
|
return RolePermissionPrefixes::Create->value . ' ' . $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(): string
|
||||||
|
{
|
||||||
|
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
app/Enums/ScheduleStatus.php
Normal file
27
app/Enums/ScheduleStatus.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum ScheduleStatus: string implements HasColor, HasLabel
|
||||||
|
{
|
||||||
|
case Inactive = 'inactive';
|
||||||
|
case Processing = 'processing';
|
||||||
|
case Active = 'active';
|
||||||
|
|
||||||
|
public function getColor(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Inactive => 'danger',
|
||||||
|
self::Processing => 'warning',
|
||||||
|
self::Active => 'success',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('server/schedule.schedule_status.' . $this->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,50 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
enum ServerResourceType
|
use App\Models\Server;
|
||||||
|
|
||||||
|
enum ServerResourceType: string
|
||||||
{
|
{
|
||||||
case Unit;
|
case Uptime = 'uptime';
|
||||||
case Percentage;
|
case CPU = 'cpu_absolute';
|
||||||
case Time;
|
case Memory = 'memory_bytes';
|
||||||
|
case Disk = 'disk_bytes';
|
||||||
|
|
||||||
|
case CPULimit = 'cpu';
|
||||||
|
case MemoryLimit = 'memory';
|
||||||
|
case DiskLimit = 'disk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int resource amount in bytes
|
||||||
|
*/
|
||||||
|
public function getResourceAmount(Server $server): int
|
||||||
|
{
|
||||||
|
if ($this->isLimit()) {
|
||||||
|
$resourceAmount = $server->{$this->value} ?? 0;
|
||||||
|
|
||||||
|
if (!$this->isPercentage()) {
|
||||||
|
// Our limits are entered as MiB/ MB so we need to convert them to bytes
|
||||||
|
$resourceAmount *= config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resourceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $server->retrieveResources()[$this->value] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLimit(): bool
|
||||||
|
{
|
||||||
|
return $this === ServerResourceType::CPULimit || $this === ServerResourceType::MemoryLimit || $this === ServerResourceType::DiskLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTime(): bool
|
||||||
|
{
|
||||||
|
return $this === ServerResourceType::Uptime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPercentage(): bool
|
||||||
|
{
|
||||||
|
return $this === ServerResourceType::CPU || $this === ServerResourceType::CPULimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
enum ServerState: string
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||||
{
|
{
|
||||||
case Normal = 'normal';
|
|
||||||
case Installing = 'installing';
|
case Installing = 'installing';
|
||||||
case InstallFailed = 'install_failed';
|
case InstallFailed = 'install_failed';
|
||||||
case ReinstallFailed = 'reinstall_failed';
|
case ReinstallFailed = 'reinstall_failed';
|
||||||
case Suspended = 'suspended';
|
case Suspended = 'suspended';
|
||||||
case RestoringBackup = 'restoring_backup';
|
case RestoringBackup = 'restoring_backup';
|
||||||
|
|
||||||
public function icon(): string
|
public function getIcon(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Normal => 'tabler-heart',
|
|
||||||
self::Installing => 'tabler-heart-bolt',
|
self::Installing => 'tabler-heart-bolt',
|
||||||
self::InstallFailed => 'tabler-heart-x',
|
self::InstallFailed => 'tabler-heart-x',
|
||||||
self::ReinstallFailed => 'tabler-heart-x',
|
self::ReinstallFailed => 'tabler-heart-x',
|
||||||
@ -23,10 +25,17 @@ enum ServerState: string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function color(): string
|
public function getColor(bool $hex = false): string
|
||||||
{
|
{
|
||||||
|
if ($hex) {
|
||||||
|
return match ($this) {
|
||||||
|
self::Installing, self::RestoringBackup => '#2563EB',
|
||||||
|
self::Suspended => '#D97706',
|
||||||
|
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Normal => 'primary',
|
|
||||||
self::Installing => 'primary',
|
self::Installing => 'primary',
|
||||||
self::InstallFailed => 'danger',
|
self::InstallFailed => 'danger',
|
||||||
self::ReinstallFailed => 'danger',
|
self::ReinstallFailed => 'danger',
|
||||||
@ -34,4 +43,9 @@ enum ServerState: string
|
|||||||
self::RestoringBackup => 'primary',
|
self::RestoringBackup => 'primary',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return str($this->value)->headline();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
app/Enums/StartupVariableType.php
Normal file
11
app/Enums/StartupVariableType.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum StartupVariableType: string
|
||||||
|
{
|
||||||
|
case Text = 'text';
|
||||||
|
case Number = 'number';
|
||||||
|
case Select = 'select';
|
||||||
|
case Toggle = 'toggle';
|
||||||
|
}
|
||||||
9
app/Enums/SuspendAction.php
Normal file
9
app/Enums/SuspendAction.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum SuspendAction: string
|
||||||
|
{
|
||||||
|
case Suspend = 'suspend';
|
||||||
|
case Unsuspend = 'unsuspend';
|
||||||
|
}
|
||||||
34
app/Enums/WebhookType.php
Normal file
34
app/Enums/WebhookType.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum WebhookType: string implements HasColor, HasIcon, HasLabel
|
||||||
|
{
|
||||||
|
case Regular = 'regular';
|
||||||
|
case Discord = 'discord';
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/webhook.' . $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColor(): ?string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Regular => null,
|
||||||
|
self::Discord => 'blurple',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Regular => 'tabler-world-www',
|
||||||
|
self::Discord => 'tabler-brand-discord',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use App\Models\ActivityLog;
|
use App\Models\ActivityLog;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ActivityLogged extends Event
|
class ActivityLogged extends Event
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Events\Auth;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Events\Event;
|
|
||||||
|
|
||||||
class DirectLogin extends Event
|
|
||||||
{
|
|
||||||
public function __construct(public User $user, public bool $remember) {}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Events\Auth;
|
|
||||||
|
|
||||||
use App\Events\Event;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class FailedPasswordReset extends Event
|
|
||||||
{
|
|
||||||
use SerializesModels;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new event instance.
|
|
||||||
*/
|
|
||||||
public function __construct(public string $ip, public string $email) {}
|
|
||||||
}
|
|
||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Events\Auth;
|
namespace App\Events\Auth;
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Events\Event;
|
use App\Events\Event;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
class ProvidedAuthenticationToken extends Event
|
class ProvidedAuthenticationToken extends Event
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,14 +4,18 @@ namespace App\Exceptions;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Container\Container;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
class DisplayException extends PanelException implements HttpExceptionInterface
|
class DisplayException extends PanelException implements HttpExceptionInterface
|
||||||
{
|
{
|
||||||
public const LEVEL_DEBUG = 'debug';
|
public const LEVEL_DEBUG = 'debug';
|
||||||
@ -25,7 +29,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
|||||||
/**
|
/**
|
||||||
* DisplayException constructor.
|
* DisplayException constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
public function __construct(string $message, ?Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
}
|
}
|
||||||
@ -40,6 +44,9 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
|||||||
return Response::HTTP_BAD_REQUEST;
|
return Response::HTTP_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
public function getHeaders(): array
|
public function getHeaders(): array
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
@ -73,11 +80,11 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
|||||||
* Log the exception to the logs using the defined error level only if the previous
|
* Log the exception to the logs using the defined error level only if the previous
|
||||||
* exception is set.
|
* exception is set.
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function report(): void
|
public function report(): void
|
||||||
{
|
{
|
||||||
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
|
if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,24 +2,28 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Illuminate\Support\Arr;
|
use Exception;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Auth\AuthenticationException;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Container\Container;
|
use Illuminate\Container\Container;
|
||||||
use Illuminate\Database\Connection;
|
use Illuminate\Database\Connection;
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Auth\AuthenticationException;
|
|
||||||
use Illuminate\Session\TokenMismatchException;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Illuminate\Auth\Access\AuthorizationException;
|
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Illuminate\Foundation\Application;
|
||||||
use Symfony\Component\Mailer\Exception\TransportException;
|
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Session\TokenMismatchException;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use PDOException;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
use Symfony\Component\Mailer\Exception\TransportException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
@ -45,6 +49,8 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Maps exceptions to a specific response code. This handles special exception
|
* Maps exceptions to a specific response code. This handles special exception
|
||||||
* types that don't have a defined response code.
|
* types that don't have a defined response code.
|
||||||
|
*
|
||||||
|
* @var array<class-string, int>
|
||||||
*/
|
*/
|
||||||
protected static array $exceptionResponseCodes = [
|
protected static array $exceptionResponseCodes = [
|
||||||
AuthenticationException::class => 401,
|
AuthenticationException::class => 401,
|
||||||
@ -76,7 +82,7 @@ class Handler extends ExceptionHandler
|
|||||||
$this->dontReport = [];
|
$this->dontReport = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->reportable(function (\PDOException $ex) {
|
$this->reportable(function (PDOException $ex) {
|
||||||
$ex = $this->generateCleanedExceptionStack($ex);
|
$ex = $this->generateCleanedExceptionStack($ex);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,7 +91,7 @@ class Handler extends ExceptionHandler
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateCleanedExceptionStack(\Throwable $exception): string
|
private function generateCleanedExceptionStack(Throwable $exception): string
|
||||||
{
|
{
|
||||||
$cleanedStack = '';
|
$cleanedStack = '';
|
||||||
foreach ($exception->getTrace() as $index => $item) {
|
foreach ($exception->getTrace() as $index => $item) {
|
||||||
@ -114,11 +120,11 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Render an exception into an HTTP response.
|
* Render an exception into an HTTP response.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function render($request, \Throwable $e): Response
|
public function render($request, Throwable $e): Response
|
||||||
{
|
{
|
||||||
$connections = $this->container->make(Connection::class);
|
$connections = $this->container->make(Connection::class);
|
||||||
|
|
||||||
@ -140,7 +146,7 @@ class Handler extends ExceptionHandler
|
|||||||
* Transform a validation exception into a consistent format to be returned for
|
* Transform a validation exception into a consistent format to be returned for
|
||||||
* calls to the API.
|
* calls to the API.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*/
|
*/
|
||||||
public function invalidJson($request, ValidationException $exception): JsonResponse
|
public function invalidJson($request, ValidationException $exception): JsonResponse
|
||||||
{
|
{
|
||||||
@ -180,9 +186,16 @@ class Handler extends ExceptionHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the exception as a JSONAPI representation for use on API requests.
|
* @param array<string, mixed> $override
|
||||||
|
* @return array{errors: array{
|
||||||
|
* code: string,
|
||||||
|
* status: string,
|
||||||
|
* detail: string,
|
||||||
|
* source?: array{line: int, file: string},
|
||||||
|
* meta?: array{trace: string[], previous: string[]}
|
||||||
|
* }}|array{errors: array{non-empty-array<string, mixed>}}
|
||||||
*/
|
*/
|
||||||
protected function convertExceptionToArray(\Throwable $e, array $override = []): array
|
public static function exceptionToArray(Throwable $e, array $override = []): array
|
||||||
{
|
{
|
||||||
$match = self::$exceptionResponseCodes[get_class($e)] ?? null;
|
$match = self::$exceptionResponseCodes[get_class($e)] ?? null;
|
||||||
|
|
||||||
@ -214,7 +227,7 @@ class Handler extends ExceptionHandler
|
|||||||
'trace' => Collection::make($e->getTrace())
|
'trace' => Collection::make($e->getTrace())
|
||||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||||
->all(),
|
->all(),
|
||||||
'previous' => Collection::make($this->extractPrevious($e))
|
'previous' => Collection::make(self::extractPrevious($e))
|
||||||
->map(fn ($exception) => $exception->getTrace())
|
->map(fn ($exception) => $exception->getTrace())
|
||||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||||
->all(),
|
->all(),
|
||||||
@ -225,10 +238,21 @@ class Handler extends ExceptionHandler
|
|||||||
return ['errors' => [array_merge($error, $override)]];
|
return ['errors' => [array_merge($error, $override)]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the exception as a JSONAPI representation for use on API requests.
|
||||||
|
*
|
||||||
|
* @param array{detail?: mixed, source?: mixed, meta?: mixed} $override
|
||||||
|
* @return array{errors?: array<mixed>}
|
||||||
|
*/
|
||||||
|
protected function convertExceptionToArray(Throwable $e, array $override = []): array
|
||||||
|
{
|
||||||
|
return self::exceptionToArray($e, $override);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of exceptions that should not be reported.
|
* Return an array of exceptions that should not be reported.
|
||||||
*/
|
*/
|
||||||
public static function isReportable(\Exception $exception): bool
|
public static function isReportable(Exception $exception): bool
|
||||||
{
|
{
|
||||||
return (new self(Container::getInstance()))->shouldReport($exception);
|
return (new self(Container::getInstance()))->shouldReport($exception);
|
||||||
}
|
}
|
||||||
@ -236,7 +260,7 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Convert an authentication exception into an unauthenticated response.
|
* Convert an authentication exception into an unauthenticated response.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*/
|
*/
|
||||||
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
|
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
|
||||||
{
|
{
|
||||||
@ -244,22 +268,19 @@ class Handler extends ExceptionHandler
|
|||||||
return new JsonResponse($this->convertExceptionToArray($exception), JsonResponse::HTTP_UNAUTHORIZED);
|
return new JsonResponse($this->convertExceptionToArray($exception), JsonResponse::HTTP_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->guest('/auth/login');
|
return redirect()->guest(route('filament.app.auth.login'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts all the previous exceptions that lead to the one passed into this
|
* Extracts all the previous exceptions that lead to the one passed into this
|
||||||
* function being thrown.
|
* function being thrown.
|
||||||
*
|
*
|
||||||
* @return \Throwable[]
|
* @return Throwable[]
|
||||||
*/
|
*/
|
||||||
protected function extractPrevious(\Throwable $e): array
|
public static function extractPrevious(Throwable $e): array
|
||||||
{
|
{
|
||||||
$previous = [];
|
$previous = [];
|
||||||
while ($value = $e->getPrevious()) {
|
while ($value = $e->getPrevious()) {
|
||||||
if (!$value instanceof \Throwable) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$previous[] = $value;
|
$previous[] = $value;
|
||||||
$e = $value;
|
$e = $value;
|
||||||
}
|
}
|
||||||
@ -270,10 +291,11 @@ class Handler extends ExceptionHandler
|
|||||||
/**
|
/**
|
||||||
* Helper method to allow reaching into the handler to convert an exception
|
* Helper method to allow reaching into the handler to convert an exception
|
||||||
* into the expected array response type.
|
* into the expected array response type.
|
||||||
|
*
|
||||||
|
* @return array<mixed>
|
||||||
*/
|
*/
|
||||||
public static function toArray(\Throwable $e): array
|
public static function toArray(Throwable $e): array
|
||||||
{
|
{
|
||||||
// @phpstan-ignore-next-line
|
return self::exceptionToArray($e);
|
||||||
return (new self(app()))->convertExceptionToArray($e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exceptions\Http\Connection;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use App\Exceptions\DisplayException;
|
|
||||||
use Illuminate\Support\Facades\Context;
|
|
||||||
|
|
||||||
class DaemonConnectionException extends DisplayException
|
|
||||||
{
|
|
||||||
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Every request to the daemon instance will return a unique X-Request-Id header
|
|
||||||
* which allows for all errors to be efficiently tied to a specific request that
|
|
||||||
* triggered them, and gives users a more direct method of informing hosts when
|
|
||||||
* something goes wrong.
|
|
||||||
*/
|
|
||||||
private ?string $requestId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throw a displayable exception caused by a daemon connection error.
|
|
||||||
*/
|
|
||||||
public function __construct(?Exception $previous, bool $useStatusCode = true)
|
|
||||||
{
|
|
||||||
/** @var \GuzzleHttp\Psr7\Response|null $response */
|
|
||||||
$response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null;
|
|
||||||
$this->requestId = $response?->getHeaderLine('X-Request-Id');
|
|
||||||
|
|
||||||
Context::add('request_id', $this->requestId);
|
|
||||||
|
|
||||||
if ($useStatusCode) {
|
|
||||||
$this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode();
|
|
||||||
// There are rare conditions where daemon encounters a panic condition and crashes the
|
|
||||||
// request being made after content has already been sent over the wire. In these cases
|
|
||||||
// you can end up with a "successful" response code that is actual an error.
|
|
||||||
//
|
|
||||||
// Handle those better here since we shouldn't ever end up in this exception state and
|
|
||||||
// be returning a 2XX level response.
|
|
||||||
if ($this->statusCode < 400) {
|
|
||||||
$this->statusCode = Response::HTTP_BAD_GATEWAY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($response)) {
|
|
||||||
$message = 'Could not establish a connection to the machine running this server. Please try again.';
|
|
||||||
} else {
|
|
||||||
$message = sprintf('There was an error while communicating with the machine running this server. This error has been logged, please try again. (code: %s) (request_id: %s)', $response->getStatusCode(), $this->requestId ?? '<nil>');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to pull the actual error message off the response and return that if it is not
|
|
||||||
// a 500 level error.
|
|
||||||
if ($this->statusCode < 500 && !is_null($response)) {
|
|
||||||
$body = json_decode($response->getBody()->__toString(), true);
|
|
||||||
$message = sprintf('An error occurred on the remote host: %s. (request id: %s)', $body['error'] ?? $message, $this->requestId ?? '<nil>');
|
|
||||||
}
|
|
||||||
|
|
||||||
$level = $this->statusCode >= 500 && $this->statusCode !== 504
|
|
||||||
? DisplayException::LEVEL_ERROR
|
|
||||||
: DisplayException::LEVEL_WARNING;
|
|
||||||
|
|
||||||
parent::__construct($message, $previous, $level);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the HTTP status code for this exception.
|
|
||||||
*/
|
|
||||||
public function getStatusCode(): int
|
|
||||||
{
|
|
||||||
return $this->statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,13 +4,14 @@ namespace App\Exceptions\Http;
|
|||||||
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class HttpForbiddenException extends HttpException
|
class HttpForbiddenException extends HttpException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* HttpForbiddenException constructor.
|
* HttpForbiddenException constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(?string $message = null, ?\Throwable $previous = null)
|
public function __construct(?string $message = null, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
|
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace App\Exceptions\Http\Server;
|
|||||||
use App\Enums\ServerState;
|
use App\Enums\ServerState;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class ServerStateConflictException extends ConflictHttpException
|
class ServerStateConflictException extends ConflictHttpException
|
||||||
{
|
{
|
||||||
@ -12,7 +13,7 @@ class ServerStateConflictException extends ConflictHttpException
|
|||||||
* Exception thrown when the server is in an unsupported state for API access or
|
* Exception thrown when the server is in an unsupported state for API access or
|
||||||
* certain operations within the codebase.
|
* certain operations within the codebase.
|
||||||
*/
|
*/
|
||||||
public function __construct(Server $server, ?\Throwable $previous = null)
|
public function __construct(Server $server, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
$message = 'This server is currently in an unsupported state, please try again later.';
|
$message = 'This server is currently in an unsupported state, please try again later.';
|
||||||
if ($server->isSuspended()) {
|
if ($server->isSuspended()) {
|
||||||
|
|||||||
@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Spatie\Ignition\Contracts\Solution;
|
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
|
||||||
|
use Exception;
|
||||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||||
|
use Spatie\Ignition\Contracts\Solution;
|
||||||
|
|
||||||
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution
|
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
|
||||||
{
|
{
|
||||||
public function getSolution(): Solution
|
public function getSolution(): Solution
|
||||||
{
|
{
|
||||||
return new Solutions\ManifestDoesNotExistSolution();
|
return new ManifestDoesNotExistSolution();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Exceptions\Model;
|
namespace App\Exceptions\Model;
|
||||||
|
|
||||||
use Illuminate\Support\MessageBag;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Contracts\Validation\Validator;
|
|
||||||
use App\Exceptions\PanelException;
|
use App\Exceptions\PanelException;
|
||||||
use Illuminate\Contracts\Support\MessageProvider;
|
use Illuminate\Contracts\Support\MessageProvider;
|
||||||
|
use Illuminate\Contracts\Validation\Validator;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
|
||||||
class DataValidationException extends PanelException implements HttpExceptionInterface, MessageProvider
|
class DataValidationException extends PanelException implements HttpExceptionInterface, MessageProvider
|
||||||
@ -42,6 +42,9 @@ class DataValidationException extends PanelException implements HttpExceptionInt
|
|||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
public function getHeaders(): array
|
public function getHeaders(): array
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
class PanelException extends \Exception {}
|
use Exception;
|
||||||
|
|
||||||
|
class PanelException extends Exception {}
|
||||||
|
|||||||
7
app/Exceptions/Repository/FileExistsException.php
Normal file
7
app/Exceptions/Repository/FileExistsException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions\Repository;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FileExistsException extends Exception {}
|
||||||
7
app/Exceptions/Repository/FileNotEditableException.php
Normal file
7
app/Exceptions/Repository/FileNotEditableException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions\Repository;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FileNotEditableException extends Exception {}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions\Service\Deployment;
|
||||||
|
|
||||||
|
use App\Exceptions\DisplayException;
|
||||||
|
|
||||||
|
class NoViableNodeException extends DisplayException {}
|
||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Exceptions\Service;
|
namespace App\Exceptions\Service;
|
||||||
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use App\Exceptions\DisplayException;
|
use App\Exceptions\DisplayException;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
class HasActiveServersException extends DisplayException
|
class HasActiveServersException extends DisplayException
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Exceptions\Service;
|
namespace App\Exceptions\Service;
|
||||||
|
|
||||||
use App\Exceptions\DisplayException;
|
use App\Exceptions\DisplayException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class ServiceLimitExceededException extends DisplayException
|
class ServiceLimitExceededException extends DisplayException
|
||||||
{
|
{
|
||||||
@ -10,7 +11,7 @@ class ServiceLimitExceededException extends DisplayException
|
|||||||
* Exception thrown when something goes over a defined limit, such as allocated
|
* Exception thrown when something goes over a defined limit, such as allocated
|
||||||
* ports, tasks, databases, etc.
|
* ports, tasks, databases, etc.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, ?\Throwable $previous = null)
|
public function __construct(string $message, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exceptions\Service\User;
|
|
||||||
|
|
||||||
use App\Exceptions\DisplayException;
|
|
||||||
|
|
||||||
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
|
||||||
{
|
|
||||||
public string $title = 'Invalid 2FA Code';
|
|
||||||
|
|
||||||
public string $icon = 'tabler-2fa';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct('The provided two-factor authentication token was not valid.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
app/Extensions/Avatar/AvatarSchemaInterface.php
Normal file
14
app/Extensions/Avatar/AvatarSchemaInterface.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Avatar;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
interface AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
public function get(User $user): ?string;
|
||||||
|
}
|
||||||
55
app/Extensions/Avatar/AvatarService.php
Normal file
55
app/Extensions/Avatar/AvatarService.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Avatar;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class AvatarService
|
||||||
|
{
|
||||||
|
/** @var AvatarSchemaInterface[] */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly bool $allowUploadedAvatars,
|
||||||
|
private readonly string $activeSchema,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function get(string $id): ?AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveSchema(): ?AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
return $this->get($this->activeSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvatarUrl(User $user): ?string
|
||||||
|
{
|
||||||
|
if ($this->allowUploadedAvatars) {
|
||||||
|
$path = "avatars/$user->id.png";
|
||||||
|
|
||||||
|
if (Storage::disk('public')->exists($path)) {
|
||||||
|
return Storage::url($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getActiveSchema()?->get($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(AvatarSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, string> */
|
||||||
|
public function getMappings(): array
|
||||||
|
{
|
||||||
|
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Extensions/Avatar/Schemas/GravatarSchema.php
Normal file
24
app/Extensions/Avatar/Schemas/GravatarSchema.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Avatar\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Avatar\AvatarSchemaInterface;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class GravatarSchema implements AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return 'gravatar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'Gravatar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(User $user): string
|
||||||
|
{
|
||||||
|
return 'https://gravatar.com/avatar/' . md5($user->email);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Extensions/Avatar/Schemas/UiAvatarsSchema.php
Normal file
25
app/Extensions/Avatar/Schemas/UiAvatarsSchema.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Avatar\Schemas;
|
||||||
|
|
||||||
|
use App\Extensions\Avatar\AvatarSchemaInterface;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class UiAvatarsSchema implements AvatarSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return 'uiavatars';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'UI Avatars';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(User $user): ?string
|
||||||
|
{
|
||||||
|
// UI Avatars is the default of filament so just return null here
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,31 +2,33 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Backups;
|
namespace App\Extensions\Backups;
|
||||||
|
|
||||||
use Closure;
|
use App\Extensions\Filesystem\S3Filesystem;
|
||||||
use Aws\S3\S3Client;
|
use Aws\S3\S3Client;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Webmozart\Assert\Assert;
|
use InvalidArgumentException;
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use League\Flysystem\FilesystemAdapter;
|
use League\Flysystem\FilesystemAdapter;
|
||||||
use App\Extensions\Filesystem\S3Filesystem;
|
|
||||||
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
|
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
class BackupManager
|
class BackupManager
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The array of resolved backup drivers.
|
* The array of resolved backup drivers.
|
||||||
|
*
|
||||||
|
* @var array<string, FilesystemAdapter>
|
||||||
*/
|
*/
|
||||||
protected array $adapters = [];
|
protected array $adapters = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The registered custom driver creators.
|
* The registered custom driver creators.
|
||||||
|
*
|
||||||
|
* @var array<string, callable>
|
||||||
*/
|
*/
|
||||||
protected array $customCreators;
|
protected array $customCreators;
|
||||||
|
|
||||||
/**
|
|
||||||
* BackupManager constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(protected Application $app) {}
|
public function __construct(protected Application $app) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +65,7 @@ class BackupManager
|
|||||||
$config = $this->getConfig($name);
|
$config = $this->getConfig($name);
|
||||||
|
|
||||||
if (empty($config['adapter'])) {
|
if (empty($config['adapter'])) {
|
||||||
throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$adapter = $config['adapter'];
|
$adapter = $config['adapter'];
|
||||||
@ -81,11 +83,13 @@ class BackupManager
|
|||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \InvalidArgumentException("Adapter [$adapter] is not supported.");
|
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls a custom creator for a given adapter type.
|
* Calls a custom creator for a given adapter type.
|
||||||
|
*
|
||||||
|
* @param array{adapter: string} $config
|
||||||
*/
|
*/
|
||||||
protected function callCustomCreator(array $config): mixed
|
protected function callCustomCreator(array $config): mixed
|
||||||
{
|
{
|
||||||
@ -94,6 +98,8 @@ class BackupManager
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new daemon adapter.
|
* Creates a new daemon adapter.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $config
|
||||||
*/
|
*/
|
||||||
public function createWingsAdapter(array $config): FilesystemAdapter
|
public function createWingsAdapter(array $config): FilesystemAdapter
|
||||||
{
|
{
|
||||||
@ -102,6 +108,8 @@ class BackupManager
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new S3 adapter.
|
* Creates a new S3 adapter.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $config
|
||||||
*/
|
*/
|
||||||
public function createS3Adapter(array $config): FilesystemAdapter
|
public function createS3Adapter(array $config): FilesystemAdapter
|
||||||
{
|
{
|
||||||
@ -118,6 +126,8 @@ class BackupManager
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the configuration associated with a given backup type.
|
* Returns the configuration associated with a given backup type.
|
||||||
|
*
|
||||||
|
* @return array<mixed>
|
||||||
*/
|
*/
|
||||||
protected function getConfig(string $name): array
|
protected function getConfig(string $name): array
|
||||||
{
|
{
|
||||||
@ -147,8 +157,9 @@ class BackupManager
|
|||||||
*/
|
*/
|
||||||
public function forget(array|string $adapter): self
|
public function forget(array|string $adapter): self
|
||||||
{
|
{
|
||||||
|
$adapters = &$this->adapters;
|
||||||
foreach ((array) $adapter as $adapterName) {
|
foreach ((array) $adapter as $adapterName) {
|
||||||
unset($this->adapters[$adapterName]);
|
unset($adapters[$adapterName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|||||||
48
app/Extensions/Captcha/CaptchaService.php
Normal file
48
app/Extensions/Captcha/CaptchaService.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha;
|
||||||
|
|
||||||
|
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class CaptchaService
|
||||||
|
{
|
||||||
|
/** @var array<string, CaptchaSchemaInterface> */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CaptchaSchemaInterface[]
|
||||||
|
*/
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $id): ?CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(CaptchaSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config()->set('captcha.' . Str::lower($schema->getId()), $schema->getConfig());
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Collection<CaptchaSchemaInterface> */
|
||||||
|
public function getActiveSchemas(): Collection
|
||||||
|
{
|
||||||
|
return collect($this->schemas)
|
||||||
|
->filter(fn (CaptchaSchemaInterface $schema) => $schema->isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActiveSchema(): ?CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
return $this->getActiveSchemas()->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
app/Extensions/Captcha/Schemas/BaseSchema.php
Normal file
59
app/Extensions/Captcha/Schemas/BaseSchema.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas;
|
||||||
|
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
abstract class BaseSchema
|
||||||
|
{
|
||||||
|
abstract public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return Str::upper($this->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
|
||||||
|
'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Component[]
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array
|
||||||
|
{
|
||||||
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
TextInput::make("CAPTCHA_{$id}_SITE_KEY")
|
||||||
|
->label('Site Key')
|
||||||
|
->placeholder('Site Key')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->autocomplete(false)
|
||||||
|
->default(env("CAPTCHA_{$id}_SITE_KEY")),
|
||||||
|
TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
|
||||||
|
->label('Secret Key')
|
||||||
|
->placeholder('Secret Key')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->autocomplete(false)
|
||||||
|
->default(env("CAPTCHA_{$id}_SECRET_KEY")),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Extensions/Captcha/Schemas/CaptchaSchemaInterface.php
Normal file
30
app/Extensions/Captcha/Schemas/CaptchaSchemaInterface.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
|
||||||
|
interface CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getConfig(): array;
|
||||||
|
|
||||||
|
public function isEnabled(): bool;
|
||||||
|
|
||||||
|
public function getFormComponent(): Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Component[]
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array;
|
||||||
|
|
||||||
|
public function getIcon(): ?string;
|
||||||
|
|
||||||
|
public function validateResponse(?string $captchaResponse = null): void;
|
||||||
|
}
|
||||||
23
app/Extensions/Captcha/Schemas/Turnstile/Component.php
Normal file
23
app/Extensions/Captcha/Schemas/Turnstile/Component.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
|
use Filament\Forms\Components\Field;
|
||||||
|
|
||||||
|
class Component extends Field
|
||||||
|
{
|
||||||
|
protected string $viewIdentifier = 'turnstile';
|
||||||
|
|
||||||
|
protected string $view = 'filament.components.turnstile-captcha';
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->hiddenLabel();
|
||||||
|
|
||||||
|
$this->required();
|
||||||
|
|
||||||
|
$this->rule(new Rule());
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Extensions/Captcha/Schemas/Turnstile/Rule.php
Normal file
23
app/Extensions/Captcha/Schemas/Turnstile/Rule.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
|
use App\Extensions\Captcha\CaptchaService;
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
|
class Rule implements ValidationRule
|
||||||
|
{
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
App::call(fn (CaptchaService $service) => $service->get('turnstile')->validateResponse($value));
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
$fail('Captcha validation failed: ' . $exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
app/Extensions/Captcha/Schemas/Turnstile/TurnstileSchema.php
Normal file
117
app/Extensions/Captcha/Schemas/Turnstile/TurnstileSchema.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
|
use App\Extensions\Captcha\Schemas\BaseSchema;
|
||||||
|
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||||
|
use Exception;
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
|
class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return 'turnstile';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEnabled(): bool
|
||||||
|
{
|
||||||
|
return env('CAPTCHA_TURNSTILE_ENABLED', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormComponent(): Component
|
||||||
|
{
|
||||||
|
return Component::make('turnstile');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|string[]|bool|null>
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
return array_merge(parent::getConfig(), [
|
||||||
|
'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Filament\Support\Components\Component[]
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array
|
||||||
|
{
|
||||||
|
return array_merge(parent::getSettingsForm(), [
|
||||||
|
Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
|
||||||
|
->label(trans('admin/setting.captcha.verify'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->inline(false)
|
||||||
|
->onIcon('tabler-check')
|
||||||
|
->offIcon('tabler-x')
|
||||||
|
->onColor('success')
|
||||||
|
->offColor('danger')
|
||||||
|
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||||
|
TextEntry::make('info')
|
||||||
|
->label(trans('admin/setting.captcha.info_label'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->state(new HtmlString(trans('admin/setting.captcha.info'))),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): ?string
|
||||||
|
{
|
||||||
|
return 'tabler-brand-cloudflare';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function validateResponse(?string $captchaResponse = null): void
|
||||||
|
{
|
||||||
|
$captchaResponse ??= request()->get('cf-turnstile-response');
|
||||||
|
|
||||||
|
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
|
||||||
|
throw new Exception('Turnstile secret key is not defined.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = Http::asJson()
|
||||||
|
->timeout(15)
|
||||||
|
->connectTimeout(5)
|
||||||
|
->throw()
|
||||||
|
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
||||||
|
'secret' => $secret,
|
||||||
|
'response' => $captchaResponse,
|
||||||
|
])
|
||||||
|
->json();
|
||||||
|
|
||||||
|
if (!$response['success']) {
|
||||||
|
match ($response['error-codes'][0] ?? null) {
|
||||||
|
'missing-input-secret' => throw new Exception('The secret parameter was not passed.'),
|
||||||
|
'invalid-input-secret' => throw new Exception('The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.'),
|
||||||
|
'missing-input-response' => throw new Exception('The response parameter (token) was not passed.'),
|
||||||
|
'invalid-input-response' => throw new Exception('The response parameter (token) is invalid or has expired.'),
|
||||||
|
'bad-request' => throw new Exception('The request was rejected because it was malformed.'),
|
||||||
|
'timeout-or-duplicate' => throw new Exception('The response parameter (token) has already been validated before.'),
|
||||||
|
default => throw new Exception('An internal error happened while validating the response.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->verifyDomain($response['hostname'] ?? '')) {
|
||||||
|
throw new Exception('Domain verification failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyDomain(string $hostname): bool
|
||||||
|
{
|
||||||
|
if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestUrl = parse_url(request()->url());
|
||||||
|
|
||||||
|
return $hostname === array_get($requestUrl, 'host');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions;
|
|
||||||
|
|
||||||
use App\Models\DatabaseHost;
|
|
||||||
|
|
||||||
class DynamicDatabaseConnection
|
|
||||||
{
|
|
||||||
public const DB_CHARSET = 'utf8';
|
|
||||||
|
|
||||||
public const DB_COLLATION = 'utf8_unicode_ci';
|
|
||||||
|
|
||||||
public const DB_DRIVER = 'mysql';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a dynamic database connection entry to the runtime config.
|
|
||||||
*/
|
|
||||||
public function set(string $connection, DatabaseHost|int $host, string $database = 'mysql'): void
|
|
||||||
{
|
|
||||||
if (!$host instanceof DatabaseHost) {
|
|
||||||
$host = DatabaseHost::query()->findOrFail($host);
|
|
||||||
}
|
|
||||||
|
|
||||||
config()->set('database.connections.' . $connection, [
|
|
||||||
'driver' => self::DB_DRIVER,
|
|
||||||
'host' => $host->host,
|
|
||||||
'port' => $host->port,
|
|
||||||
'database' => $database,
|
|
||||||
'username' => $host->username,
|
|
||||||
'password' => $host->password,
|
|
||||||
'charset' => self::DB_CHARSET,
|
|
||||||
'collation' => self::DB_COLLATION,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
app/Extensions/Features/FeatureSchemaInterface.php
Normal file
15
app/Extensions/Features/FeatureSchemaInterface.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Features;
|
||||||
|
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
|
||||||
|
interface FeatureSchemaInterface
|
||||||
|
{
|
||||||
|
/** @return string[] */
|
||||||
|
public function getListeners(): array;
|
||||||
|
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getAction(): Action;
|
||||||
|
}
|
||||||
52
app/Extensions/Features/FeatureService.php
Normal file
52
app/Extensions/Features/FeatureService.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Extensions\Features;
|
||||||
|
|
||||||
|
class FeatureService
|
||||||
|
{
|
||||||
|
/** @var FeatureSchemaInterface[] */
|
||||||
|
private array $schemas = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FeatureSchemaInterface[]
|
||||||
|
*/
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $id): ?FeatureSchemaInterface
|
||||||
|
{
|
||||||
|
return array_get($this->schemas, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ?string[] $features
|
||||||
|
* @return FeatureSchemaInterface[]
|
||||||
|
*/
|
||||||
|
public function getActiveSchemas(?array $features = []): array
|
||||||
|
{
|
||||||
|
return collect($this->schemas)->only($features)->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(FeatureSchemaInterface $schema): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->schemas[$schema->getId()] = $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ?string[] $features
|
||||||
|
* @return array<string, array<string>>
|
||||||
|
*/
|
||||||
|
public function getMappings(?array $features = []): array
|
||||||
|
{
|
||||||
|
return collect($this->getActiveSchemas($features))
|
||||||
|
->mapWithKeys(fn (FeatureSchemaInterface $schema) => [
|
||||||
|
$schema->getId() => $schema->getListeners(),
|
||||||
|
])->all();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user