mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
1101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be934065d9 | ||
|
|
82fc9897d2 | ||
|
|
8162dcfb71 | ||
|
|
b6ab653ef3 | ||
|
|
17e9a1a497 | ||
|
|
46f7d43595 | ||
|
|
59bb59ee24 | ||
|
|
d081d477ef | ||
|
|
cf73f1f764 | ||
|
|
0e433a3d36 | ||
|
|
b08a2f54f0 | ||
|
|
29ffdf2c71 | ||
|
|
58b185f6dd | ||
|
|
819de5a32e | ||
|
|
e0fe4e4995 | ||
|
|
461d30fc26 | ||
|
|
3fee18d06e | ||
|
|
046923aeb3 | ||
|
|
4646b7d0ec | ||
|
|
a02d51e504 | ||
|
|
9728c49edd | ||
|
|
ed5b01c78d | ||
|
|
000091cfb9 | ||
|
|
2774701895 | ||
|
|
e238dd8510 | ||
|
|
56b565b512 | ||
|
|
237106428b | ||
|
|
06e1e1ba76 | ||
|
|
9eb4c3e77d | ||
|
|
f3d8351208 | ||
|
|
1e1409e651 | ||
|
|
ceaa32fd00 | ||
|
|
f466e697dd | ||
|
|
476057663b | ||
|
|
1b1d0597fe | ||
|
|
dfa73a3d7c | ||
|
|
2a24e1d7e8 | ||
|
|
5cd624c7ea | ||
|
|
34b12a0315 | ||
|
|
e9e6064eb6 | ||
|
|
6b7712e35f | ||
|
|
84aac40410 | ||
|
|
e40a0fdc50 | ||
|
|
e3677995b9 | ||
|
|
0468c25bf8 | ||
|
|
89c7e96df0 | ||
|
|
8af5afbb6c | ||
|
|
ef9f16a3c3 | ||
|
|
d15b6387a3 | ||
|
|
2e9e39dcf5 | ||
|
|
d901b02e92 | ||
|
|
766a25ccad | ||
|
|
fae97b1817 | ||
|
|
b2cc5e58a3 | ||
|
|
2f8b89c8a6 | ||
|
|
2da650610c | ||
|
|
980024c9d2 | ||
|
|
1b56a6b400 | ||
|
|
996d449f0f | ||
|
|
3b197f3624 | ||
|
|
bba7d0c828 | ||
|
|
a04b69d0fd | ||
|
|
0e136ffb8f | ||
|
|
95e53169b1 | ||
|
|
6b4e52fb37 | ||
|
|
52a19a325e | ||
|
|
9d891e87e0 | ||
|
|
41b274fbb3 | ||
|
|
6417e13336 | ||
|
|
db9136d981 | ||
|
|
c13249e887 | ||
|
|
e83499f90d | ||
|
|
6c92e6efc9 | ||
|
|
97715a47b3 | ||
|
|
3a9698c3a3 | ||
|
|
d47ccae190 | ||
|
|
9295c6545f | ||
|
|
2a01566c34 | ||
|
|
b5cf59e743 | ||
|
|
de02a00ce9 | ||
|
|
25803f371c | ||
|
|
92eaa1a470 | ||
|
|
2cbb7e4962 | ||
|
|
34d73957f5 | ||
|
|
d571c6d9a2 | ||
|
|
985c7515b1 | ||
|
|
cf666ceb19 | ||
|
|
a4eb5c07e6 | ||
|
|
bad11f13f5 | ||
|
|
02d52d63b9 | ||
|
|
2821e43cdd | ||
|
|
0e8a3c36f3 | ||
|
|
c04d2f508b | ||
|
|
5c85b82257 | ||
|
|
adea440931 | ||
|
|
ae5df82887 | ||
|
|
bbef99c3c2 | ||
|
|
527c01e7dc | ||
|
|
9872c4fee3 | ||
|
|
3f40ad3250 | ||
|
|
2a5a67e63c | ||
|
|
15051a1bc2 | ||
|
|
7d882b3df5 | ||
|
|
b7d45341bc | ||
|
|
3808fd83a6 | ||
|
|
1695c7cc81 | ||
|
|
707d609b46 | ||
|
|
800e5cbf43 | ||
|
|
2107d782f5 | ||
|
|
80015d8d71 | ||
|
|
2c2d59a87c | ||
|
|
93844928a8 | ||
|
|
cb59d121be | ||
|
|
42b3db37ac | ||
|
|
c59975a7ff | ||
|
|
c0df4d330a | ||
|
|
b56532f25e | ||
|
|
f34a209c67 | ||
|
|
e24f0a395d | ||
|
|
1a6bf06929 | ||
|
|
d239ca9b49 | ||
|
|
ecc98b0944 | ||
|
|
950ee77a49 | ||
|
|
38a72806a7 | ||
|
|
fa70696c71 | ||
|
|
b123baafa4 | ||
|
|
d6fa416a3f | ||
|
|
d2c0f19353 | ||
|
|
3ef6c711e6 | ||
|
|
64a4d51f9c | ||
|
|
1a20e4f813 | ||
|
|
250c14738c | ||
|
|
28221a4e7a | ||
|
|
680c22a41e | ||
|
|
9145fd3408 | ||
|
|
428b9cf0ec | ||
|
|
78659b2ad9 | ||
|
|
4b5408c050 | ||
|
|
5417f6376b | ||
|
|
27c33c7661 | ||
|
|
226c43047e | ||
|
|
0873618c63 | ||
|
|
2db8057d61 | ||
|
|
71a582026e | ||
|
|
fbec26fc31 | ||
|
|
5f13fb2316 | ||
|
|
bc91bb3132 | ||
|
|
e3b20268d5 | ||
|
|
742e5a244d | ||
|
|
1f208f5f5b | ||
|
|
a53ade88e7 | ||
|
|
a93f18eb4a | ||
|
|
77e9617770 | ||
|
|
21e97b0175 | ||
|
|
a6618a14d5 | ||
|
|
59308ab013 | ||
|
|
e19c8d7a7a | ||
|
|
421c93795b | ||
|
|
182f908c31 | ||
|
|
20616363e9 | ||
|
|
d85073b26d | ||
|
|
303d1b1b87 | ||
|
|
60d4e1ba63 | ||
|
|
83d52b68f0 | ||
|
|
af3b1a27f4 | ||
|
|
7f94593c07 | ||
|
|
5df7654873 | ||
|
|
054836fd4c | ||
|
|
484ead1f1f | ||
|
|
fbada4c5de | ||
|
|
491113416b | ||
|
|
c42f5cb799 | ||
|
|
47aa223f87 | ||
|
|
cb586c9b74 | ||
|
|
554ac59b97 | ||
|
|
0247898876 | ||
|
|
467acc4d4d | ||
|
|
fc2778db35 | ||
|
|
85d6ff9012 | ||
|
|
fa053b4d1f | ||
|
|
522f8baec7 | ||
|
|
bcc7afa3e4 | ||
|
|
647a5d05a6 | ||
|
|
e15d41f80d | ||
|
|
6c7c919d49 | ||
|
|
22eb965919 | ||
|
|
c0746b95b3 | ||
|
|
dfdedf9e48 | ||
|
|
7e76eb4dd1 | ||
|
|
c1f777e23e | ||
|
|
975d13c7e1 | ||
|
|
017bdd2778 | ||
|
|
01e5cf0852 | ||
|
|
548df8c0f4 | ||
|
|
8faa6ae1cf | ||
|
|
76ed1107c2 | ||
|
|
cff5049096 | ||
|
|
cb5ca100a6 | ||
|
|
03d1e974dd | ||
|
|
1e6dbb5e8e | ||
|
|
431dadb6c2 | ||
|
|
22e42b62ad | ||
|
|
49ee8ce132 | ||
|
|
b609d72d1c | ||
|
|
d7071fba60 | ||
|
|
76585991ec | ||
|
|
da6efcf733 | ||
|
|
64b0770cfb | ||
|
|
1ec83a3236 | ||
|
|
4431f5601a | ||
|
|
bf48aa03a2 | ||
|
|
9bace8e58b | ||
|
|
c0afcaf3f6 | ||
|
|
53edf06476 | ||
|
|
255e9e4095 | ||
|
|
03f923c6e2 | ||
|
|
4685ef7439 | ||
|
|
626cfb80b4 | ||
|
|
9591fbff08 | ||
|
|
fbda00f059 | ||
|
|
1907e7e59c | ||
|
|
ffe7b04bea | ||
|
|
fe0a662afd | ||
|
|
df9fad088f | ||
|
|
319584d911 | ||
|
|
7d5a660f4d | ||
|
|
f7f0cbf318 | ||
|
|
841c0731aa | ||
|
|
137cd25267 | ||
|
|
2d40b2dfe5 | ||
|
|
e32b713742 | ||
|
|
4dcd16c41e | ||
|
|
60497fe59d | ||
|
|
8536945a60 | ||
|
|
e0a8d8258c | ||
|
|
988357fb55 | ||
|
|
fe19cdb5e4 | ||
|
|
c4654a9619 | ||
|
|
0a123a652b | ||
|
|
5d437c29b2 | ||
|
|
53f345ab1d | ||
|
|
059c21c990 | ||
|
|
2644b638d1 | ||
|
|
8785282133 | ||
|
|
35c084af1d | ||
|
|
b63488baba | ||
|
|
7e5f21b28e | ||
|
|
8d41bafb93 | ||
|
|
6f5049efd5 | ||
|
|
6dd6b636e5 | ||
|
|
8488d530f3 | ||
|
|
6c61d5cdf5 | ||
|
|
8036455c2d | ||
|
|
bf44eeab3d | ||
|
|
0cd185696d | ||
|
|
339697437a | ||
|
|
e0b15fe971 | ||
|
|
afc5ea43da | ||
|
|
bb337e819e | ||
|
|
160dd10f77 | ||
|
|
49b096fef6 | ||
|
|
4828b840cb | ||
|
|
67af70448b | ||
|
|
946acf5245 | ||
|
|
390b3a835a | ||
|
|
2842bf9a91 | ||
|
|
8a0e10f6f4 | ||
|
|
67efa82b91 | ||
|
|
546d6b87ea | ||
|
|
d012d19253 | ||
|
|
e925ed9ea4 | ||
|
|
0c05809d7d | ||
|
|
29f1631950 | ||
|
|
9c0c58035a | ||
|
|
f4262569dd | ||
|
|
99cf6eae49 | ||
|
|
0488546706 | ||
|
|
dc32cd71e5 | ||
|
|
efdfd5d13c | ||
|
|
d31cab76f0 | ||
|
|
00ed202127 | ||
|
|
aaa4ca297d | ||
|
|
629871e683 | ||
|
|
a237c651c3 | ||
|
|
4f9b0d9d59 | ||
|
|
b701a0b504 | ||
|
|
39036202bb | ||
|
|
4225ad83e7 | ||
|
|
38c4e0ede1 | ||
|
|
25a64c703f | ||
|
|
ab5871add7 | ||
|
|
2b0e009f6a | ||
|
|
9b6ea99eea | ||
|
|
c4cf545d85 | ||
|
|
1edd717432 | ||
|
|
5b88af6158 | ||
|
|
e995d894d8 | ||
|
|
5f56512e56 | ||
|
|
24e4930fc1 | ||
|
|
541728805f | ||
|
|
9e4bac1386 | ||
|
|
ed8d32d050 | ||
|
|
7fb66bc58b | ||
|
|
58c06fba86 | ||
|
|
3cf27a068a | ||
|
|
7cfbea3f60 | ||
|
|
7907e33431 | ||
|
|
89f3078ce5 | ||
|
|
f3ce69b656 | ||
|
|
43555cdabe | ||
|
|
651bf3a303 | ||
|
|
4cde1a8a7d | ||
|
|
405efcac0b | ||
|
|
8397de0dca | ||
|
|
06b58e6495 | ||
|
|
24db4006cf | ||
|
|
84009c5e9b | ||
|
|
e32afde973 | ||
|
|
fecffac573 | ||
|
|
b4448e013c | ||
|
|
c3f06a6272 | ||
|
|
2be724f780 | ||
|
|
bf78326c96 | ||
|
|
4ca8722c6e | ||
|
|
e56e1eb687 | ||
|
|
5a5c302bdc | ||
|
|
997dc85985 | ||
|
|
09ef851372 | ||
|
|
7c4987d84d | ||
|
|
5cebf5540a | ||
|
|
3df2f8e58c | ||
|
|
a642d36a23 | ||
|
|
72bceec62d | ||
|
|
3d32314e80 | ||
|
|
7259830ac1 | ||
|
|
daa87c0dc7 | ||
|
|
a2ee55e0e9 | ||
|
|
de6aeac243 | ||
|
|
f640b4a87f | ||
|
|
3747db08d4 | ||
|
|
ab4677ac0e | ||
|
|
172d55311e | ||
|
|
3ce25e2ac8 | ||
|
|
388ded9aa5 | ||
|
|
767d3e1944 | ||
|
|
ec1d6c7430 | ||
|
|
8abeae5e63 | ||
|
|
d833623ebf | ||
|
|
cc90d9ec9b | ||
|
|
6b5de00fb0 | ||
|
|
5ed96fb0ce | ||
|
|
a73af1d578 | ||
|
|
5867a27901 | ||
|
|
8f7bffc349 | ||
|
|
21ee22d4f5 | ||
|
|
fb72132a4b | ||
|
|
ca904c15d9 | ||
|
|
682863f83e | ||
|
|
acd722678e | ||
|
|
3750977f41 | ||
|
|
9b401059b0 | ||
|
|
6a3ef5c860 | ||
|
|
a36518a8f0 | ||
|
|
a5eb4b0a72 | ||
|
|
b5c0876dd4 | ||
|
|
9745d12ac8 | ||
|
|
5c72e5a452 | ||
|
|
600f4b2106 | ||
|
|
c12d37fe0a | ||
|
|
bba8d00ba2 | ||
|
|
78665ffbfa | ||
|
|
d41c8c70c3 | ||
|
|
f13e5d449c | ||
|
|
d256998677 | ||
|
|
4aaf04ce74 | ||
|
|
ecfca9419a | ||
|
|
dfd6764320 | ||
|
|
73bf5274f5 | ||
|
|
fc38a42587 | ||
|
|
9b255964fe | ||
|
|
29f55ca1a0 | ||
|
|
6a5fb8faff | ||
|
|
5c225c8d42 | ||
|
|
c1c5fc978b | ||
|
|
ffd19f591d | ||
|
|
81a41a7f31 | ||
|
|
1c9b704ecc | ||
|
|
edf1fdedf0 | ||
|
|
8484649071 | ||
|
|
1e68248611 | ||
|
|
b3e35c5838 | ||
|
|
ddd4ba8135 | ||
|
|
539544d0de | ||
|
|
123b5d098b | ||
|
|
796a9ca11f | ||
|
|
2872ef3ccb | ||
|
|
06a772e344 | ||
|
|
e99666f4c0 | ||
|
|
bd243d79e2 | ||
|
|
18b4b23f79 | ||
|
|
071a9d5104 | ||
|
|
61ebd1b16e | ||
|
|
9836c988a0 | ||
|
|
03d7738032 | ||
|
|
98aa474975 | ||
|
|
7bd6b66551 | ||
|
|
727e50648e | ||
|
|
0b2b20caeb | ||
|
|
6cc64b4454 | ||
|
|
349bc89851 | ||
|
|
7046d05f63 | ||
|
|
cef21ac8b5 | ||
|
|
2ae7e562bb | ||
|
|
e4b998c608 | ||
|
|
9b7aacc934 | ||
|
|
7027f39c48 | ||
|
|
9f6f872536 | ||
|
|
cb03b153ac | ||
|
|
e5d7a0cb10 | ||
|
|
bf65bc9462 | ||
|
|
b48b9765cd | ||
|
|
7cce02f74d | ||
|
|
3dc3672406 | ||
|
|
bbfe095045 | ||
|
|
65c1001751 | ||
|
|
5212bde021 | ||
|
|
9059f42b03 | ||
|
|
3b9f5d6f5c | ||
|
|
0aff344bc0 | ||
|
|
4715f34e15 | ||
|
|
59386ed4b7 | ||
|
|
21dee4abac | ||
|
|
e378d89477 | ||
|
|
b04c1206e4 | ||
|
|
639bc0e8db | ||
|
|
9a850d388d | ||
|
|
6c5c374139 | ||
|
|
0b05f8b83c | ||
|
|
63d5b775e6 | ||
|
|
cb16de63df | ||
|
|
31a4a0814e | ||
|
|
14302ed240 | ||
|
|
31c55f772d | ||
|
|
f0f34df13c | ||
|
|
1a877340d3 | ||
|
|
f7e43fa1c1 | ||
|
|
906906102b | ||
|
|
245a5175a8 | ||
|
|
f427014f52 | ||
|
|
0465a71d86 | ||
|
|
3de8a18ef9 | ||
|
|
e317d0c808 | ||
|
|
ff482ffe28 | ||
|
|
82588f3e16 | ||
|
|
069f1a7b7a | ||
|
|
807137d3b1 | ||
|
|
c03c154fc4 | ||
|
|
698ff9e918 | ||
|
|
8bf6a22db8 | ||
|
|
497d45129c | ||
|
|
8b855d7ee4 | ||
|
|
706cde4ffd | ||
|
|
0b22b694e6 | ||
|
|
ee5516bb91 | ||
|
|
e90b98e629 | ||
|
|
ff382d2029 | ||
|
|
4a37f85a51 | ||
|
|
6bdc833413 | ||
|
|
17a64a9402 | ||
|
|
a22b0797b1 | ||
|
|
f3b351245a | ||
|
|
0cb74c5fde | ||
|
|
9a828d4966 | ||
|
|
4845c1ad5d | ||
|
|
6159786dfe | ||
|
|
b473062f40 | ||
|
|
6c08f33ebb | ||
|
|
63e7eacae9 | ||
|
|
f4ab588516 | ||
|
|
3ded0d21d0 | ||
|
|
9ee8fb1894 | ||
|
|
72b1600cd4 | ||
|
|
04a59c5e21 | ||
|
|
c8f990d541 | ||
|
|
5b0bf99cbf | ||
|
|
8e227a3286 | ||
|
|
54f855e738 | ||
|
|
65a70c09c1 | ||
|
|
f25d78a87d | ||
|
|
79f39db502 | ||
|
|
a46e7759b2 | ||
|
|
869e58739f | ||
|
|
a39a7a276d | ||
|
|
0327334fcd | ||
|
|
3e0d4ebbd6 | ||
|
|
f001a50278 | ||
|
|
4c3bc8efdc | ||
|
|
a591e02ffa | ||
|
|
abe787593c | ||
|
|
9b312cd9d7 | ||
|
|
4d8a0ba58f | ||
|
|
66a4e86209 | ||
|
|
92df2472ae | ||
|
|
5f558f3773 | ||
|
|
754bb75e2a | ||
|
|
c84d39a20f | ||
|
|
847d6ecab1 | ||
|
|
8f83ecb9ef | ||
|
|
2f9448dde9 | ||
|
|
6415a66603 | ||
|
|
66567c8f2b | ||
|
|
e1ec0aee69 | ||
|
|
5b5aeb545a | ||
|
|
12c263c1ce | ||
|
|
7f378b12ae | ||
|
|
fac984d299 | ||
|
|
4f3eb7b362 | ||
|
|
d8d0b60cb3 | ||
|
|
19295ba746 | ||
|
|
0d3c978aad | ||
|
|
d2c8632c4f | ||
|
|
b419da427f | ||
|
|
033bf66405 | ||
|
|
c549ea17d8 | ||
|
|
c412dabc54 | ||
|
|
0bd0da2ee4 | ||
|
|
bf58ae0f0f | ||
|
|
e7ed3c300b | ||
|
|
f876457fbd | ||
|
|
a8d714c20d | ||
|
|
86f1bf31b8 | ||
|
|
95f75fdccb | ||
|
|
ac4f327775 | ||
|
|
6c0205c0d9 | ||
|
|
950c0abf9d | ||
|
|
8b66a5ca9e | ||
|
|
5afe1645a0 | ||
|
|
4a82125612 | ||
|
|
3bb19cd324 | ||
|
|
8c121a07aa | ||
|
|
d4c8c63691 | ||
|
|
cf06162be7 | ||
|
|
ea5349c844 | ||
|
|
6007427a6c | ||
|
|
0a889c5db1 | ||
|
|
3d60236b36 | ||
|
|
83009fd0b7 | ||
|
|
b9c7e5f6bb | ||
|
|
1a34ba175e | ||
|
|
bd0bbdea26 | ||
|
|
0b18f86a91 | ||
|
|
e88cd11041 | ||
|
|
194e39fa51 | ||
|
|
1bec376f6d | ||
|
|
9766e590b0 | ||
|
|
685a825881 | ||
|
|
c152304c15 | ||
|
|
b50c5c1363 | ||
|
|
6e6df2c771 | ||
|
|
5f174a883b | ||
|
|
05d494ad11 | ||
|
|
78ed940280 | ||
|
|
189c2b768d | ||
|
|
22b675373a | ||
|
|
e6a55920b5 | ||
|
|
25753b6027 | ||
|
|
7bdb572f91 | ||
|
|
922dcc5de8 | ||
|
|
acdc0ce1b0 | ||
|
|
e57f8a32ce | ||
|
|
c482230995 | ||
|
|
d3a54163bf | ||
|
|
7acb86a83e | ||
|
|
3e6a519c8b | ||
|
|
c3ccd2a6b7 | ||
|
|
94587c3472 | ||
|
|
27b83e471e | ||
|
|
137c219402 | ||
|
|
fe032d3d0f | ||
|
|
d3108ebf65 | ||
|
|
458ddc6e0a | ||
|
|
9c36f30bb0 | ||
|
|
c4dca57614 | ||
|
|
d88633f2c6 | ||
|
|
c64dd0d6ff | ||
|
|
bcbc5da30e | ||
|
|
e588551c33 | ||
|
|
081cae724b | ||
|
|
ae972ba1dd | ||
|
|
32bb6a8087 | ||
|
|
eb27b0305e | ||
|
|
65dbf80f84 | ||
|
|
fd17f3f25c | ||
|
|
3803a16bf5 | ||
|
|
bba51fcd11 | ||
|
|
66cd434839 | ||
|
|
4f49a10aef | ||
|
|
b43194df18 | ||
|
|
cf6549f2a3 | ||
|
|
2a554f6279 | ||
|
|
86139bb47d | ||
|
|
21a646ce66 | ||
|
|
bc673d6f67 | ||
|
|
fca0abd585 | ||
|
|
b6fd92aadd | ||
|
|
e67dbb4758 | ||
|
|
beb84554e1 | ||
|
|
9c89208d83 | ||
|
|
a3be030fac | ||
|
|
7c920dde71 | ||
|
|
b1b01373ca | ||
|
|
39f6fb5af3 | ||
|
|
f8721d3e04 | ||
|
|
a6c7c3b031 | ||
|
|
9bac042498 | ||
|
|
fae091d183 | ||
|
|
2929e01f09 | ||
|
|
8b2fd07a27 | ||
|
|
a9fa017252 | ||
|
|
e1681f0f03 | ||
|
|
5242ad573f | ||
|
|
0d6c237223 | ||
|
|
f43776d27d | ||
|
|
c6638b9fc9 | ||
|
|
2d56522733 | ||
|
|
463fcd17e7 | ||
|
|
ee98637072 | ||
|
|
d9f5d20473 | ||
|
|
2874c45227 | ||
|
|
8a7ceae03a | ||
|
|
f9b1194d16 | ||
|
|
8275aec7d1 | ||
|
|
2593736179 | ||
|
|
68d2e73e7a | ||
|
|
99f63597a8 | ||
|
|
d261fd4efe | ||
|
|
32ebd9b3b9 | ||
|
|
879311c332 | ||
|
|
1650e1bb74 | ||
|
|
58aaf6e002 | ||
|
|
d57bbff87c | ||
|
|
4c99e6000a | ||
|
|
2ec364ed68 | ||
|
|
f05c811bdc | ||
|
|
e609714f1e | ||
|
|
6c7b1c6c7c | ||
|
|
1be580807f | ||
|
|
766b166bf2 | ||
|
|
de72652297 | ||
|
|
73efe0d0ed | ||
|
|
ed46fd3cef | ||
|
|
c89e558143 | ||
|
|
249fe8c7fe | ||
|
|
a8408a11d9 | ||
|
|
6d945371c9 | ||
|
|
28d8fa9834 | ||
|
|
d7b9402528 | ||
|
|
42ce03ea5a | ||
|
|
f5099c15a1 | ||
|
|
0f1f237019 | ||
|
|
b98476c36a | ||
|
|
aeddf2d554 | ||
|
|
4b4a31dc57 | ||
|
|
fa7e6d7c2f | ||
|
|
82d2e00027 | ||
|
|
536a6ba2ff | ||
|
|
95ab755253 | ||
|
|
b9d6fdafac | ||
|
|
7999a4bdda | ||
|
|
50da20907f | ||
|
|
a773cfffa5 | ||
|
|
648386281f | ||
|
|
3674f3a4d6 | ||
|
|
1fea9dcf29 | ||
|
|
fb2a4d91e1 | ||
|
|
3e1063306f | ||
|
|
4d2354df47 | ||
|
|
7104fb0461 | ||
|
|
fbbbebbbd0 | ||
|
|
d18e315a28 | ||
|
|
d47efec45f | ||
|
|
4035c9a08d | ||
|
|
c5f3c61275 | ||
|
|
9436477f41 | ||
|
|
43d48520be | ||
|
|
1d1a3cede1 | ||
|
|
213fa08210 | ||
|
|
1eed1a356d | ||
|
|
a8f21ad717 | ||
|
|
c1420bd6d8 | ||
|
|
74ea9debd5 | ||
|
|
da8955dabb | ||
|
|
5dbf18605f | ||
|
|
ba01c5056e | ||
|
|
5ea7a31c6d | ||
|
|
e0a9eb0366 | ||
|
|
14e8e14b7d | ||
|
|
179de344c2 | ||
|
|
1250949c05 | ||
|
|
e12105f5b9 | ||
|
|
b8cc0cd11b | ||
|
|
be39dfee9e | ||
|
|
67ae2b19df | ||
|
|
1df9f1f4df | ||
|
|
f06ac587c9 | ||
|
|
c084cf84a0 | ||
|
|
5e5cbdeef9 | ||
|
|
24929d8a4d | ||
|
|
1e6e85ed5b | ||
|
|
137edf1250 | ||
|
|
b8741f1702 | ||
|
|
ac28aff022 | ||
|
|
7b9abef687 | ||
|
|
217be3c6e9 | ||
|
|
27a0dc3770 | ||
|
|
53b24534a8 | ||
|
|
5a3d0f8288 | ||
|
|
ff3e3513ef | ||
|
|
7a1fba38b3 | ||
|
|
2b65a3c119 | ||
|
|
6d841497cc | ||
|
|
d37dc7c372 | ||
|
|
f3e0cf861f | ||
|
|
e2578e5794 | ||
|
|
16deec381c | ||
|
|
91dc35a138 | ||
|
|
acfa032e61 | ||
|
|
83ee4b2c59 | ||
|
|
ac1637eaf8 | ||
|
|
d5c6a601d8 | ||
|
|
afbe42a577 | ||
|
|
866f700abf | ||
|
|
f2b6b33b1f | ||
|
|
813ffabb8c | ||
|
|
b5da9291b4 | ||
|
|
b0d604d12b | ||
|
|
e9f40e1644 | ||
|
|
61d520c239 | ||
|
|
124a884d2e | ||
|
|
b5e4b9af60 | ||
|
|
840c24e3ca | ||
|
|
d300eb73fb | ||
|
|
fb4e06116c | ||
|
|
2d3b903edc | ||
|
|
75c13df22f | ||
|
|
68b81cb48d | ||
|
|
9d6f2df25a | ||
|
|
452793c8e5 | ||
|
|
724de2c1b9 | ||
|
|
86946b6b15 | ||
|
|
957bb3d3e6 | ||
|
|
38a75b07fb | ||
|
|
378b93f996 | ||
|
|
eb62d124bd | ||
|
|
7558029271 | ||
|
|
757c28dad1 | ||
|
|
8d3dc38816 | ||
|
|
9c8061a447 | ||
|
|
3a8b2867b6 | ||
|
|
389956d1a2 | ||
|
|
bf6ed15ba7 | ||
|
|
31a66ce798 | ||
|
|
38c1d86e2f | ||
|
|
d08e232f50 | ||
|
|
3d49383c42 | ||
|
|
27706eaae4 | ||
|
|
c74b5a2677 | ||
|
|
0374165a7f | ||
|
|
b7dad5e1d9 | ||
|
|
65527bc39a | ||
|
|
a84bdd1c8e | ||
|
|
eb219221be | ||
|
|
7b176bd877 | ||
|
|
6970923253 | ||
|
|
a3e23d54d8 | ||
|
|
8f11207d72 | ||
|
|
6bd98350d9 | ||
|
|
096ef8cd93 | ||
|
|
d6eafcbb9b | ||
|
|
c0261384ca | ||
|
|
ca733addc2 | ||
|
|
7497671033 | ||
|
|
385fbf4af5 | ||
|
|
44e75ee7e1 | ||
|
|
6b4d6eac1d | ||
|
|
9379d4a31d | ||
|
|
dde799f510 | ||
|
|
ecb919e109 | ||
|
|
29ca894a97 | ||
|
|
84ba74a673 | ||
|
|
32b0d51e79 | ||
|
|
1288660fd6 | ||
|
|
5c1e24f4f3 | ||
|
|
3e12e1b1b3 | ||
|
|
175e84f50e | ||
|
|
efb646c43d | ||
|
|
fa950dae39 | ||
|
|
712ad25e7a | ||
|
|
35a41e774e | ||
|
|
c2ac193fbe | ||
|
|
ce3c89a715 | ||
|
|
96f7206a1d | ||
|
|
b7ace886f3 | ||
|
|
5dc330eaa3 | ||
|
|
b7f5bee2f8 | ||
|
|
19ee5f073b | ||
|
|
1fd4a6ae80 | ||
|
|
3c8a412014 | ||
|
|
eee617719b | ||
|
|
fc611946a6 | ||
|
|
bd84793780 | ||
|
|
9922c0ed66 | ||
|
|
af13c84968 | ||
|
|
ddb78ef8dd | ||
|
|
3590f3bed2 | ||
|
|
c70089ee53 | ||
|
|
161e479a0b | ||
|
|
bd735bfb64 | ||
|
|
85c814620e | ||
|
|
fb013fe4ec | ||
|
|
90a1bd9027 | ||
|
|
610ef8f35c | ||
|
|
9b2fcaea31 | ||
|
|
2ffa95b9fa | ||
|
|
559872c4e4 | ||
|
|
85642ed5f2 | ||
|
|
8bf701d2f2 | ||
|
|
38809a2034 | ||
|
|
4bd6ec2232 | ||
|
|
95899b7208 | ||
|
|
ac26bb95e3 | ||
|
|
5abcc82215 | ||
|
|
16791a9f4b | ||
|
|
54ab6e3436 | ||
|
|
f5ca72ddd7 | ||
|
|
7245e7dfd7 | ||
|
|
547d149987 | ||
|
|
3b2d29514c | ||
|
|
115abd378f | ||
|
|
9c101d78d1 | ||
|
|
610f8fa5bc | ||
|
|
e201bf12f8 | ||
|
|
bf872200a7 | ||
|
|
abc6906349 | ||
|
|
9440fd89ae | ||
|
|
bce1eb8907 | ||
|
|
4bf44b3275 | ||
|
|
06355ff089 | ||
|
|
95ecf4fe21 | ||
|
|
d50a6ce76f | ||
|
|
2d951e0b1f | ||
|
|
416de9879b | ||
|
|
082aff58a9 | ||
|
|
b74666fc2f | ||
|
|
dc626f1a94 | ||
|
|
533a5e490f | ||
|
|
cf54e4f5c2 | ||
|
|
d84c808887 | ||
|
|
89cd35adc6 | ||
|
|
6299385bb4 | ||
|
|
e6f9867500 | ||
|
|
ee855452e3 | ||
|
|
1391c4e3d6 | ||
|
|
d000b526d3 | ||
|
|
fa3f2ff867 | ||
|
|
8d1ddf685b | ||
|
|
b45c2d3538 | ||
|
|
5888494fcc | ||
|
|
679c3238e2 | ||
|
|
7547f5ec27 | ||
|
|
e7051d2bd1 | ||
|
|
f0a409c777 | ||
|
|
32ee2364c8 | ||
|
|
eb42277c49 | ||
|
|
f9afca7ffd | ||
|
|
c43af85c7c | ||
|
|
19e5d1bacc | ||
|
|
2276f11019 | ||
|
|
5ad1ba8985 | ||
|
|
1d91131d9a | ||
|
|
1460a667e7 | ||
|
|
2291e8d9f5 | ||
|
|
f64deb7976 | ||
|
|
b1b0dc3066 | ||
|
|
ac30a409e5 | ||
|
|
3fdd3ddc74 | ||
|
|
6ed379243e | ||
|
|
479dabc8e8 | ||
|
|
5aaa1e55f9 | ||
|
|
288af26b00 | ||
|
|
27256c609a | ||
|
|
c4d59177bf | ||
|
|
3c8ca2b012 | ||
|
|
88a1bee22c | ||
|
|
5491653fe9 | ||
|
|
a6cef7d601 | ||
|
|
efc3c85d55 | ||
|
|
75f9ccb85c | ||
|
|
7abd14b6bc | ||
|
|
62bbf2da7c | ||
|
|
340621b898 | ||
|
|
1e212d4e78 | ||
|
|
425c493889 | ||
|
|
5280c861e8 | ||
|
|
797931dfe9 | ||
|
|
cc39036479 | ||
|
|
9bf88b90c3 | ||
|
|
947d2217df | ||
|
|
9eba4f8b84 | ||
|
|
f0d0e4c1e2 | ||
|
|
24cb47bcb1 | ||
|
|
9cf4a5e7a3 | ||
|
|
7b91d67655 | ||
|
|
ca0acf0445 | ||
|
|
576ff02773 | ||
|
|
d8b28107cd | ||
|
|
9898cac2f5 | ||
|
|
dd9bed4c2b | ||
|
|
14c8ae675a | ||
|
|
bda0689e18 | ||
|
|
7e39be4ca1 | ||
|
|
2e3a7c6164 | ||
|
|
f02f75e3a0 | ||
|
|
fe51dd6b0a | ||
|
|
2724336cad | ||
|
|
12bd017d07 | ||
|
|
a2eff67d44 | ||
|
|
71555a15f8 | ||
|
|
c681aa2e9f | ||
|
|
1f81ebd4fe | ||
|
|
d243470029 | ||
|
|
054e581023 | ||
|
|
f866250c25 | ||
|
|
ee58672d58 | ||
|
|
135894da2a | ||
|
|
06c8688ddd | ||
|
|
d8474b8aa3 | ||
|
|
9a4b474cdc | ||
|
|
1519a71535 | ||
|
|
9e47103131 | ||
|
|
ef689f06d6 | ||
|
|
54c7572447 | ||
|
|
4cacc6b3d1 | ||
|
|
8b193d4317 | ||
|
|
e72add74c3 | ||
|
|
115c8641e7 | ||
|
|
0af532f87e | ||
|
|
d1bd2b29fe | ||
|
|
734a6607c8 | ||
|
|
d3a2b03bb7 | ||
|
|
9a1436d0ae | ||
|
|
087e2c81cc | ||
|
|
32f35a6ca0 | ||
|
|
7fd35999b1 | ||
|
|
fa5b75e6fb | ||
|
|
7262e0debe | ||
|
|
13c686c228 | ||
|
|
083bb7b87d | ||
|
|
1b7ecd5a41 | ||
|
|
56b52b3f9c | ||
|
|
88f67b1c71 | ||
|
|
75bd10cbd5 | ||
|
|
d3397cfbd0 | ||
|
|
a4bf48fa68 | ||
|
|
dfe3294088 | ||
|
|
5b1ca4eafc | ||
|
|
82721251cc | ||
|
|
cd051b72fc | ||
|
|
411070cfcc | ||
|
|
8230c1ba91 | ||
|
|
8b7df6ce16 | ||
|
|
6d71eac221 | ||
|
|
fd3e4a8bc7 | ||
|
|
c13eb65b5a | ||
|
|
bb13a09def | ||
|
|
94bcea36c6 | ||
|
|
028b9b3f7b | ||
|
|
b85639f98e | ||
|
|
7c981b2aac | ||
|
|
59b072e7e0 | ||
|
|
6780fa9688 | ||
|
|
ff47a157c7 | ||
|
|
3601abc4c1 | ||
|
|
b1a48d4636 | ||
|
|
a15eb3b229 | ||
|
|
c34c4b244e | ||
|
|
4991e4b1f2 | ||
|
|
b3b7439617 | ||
|
|
b7c061dcb4 | ||
|
|
e00cbaeb8a | ||
|
|
655e29d96b | ||
|
|
7240ff38f1 | ||
|
|
4a08bacba0 | ||
|
|
54aaa511d5 | ||
|
|
701319efdd | ||
|
|
0fdf176648 | ||
|
|
f62d835f57 | ||
|
|
5bb4710952 | ||
|
|
9b71ce9388 | ||
|
|
175d1ec432 | ||
|
|
8454e4f781 | ||
|
|
2e79c7230f | ||
|
|
7ddd3bb8a0 | ||
|
|
b889a9d248 | ||
|
|
fc21c96cd1 | ||
|
|
0341b19c9f | ||
|
|
43095f2435 | ||
|
|
ad696ea54a | ||
|
|
f6128bdf0c | ||
|
|
6be6ec940a | ||
|
|
ba9fc59805 | ||
|
|
63a1039439 | ||
|
|
d52692c6a3 | ||
|
|
b4511ca7a2 | ||
|
|
a3c24f1f2a | ||
|
|
ca599f27f7 | ||
|
|
f36de7b2f5 | ||
|
|
0ce055c001 | ||
|
|
fc011a5661 | ||
|
|
60d6d781be | ||
|
|
9270739eb6 | ||
|
|
87b87b85c0 | ||
|
|
496fd40fa3 | ||
|
|
25fe080582 | ||
|
|
1c41091372 | ||
|
|
9230178005 | ||
|
|
fd092f1248 | ||
|
|
736c186a66 | ||
|
|
3d348ee762 | ||
|
|
6e78f49c2f | ||
|
|
e77b30671b | ||
|
|
244e1227c4 | ||
|
|
9934dac203 | ||
|
|
44ee326057 | ||
|
|
18f892096b | ||
|
|
9954d5b209 | ||
|
|
e0bde5cec9 | ||
|
|
2d4eaeb8b5 | ||
|
|
787506fb6b | ||
|
|
50c8c3a43a | ||
|
|
1f09c06274 | ||
|
|
bb59a0cd3f | ||
|
|
1e4217315b | ||
|
|
b373aca0ff | ||
|
|
87e90cb30b | ||
|
|
135fabb852 | ||
|
|
b0b5b94bb7 | ||
|
|
8f1c1e5202 | ||
|
|
e4c243d7a6 | ||
|
|
c3bca21d14 | ||
|
|
1befdb76e7 | ||
|
|
35652c5c53 | ||
|
|
9524609092 | ||
|
|
38c16fe839 | ||
|
|
c0587b9409 | ||
|
|
e249e878f6 | ||
|
|
2d64815c12 | ||
|
|
af11bc8cd2 | ||
|
|
6779dec1ff | ||
|
|
191a6112ce | ||
|
|
1bf518f768 | ||
|
|
d0d4182fc1 | ||
|
|
736f7c2d77 | ||
|
|
6e38508477 | ||
|
|
afe8160d46 | ||
|
|
ceb4ae62e2 | ||
|
|
67d0dd5bf7 | ||
|
|
6e28545c3f | ||
|
|
382208ae13 | ||
|
|
906e8de13b | ||
|
|
7a5c71cda3 | ||
|
|
7bc6d0777d | ||
|
|
f684ba7b1f | ||
|
|
86ed884162 | ||
|
|
4ff178ea34 | ||
|
|
2eb5c331a1 | ||
|
|
79ad0818f5 | ||
|
|
84c10eec66 | ||
|
|
61673a40e3 | ||
|
|
363ba1d20e | ||
|
|
5fadd73732 | ||
|
|
44e6a117dd | ||
|
|
86bb119052 | ||
|
|
54c70ff177 | ||
|
|
eabe14e4c3 | ||
|
|
342ff4b589 | ||
|
|
680811357b | ||
|
|
9d0edd810e | ||
|
|
35ff65a871 | ||
|
|
675fbb7692 | ||
|
|
295bd06918 | ||
|
|
89635fa27b | ||
|
|
60b19616c1 | ||
|
|
3884dc9259 | ||
|
|
e8648732be | ||
|
|
0f0f32a40d | ||
|
|
1b91376f5e | ||
|
|
aa347abfcd | ||
|
|
e49b190c32 | ||
|
|
66017c8623 | ||
|
|
bab93f8145 |
106
.circleci/config.yml
Normal file
106
.circleci/config.yml
Normal file
@@ -0,0 +1,106 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp apps/dokploy/.env.production.example .env.production
|
||||
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
|
||||
|
||||
- run:
|
||||
name: Build and push AMD64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
TAG="latest"
|
||||
else
|
||||
TAG="canary"
|
||||
fi
|
||||
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
|
||||
docker push dokploy/dokploy:${TAG}-amd64
|
||||
|
||||
build-arm64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp apps/dokploy/.env.production.example .env.production
|
||||
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
|
||||
- run:
|
||||
name: Build and push ARM64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
TAG="latest"
|
||||
else
|
||||
TAG="canary"
|
||||
fi
|
||||
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
||||
docker push dokploy/dokploy:${TAG}-arm64
|
||||
|
||||
combine-manifests:
|
||||
docker:
|
||||
- image: cimg/node:18.18.0
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Create and push multi-arch manifest
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
|
||||
echo $VERSION
|
||||
TAG="latest"
|
||||
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
|
||||
docker manifest create dokploy/dokploy:${VERSION} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${VERSION}
|
||||
else
|
||||
TAG="canary"
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
fi
|
||||
|
||||
workflows:
|
||||
build-all:
|
||||
jobs:
|
||||
- build-amd64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
4
.config/.husky/commit-msg
Normal file
4
.config/.husky/commit-msg
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm commitlint --edit $1
|
||||
6
.config/.husky/install.mjs
Normal file
6
.config/.husky/install.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Skip Husky install in production and CI
|
||||
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
|
||||
process.exit(0);
|
||||
}
|
||||
const husky = (await import("husky")).default;
|
||||
console.log(husky());
|
||||
1
.config/.husky/pre-commit
Normal file
1
.config/.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
pnpm lint-staged
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [siumauricio]
|
||||
patreon: #
|
||||
open_collective: dokploy
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
||||
BIN
.github/sponsors/hostinger.jpg
vendored
Normal file
BIN
.github/sponsors/hostinger.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
BIN
.github/sponsors/logo.png
vendored
Normal file
BIN
.github/sponsors/logo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
.github/sponsors/lxaer.png
vendored
Normal file
BIN
.github/sponsors/lxaer.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
BIN
.github/sponsors/mandarin.png
vendored
Normal file
BIN
.github/sponsors/mandarin.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
80
.github/workflows/deploy.yml
vendored
Normal file
80
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Build Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["canary", "main"]
|
||||
|
||||
jobs:
|
||||
build-and-push-cloud-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.cloud
|
||||
push: true
|
||||
tags: |
|
||||
siumauricio/cloud:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
NEXT_PUBLIC_UMAMI_HOST=${{ secrets.NEXT_PUBLIC_UMAMI_HOST }}
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${{ secrets.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY }}
|
||||
|
||||
build-and-push-schedule-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.schedule
|
||||
push: true
|
||||
tags: |
|
||||
siumauricio/schedule:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
|
||||
platforms: linux/amd64
|
||||
|
||||
|
||||
build-and-push-server-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.server
|
||||
push: true
|
||||
tags: |
|
||||
siumauricio/server:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
|
||||
platforms: linux/amd64
|
||||
91
.github/workflows/pull-request.yml
vendored
91
.github/workflows/pull-request.yml
vendored
@@ -1,59 +1,50 @@
|
||||
name: Pull request
|
||||
name: Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
branches: [main, canary]
|
||||
|
||||
env:
|
||||
HUSKY: 0
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
jobs:
|
||||
build-app:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.18.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run Build
|
||||
run: pnpm build
|
||||
- name: Run Tests
|
||||
run: pnpm run test
|
||||
|
||||
build-and-push-docker-on-push:
|
||||
if: github.event_name == 'push'
|
||||
lint-and-typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
node-version: 18.18.0
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run server:build
|
||||
- run: pnpm biome ci
|
||||
- run: pnpm typecheck
|
||||
|
||||
- name: Prepare .env file
|
||||
run: |
|
||||
cp .env.production.example .env.production
|
||||
build-and-test:
|
||||
needs: lint-and-typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.18.0
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run server:build
|
||||
- run: pnpm build
|
||||
|
||||
- name: Build and push Docker image using custom script
|
||||
run: |
|
||||
chmod +x ./docker/push.sh
|
||||
./docker/push.sh ${{ github.ref_name == 'canary' && 'canary' || '' }}
|
||||
parallel-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.18.0
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run server:build
|
||||
- run: pnpm test
|
||||
|
||||
72
.gitignore
vendored
72
.gitignore
vendored
@@ -1,58 +1,42 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
/redis-data
|
||||
traefik.yml
|
||||
.docker
|
||||
.env.production
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/dist
|
||||
/production-server
|
||||
# database
|
||||
/prisma/db.sqlite
|
||||
/prisma/db.sqlite-journal
|
||||
/logs
|
||||
# Local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
next-env.d.ts
|
||||
/dokploy
|
||||
/config
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
# Turbo
|
||||
.turbo
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
# Vercel
|
||||
.vercel
|
||||
|
||||
# debug
|
||||
# Build Outputs
|
||||
.next/
|
||||
out/
|
||||
dist
|
||||
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# otros
|
||||
/.data
|
||||
/.main
|
||||
|
||||
*.lockb
|
||||
*.rdb
|
||||
# Editor
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
1
.husky/commit-msg
Normal file
1
.husky/commit-msg
Normal file
@@ -0,0 +1 @@
|
||||
npx commitlint --edit "$1"
|
||||
6
.husky/install.mjs
Normal file
6
.husky/install.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Skip Husky install in production and CI
|
||||
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
|
||||
process.exit(0);
|
||||
}
|
||||
const husky = (await import("husky")).default;
|
||||
console.log(husky());
|
||||
2
.husky/pre-commit
Normal file
2
.husky/pre-commit
Normal file
@@ -0,0 +1,2 @@
|
||||
pnpm run check
|
||||
git add .
|
||||
126
CONTRIBUTING.md
126
CONTRIBUTING.md
@@ -1,10 +1,7 @@
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
Hey, thanks for your interest in contributing to Dokploy! We appreciate your help and taking your time to contribute.
|
||||
|
||||
|
||||
Before you start, please first discuss the feature/bug you want to add with the owners and comunity via github issues.
|
||||
|
||||
We have a few guidelines to follow when contributing to this project:
|
||||
@@ -17,9 +14,10 @@ We have a few guidelines to follow when contributing to this project:
|
||||
|
||||
## Commit Convention
|
||||
|
||||
Before you craete a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||
Before you create a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
@@ -29,28 +27,27 @@ Before you craete a Pull Request, please make sure your commit message follows t
|
||||
```
|
||||
|
||||
#### Type
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **docs**: Documentation only changes
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **perf**: A code change that improves performance
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||
* **chore**: Other changes that don't modify `src` or `test` files
|
||||
* **revert**: Reverts a previous commit
|
||||
- **feat**: A new feature
|
||||
- **fix**: A bug fix
|
||||
- **docs**: Documentation only changes
|
||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
- **perf**: A code change that improves performance
|
||||
- **test**: Adding missing tests or correcting existing tests
|
||||
- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||
- **chore**: Other changes that don't modify `src` or `test` files
|
||||
- **revert**: Reverts a previous commit
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
feat: add new feature
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch.
|
||||
@@ -59,45 +56,50 @@ Before you start, please make the clone based on the `canary` branch, since the
|
||||
git clone https://github.com/dokploy/dokploy.git
|
||||
cd dokploy
|
||||
pnpm install
|
||||
cp .env.example .env
|
||||
cp apps/dokploy/.env.example apps/dokploy/.env
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Is required to have **Docker** installed on your machine.
|
||||
|
||||
|
||||
### Setup
|
||||
|
||||
Run the command that will spin up all the required services and files.
|
||||
|
||||
```bash
|
||||
pnpm run setup
|
||||
pnpm run dokploy:setup
|
||||
```
|
||||
|
||||
Run this script
|
||||
```bash
|
||||
pnpm run server:script
|
||||
```
|
||||
|
||||
Now run the development server.
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
pnpm run dokploy:dev
|
||||
```
|
||||
|
||||
|
||||
Go to http://localhost:3000 to see the development server
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
pnpm run dokploy:build
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
To build the docker image
|
||||
|
||||
```bash
|
||||
pnpm run docker:build
|
||||
```
|
||||
|
||||
To push the docker image
|
||||
|
||||
```bash
|
||||
pnpm run docker:push
|
||||
```
|
||||
@@ -138,7 +140,6 @@ curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
||||
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||
```
|
||||
|
||||
|
||||
## Pull Request
|
||||
|
||||
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
||||
@@ -152,10 +153,6 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.
|
||||
|
||||
Thank you for your contribution!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
To add a new template, go to `templates` folder and create a new folder with the name of the template.
|
||||
@@ -170,42 +167,47 @@ Let's take the example of `plausible` template.
|
||||
```typescript
|
||||
// EXAMPLE
|
||||
import {
|
||||
generateHash,
|
||||
generateRandomDomain,
|
||||
type Template,
|
||||
type Schema,
|
||||
generateHash,
|
||||
generateRandomDomain,
|
||||
type Template,
|
||||
type Schema,
|
||||
type DomainSchema,
|
||||
} from "../utils";
|
||||
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
// do your stuff here, like create a new domain, generate random passwords, mounts.
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const secretBase = generateBase64(64);
|
||||
const toptKeyBase = generateBase64(32);
|
||||
|
||||
// do your stuff here, like create a new domain, generate random passwords, mounts.
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const randomDomain = generateRandomDomain(schema);
|
||||
const secretBase = generateBase64(64);
|
||||
const toptKeyBase = generateBase64(32);
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 8000,
|
||||
serviceName: "plausible",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
// If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
||||
`PLAUSIBLE_HOST=${randomDomain}`,
|
||||
"PLAUSIBLE_PORT=8000",
|
||||
`BASE_URL=http://${randomDomain}`,
|
||||
`SECRET_KEY_BASE=${secretBase}`,
|
||||
`TOTP_VAULT_KEY=${toptKeyBase}`,
|
||||
`HASH=${mainServiceHash}`,
|
||||
];
|
||||
const envs = [
|
||||
`BASE_URL=http://${mainDomain}`,
|
||||
`SECRET_KEY_BASE=${secretBase}`,
|
||||
`TOTP_VAULT_KEY=${toptKeyBase}`,
|
||||
`HASH=${mainServiceHash}`,
|
||||
];
|
||||
|
||||
const mounts: Template["mounts"] = [
|
||||
{
|
||||
mountPath: "./clickhouse/clickhouse-config.xml",
|
||||
content: `some content......`,
|
||||
},
|
||||
];
|
||||
const mounts: Template["mounts"] = [
|
||||
{
|
||||
mountPath: "./clickhouse/clickhouse-config.xml",
|
||||
content: `some content......`,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
mounts,
|
||||
};
|
||||
return {
|
||||
envs,
|
||||
mounts,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
@@ -233,10 +235,14 @@ export function generate(schema: Schema): Template {
|
||||
|
||||
5. Add the logo or image of the template to `public/templates/plausible.svg`
|
||||
|
||||
|
||||
### Recomendations
|
||||
|
||||
- Use the same name of the folder as the id of the template.
|
||||
- The logo should be in the public folder.
|
||||
- If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
||||
- If you want to show a domain in the UI, please add the prefix \_HOST at the end of the variable name.
|
||||
- Test first on a vps or a server to make sure the template works.
|
||||
|
||||
## Docs & Website
|
||||
|
||||
To contribute to the Dokploy docs or website, please go to this [repository](https://github.com/Dokploy/website).
|
||||
|
||||
|
||||
74
Dockerfile
74
Dockerfile
@@ -1,51 +1,50 @@
|
||||
# Etapa 1: Prepare image for building
|
||||
FROM node:18-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
FROM base AS build
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and pnpm-lock.yaml
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install dependencies only for building
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
# Deploy only the dokploy app
|
||||
|
||||
# Build the application
|
||||
RUN pnpm run build
|
||||
ENV NODE_ENV=production
|
||||
RUN pnpm --filter=@dokploy/server build
|
||||
RUN pnpm --filter=./apps/dokploy run build
|
||||
|
||||
# Stage 2: Prepare image for production
|
||||
FROM node:18-slim AS production
|
||||
RUN pnpm --filter=./apps/dokploy --prod deploy /prod/dokploy
|
||||
|
||||
# Install dependencies only for production
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && apt-get update && apt-get install -y curl && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/*
|
||||
RUN cp -R /usr/src/app/apps/dokploy/.next /prod/dokploy/.next
|
||||
RUN cp -R /usr/src/app/apps/dokploy/dist /prod/dokploy/dist
|
||||
|
||||
FROM base AS dokploy
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY --from=base /app/.next ./.next
|
||||
COPY --from=base /app/dist ./dist
|
||||
COPY --from=base /app/next.config.mjs ./next.config.mjs
|
||||
COPY --from=base /app/public ./public
|
||||
COPY --from=base /app/package.json ./package.json
|
||||
COPY --from=base /app/drizzle ./drizzle
|
||||
COPY --from=base /app/.env.production ./.env
|
||||
COPY --from=base /app/components.json ./components.json
|
||||
# Set production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy only the necessary files
|
||||
COPY --from=build /prod/dokploy/.next ./.next
|
||||
COPY --from=build /prod/dokploy/dist ./dist
|
||||
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
||||
COPY --from=build /prod/dokploy/public ./public
|
||||
COPY --from=build /prod/dokploy/package.json ./package.json
|
||||
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
||||
COPY .env.production ./.env
|
||||
COPY --from=build /prod/dokploy/components.json ./components.json
|
||||
COPY --from=build /prod/dokploy/node_modules ./node_modules
|
||||
|
||||
# Install dependencies only for production
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
# Install docker
|
||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
|
||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh && curl https://rclone.org/install.sh | bash
|
||||
|
||||
# Install Nixpacks and tsx
|
||||
# | VERBOSE=1 VERSION=1.21.0 bash
|
||||
@@ -54,11 +53,8 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
||||
&& ./install.sh \
|
||||
&& pnpm install -g tsx
|
||||
|
||||
|
||||
# Install buildpacks
|
||||
RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||
COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["pnpm", "start"]
|
||||
CMD [ "pnpm", "start" ]
|
||||
61
Dockerfile.cloud
Normal file
61
Dockerfile.cloud
Normal file
@@ -0,0 +1,61 @@
|
||||
FROM node:18-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
FROM base AS build
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/dokploy install --frozen-lockfile
|
||||
|
||||
|
||||
# Deploy only the dokploy app
|
||||
ARG NEXT_PUBLIC_UMAMI_HOST
|
||||
ENV NEXT_PUBLIC_UMAMI_HOST=$NEXT_PUBLIC_UMAMI_HOST
|
||||
|
||||
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||
|
||||
ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN pnpm --filter=@dokploy/server build
|
||||
RUN pnpm --filter=./apps/dokploy run build
|
||||
|
||||
RUN pnpm --filter=./apps/dokploy --prod deploy /prod/dokploy
|
||||
|
||||
RUN cp -R /usr/src/app/apps/dokploy/.next /prod/dokploy/.next
|
||||
RUN cp -R /usr/src/app/apps/dokploy/dist /prod/dokploy/dist
|
||||
|
||||
FROM base AS dokploy
|
||||
WORKDIR /app
|
||||
|
||||
# Set production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy only the necessary files
|
||||
COPY --from=build /prod/dokploy/.next ./.next
|
||||
COPY --from=build /prod/dokploy/dist ./dist
|
||||
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
||||
COPY --from=build /prod/dokploy/public ./public
|
||||
COPY --from=build /prod/dokploy/package.json ./package.json
|
||||
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
||||
COPY --from=build /prod/dokploy/components.json ./components.json
|
||||
COPY --from=build /prod/dokploy/node_modules ./node_modules
|
||||
|
||||
|
||||
# Install RCLONE
|
||||
RUN curl https://rclone.org/install.sh | bash
|
||||
|
||||
# tsx
|
||||
RUN pnpm install -g tsx
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "pnpm", "start" ]
|
||||
36
Dockerfile.schedule
Normal file
36
Dockerfile.schedule
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM node:18-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
FROM base AS build
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/schedules install --frozen-lockfile
|
||||
|
||||
# Deploy only the dokploy app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN pnpm --filter=@dokploy/server build
|
||||
RUN pnpm --filter=./apps/schedules run build
|
||||
|
||||
RUN pnpm --filter=./apps/schedules --prod deploy /prod/schedules
|
||||
|
||||
RUN cp -R /usr/src/app/apps/schedules/dist /prod/schedules/dist
|
||||
|
||||
FROM base AS dokploy
|
||||
WORKDIR /app
|
||||
|
||||
# Set production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Copy only the necessary files
|
||||
COPY --from=build /prod/schedules/dist ./dist
|
||||
COPY --from=build /prod/schedules/package.json ./package.json
|
||||
COPY --from=build /prod/schedules/node_modules ./node_modules
|
||||
|
||||
CMD HOSTNAME=0.0.0.0 && pnpm start
|
||||
36
Dockerfile.server
Normal file
36
Dockerfile.server
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM node:18-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
FROM base AS build
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/api install --frozen-lockfile
|
||||
|
||||
# Deploy only the dokploy app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN pnpm --filter=@dokploy/server build
|
||||
RUN pnpm --filter=./apps/api run build
|
||||
|
||||
RUN pnpm --filter=./apps/api --prod deploy /prod/api
|
||||
|
||||
RUN cp -R /usr/src/app/apps/api/dist /prod/api/dist
|
||||
|
||||
FROM base AS dokploy
|
||||
WORKDIR /app
|
||||
|
||||
# Set production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Copy only the necessary files
|
||||
COPY --from=build /prod/api/dist ./dist
|
||||
COPY --from=build /prod/api/package.json ./package.json
|
||||
COPY --from=build /prod/api/node_modules ./node_modules
|
||||
|
||||
CMD HOSTNAME=0.0.0.0 && pnpm start
|
||||
@@ -17,10 +17,10 @@ See the License for the specific language governing permissions and limitations
|
||||
|
||||
## Additional Terms for Specific Features
|
||||
|
||||
The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
|
||||
The following additional terms apply to the multi-node support, Docker Compose file and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
|
||||
|
||||
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support and Docker Compose file support, will always be free to use in the self-hosted version.
|
||||
- **Restriction on Resale**: The multi-node support and Docker Compose file support features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
|
||||
- **Modification Distribution**: Any modifications to the multi-node support and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service.
|
||||
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support and Multi Server, will always be free to use in the self-hosted version.
|
||||
- **Restriction on Resale**: The multi-node support, Docker Compose file support and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
|
||||
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support and Multi Server features must be distributed freely and cannot be sold or offered as a service.
|
||||
|
||||
For further inquiries or permissions, please contact us directly.
|
||||
|
||||
57
README-de.md
57
README-de.md
@@ -1,57 +0,0 @@
|
||||
|
||||
|
||||
<div align="center">
|
||||
<h1 align="center">Dokploy</h1>
|
||||
</div>
|
||||
|
||||
<div align="center" style="width:100%;">
|
||||
<img src="https://raw.githubusercontent.com/Dokploy/dokploy/main/logo.png" alt="Reflex Logo" style="width:60%;">
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
|
||||
Dokploy ist eine kostenlose und self-hostable Platform as a Service (PaaS), welche das hosten und managen von deinen Projekten und Datenbanken vereinfacht, das geschieht mithilfe von Docker und Treafik. Es ist designt, um deine Leistung und die Sicherheit deiner Projekte zu verbessern. Dokploy erlaubt dir schnell und einfach auf jeder VPS deine Projekte zu verwirklichen.
|
||||
|
||||
|
||||
## Erklärung
|
||||
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md) | [Русский Язык](README-ru.md)
|
||||
|
||||
|
||||
|
||||
|
||||
## 🌟 Vorteile
|
||||
|
||||
- **Projekte**: - **Projekte**: Hoste jegliche Art von Projekt (Node.js, PHP, Python, Go, Ruby, etc.) mit Einfachheit.
|
||||
- **Datenbanken**: Erstelle und manage Datenbanken, wie MySQL, PostgreSQL, MongoDB, MariaDB, Redis, und mehr.
|
||||
- **Docker Management**: Einfach Docker container hosten und managen.
|
||||
- **Traefik Integration**: Automatische Integration mit Traefik für routing und load balancing
|
||||
- **Real-time Monitoring**: Monitor von CPU, RAM, Speicher, und network Nutzung.
|
||||
- **Database Backups**: Automatische Backups mit Support für mehrere Speicher Systeme.
|
||||
|
||||
|
||||
## 🚀 Loslegen
|
||||
|
||||
Um anzufangen führe einfach den folgende command in einer VPS aus:
|
||||
|
||||
```bash
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
```
|
||||
|
||||
Getestete Systems:
|
||||
|
||||
- Ubuntu 24.04 LTS (Noble Numbat)
|
||||
- Ubuntu 23.10 (Mantic Minotaur)
|
||||
- Ubuntu 22.04 LTS (Jammy Jellyfish)
|
||||
- Ubuntu 20.04 LTS (Focal Fossa)
|
||||
- Ubuntu 18.04 LTS (Bionic Beaver)
|
||||
- Debian 12
|
||||
- Debian 11
|
||||
- Fedora 40
|
||||
- Centos 9
|
||||
- Centos 8
|
||||
|
||||
|
||||
## 📄 Dokumentation
|
||||
|
||||
Für eine detaillierte Dokumentation, siehe [docs.dokploy.com/docs](https://docs.dokploy.com)
|
||||
|
||||
56
README-ru.md
56
README-ru.md
@@ -1,56 +0,0 @@
|
||||
<div align="center">
|
||||
<h1 align="center">Dokploy</h1>
|
||||
</div>
|
||||
|
||||
<div align="center" style="width:100%;">
|
||||
<img src="https://raw.githubusercontent.com/Dokploy/dokploy/main/logo.png" alt="Логотип Dokploy" style="width:60%;">
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
Dokploy - это бесплатная самоустанавливаемая Платформа как Сервис (PaaS), которая упрощает развертывание и управление приложениями и базами данных с использованием Docker и Traefik. Разработанный для повышения эффективности и безопасности, Dokploy позволяет развертывать ваши приложения на любом VPS.
|
||||
|
||||
|
||||
|
||||
## Объяснение
|
||||
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md)
|
||||
|
||||
|
||||
|
||||
|
||||
## 🌟 Особенности
|
||||
|
||||
- **Приложения**: Легко развертывать любой тип приложения (Node.js, PHP, Python, Go, Ruby и др.).
|
||||
- **Базы данных**: Создавайте и управляйте базами данных с поддержкой MySQL, PostgreSQL, MongoDB, MariaDB, Redis и других.
|
||||
- **Управление Docker**: Легко развертывать и управляйте контейнерами Docker.
|
||||
- **Интеграция с Traefik**: Автоматически интегрируется с Traefik для маршрутизации и балансировки нагрузки.
|
||||
- **Мониторинг в реальном времени**: Отслеживайте использование CPU, памяти, хранилища и сети.
|
||||
- **Резервное копирование баз данных**: Автоматизируйте резервное копирование с поддержкой нескольких мест хранения.
|
||||
|
||||
|
||||
## 🚀 Начало работы
|
||||
|
||||
Чтобы установить, выполните следующую команду на VPS:
|
||||
|
||||
|
||||
```bash
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
```
|
||||
|
||||
Проверенные системы:
|
||||
|
||||
- Ubuntu 24.04 LTS (Noble Numbat)
|
||||
- Ubuntu 23.10 (Mantic Minotaur)
|
||||
- Ubuntu 22.04 LTS (Jammy Jellyfish)
|
||||
- Ubuntu 20.04 LTS (Focal Fossa)
|
||||
- Ubuntu 18.04 LTS (Bionic Beaver)
|
||||
- Debian 12
|
||||
- Debian 11
|
||||
- Fedora 40
|
||||
- Centos 9
|
||||
- Centos 8
|
||||
|
||||
|
||||
## 📄 Документация
|
||||
Для подробной документации посетите [docs.dokploy.com/docs](https://docs.dokploy.com).
|
||||
60
README-zh.md
60
README-zh.md
@@ -1,60 +0,0 @@
|
||||
|
||||
|
||||
<div align="center">
|
||||
<h1 align="center">Dokploy</h1>
|
||||
</div>
|
||||
|
||||
<div align="center" style="width:100%;">
|
||||
<img src="https://raw.githubusercontent.com/Dokploy/dokploy/main/logo.png" alt="Reflex Logo" style="width:60%;">
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
Dokploy 是一个免费的自托管平台即服务 (PaaS),它使用 Docker 和 Traefik 简化了应用程序和数据库的部署和管理。 Dokploy 旨在提高效率和安全性,允许您在任何 VPS 上部署应用程序。
|
||||
|
||||
## 语言
|
||||
[English](README.md)
|
||||
|
||||
[中文](README-zh.md)
|
||||
|
||||
[Deutsch](README-de.md)
|
||||
|
||||
[Русский Язык](README-ru.md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 🌟 功能
|
||||
|
||||
- **应用程序**: 轻松部署任何类型的应用程序(Node.js,PHP,Python,Go、Ruby 等)。数据库: 创建和管理数据库,支持 MySQL,PostgreSQL,MongoDB、MariaDB、Redis 等。
|
||||
- **Docker 管理**: 轻松部署和管理 Docker 容器。
|
||||
- **Traefik 集成**: 自动与 Traefik 集成,用于路由和负载均衡。
|
||||
- **实时监控**: 监控 CPU,内存,存储和网络使用情况。
|
||||
- **数据库备份**: 支持多种存储目的地自动备份。
|
||||
|
||||
## 🚀 入门
|
||||
要开始使用 请在VPS 上运行以下命令:
|
||||
|
||||
```bash
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
```
|
||||
|
||||
经过测试的系统:
|
||||
|
||||
- Ubuntu 24.04 LTS (Noble Numbat)
|
||||
- Ubuntu 23.10 (Mantic Minotaur)
|
||||
- Ubuntu 22.04 LTS (Jammy Jellyfish)
|
||||
- Ubuntu 20.04 LTS (Focal Fossa)
|
||||
- Ubuntu 18.04 LTS (Bionic Beaver)
|
||||
- Debian 12
|
||||
- Debian 11
|
||||
- Fedora 40
|
||||
- Centos 9
|
||||
- Centos 8
|
||||
|
||||
|
||||
## 📄 文档
|
||||
|
||||
如需查看详细的文档资料 请访问[docs.dokploy.com/docs](https://docs.dokploy.com)
|
||||
159
README.md
159
README.md
@@ -1,94 +1,137 @@
|
||||
|
||||
<div align="center">
|
||||
<h1 align="center">Dokploy</h1>
|
||||
<div>
|
||||
<img style="object-fit: cover; border-radius:20px;" align="center" width="50%"src="https://raw.githubusercontent.com/Dokploy/docs/main/public/logo.png" >
|
||||
|
||||
<a href="https://dokploy.com" target="_blank" rel="noopener">
|
||||
<img style="object-fit: cover;" align="center" width="100%"src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</br>
|
||||
<div align="center">
|
||||
<div>Join us on Discord for help, feedback, and discussions!</div>
|
||||
</br>
|
||||
<a href="https://discord.gg/2tBnJ3jDJc">
|
||||
<img src="https://discordapp.com/api/guilds/1234073262418563112/widget.png?style=banner2" alt="Discord Shield"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
Dokploy is a free self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
||||
|
||||
Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
||||
|
||||
### Features
|
||||
|
||||
Dokploy include multiples features to make your life easier.
|
||||
|
||||
|
||||
* **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||
* **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, Redis.
|
||||
* **Backups**: Automate backups for databases to a external storage destination.
|
||||
* **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||
* **Multi Node**: Scale applications to multiples nodes using docker swarm to manage the cluster.
|
||||
* **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
|
||||
* **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||
* **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
||||
* **Docker Management**: Easily deploy and manage Docker containers.
|
||||
* **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
||||
* **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||
|
||||
|
||||
Dokploy includes multiple features to make your life easier.
|
||||
|
||||
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, and Redis.
|
||||
- **Backups**: Automate backups for databases to an external storage destination.
|
||||
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||
- **Multi Node**: Scale applications to multiple nodes using Docker Swarm to manage the cluster.
|
||||
- **Templates**: Deploy open-source templates (Plausible, Pocketbase, Calcom, etc.) with a single click.
|
||||
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage for every resource.
|
||||
- **Docker Management**: Easily deploy and manage Docker containers.
|
||||
- **CLI/API**: Manage your applications and databases using the command line or through the API.
|
||||
- **Notifications**: Get notified when your deployments succeed or fail (via Slack, Discord, Telegram, Email, etc.).
|
||||
- **Multi Server**: Deploy and manage your applications remotely to external servers.
|
||||
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
To get started run the following command in a VPS:
|
||||
To get started, run the following command on a VPS:
|
||||
|
||||
Want to skip the installation process? [Try the Dokploy Cloud](https://app.dokploy.com).
|
||||
|
||||
```bash
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
```
|
||||
|
||||
|
||||
## 📄 Documentation
|
||||
|
||||
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||
|
||||
## Sponsors
|
||||
|
||||
🙏 We're deeply grateful to all our sponsors who make Dokploy possible! Your support helps cover the costs of hosting, testing, and developing new features.
|
||||
|
||||
[Dokploy Open Collective](https://opencollective.com/dokploy)
|
||||
|
||||
[Github Sponsors](https://github.com/sponsors/Siumauricio)
|
||||
|
||||
<!-- Hero Sponsors 🎖 -->
|
||||
|
||||
<!-- Add Hero Sponsors here -->
|
||||
|
||||
### Hero Sponsors 🎖
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 20px;">
|
||||
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
||||
<img src=".github/sponsors/hostinger.jpg" alt="Hostinger" height="50"/>
|
||||
</a>
|
||||
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
||||
<img src=".github/sponsors/lxaer.png" alt="LX Aer" height="50"/>
|
||||
</a>
|
||||
<a href="https://mandarin3d.com/?ref=dokploy" target="_blank" style="display: inline-block;">
|
||||
<img src=".github/sponsors/mandarin.png" alt="Mandarin" height="50"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Premium Supporters 🥇
|
||||
|
||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||
<a href="https://supafort.com/?ref=dokploy" target="_blank"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" width="190"/></a>
|
||||
</div>
|
||||
|
||||
<!-- Elite Contributors 🥈 -->
|
||||
|
||||
<!-- Add Elite Contributors here -->
|
||||
|
||||
### Supporting Members 🥉
|
||||
|
||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Lightspeed.run"/></a>
|
||||
</div>
|
||||
|
||||
### Community Backers 🤝
|
||||
|
||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
|
||||
<a href="https://rivo.gg/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/126797452?s=200&v=4" width="60px" alt="Rivo.gg"/></a>
|
||||
<a href="https://photoquest.wedding/?ref=dokploy"><img src="https://photoquest.wedding/favicon/android-chrome-512x512.png" width="60px" alt="Rivo.gg"/></a>
|
||||
|
||||
</div>
|
||||
|
||||
#### Organizations:
|
||||
|
||||
[](https://opencollective.com/dokploy)
|
||||
|
||||
#### Individuals:
|
||||
|
||||
[](https://opencollective.com/dokploy)
|
||||
|
||||
### Contributors 🤝
|
||||
|
||||
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
|
||||
</a>
|
||||
|
||||
## Video Tutorial
|
||||
|
||||
<a href="https://youtu.be/mznYKPvhcfw">
|
||||
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
|
||||
</a>
|
||||
|
||||
<!-- ## Supported OS
|
||||
|
||||
## Donations
|
||||
|
||||
If you like dokploy, and want to support the project to cover the costs of hosting, testing and development new features, you can donate to the project using the following link:
|
||||
|
||||
Thanks to all the supporters!
|
||||
|
||||
https://opencollective.com/dokploy
|
||||
|
||||
|
||||
<a href="https://opencollective.com/dokploy"><img src="https://opencollective.com/dokploy/individuals.svg?width=890"></a>
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
## Support OS
|
||||
|
||||
- Ubuntu 24.04 LTS
|
||||
- Ubuntu 24.04 LTS
|
||||
- Ubuntu 23.10
|
||||
- Ubuntu 22.04 LTS
|
||||
- Ubuntu 20.04 LTS
|
||||
- Ubuntu 22.04 LTS
|
||||
- Ubuntu 20.04 LTS
|
||||
- Ubuntu 18.04 LTS
|
||||
- Debian 12
|
||||
- Debian 11
|
||||
- Fedora 40
|
||||
- Centos 9
|
||||
- Centos 8
|
||||
|
||||
|
||||
|
||||
## Explanation
|
||||
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md) | [Русский Язык](README-ru.md)
|
||||
- Centos 8 -->
|
||||
|
||||
## Contributing
|
||||
|
||||
Check out the [Contributing Guide](CONTRIBUTING.md) for more information.
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNetworks } from "@/server/utils/docker/compose/network";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
- backend
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData?.services?.web?.networks).toContain(
|
||||
`frontend-${prefix}`,
|
||||
);
|
||||
|
||||
expect(actualComposeData?.services?.api?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
);
|
||||
|
||||
const apiNetworks = actualComposeData?.services?.api?.networks;
|
||||
|
||||
expect(apiNetworks).toBeDefined();
|
||||
expect(actualComposeData?.services?.api?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
);
|
||||
});
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const composeFile2 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services with aliases", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).toHaveProperty(
|
||||
`frontend-${prefix}`,
|
||||
);
|
||||
|
||||
const networkConfig =
|
||||
actualComposeData?.services?.api?.networks[`frontend-${prefix}`];
|
||||
expect(networkConfig).toBeDefined();
|
||||
expect(networkConfig?.aliases).toContain("api");
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
|
||||
"frontend-ash",
|
||||
);
|
||||
});
|
||||
|
||||
const composeFile3 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
backend:
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services (Object with simple networks)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.redis?.networks).toHaveProperty(
|
||||
`backend-${prefix}`,
|
||||
);
|
||||
});
|
||||
|
||||
const composeFileCombined = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
backend:
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
|
||||
backend:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToServiceNetworks(composeData.services, prefix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
// Caso 1: ListOfStrings
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`frontend-${prefix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks;
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(apiNetworks[`frontend-${prefix}`]).toBeDefined();
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
// Caso 3: Objeto con redes simples
|
||||
const redisNetworks = actualComposeData.services?.redis?.networks;
|
||||
expect(redisNetworks).toHaveProperty(`backend-${prefix}`);
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
2
apps/api/.env.example
Normal file
2
apps/api/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
LEMON_SQUEEZY_API_KEY=""
|
||||
LEMON_SQUEEZY_STORE_ID=""
|
||||
28
apps/api/.gitignore
vendored
Normal file
28
apps/api/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# dev
|
||||
.yarn/
|
||||
!.yarn/releases
|
||||
.vscode/*
|
||||
!.vscode/launch.json
|
||||
!.vscode/*.code-snippets
|
||||
.idea/workspace.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf
|
||||
|
||||
# deps
|
||||
node_modules/
|
||||
|
||||
# env
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
8
apps/api/README.md
Normal file
8
apps/api/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
```
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
```
|
||||
open http://localhost:3000
|
||||
```
|
||||
33
apps/api/package.json
Normal file
33
apps/api/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@dokploy/api",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "PORT=4000 tsx watch src/index.ts",
|
||||
"build": "tsc --project tsconfig.json",
|
||||
"start": "node --experimental-specifier-resolution=node dist/index.js",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"pino": "9.4.0",
|
||||
"pino-pretty": "11.2.2",
|
||||
"@hono/zod-validator": "0.3.0",
|
||||
"zod": "^3.23.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@dokploy/server": "workspace:*",
|
||||
"@hono/node-server": "^1.12.1",
|
||||
"hono": "^4.5.8",
|
||||
"dotenv": "^16.3.1",
|
||||
"redis": "4.7.0",
|
||||
"@nerimity/mimiqueue": "1.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.2",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/node": "^20.11.17",
|
||||
"tsx": "^4.7.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.5.0"
|
||||
}
|
||||
61
apps/api/src/index.ts
Normal file
61
apps/api/src/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { serve } from "@hono/node-server";
|
||||
import { Hono } from "hono";
|
||||
import "dotenv/config";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { Queue } from "@nerimity/mimiqueue";
|
||||
import { createClient } from "redis";
|
||||
import { logger } from "./logger";
|
||||
import { type DeployJob, deployJobSchema } from "./schema";
|
||||
import { deploy } from "./utils";
|
||||
|
||||
const app = new Hono();
|
||||
const redisClient = createClient({
|
||||
url: process.env.REDIS_URL,
|
||||
});
|
||||
|
||||
app.use(async (c, next) => {
|
||||
if (c.req.path === "/health") {
|
||||
return next();
|
||||
}
|
||||
const authHeader = c.req.header("X-API-Key");
|
||||
|
||||
if (process.env.API_KEY !== authHeader) {
|
||||
return c.json({ message: "Invalid API Key" }, 403);
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
|
||||
const data = c.req.valid("json");
|
||||
const res = queue.add(data, { groupName: data.serverId });
|
||||
return c.json(
|
||||
{
|
||||
message: "Deployment Added",
|
||||
},
|
||||
200,
|
||||
);
|
||||
});
|
||||
|
||||
app.get("/health", async (c) => {
|
||||
return c.json({ status: "ok" });
|
||||
});
|
||||
|
||||
const queue = new Queue({
|
||||
name: "deployments",
|
||||
process: async (job: DeployJob) => {
|
||||
logger.info("Deploying job", job);
|
||||
return await deploy(job);
|
||||
},
|
||||
redisClient,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await redisClient.connect();
|
||||
await redisClient.flushAll();
|
||||
logger.info("Redis Cleaned");
|
||||
})();
|
||||
|
||||
const port = Number.parseInt(process.env.PORT || "3000");
|
||||
logger.info("Starting Deployments Server ✅", port);
|
||||
serve({ fetch: app.fetch, port });
|
||||
10
apps/api/src/logger.ts
Normal file
10
apps/api/src/logger.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import pino from "pino";
|
||||
|
||||
export const logger = pino({
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
24
apps/api/src/schema.ts
Normal file
24
apps/api/src/schema.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
||||
z.object({
|
||||
applicationId: z.string(),
|
||||
titleLog: z.string(),
|
||||
descriptionLog: z.string(),
|
||||
server: z.boolean().optional(),
|
||||
type: z.enum(["deploy", "redeploy"]),
|
||||
applicationType: z.literal("application"),
|
||||
serverId: z.string().min(1),
|
||||
}),
|
||||
z.object({
|
||||
composeId: z.string(),
|
||||
titleLog: z.string(),
|
||||
descriptionLog: z.string(),
|
||||
server: z.boolean().optional(),
|
||||
type: z.enum(["deploy", "redeploy"]),
|
||||
applicationType: z.literal("compose"),
|
||||
serverId: z.string().min(1),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type DeployJob = z.infer<typeof deployJobSchema>;
|
||||
62
apps/api/src/utils.ts
Normal file
62
apps/api/src/utils.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
deployRemoteApplication,
|
||||
deployRemoteCompose,
|
||||
rebuildRemoteApplication,
|
||||
rebuildRemoteCompose,
|
||||
updateApplicationStatus,
|
||||
updateCompose,
|
||||
} from "@dokploy/server";
|
||||
import type { DeployJob } from "./schema";
|
||||
|
||||
export const deploy = async (job: DeployJob) => {
|
||||
try {
|
||||
if (job.applicationType === "application") {
|
||||
await updateApplicationStatus(job.applicationId, "running");
|
||||
if (job.server) {
|
||||
if (job.type === "redeploy") {
|
||||
await rebuildRemoteApplication({
|
||||
applicationId: job.applicationId,
|
||||
titleLog: job.titleLog,
|
||||
descriptionLog: job.descriptionLog,
|
||||
});
|
||||
} else if (job.type === "deploy") {
|
||||
await deployRemoteApplication({
|
||||
applicationId: job.applicationId,
|
||||
titleLog: job.titleLog,
|
||||
descriptionLog: job.descriptionLog,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (job.applicationType === "compose") {
|
||||
await updateCompose(job.composeId, {
|
||||
composeStatus: "running",
|
||||
});
|
||||
|
||||
if (job.server) {
|
||||
if (job.type === "redeploy") {
|
||||
await rebuildRemoteCompose({
|
||||
composeId: job.composeId,
|
||||
titleLog: job.titleLog,
|
||||
descriptionLog: job.descriptionLog,
|
||||
});
|
||||
} else if (job.type === "deploy") {
|
||||
await deployRemoteCompose({
|
||||
composeId: job.composeId,
|
||||
titleLog: job.titleLog,
|
||||
descriptionLog: job.descriptionLog,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (job.applicationType === "application") {
|
||||
await updateApplicationStatus(job.applicationId, "error");
|
||||
} else if (job.applicationType === "compose") {
|
||||
await updateCompose(job.composeId, {
|
||||
composeStatus: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
18
apps/api/tsconfig.json
Normal file
18
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "hono/jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@dokploy/server/*": ["../../packages/server/src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
5
apps/dokploy/.dockerignore
Normal file
5
apps/dokploy/.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
dist
|
||||
59
apps/dokploy/.gitignore
vendored
Normal file
59
apps/dokploy/.gitignore
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
/redis-data
|
||||
traefik.yml
|
||||
.docker
|
||||
.env.production
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/dist
|
||||
/production-server
|
||||
# database
|
||||
/prisma/db.sqlite
|
||||
/prisma/db.sqlite-journal
|
||||
/logs
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
next-env.d.ts
|
||||
/dokploy
|
||||
/config
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# otros
|
||||
/.data
|
||||
/.main
|
||||
.vscode
|
||||
|
||||
*.lockb
|
||||
*.rdb
|
||||
.idea
|
||||
1
apps/dokploy/.nvmrc
Normal file
1
apps/dokploy/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
18.18.0
|
||||
242
apps/dokploy/CONTRIBUTING.md
Normal file
242
apps/dokploy/CONTRIBUTING.md
Normal file
@@ -0,0 +1,242 @@
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
Hey, thanks for your interest in contributing to Dokploy! We appreciate your help and taking your time to contribute.
|
||||
|
||||
|
||||
Before you start, please first discuss the feature/bug you want to add with the owners and comunity via github issues.
|
||||
|
||||
We have a few guidelines to follow when contributing to this project:
|
||||
|
||||
- [Commit Convention](#commit-convention)
|
||||
- [Setup](#setup)
|
||||
- [Development](#development)
|
||||
- [Build](#build)
|
||||
- [Pull Request](#pull-request)
|
||||
|
||||
## Commit Convention
|
||||
|
||||
Before you craete a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||
|
||||
### Commit Message Format
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
#### Type
|
||||
Must be one of the following:
|
||||
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **docs**: Documentation only changes
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **perf**: A code change that improves performance
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||
* **chore**: Other changes that don't modify `src` or `test` files
|
||||
* **revert**: Reverts a previous commit
|
||||
|
||||
Example:
|
||||
```
|
||||
feat: add new feature
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dokploy/dokploy.git
|
||||
cd dokploy
|
||||
pnpm install
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Is required to have **Docker** installed on your machine.
|
||||
|
||||
|
||||
### Setup
|
||||
|
||||
Run the command that will spin up all the required services and files.
|
||||
|
||||
```bash
|
||||
pnpm run setup
|
||||
```
|
||||
|
||||
Now run the development server.
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
|
||||
Go to http://localhost:3000 to see the development server
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
To build the docker image
|
||||
```bash
|
||||
pnpm run docker:build
|
||||
```
|
||||
|
||||
To push the docker image
|
||||
```bash
|
||||
pnpm run docker:push
|
||||
```
|
||||
|
||||
## Password Reset
|
||||
|
||||
In the case you lost your password, you can reset it using the following command
|
||||
|
||||
```bash
|
||||
pnpm run reset-password
|
||||
```
|
||||
|
||||
If you want to test the webhooks on development mode using localtunnel, make sure to install `localtunnel`
|
||||
|
||||
```bash
|
||||
bunx lt --port 3000
|
||||
```
|
||||
|
||||
If you run into permission issues of docker run the following command
|
||||
|
||||
```bash
|
||||
sudo chown -R USERNAME dokploy or sudo chown -R $(whoami) ~/.docker
|
||||
```
|
||||
|
||||
## Application deploy
|
||||
|
||||
In case you want to deploy the application on your machine and you selected nixpacks or buildpacks, you need to install first.
|
||||
|
||||
```bash
|
||||
# Install Nixpacks
|
||||
curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
||||
&& chmod +x install.sh \
|
||||
&& ./install.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
# Install Buildpacks
|
||||
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||
```
|
||||
|
||||
|
||||
## Pull Request
|
||||
|
||||
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
||||
- Create a new branch for each feature or bug fix.
|
||||
- Make sure to add tests for your changes.
|
||||
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
|
||||
- When creating a pull request, please provide a clear and concise description of the changes made.
|
||||
- If you include a video or screenshot, would be awesome so we can see the changes in action.
|
||||
- If your pull request fixes an open issue, please reference the issue in the pull request description.
|
||||
- Once your pull request is merged, you will be automatically added as a contributor to the project.
|
||||
|
||||
Thank you for your contribution!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
To add a new template, go to `templates` folder and create a new folder with the name of the template.
|
||||
|
||||
Let's take the example of `plausible` template.
|
||||
|
||||
1. create a folder in `templates/plausible`
|
||||
2. create a `docker-compose.yml` file inside the folder with the content of compose.
|
||||
3. create a `index.ts` file inside the folder with the following code as base:
|
||||
4. When creating a pull request, please provide a video of the template working in action.
|
||||
|
||||
```typescript
|
||||
// EXAMPLE
|
||||
import {
|
||||
generateHash,
|
||||
generateRandomDomain,
|
||||
type Template,
|
||||
type Schema,
|
||||
} from "../utils";
|
||||
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
|
||||
// do your stuff here, like create a new domain, generate random passwords, mounts.
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const randomDomain = generateRandomDomain(schema);
|
||||
const secretBase = generateBase64(64);
|
||||
const toptKeyBase = generateBase64(32);
|
||||
|
||||
const envs = [
|
||||
// If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
||||
`PLAUSIBLE_HOST=${randomDomain}`,
|
||||
"PLAUSIBLE_PORT=8000",
|
||||
`BASE_URL=http://${randomDomain}`,
|
||||
`SECRET_KEY_BASE=${secretBase}`,
|
||||
`TOTP_VAULT_KEY=${toptKeyBase}`,
|
||||
`HASH=${mainServiceHash}`,
|
||||
];
|
||||
|
||||
const mounts: Template["mounts"] = [
|
||||
{
|
||||
mountPath: "./clickhouse/clickhouse-config.xml",
|
||||
content: `some content......`,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
mounts,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
4. Now you need to add the information about the template to the `templates/templates.ts` is a object with the following properties:
|
||||
|
||||
**Make sure the id of the template is the same as the folder name and don't have any spaces, only slugified names and lowercase.**
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: "plausible",
|
||||
name: "Plausible",
|
||||
version: "v2.1.0",
|
||||
description:
|
||||
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
||||
logo: "plausible.svg", // we defined the name and the extension of the logo
|
||||
links: {
|
||||
github: "https://github.com/plausible/plausible",
|
||||
website: "https://plausible.io/",
|
||||
docs: "https://plausible.io/docs",
|
||||
},
|
||||
tags: ["analytics"],
|
||||
load: () => import("./plausible/index").then((m) => m.generate),
|
||||
},
|
||||
```
|
||||
|
||||
5. Add the logo or image of the template to `public/templates/plausible.svg`
|
||||
|
||||
|
||||
### Recomendations
|
||||
- Use the same name of the folder as the id of the template.
|
||||
- The logo should be in the public folder.
|
||||
- If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
||||
- Test first on a vps or a server to make sure the template works.
|
||||
|
||||
26
apps/dokploy/Dockerfile
Normal file
26
apps/dokploy/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:18-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
FROM base AS build
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
# Build only the dokploy app
|
||||
RUN pnpm run dokploy:build
|
||||
|
||||
# Deploy only the dokploy app
|
||||
RUN pnpm deploy --filter=dokploy --prod /prod/dokploy
|
||||
|
||||
FROM base AS dokploy
|
||||
COPY --from=build /prod/dokploy /prod/dokploy
|
||||
WORKDIR /prod/dokploy
|
||||
EXPOSE 3000
|
||||
CMD [ "pnpm", "start" ]
|
||||
26
apps/dokploy/LICENSE.MD
Normal file
26
apps/dokploy/LICENSE.MD
Normal file
@@ -0,0 +1,26 @@
|
||||
# License
|
||||
|
||||
## Core License (Apache License 2.0)
|
||||
|
||||
Copyright 2024 Mauricio Siu.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
## Additional Terms for Specific Features
|
||||
|
||||
The following additional terms apply to the multi-node support, Docker Compose file and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
|
||||
|
||||
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support and Multi Server, will always be free to use in the self-hosted version.
|
||||
- **Restriction on Resale**: The multi-node support, Docker Compose file support and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
|
||||
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support and Multi Server features must be distributed freely and cannot be sold or offered as a service.
|
||||
|
||||
For further inquiries or permissions, please contact us directly.
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { addSuffixToAllProperties } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { addPrefixToAllProperties } from "@/server/utils/docker/compose";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const composeFile1 = `
|
||||
version: "3.8"
|
||||
@@ -119,11 +119,11 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in compose file 1", () => {
|
||||
test("Add suffix to all properties in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile1);
|
||||
});
|
||||
@@ -242,11 +242,11 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in compose file 2", () => {
|
||||
test("Add suffix to all properties in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile2);
|
||||
});
|
||||
@@ -365,11 +365,11 @@ secrets:
|
||||
file: ./service_secret.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in compose file 3", () => {
|
||||
test("Add suffix to all properties in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile3);
|
||||
});
|
||||
@@ -466,11 +466,11 @@ volumes:
|
||||
driver: local
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all properties in Plausible compose file", () => {
|
||||
test("Add suffix to all properties in Plausible compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllProperties(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToConfigsRoot } from "@/server/utils/docker/compose/configs";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToConfigsRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -23,19 +23,19 @@ configs:
|
||||
file: ./web-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in root property", () => {
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
|
||||
expect(configs).toBeDefined();
|
||||
for (const configKey of Object.keys(configs)) {
|
||||
expect(configKey).toContain(`-${prefix}`);
|
||||
expect(configKey).toContain(`-${suffix}`);
|
||||
expect(configs[configKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -59,23 +59,23 @@ configs:
|
||||
file: ./another-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to multiple configs in root property", () => {
|
||||
test("Add suffix to multiple configs in root property", () => {
|
||||
const composeData = load(composeFileMultipleConfigs) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
|
||||
expect(configs).toBeDefined();
|
||||
for (const configKey of Object.keys(configs)) {
|
||||
expect(configKey).toContain(`-${prefix}`);
|
||||
expect(configKey).toContain(`-${suffix}`);
|
||||
expect(configs[configKey]).toBeDefined();
|
||||
}
|
||||
expect(configs).toHaveProperty(`web-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`another-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`web-config-${suffix}`);
|
||||
expect(configs).toHaveProperty(`another-config-${suffix}`);
|
||||
});
|
||||
|
||||
const composeFileDifferentProperties = `
|
||||
@@ -92,25 +92,25 @@ configs:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to configs with different properties in root property", () => {
|
||||
test("Add suffix to configs with different properties in root property", () => {
|
||||
const composeData = load(
|
||||
composeFileDifferentProperties,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
|
||||
expect(configs).toBeDefined();
|
||||
for (const configKey of Object.keys(configs)) {
|
||||
expect(configKey).toContain(`-${prefix}`);
|
||||
expect(configKey).toContain(`-${suffix}`);
|
||||
expect(configs[configKey]).toBeDefined();
|
||||
}
|
||||
expect(configs).toHaveProperty(`web-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`special-config-${prefix}`);
|
||||
expect(configs).toHaveProperty(`web-config-${suffix}`);
|
||||
expect(configs).toHaveProperty(`special-config-${suffix}`);
|
||||
});
|
||||
|
||||
const composeFileConfigRoot = `
|
||||
@@ -162,15 +162,15 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs in root property", () => {
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFileConfigRoot) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.configs) {
|
||||
return;
|
||||
}
|
||||
const configs = addPrefixToConfigsRoot(composeData.configs, prefix);
|
||||
const configs = addSuffixToConfigsRoot(composeData.configs, suffix);
|
||||
const updatedComposeData = { ...composeData, configs };
|
||||
|
||||
// Verificar que el resultado coincide con el archivo esperado
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToConfigsInServices } from "@/server/utils/docker/compose/configs";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToConfigsInServices } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -19,19 +19,19 @@ configs:
|
||||
file: ./web-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in services", () => {
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToConfigsInServices(composeData.services, prefix);
|
||||
const services = addSuffixToConfigsInServices(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.web?.configs).toContainEqual({
|
||||
source: `web-config-${prefix}`,
|
||||
source: `web-config-${suffix}`,
|
||||
target: "/etc/nginx/nginx.conf",
|
||||
});
|
||||
});
|
||||
@@ -51,17 +51,17 @@ configs:
|
||||
file: ./web-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in services with single config", () => {
|
||||
test("Add suffix to configs in services with single config", () => {
|
||||
const composeData = load(
|
||||
composeFileSingleServiceConfig,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToConfigsInServices(composeData.services, prefix);
|
||||
const services = addSuffixToConfigsInServices(composeData.services, suffix);
|
||||
|
||||
expect(services).toBeDefined();
|
||||
for (const serviceKey of Object.keys(services)) {
|
||||
@@ -69,7 +69,7 @@ test("Add prefix to configs in services with single config", () => {
|
||||
if (serviceConfigs) {
|
||||
for (const config of serviceConfigs) {
|
||||
if (typeof config === "object") {
|
||||
expect(config.source).toContain(`-${prefix}`);
|
||||
expect(config.source).toContain(`-${suffix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,17 +105,17 @@ configs:
|
||||
file: ./common-config.yml
|
||||
`;
|
||||
|
||||
test("Add prefix to configs in services with multiple configs", () => {
|
||||
test("Add suffix to configs in services with multiple configs", () => {
|
||||
const composeData = load(
|
||||
composeFileMultipleServicesConfigs,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addPrefixToConfigsInServices(composeData.services, prefix);
|
||||
const services = addSuffixToConfigsInServices(composeData.services, suffix);
|
||||
|
||||
expect(services).toBeDefined();
|
||||
for (const serviceKey of Object.keys(services)) {
|
||||
@@ -123,7 +123,7 @@ test("Add prefix to configs in services with multiple configs", () => {
|
||||
if (serviceConfigs) {
|
||||
for (const config of serviceConfigs) {
|
||||
if (typeof config === "object") {
|
||||
expect(config.source).toContain(`-${prefix}`);
|
||||
expect(config.source).toContain(`-${suffix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,17 +179,17 @@ services:
|
||||
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs in services", () => {
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFileConfigServices) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToConfigsInServices(
|
||||
const updatedComposeData = addSuffixToConfigsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToAllConfigs,
|
||||
addPrefixToConfigsRoot,
|
||||
} from "@/server/utils/docker/compose/configs";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToAllConfigs, addSuffixToConfigsRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -80,12 +77,12 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all configs in root and services", () => {
|
||||
test("Add suffix to all configs in root and services", () => {
|
||||
const composeData = load(composeFileCombinedConfigs) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllConfigs(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllConfigs(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedConfigs);
|
||||
});
|
||||
@@ -162,14 +159,14 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs with environment and external", () => {
|
||||
test("Add suffix to configs with environment and external", () => {
|
||||
const composeData = load(
|
||||
composeFileWithEnvAndExternal,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllConfigs(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllConfigs(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileWithEnvAndExternal);
|
||||
});
|
||||
@@ -234,14 +231,14 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to configs with template driver and labels", () => {
|
||||
test("Add suffix to configs with template driver and labels", () => {
|
||||
const composeData = load(
|
||||
composeFileWithTemplateDriverAndLabels,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllConfigs(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllConfigs(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(
|
||||
expectedComposeFileWithTemplateDriverAndLabels,
|
||||
107
apps/dokploy/__test__/compose/domain/labels.test.ts
Normal file
107
apps/dokploy/__test__/compose/domain/labels.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { Domain } from "@dokploy/server";
|
||||
import { createDomainLabels } from "@dokploy/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("createDomainLabels", () => {
|
||||
const appName = "test-app";
|
||||
const baseDomain: Domain = {
|
||||
host: "example.com",
|
||||
port: 8080,
|
||||
https: false,
|
||||
uniqueConfigKey: 1,
|
||||
certificateType: "none",
|
||||
applicationId: "",
|
||||
composeId: "",
|
||||
domainType: "compose",
|
||||
serviceName: "test-app",
|
||||
domainId: "",
|
||||
path: "/",
|
||||
createdAt: "",
|
||||
};
|
||||
|
||||
it("should create basic labels for web entrypoint", async () => {
|
||||
const labels = await createDomainLabels(appName, baseDomain, "web");
|
||||
expect(labels).toEqual([
|
||||
"traefik.http.routers.test-app-1-web.rule=Host(`example.com`)",
|
||||
"traefik.http.routers.test-app-1-web.entrypoints=web",
|
||||
"traefik.http.services.test-app-1-web.loadbalancer.server.port=8080",
|
||||
"traefik.http.routers.test-app-1-web.service=test-app-1-web",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should create labels for websecure entrypoint", async () => {
|
||||
const labels = await createDomainLabels(appName, baseDomain, "websecure");
|
||||
expect(labels).toEqual([
|
||||
"traefik.http.routers.test-app-1-websecure.rule=Host(`example.com`)",
|
||||
"traefik.http.routers.test-app-1-websecure.entrypoints=websecure",
|
||||
"traefik.http.services.test-app-1-websecure.loadbalancer.server.port=8080",
|
||||
"traefik.http.routers.test-app-1-websecure.service=test-app-1-websecure",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should add the path prefix if is different than / empty", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
path: "/hello",
|
||||
},
|
||||
"websecure",
|
||||
);
|
||||
|
||||
expect(labels).toEqual([
|
||||
"traefik.http.routers.test-app-1-websecure.rule=Host(`example.com`) && PathPrefix(`/hello`)",
|
||||
"traefik.http.routers.test-app-1-websecure.entrypoints=websecure",
|
||||
"traefik.http.services.test-app-1-websecure.loadbalancer.server.port=8080",
|
||||
"traefik.http.routers.test-app-1-websecure.service=test-app-1-websecure",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should add redirect middleware for https on web entrypoint", async () => {
|
||||
const httpsBaseDomain = { ...baseDomain, https: true };
|
||||
const labels = await createDomainLabels(appName, httpsBaseDomain, "web");
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file",
|
||||
);
|
||||
});
|
||||
|
||||
it("should add Let's Encrypt configuration for websecure with letsencrypt certificate", async () => {
|
||||
const letsencryptDomain = {
|
||||
...baseDomain,
|
||||
https: true,
|
||||
certificateType: "letsencrypt" as const,
|
||||
};
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
letsencryptDomain,
|
||||
"websecure",
|
||||
);
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-websecure.tls.certresolver=letsencrypt",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not add Let's Encrypt configuration for non-letsencrypt certificate", async () => {
|
||||
const nonLetsencryptDomain = {
|
||||
...baseDomain,
|
||||
https: true,
|
||||
certificateType: "none" as const,
|
||||
};
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
nonLetsencryptDomain,
|
||||
"websecure",
|
||||
);
|
||||
expect(labels).not.toContain(
|
||||
"traefik.http.routers.test-app-1-websecure.tls.certresolver=letsencrypt",
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle different ports correctly", async () => {
|
||||
const customPortDomain = { ...baseDomain, port: 3000 };
|
||||
const labels = await createDomainLabels(appName, customPortDomain, "web");
|
||||
expect(labels).toContain(
|
||||
"traefik.http.services.test-app-1-web.loadbalancer.server.port=3000",
|
||||
);
|
||||
});
|
||||
});
|
||||
29
apps/dokploy/__test__/compose/domain/network-root.test.ts
Normal file
29
apps/dokploy/__test__/compose/domain/network-root.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { addDokployNetworkToRoot } from "@dokploy/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("addDokployNetworkToRoot", () => {
|
||||
it("should create network object if networks is undefined", () => {
|
||||
const result = addDokployNetworkToRoot(undefined);
|
||||
expect(result).toEqual({ "dokploy-network": { external: true } });
|
||||
});
|
||||
|
||||
it("should add network to an empty object", () => {
|
||||
const result = addDokployNetworkToRoot({});
|
||||
expect(result).toEqual({ "dokploy-network": { external: true } });
|
||||
});
|
||||
|
||||
it("should not modify existing network configuration", () => {
|
||||
const existing = { "dokploy-network": { external: false } };
|
||||
const result = addDokployNetworkToRoot(existing);
|
||||
expect(result).toEqual({ "dokploy-network": { external: true } });
|
||||
});
|
||||
|
||||
it("should add network alongside existing networks", () => {
|
||||
const existing = { "other-network": { external: true } };
|
||||
const result = addDokployNetworkToRoot(existing);
|
||||
expect(result).toEqual({
|
||||
"other-network": { external: true },
|
||||
"dokploy-network": { external: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
24
apps/dokploy/__test__/compose/domain/network-service.test.ts
Normal file
24
apps/dokploy/__test__/compose/domain/network-service.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { addDokployNetworkToService } from "@dokploy/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("addDokployNetworkToService", () => {
|
||||
it("should add network to an empty array", () => {
|
||||
const result = addDokployNetworkToService([]);
|
||||
expect(result).toEqual(["dokploy-network"]);
|
||||
});
|
||||
|
||||
it("should not add duplicate network to an array", () => {
|
||||
const result = addDokployNetworkToService(["dokploy-network"]);
|
||||
expect(result).toEqual(["dokploy-network"]);
|
||||
});
|
||||
|
||||
it("should add network to an existing array with other networks", () => {
|
||||
const result = addDokployNetworkToService(["other-network"]);
|
||||
expect(result).toEqual(["other-network", "dokploy-network"]);
|
||||
});
|
||||
|
||||
it("should add network to an object if networks is an object", () => {
|
||||
const result = addDokployNetworkToService({ "other-network": {} });
|
||||
expect(result).toEqual({ "other-network": {}, "dokploy-network": {} });
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToNetworksRoot } from "@/server/utils/docker/compose/network";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToNetworksRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -35,19 +35,19 @@ test("Generate random hash with 8 characters", () => {
|
||||
expect(hash.length).toBe(8);
|
||||
});
|
||||
|
||||
test("Add prefix to networks root property", () => {
|
||||
test("Add suffix to networks root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const volumeKey of Object.keys(networks)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,19 +79,19 @@ networks:
|
||||
internal: true
|
||||
`;
|
||||
|
||||
test("Add prefix to advanced networks root property (2 TRY)", () => {
|
||||
test("Add suffix to advanced networks root property (2 TRY)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,19 +120,19 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with external properties", () => {
|
||||
test("Add suffix to networks with external properties", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,19 +160,19 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with IPAM configurations", () => {
|
||||
test("Add suffix to networks with IPAM configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,19 +201,19 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with custom options", () => {
|
||||
test("Add suffix to networks with custom options", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain(`-${prefix}`);
|
||||
expect(networkKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -240,7 +240,7 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
// Expected compose file with static prefix `testhash`
|
||||
// Expected compose file with static suffix `testhash`
|
||||
const expectedComposeFile6 = `
|
||||
version: "3.8"
|
||||
|
||||
@@ -264,18 +264,70 @@ networks:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to networks with static prefix", () => {
|
||||
test("Add suffix to networks with static suffix", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addPrefixToNetworksRoot(composeData.networks, prefix);
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
const expectedComposeData = load(
|
||||
expectedComposeFile6,
|
||||
) as ComposeSpecification;
|
||||
expect(networks).toStrictEqual(expectedComposeData.networks);
|
||||
});
|
||||
|
||||
const composeFile7 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
`;
|
||||
|
||||
const expectedComposeFile7 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.driver.mtu: 1200
|
||||
|
||||
backend:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
external_network:
|
||||
external: true
|
||||
name: dokploy-network
|
||||
`;
|
||||
test("It shoudn't add suffix to dokploy-network", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
expect(networks).toBeDefined();
|
||||
for (const networkKey of Object.keys(networks)) {
|
||||
expect(networkKey).toContain("dokploy-network");
|
||||
}
|
||||
});
|
||||
273
apps/dokploy/__test__/compose/network/network-service.test.ts
Normal file
273
apps/dokploy/__test__/compose/network/network-service.test.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNetworks } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
- backend
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData?.services?.web?.networks).toContain(
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
|
||||
expect(actualComposeData?.services?.api?.networks).toContain(
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
|
||||
const apiNetworks = actualComposeData?.services?.api?.networks;
|
||||
|
||||
expect(apiNetworks).toBeDefined();
|
||||
expect(actualComposeData?.services?.api?.networks).toContain(
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const composeFile2 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services with aliases", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).toHaveProperty(
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
|
||||
const networkConfig = actualComposeData?.services?.api?.networks as {
|
||||
[key: string]: { aliases?: string[] };
|
||||
};
|
||||
expect(networkConfig[`frontend-${suffix}`]).toBeDefined();
|
||||
expect(networkConfig[`frontend-${suffix}`]?.aliases).toContain("api");
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
|
||||
"frontend-ash",
|
||||
);
|
||||
});
|
||||
|
||||
const composeFile3 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
backend:
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services (Object with simple networks)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
expect(actualComposeData.services?.redis?.networks).toHaveProperty(
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
const composeFileCombined = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
backend:
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
|
||||
backend:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const services = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const actualComposeData = { ...composeData, services };
|
||||
|
||||
// Caso 1: ListOfStrings
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${suffix}`);
|
||||
expect(apiNetworks[`frontend-${suffix}`]).toBeDefined();
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
// Caso 3: Objeto con redes simples
|
||||
const redisNetworks = actualComposeData.services?.redis?.networks;
|
||||
expect(redisNetworks).toHaveProperty(`backend-${suffix}`);
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
|
||||
const composeFile7 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const networks = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const service = networks.web;
|
||||
|
||||
expect(service).toBeDefined();
|
||||
expect(service?.networks).toContain("dokploy-network");
|
||||
});
|
||||
|
||||
const composeFile8 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
- dokploy-network
|
||||
|
||||
|
||||
api:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api
|
||||
dokploy-network:
|
||||
aliases:
|
||||
- api
|
||||
redis:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
dokploy-network:
|
||||
db:
|
||||
image: myapi:latest
|
||||
networks:
|
||||
dokploy-network:
|
||||
aliases:
|
||||
- apid
|
||||
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services multiples cases", () => {
|
||||
const composeData = load(composeFile8) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.services) {
|
||||
return;
|
||||
}
|
||||
const networks = addSuffixToServiceNetworks(composeData.services, suffix);
|
||||
const service = networks.web;
|
||||
const api = networks.api;
|
||||
const redis = networks.redis;
|
||||
const db = networks.db;
|
||||
|
||||
const dbNetworks = db?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const apiNetworks = api?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
expect(service).toBeDefined();
|
||||
expect(service?.networks).toContain("dokploy-network");
|
||||
|
||||
expect(redis?.networks).toHaveProperty("dokploy-network");
|
||||
expect(dbNetworks["dokploy-network"]).toBeDefined();
|
||||
expect(apiNetworks["dokploy-network"]).toBeDefined();
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import {
|
||||
addPrefixToAllNetworks,
|
||||
addPrefixToServiceNetworks,
|
||||
} from "@/server/utils/docker/compose/network";
|
||||
import { addPrefixToNetworksRoot } from "@/server/utils/docker/compose/network";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
addSuffixToAllNetworks,
|
||||
addSuffixToServiceNetworks,
|
||||
} from "@dokploy/server";
|
||||
import { addSuffixToNetworksRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -38,52 +38,54 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to networks in services and root (combined case)", () => {
|
||||
test("Add suffix to networks in services and root (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
// Prefijo para redes definidas en el root
|
||||
if (composeData.networks) {
|
||||
composeData.networks = addPrefixToNetworksRoot(
|
||||
composeData.networks = addSuffixToNetworksRoot(
|
||||
composeData.networks,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
}
|
||||
|
||||
// Prefijo para redes definidas en los servicios
|
||||
if (composeData.services) {
|
||||
composeData.services = addPrefixToServiceNetworks(
|
||||
composeData.services = addSuffixToServiceNetworks(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
}
|
||||
|
||||
const actualComposeData = { ...composeData };
|
||||
|
||||
// Verificar redes en root
|
||||
expect(actualComposeData.networks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(actualComposeData.networks).toHaveProperty(`backend-${prefix}`);
|
||||
expect(actualComposeData.networks).toHaveProperty(`frontend-${suffix}`);
|
||||
expect(actualComposeData.networks).toHaveProperty(`backend-${suffix}`);
|
||||
expect(actualComposeData.networks).not.toHaveProperty("frontend");
|
||||
expect(actualComposeData.networks).not.toHaveProperty("backend");
|
||||
|
||||
// Caso 1: ListOfStrings
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`frontend-${prefix}`,
|
||||
`frontend-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.web?.networks).toContain(
|
||||
`backend-${prefix}`,
|
||||
`backend-${suffix}`,
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks;
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(apiNetworks[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||
[key: string]: { aliases?: string[] };
|
||||
};
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${suffix}`);
|
||||
expect(apiNetworks?.[`frontend-${suffix}`]?.aliases).toContain("api");
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
// Caso 3: Objeto con redes simples
|
||||
const redisNetworks = actualComposeData.services?.redis?.networks;
|
||||
expect(redisNetworks).toHaveProperty(`backend-${prefix}`);
|
||||
expect(redisNetworks).toHaveProperty(`backend-${suffix}`);
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
|
||||
@@ -117,14 +119,14 @@ networks:
|
||||
driver: bridge
|
||||
`);
|
||||
|
||||
test("Add prefix to networks in compose file", () => {
|
||||
test("Add suffix to networks in compose file", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
if (!composeData?.networks) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToAllNetworks(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile);
|
||||
});
|
||||
@@ -179,11 +181,11 @@ networks:
|
||||
driver: bridge
|
||||
`);
|
||||
|
||||
test("Add prefix to networks in compose file with external and internal networks", () => {
|
||||
test("Add suffix to networks in compose file with external and internal networks", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const updatedComposeData = addPrefixToAllNetworks(composeData, prefix);
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile2);
|
||||
});
|
||||
@@ -244,11 +246,89 @@ networks:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
`);
|
||||
|
||||
test("Add prefix to networks in compose file with multiple services and complex network configurations", () => {
|
||||
test("Add suffix to networks in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const updatedComposeData = addPrefixToAllNetworks(composeData, prefix);
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile3);
|
||||
});
|
||||
|
||||
const composeFile4 = `
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- app
|
||||
backend:
|
||||
dokploy-network:
|
||||
|
||||
worker:
|
||||
image: worker:latest
|
||||
networks:
|
||||
- backend
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
backend:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
|
||||
dokploy-network:
|
||||
driver: bridge
|
||||
|
||||
`;
|
||||
|
||||
const expectedComposeFile4 = load(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
networks:
|
||||
frontend-testhash:
|
||||
aliases:
|
||||
- app
|
||||
backend-testhash:
|
||||
dokploy-network:
|
||||
|
||||
worker:
|
||||
image: worker:latest
|
||||
networks:
|
||||
- backend-testhash
|
||||
- dokploy-network
|
||||
|
||||
networks:
|
||||
frontend-testhash:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
backend-testhash:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
|
||||
dokploy-network:
|
||||
driver: bridge
|
||||
|
||||
|
||||
|
||||
`);
|
||||
|
||||
test("Expect don't add suffix to dokploy-network in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile4);
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToSecretsRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { load, dump } from "js-yaml";
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { addPrefixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -23,18 +23,18 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in root property", () => {
|
||||
test("Add suffix to secrets in root property", () => {
|
||||
const composeData = load(composeFileSecretsRoot) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
return;
|
||||
}
|
||||
const secrets = addPrefixToSecretsRoot(composeData.secrets, prefix);
|
||||
const secrets = addSuffixToSecretsRoot(composeData.secrets, suffix);
|
||||
expect(secrets).toBeDefined();
|
||||
if (secrets) {
|
||||
for (const secretKey of Object.keys(secrets)) {
|
||||
expect(secretKey).toContain(`-${prefix}`);
|
||||
expect(secretKey).toContain(`-${suffix}`);
|
||||
expect(secrets[secretKey]).toBeDefined();
|
||||
}
|
||||
}
|
||||
@@ -52,19 +52,19 @@ secrets:
|
||||
file: ./api_key.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in root property (Test 1)", () => {
|
||||
test("Add suffix to secrets in root property (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsRoot1) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
return;
|
||||
}
|
||||
const secrets = addPrefixToSecretsRoot(composeData.secrets, prefix);
|
||||
const secrets = addSuffixToSecretsRoot(composeData.secrets, suffix);
|
||||
expect(secrets).toBeDefined();
|
||||
|
||||
if (secrets) {
|
||||
for (const secretKey of Object.keys(secrets)) {
|
||||
expect(secretKey).toContain(`-${prefix}`);
|
||||
expect(secretKey).toContain(`-${suffix}`);
|
||||
expect(secrets[secretKey]).toBeDefined();
|
||||
}
|
||||
}
|
||||
@@ -84,19 +84,19 @@ secrets:
|
||||
external: true
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in root property (Test 2)", () => {
|
||||
test("Add suffix to secrets in root property (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsRoot2) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
return;
|
||||
}
|
||||
const secrets = addPrefixToSecretsRoot(composeData.secrets, prefix);
|
||||
const secrets = addSuffixToSecretsRoot(composeData.secrets, suffix);
|
||||
expect(secrets).toBeDefined();
|
||||
|
||||
if (secrets) {
|
||||
for (const secretKey of Object.keys(secrets)) {
|
||||
expect(secretKey).toContain(`-${prefix}`);
|
||||
expect(secretKey).toContain(`-${suffix}`);
|
||||
expect(secrets[secretKey]).toBeDefined();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToSecretsInServices } from "@/server/utils/docker/compose/secrets";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToSecretsInServices } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -18,22 +18,22 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in services", () => {
|
||||
test("Add suffix to secrets in services", () => {
|
||||
const composeData = load(composeFileSecretsServices) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToSecretsInServices(
|
||||
const updatedComposeData = addSuffixToSecretsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.db?.secrets).toContain(
|
||||
`db_password-${prefix}`,
|
||||
`db_password-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -51,22 +51,22 @@ secrets:
|
||||
file: ./app_secret.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in services (Test 1)", () => {
|
||||
test("Add suffix to secrets in services (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsServices1) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToSecretsInServices(
|
||||
const updatedComposeData = addSuffixToSecretsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.app?.secrets).toContain(
|
||||
`app_secret-${prefix}`,
|
||||
`app_secret-${suffix}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -90,24 +90,24 @@ secrets:
|
||||
file: ./frontend_secret.txt
|
||||
`;
|
||||
|
||||
test("Add prefix to secrets in services (Test 2)", () => {
|
||||
test("Add suffix to secrets in services (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsServices2) as ComposeSpecification;
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToSecretsInServices(
|
||||
const updatedComposeData = addSuffixToSecretsInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.backend?.secrets).toContain(
|
||||
`backend_secret-${prefix}`,
|
||||
`backend_secret-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services?.frontend?.secrets).toContain(
|
||||
`frontend_secret-${prefix}`,
|
||||
`frontend_secret-${suffix}`,
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { addPrefixToAllSecrets } from "@/server/utils/docker/compose/secrets";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { addSuffixToAllSecrets } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -47,11 +47,11 @@ secrets:
|
||||
file: ./app_secret.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all secrets", () => {
|
||||
test("Add suffix to all secrets", () => {
|
||||
const composeData = load(composeFileCombinedSecrets) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllSecrets(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedSecrets);
|
||||
});
|
||||
@@ -98,11 +98,11 @@ secrets:
|
||||
file: ./cache_secret.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all secrets (3rd Case)", () => {
|
||||
test("Add suffix to all secrets (3rd Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllSecrets(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedSecrets3);
|
||||
});
|
||||
@@ -149,11 +149,11 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all secrets (4th Case)", () => {
|
||||
test("Add suffix to all secrets (4th Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets4) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllSecrets(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFileCombinedSecrets4);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -27,33 +27,33 @@ test("Generate random hash with 8 characters", () => {
|
||||
expect(hash.length).toBe(8);
|
||||
});
|
||||
|
||||
test("Add prefix to service names with container_name in compose file", () => {
|
||||
test("Add suffix to service names with container_name in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que el nombre del contenedor ha cambiado correctamente
|
||||
expect(actualComposeData.services[`web-${prefix}`].container_name).toBe(
|
||||
`web_container-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.container_name).toBe(
|
||||
`web_container-${suffix}`,
|
||||
);
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -32,49 +32,49 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with depends_on (array) in compose file", () => {
|
||||
test("Add suffix to service names with depends_on (array) in compose file", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en depends_on tienen el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
|
||||
`db-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.depends_on).toContain(
|
||||
`db-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
|
||||
`api-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.depends_on).toContain(
|
||||
`api-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${suffix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
@@ -102,49 +102,49 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with depends_on (object) in compose file", () => {
|
||||
test("Add suffix to service names with depends_on (object) in compose file", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en depends_on tienen el prefijo
|
||||
const webDependsOn = actualComposeData.services[`web-${prefix}`]
|
||||
.depends_on as Record<string, any>;
|
||||
expect(webDependsOn).toHaveProperty(`db-${prefix}`);
|
||||
expect(webDependsOn).toHaveProperty(`api-${prefix}`);
|
||||
expect(webDependsOn[`db-${prefix}`].condition).toBe("service_healthy");
|
||||
expect(webDependsOn[`api-${prefix}`].condition).toBe("service_started");
|
||||
const webDependsOn = actualComposeData.services?.[`web-${suffix}`]
|
||||
?.depends_on as Record<string, any>;
|
||||
expect(webDependsOn).toHaveProperty(`db-${suffix}`);
|
||||
expect(webDependsOn).toHaveProperty(`api-${suffix}`);
|
||||
expect(webDependsOn[`db-${suffix}`].condition).toBe("service_healthy");
|
||||
expect(webDependsOn[`api-${suffix}`].condition).toBe("service_started");
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${suffix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -30,41 +30,41 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with extends (string) in compose file", () => {
|
||||
test("Add suffix to service names with extends (string) in compose file", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que el nombre en extends tiene el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].extends).toBe(
|
||||
`base_service-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.extends).toBe(
|
||||
`base_service-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||
expect(actualComposeData.services[`base_service-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`base_service-${suffix}`]?.image).toBe(
|
||||
"base:latest",
|
||||
);
|
||||
});
|
||||
@@ -90,42 +90,42 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with extends (object) in compose file", () => {
|
||||
test("Add suffix to service names with extends (object) in compose file", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que el nombre en extends.service tiene el prefijo
|
||||
const webExtends = actualComposeData.services[`web-${prefix}`].extends;
|
||||
const webExtends = actualComposeData.services?.[`web-${suffix}`]?.extends;
|
||||
if (typeof webExtends !== "string") {
|
||||
expect(webExtends.service).toBe(`base_service-${prefix}`);
|
||||
expect(webExtends?.service).toBe(`base_service-${suffix}`);
|
||||
}
|
||||
|
||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||
expect(actualComposeData.services[`base_service-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`base_service-${suffix}`]?.image).toBe(
|
||||
"base:latest",
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -31,46 +31,46 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with links in compose file", () => {
|
||||
test("Add suffix to service names with links in compose file", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en links tienen el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].links).toContain(
|
||||
`db-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.links).toContain(
|
||||
`db-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${suffix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -26,23 +26,23 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names in compose file", () => {
|
||||
test("Add suffix to service names in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que los nombres de los servicios han cambiado correctamente
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${suffix}`);
|
||||
// Verificar que las claves originales no existen
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
addPrefixToAllServiceNames,
|
||||
addPrefixToServiceNames,
|
||||
} from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
addSuffixToAllServiceNames,
|
||||
addSuffixToServiceNames,
|
||||
} from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -70,17 +70,17 @@ networks:
|
||||
driver: bridge
|
||||
`);
|
||||
|
||||
test("Add prefix to all service names in compose file", () => {
|
||||
test("Add suffix to all service names in compose file", () => {
|
||||
const composeData = load(composeFileCombinedAllCases) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
@@ -175,11 +175,11 @@ networks:
|
||||
driver: bridge
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all service names in compose file 1", () => {
|
||||
test("Add suffix to all service names in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllServiceNames(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile1);
|
||||
});
|
||||
@@ -270,11 +270,11 @@ networks:
|
||||
driver: bridge
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all service names in compose file 2", () => {
|
||||
test("Add suffix to all service names in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllServiceNames(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile2);
|
||||
});
|
||||
@@ -365,11 +365,11 @@ networks:
|
||||
driver: bridge
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to all service names in compose file 3", () => {
|
||||
test("Add suffix to all service names in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllServiceNames(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedComposeFile3);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToServiceNames } from "@/server/utils/docker/compose/service";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -35,42 +35,44 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to service names with volumes_from in compose file", () => {
|
||||
test("Add suffix to service names with volumes_from in compose file", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
const updatedComposeData = addPrefixToServiceNames(
|
||||
const updatedComposeData = addSuffixToServiceNames(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`web-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en volumes_from tienen el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].volumes_from).toContain(
|
||||
`shared-${prefix}`,
|
||||
expect(actualComposeData.services?.[`web-${suffix}`]?.volumes_from).toContain(
|
||||
`shared-${suffix}`,
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].volumes_from).toContain(
|
||||
`shared-${prefix}`,
|
||||
expect(actualComposeData.services?.[`api-${suffix}`]?.volumes_from).toContain(
|
||||
`shared-${suffix}`,
|
||||
);
|
||||
|
||||
// Verificar que el servicio shared también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`shared-${prefix}`);
|
||||
expect(actualComposeData.services).toHaveProperty(`shared-${suffix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("shared");
|
||||
expect(actualComposeData.services[`shared-${prefix}`].image).toBe("busybox");
|
||||
expect(actualComposeData.services?.[`shared-${suffix}`]?.image).toBe(
|
||||
"busybox",
|
||||
);
|
||||
});
|
||||
@@ -1,9 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToVolumesRoot,
|
||||
addPrefixToAllVolumes,
|
||||
} from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToAllVolumes, addSuffixToVolumesRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -142,15 +139,15 @@ test("Generate random hash with 8 characters", () => {
|
||||
|
||||
// Docker compose needs unique names for services, volumes, networks and containers
|
||||
// So base on a input which is a dockercompose file, it should replace the name with a hash and return a new dockercompose file
|
||||
test("Add prefix to volumes root property", () => {
|
||||
test("Add suffix to volumes root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
|
||||
// {
|
||||
// 'db-data-af045046': { driver: 'local' },
|
||||
@@ -160,15 +157,15 @@ test("Add prefix to volumes root property", () => {
|
||||
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("Expect to change the prefix in all the possible places", () => {
|
||||
test("Expect to change the suffix in all the possible places", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerCompose);
|
||||
});
|
||||
@@ -217,11 +214,11 @@ volumes:
|
||||
mongo-data-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (2 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (2 Try)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerCompose2);
|
||||
});
|
||||
@@ -270,11 +267,11 @@ volumes:
|
||||
mongo-data-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (3 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (3 Try)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerCompose3);
|
||||
});
|
||||
@@ -1011,11 +1008,11 @@ volumes:
|
||||
db-config-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (4 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (4 Try)", () => {
|
||||
const composeData = load(composeFileComplex) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerComposeComplex);
|
||||
});
|
||||
@@ -1110,11 +1107,11 @@ volumes:
|
||||
db-data-testhash:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the prefix in all the possible places (5 Try)", () => {
|
||||
test("Expect to change the suffix in all the possible places (5 Try)", () => {
|
||||
const composeData = load(composeFileExample1) as ComposeSpecification;
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
expect(updatedComposeData).toEqual(expectedDockerComposeExample1);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToVolumesRoot } from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToVolumesRoot } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -29,18 +29,18 @@ test("Generate random hash with 8 characters", () => {
|
||||
expect(hash.length).toBe(8);
|
||||
});
|
||||
|
||||
test("Add prefix to volumes in root property", () => {
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
expect(volumes[volumeKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -67,18 +67,18 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes in root property (Case 2)", () => {
|
||||
test("Add suffix to volumes in root property (Case 2)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
expect(volumes[volumeKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -101,19 +101,19 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes in root property (Case 3)", () => {
|
||||
test("Add suffix to volumes in root property (Case 3)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
|
||||
expect(volumes).toBeDefined();
|
||||
for (const volumeKey of Object.keys(volumes)) {
|
||||
expect(volumeKey).toContain(`-${prefix}`);
|
||||
expect(volumeKey).toContain(`-${suffix}`);
|
||||
expect(volumes[volumeKey]).toBeDefined();
|
||||
}
|
||||
});
|
||||
@@ -179,15 +179,15 @@ volumes:
|
||||
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to volumes in root property", () => {
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
if (!composeData?.volumes) {
|
||||
return;
|
||||
}
|
||||
const volumes = addPrefixToVolumesRoot(composeData.volumes, prefix);
|
||||
const volumes = addSuffixToVolumesRoot(composeData.volumes, suffix);
|
||||
const updatedComposeData = { ...composeData, volumes };
|
||||
|
||||
// Verificar que el resultado coincide con el archivo esperado
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { addPrefixToVolumesInServices } from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import { addSuffixToVolumesInServices } from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -21,22 +21,22 @@ services:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes declared directly in services", () => {
|
||||
test("Add suffix to volumes declared directly in services", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToVolumesInServices(
|
||||
const updatedComposeData = addSuffixToVolumesInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
expect(actualComposeData.services?.db?.volumes).toContain(
|
||||
`db_data-${prefix}:/var/lib/postgresql/data`,
|
||||
`db_data-${suffix}:/var/lib/postgresql/data`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -56,25 +56,25 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
test("Add prefix to volumes declared directly in services (Case 2)", () => {
|
||||
test("Add suffix to volumes declared directly in services (Case 2)", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const prefix = generateRandomHash();
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedComposeData = addPrefixToVolumesInServices(
|
||||
const updatedComposeData = addSuffixToVolumesInServices(
|
||||
composeData.services,
|
||||
prefix,
|
||||
suffix,
|
||||
);
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
expect(actualComposeData.services?.db?.volumes).toEqual([
|
||||
{
|
||||
type: "volume",
|
||||
source: `db-test-${prefix}`,
|
||||
source: `db-test-${suffix}`,
|
||||
target: "/var/lib/postgresql/data",
|
||||
},
|
||||
]);
|
||||
@@ -1,9 +1,9 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import { generateRandomHash } from "@dokploy/server";
|
||||
import {
|
||||
addPrefixToAllVolumes,
|
||||
addPrefixToVolumesInServices,
|
||||
} from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
addSuffixToAllVolumes,
|
||||
addSuffixToVolumesInServices,
|
||||
} from "@dokploy/server";
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
@@ -47,12 +47,12 @@ volumes:
|
||||
driver: local
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to volumes with type: volume in services", () => {
|
||||
test("Add suffix to volumes with type: volume in services", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume);
|
||||
@@ -96,12 +96,12 @@ volumes:
|
||||
driver: local
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to mixed volumes in services", () => {
|
||||
test("Add suffix to mixed volumes in services", () => {
|
||||
const composeData = load(composeFileTypeVolume1) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume1);
|
||||
@@ -157,12 +157,12 @@ volumes:
|
||||
device: /path/to/app/logs
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to complex volume configurations in services", () => {
|
||||
test("Add suffix to complex volume configurations in services", () => {
|
||||
const composeData = load(composeFileTypeVolume2) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume2);
|
||||
@@ -276,12 +276,12 @@ volumes:
|
||||
device: /path/to/shared/logs
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add prefix to complex nested volumes configuration in services", () => {
|
||||
test("Add suffix to complex nested volumes configuration in services", () => {
|
||||
const composeData = load(composeFileTypeVolume3) as ComposeSpecification;
|
||||
|
||||
const prefix = "testhash";
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addPrefixToAllVolumes(composeData, prefix);
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
const actualComposeData = { ...composeData, ...updatedComposeData };
|
||||
|
||||
expect(actualComposeData).toEqual(expectedComposeFileTypeVolume3);
|
||||
186
apps/dokploy/__test__/drop/drop.test.test.ts
Normal file
186
apps/dokploy/__test__/drop/drop.test.test.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
const { APPLICATIONS_PATH } = paths();
|
||||
import type { ApplicationNested } from "@dokploy/server";
|
||||
import { unzipDrop } from "@dokploy/server";
|
||||
import AdmZip from "adm-zip";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@dokploy/server/constants", async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
// @ts-ignore
|
||||
...actual,
|
||||
paths: () => ({
|
||||
APPLICATIONS_PATH: "./__test__/drop/zips/output",
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
const undici = require("undici");
|
||||
globalThis.File = undici.File as any;
|
||||
globalThis.FileList = undici.FileList as any;
|
||||
}
|
||||
|
||||
const baseApp: ApplicationNested = {
|
||||
applicationId: "",
|
||||
applicationStatus: "done",
|
||||
appName: "",
|
||||
autoDeploy: true,
|
||||
serverId: "",
|
||||
branch: null,
|
||||
dockerBuildStage: "",
|
||||
buildArgs: null,
|
||||
buildPath: "/",
|
||||
gitlabPathNamespace: "",
|
||||
buildType: "nixpacks",
|
||||
bitbucketBranch: "",
|
||||
bitbucketBuildPath: "",
|
||||
bitbucketId: "",
|
||||
bitbucketRepository: "",
|
||||
bitbucketOwner: "",
|
||||
githubId: "",
|
||||
gitlabProjectId: 0,
|
||||
gitlabBranch: "",
|
||||
gitlabBuildPath: "",
|
||||
gitlabId: "",
|
||||
gitlabRepository: "",
|
||||
gitlabOwner: "",
|
||||
command: null,
|
||||
cpuLimit: null,
|
||||
cpuReservation: null,
|
||||
createdAt: "",
|
||||
customGitBranch: "",
|
||||
customGitBuildPath: "",
|
||||
customGitSSHKeyId: null,
|
||||
customGitUrl: "",
|
||||
description: "",
|
||||
dockerfile: null,
|
||||
dockerImage: null,
|
||||
dropBuildPath: null,
|
||||
enabled: null,
|
||||
env: null,
|
||||
healthCheckSwarm: null,
|
||||
labelsSwarm: null,
|
||||
memoryLimit: null,
|
||||
memoryReservation: null,
|
||||
modeSwarm: null,
|
||||
mounts: [],
|
||||
name: "",
|
||||
networkSwarm: null,
|
||||
owner: null,
|
||||
password: null,
|
||||
placementSwarm: null,
|
||||
ports: [],
|
||||
projectId: "",
|
||||
publishDirectory: null,
|
||||
redirects: [],
|
||||
refreshToken: "",
|
||||
registry: null,
|
||||
registryId: null,
|
||||
replicas: 1,
|
||||
repository: null,
|
||||
restartPolicySwarm: null,
|
||||
rollbackConfigSwarm: null,
|
||||
security: [],
|
||||
sourceType: "git",
|
||||
subtitle: null,
|
||||
title: null,
|
||||
updateConfigSwarm: null,
|
||||
username: null,
|
||||
dockerContextPath: null,
|
||||
};
|
||||
|
||||
describe("unzipDrop using real zip files", () => {
|
||||
// const { APPLICATIONS_PATH } = paths();
|
||||
beforeAll(async () => {
|
||||
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root folder", async () => {
|
||||
baseApp.appName = "single-file";
|
||||
// const appName = "single-file";
|
||||
try {
|
||||
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
|
||||
console.log(`Output Path: ${outputPath}`);
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, baseApp);
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
expect(files.some((f) => f.name === "test.txt")).toBe(true);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
}
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root folder and a subfolder", async () => {
|
||||
baseApp.appName = "folderwithfile";
|
||||
// const appName = "folderwithfile";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/folder-with-file.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, baseApp);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
expect(files.some((f) => f.name === "folder1.txt")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with multiple root folders", async () => {
|
||||
baseApp.appName = "two-folders";
|
||||
// const appName = "two-folders";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/two-folders.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, baseApp);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
|
||||
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||
expect(files.some((f) => f.name === "folder2")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root with a file", async () => {
|
||||
baseApp.appName = "nested";
|
||||
// const appName = "nested";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/nested.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, baseApp);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
|
||||
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||
expect(files.some((f) => f.name === "folder2")).toBe(true);
|
||||
expect(files.some((f) => f.name === "folder3")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root with a folder", async () => {
|
||||
baseApp.appName = "folder-with-sibling-file";
|
||||
// const appName = "folder-with-sibling-file";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/folder-with-sibling-file.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, baseApp);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
|
||||
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||
expect(files.some((f) => f.name === "test.txt")).toBe(true);
|
||||
});
|
||||
});
|
||||
BIN
apps/dokploy/__test__/drop/zips/folder-with-file.zip
Normal file
BIN
apps/dokploy/__test__/drop/zips/folder-with-file.zip
Normal file
Binary file not shown.
BIN
apps/dokploy/__test__/drop/zips/folder-with-sibling-file.zip
Normal file
BIN
apps/dokploy/__test__/drop/zips/folder-with-sibling-file.zip
Normal file
Binary file not shown.
1
apps/dokploy/__test__/drop/zips/folder1/folder1.txt
Normal file
1
apps/dokploy/__test__/drop/zips/folder1/folder1.txt
Normal file
@@ -0,0 +1 @@
|
||||
Gogogogogogo
|
||||
1
apps/dokploy/__test__/drop/zips/folder2/folder2.txt
Normal file
1
apps/dokploy/__test__/drop/zips/folder2/folder2.txt
Normal file
@@ -0,0 +1 @@
|
||||
gogogogogog
|
||||
1
apps/dokploy/__test__/drop/zips/folder3/file3.txt
Normal file
1
apps/dokploy/__test__/drop/zips/folder3/file3.txt
Normal file
@@ -0,0 +1 @@
|
||||
gogogogogogogogogo
|
||||
BIN
apps/dokploy/__test__/drop/zips/nested.zip
Normal file
BIN
apps/dokploy/__test__/drop/zips/nested.zip
Normal file
Binary file not shown.
BIN
apps/dokploy/__test__/drop/zips/single-file.zip
Normal file
BIN
apps/dokploy/__test__/drop/zips/single-file.zip
Normal file
Binary file not shown.
1
apps/dokploy/__test__/drop/zips/test.txt
Normal file
1
apps/dokploy/__test__/drop/zips/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
dsafasdfasdf
|
||||
BIN
apps/dokploy/__test__/drop/zips/two-folders.zip
Normal file
BIN
apps/dokploy/__test__/drop/zips/two-folders.zip
Normal file
Binary file not shown.
56
apps/dokploy/__test__/requests/request.test.ts
Normal file
56
apps/dokploy/__test__/requests/request.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { parseRawConfig, processLogs } from "@dokploy/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
const sampleLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"s222-umami-c381af.traefik.me","RequestContentSize":0,"RequestCount":122,"RequestHost":"s222-umami-c381af.traefik.me","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-umami-60e104-47-web@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"s222-umami-60e104-47-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`;
|
||||
|
||||
describe("processLogs", () => {
|
||||
it("should process a single log entry correctly", () => {
|
||||
const result = processLogs(sampleLogEntry);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({
|
||||
hour: "2024-08-25T04:00:00Z",
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("should process multiple log entries and group by hour", () => {
|
||||
const sampleLogEntry = `{"ClientAddr":"172.19.0.1:58094","ClientHost":"172.19.0.1","ClientPort":"58094","ClientUsername":"-","DownstreamContentSize":50,"DownstreamStatus":200,"Duration":35914250,"OriginContentSize":50,"OriginDuration":35817959,"OriginStatus":200,"Overhead":96291,"RequestAddr":"s222-pocketbase-f4a6e5.traefik.me","RequestContentSize":0,"RequestCount":991,"RequestHost":"s222-pocketbase-f4a6e5.traefik.me","RequestMethod":"GET","RequestPath":"/api/logs/stats?filter=","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-pocketbase-e94e25-44-web@docker","ServiceAddr":"10.0.1.12:80","ServiceName":"s222-pocketbase-e94e25-44-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.12:80","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T17:44:29.274072471Z","StartUTC":"2024-08-25T17:44:29.274072471Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T17:44:29Z"}
|
||||
{"ClientAddr":"172.19.0.1:58108","ClientHost":"172.19.0.1","ClientPort":"58108","ClientUsername":"-","DownstreamContentSize":30975,"DownstreamStatus":200,"Duration":31406458,"OriginContentSize":30975,"OriginDuration":31046791,"OriginStatus":200,"Overhead":359667,"RequestAddr":"s222-pocketbase-f4a6e5.traefik.me","RequestContentSize":0,"RequestCount":992,"RequestHost":"s222-pocketbase-f4a6e5.traefik.me","RequestMethod":"GET","RequestPath":"/api/logs?page=1\u0026perPage=50\u0026sort=-rowid\u0026skipTotal=1\u0026filter=","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-pocketbase-e94e25-44-web@docker","ServiceAddr":"10.0.1.12:80","ServiceName":"s222-pocketbase-e94e25-44-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.12:80","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T17:44:29.278990221Z","StartUTC":"2024-08-25T17:44:29.278990221Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T17:44:29Z"}
|
||||
`;
|
||||
|
||||
const result = processLogs(sampleLogEntry);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([{ hour: "2024-08-25T17:00:00Z", count: 2 }]);
|
||||
});
|
||||
|
||||
it("should return an empty array for empty input", () => {
|
||||
expect(processLogs("")).toEqual([]);
|
||||
expect(processLogs(null as any)).toEqual([]);
|
||||
expect(processLogs(undefined as any)).toEqual([]);
|
||||
});
|
||||
|
||||
// it("should parse a single log entry correctly", () => {
|
||||
// const result = parseRawConfig(sampleLogEntry);
|
||||
// expect(result).toHaveLength(1);
|
||||
// expect(result.data[0]).toHaveProperty("ClientAddr", "172.19.0.1:56732");
|
||||
// expect(result.data[0]).toHaveProperty(
|
||||
// "StartUTC",
|
||||
// "2024-08-25T04:34:37.306691884Z",
|
||||
// );
|
||||
// });
|
||||
|
||||
it("should parse multiple log entries", () => {
|
||||
const multipleEntries = `${sampleLogEntry}\n${sampleLogEntry}`;
|
||||
const result = parseRawConfig(multipleEntries);
|
||||
expect(result.data).toHaveLength(2);
|
||||
|
||||
for (const entry of result.data) {
|
||||
expect(entry).toHaveProperty("ClientAddr", "172.19.0.1:56732");
|
||||
}
|
||||
});
|
||||
|
||||
it("should handle whitespace and empty lines", () => {
|
||||
const entryWithWhitespace = `\n${sampleLogEntry}\n\n${sampleLogEntry}\n`;
|
||||
const result = parseRawConfig(entryWithWhitespace);
|
||||
expect(result.data).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { fs, vol } from "memfs";
|
||||
|
||||
vi.mock("node:fs", () => ({
|
||||
...fs,
|
||||
default: fs,
|
||||
}));
|
||||
|
||||
import type { Admin, FileConfig } from "@dokploy/server";
|
||||
import {
|
||||
createDefaultServerTraefikConfig,
|
||||
loadOrCreateConfig,
|
||||
updateServerTraefik,
|
||||
} from "@dokploy/server";
|
||||
import { beforeEach, expect, test, vi } from "vitest";
|
||||
|
||||
const baseAdmin: Admin = {
|
||||
createdAt: "",
|
||||
authId: "",
|
||||
adminId: "string",
|
||||
serverIp: null,
|
||||
certificateType: "none",
|
||||
host: null,
|
||||
letsEncryptEmail: null,
|
||||
sshPrivateKey: null,
|
||||
enableDockerCleanup: false,
|
||||
enableLogRotation: false,
|
||||
serversQuantity: 0,
|
||||
stripeCustomerId: "",
|
||||
stripeSubscriptionId: "",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
createDefaultServerTraefikConfig();
|
||||
});
|
||||
|
||||
test("Should read the configuration file", () => {
|
||||
const config: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe(
|
||||
"dokploy-service-app",
|
||||
);
|
||||
});
|
||||
|
||||
test("Should apply redirect-to-https", () => {
|
||||
updateServerTraefik(
|
||||
{
|
||||
...baseAdmin,
|
||||
certificateType: "letsencrypt",
|
||||
},
|
||||
"example.com",
|
||||
);
|
||||
|
||||
const config: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
expect(config.http?.routers?.["dokploy-router-app"]?.middlewares).toContain(
|
||||
"redirect-to-https",
|
||||
);
|
||||
});
|
||||
|
||||
test("Should change only host when no certificate", () => {
|
||||
updateServerTraefik(baseAdmin, "example.com");
|
||||
|
||||
const config: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Should not touch config without host", () => {
|
||||
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
updateServerTraefik(baseAdmin, null);
|
||||
|
||||
const config: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
expect(originalConfig).toEqual(config);
|
||||
});
|
||||
|
||||
test("Should remove websecure if https rollback to http", () => {
|
||||
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
updateServerTraefik(
|
||||
{ ...baseAdmin, certificateType: "letsencrypt" },
|
||||
"example.com",
|
||||
);
|
||||
|
||||
updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com");
|
||||
|
||||
const config: FileConfig = loadOrCreateConfig("dokploy");
|
||||
|
||||
expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined();
|
||||
expect(
|
||||
config.http?.routers?.["dokploy-router-app"]?.middlewares,
|
||||
).not.toContain("redirect-to-https");
|
||||
});
|
||||
218
apps/dokploy/__test__/traefik/traefik.test.ts
Normal file
218
apps/dokploy/__test__/traefik/traefik.test.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import type { Domain } from "@dokploy/server";
|
||||
import type { Redirect } from "@dokploy/server";
|
||||
import type { ApplicationNested } from "@dokploy/server";
|
||||
import { createRouterConfig } from "@dokploy/server";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const baseApp: ApplicationNested = {
|
||||
applicationId: "",
|
||||
applicationStatus: "done",
|
||||
appName: "",
|
||||
autoDeploy: true,
|
||||
serverId: "",
|
||||
branch: null,
|
||||
dockerBuildStage: "",
|
||||
buildArgs: null,
|
||||
buildPath: "/",
|
||||
gitlabPathNamespace: "",
|
||||
buildType: "nixpacks",
|
||||
bitbucketBranch: "",
|
||||
bitbucketBuildPath: "",
|
||||
bitbucketId: "",
|
||||
bitbucketRepository: "",
|
||||
bitbucketOwner: "",
|
||||
githubId: "",
|
||||
gitlabProjectId: 0,
|
||||
gitlabBranch: "",
|
||||
gitlabBuildPath: "",
|
||||
gitlabId: "",
|
||||
gitlabRepository: "",
|
||||
gitlabOwner: "",
|
||||
command: null,
|
||||
cpuLimit: null,
|
||||
cpuReservation: null,
|
||||
createdAt: "",
|
||||
customGitBranch: "",
|
||||
customGitBuildPath: "",
|
||||
customGitSSHKeyId: null,
|
||||
customGitUrl: "",
|
||||
description: "",
|
||||
dockerfile: null,
|
||||
dockerImage: null,
|
||||
dropBuildPath: null,
|
||||
enabled: null,
|
||||
env: null,
|
||||
healthCheckSwarm: null,
|
||||
labelsSwarm: null,
|
||||
memoryLimit: null,
|
||||
memoryReservation: null,
|
||||
modeSwarm: null,
|
||||
mounts: [],
|
||||
name: "",
|
||||
networkSwarm: null,
|
||||
owner: null,
|
||||
password: null,
|
||||
placementSwarm: null,
|
||||
ports: [],
|
||||
projectId: "",
|
||||
publishDirectory: null,
|
||||
redirects: [],
|
||||
refreshToken: "",
|
||||
registry: null,
|
||||
registryId: null,
|
||||
replicas: 1,
|
||||
repository: null,
|
||||
restartPolicySwarm: null,
|
||||
rollbackConfigSwarm: null,
|
||||
security: [],
|
||||
sourceType: "git",
|
||||
subtitle: null,
|
||||
title: null,
|
||||
updateConfigSwarm: null,
|
||||
username: null,
|
||||
dockerContextPath: null,
|
||||
};
|
||||
|
||||
const baseDomain: Domain = {
|
||||
applicationId: "",
|
||||
certificateType: "none",
|
||||
createdAt: "",
|
||||
domainId: "",
|
||||
host: "",
|
||||
https: false,
|
||||
path: null,
|
||||
port: null,
|
||||
serviceName: "",
|
||||
composeId: "",
|
||||
domainType: "application",
|
||||
uniqueConfigKey: 1,
|
||||
};
|
||||
|
||||
const baseRedirect: Redirect = {
|
||||
redirectId: "",
|
||||
regex: "",
|
||||
replacement: "",
|
||||
permanent: false,
|
||||
uniqueConfigKey: 1,
|
||||
createdAt: "",
|
||||
applicationId: "",
|
||||
};
|
||||
|
||||
/** Middlewares */
|
||||
|
||||
test("Web entrypoint on http domain", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, https: false },
|
||||
"web",
|
||||
);
|
||||
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
expect(router.rule).not.toContain("PathPrefix");
|
||||
});
|
||||
|
||||
test("Web entrypoint on http domain with custom path", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, path: "/foo", https: false },
|
||||
"web",
|
||||
);
|
||||
|
||||
expect(router.rule).toContain("PathPrefix(`/foo`)");
|
||||
});
|
||||
|
||||
test("Web entrypoint on http domain with redirect", async () => {
|
||||
const router = await createRouterConfig(
|
||||
{
|
||||
...baseApp,
|
||||
appName: "test",
|
||||
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
||||
},
|
||||
{ ...baseDomain, https: false },
|
||||
"web",
|
||||
);
|
||||
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
expect(router.middlewares).toContain("redirect-test-1");
|
||||
});
|
||||
|
||||
test("Web entrypoint on http domain with multiple redirect", async () => {
|
||||
const router = await createRouterConfig(
|
||||
{
|
||||
...baseApp,
|
||||
appName: "test",
|
||||
redirects: [
|
||||
{ ...baseRedirect, uniqueConfigKey: 1 },
|
||||
{ ...baseRedirect, uniqueConfigKey: 2 },
|
||||
],
|
||||
},
|
||||
{ ...baseDomain, https: false },
|
||||
"web",
|
||||
);
|
||||
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
expect(router.middlewares).toContain("redirect-test-1");
|
||||
expect(router.middlewares).toContain("redirect-test-2");
|
||||
});
|
||||
|
||||
test("Web entrypoint on https domain", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, https: true },
|
||||
"web",
|
||||
);
|
||||
|
||||
expect(router.middlewares).toContain("redirect-to-https");
|
||||
});
|
||||
|
||||
test("Web entrypoint on https domain with redirect", async () => {
|
||||
const router = await createRouterConfig(
|
||||
{
|
||||
...baseApp,
|
||||
appName: "test",
|
||||
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
||||
},
|
||||
{ ...baseDomain, https: true },
|
||||
"web",
|
||||
);
|
||||
|
||||
expect(router.middlewares).toContain("redirect-to-https");
|
||||
expect(router.middlewares).not.toContain("redirect-test-1");
|
||||
});
|
||||
|
||||
test("Websecure entrypoint on https domain", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, https: true },
|
||||
"websecure",
|
||||
);
|
||||
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
});
|
||||
|
||||
test("Websecure entrypoint on https domain with redirect", async () => {
|
||||
const router = await createRouterConfig(
|
||||
{
|
||||
...baseApp,
|
||||
appName: "test",
|
||||
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
||||
},
|
||||
{ ...baseDomain, https: true },
|
||||
"websecure",
|
||||
);
|
||||
|
||||
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||
expect(router.middlewares).toContain("redirect-test-1");
|
||||
});
|
||||
|
||||
/** Certificates */
|
||||
|
||||
test("CertificateType on websecure entrypoint", async () => {
|
||||
const router = await createRouterConfig(
|
||||
baseApp,
|
||||
{ ...baseDomain, certificateType: "letsencrypt" },
|
||||
"websecure",
|
||||
);
|
||||
|
||||
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||
});
|
||||
@@ -1,16 +1,24 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import path from "node:path";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
root: "./",
|
||||
projects: ["tsconfig.json"],
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
include: ["__test__/**/*.test.ts"], // Incluir solo los archivos de test en el directorio __test__
|
||||
exclude: ["**/node_modules/**", "**/dist/**", "**/.docker/**"],
|
||||
pool: "forks",
|
||||
},
|
||||
define: {
|
||||
"process.env": {
|
||||
NODE: "test",
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@dokploy/server": path.resolve(
|
||||
__dirname,
|
||||
"../../../packages/server/src",
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
17
apps/dokploy/components.json
Normal file
17
apps/dokploy/components.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,19 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Login2FASchema = z.object({
|
||||
pin: z.string().min(6, {
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,21 +19,19 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { HelpCircle, Settings } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { HelpCircle, Settings } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const HealthCheckSwarmSchema = z
|
||||
.object({
|
||||
@@ -278,6 +278,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<div className="px-4">
|
||||
<AlertBlock type="info">
|
||||
Changing settings such as placements may cause the logs/monitoring
|
||||
to be unavailable.
|
||||
</AlertBlock>
|
||||
</div>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,8 +7,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,11 +15,6 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { toast } from "sonner";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
@@ -31,10 +25,16 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import Link from "next/link";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Server } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AddSwarmSettings } from "./modify-swarm-settings";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
@@ -81,7 +81,10 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
||||
const onSubmit = async (data: AddCommand) => {
|
||||
await mutateAsync({
|
||||
applicationId,
|
||||
registryId: data?.registryId === "none" ? null : data?.registryId,
|
||||
registryId:
|
||||
data?.registryId === "none" || !data?.registryId
|
||||
? null
|
||||
: data?.registryId,
|
||||
replicas: data?.replicas,
|
||||
})
|
||||
.then(async () => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,12 +14,14 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { toast } from "sonner";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,13 +18,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -31,6 +25,12 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const AddPortSchema = z.object({
|
||||
@@ -52,6 +52,7 @@ export const AddPort = ({
|
||||
applicationId,
|
||||
children = <PlusIcon className="h-4 w-4" />,
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
@@ -82,6 +83,7 @@ export const AddPort = ({
|
||||
await utils.application.one.invalidate({
|
||||
applicationId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to create the port");
|
||||
@@ -89,7 +91,7 @@ export const AddPort = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>{children}</Button>
|
||||
</DialogTrigger>
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { Rss } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddPort } from "./add-port";
|
||||
import { DeletePort } from "./delete-port";
|
||||
import { UpdatePort } from "./update-port";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -16,15 +17,7 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { Input, NumberInput } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -32,6 +25,13 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const UpdatePortSchema = z.object({
|
||||
publishedPort: z.number().int().min(1).max(65535),
|
||||
@@ -49,6 +49,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const UpdatePort = ({ portId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { data } = api.port.one.useQuery(
|
||||
{
|
||||
@@ -89,6 +90,7 @@ export const UpdatePort = ({ portId }: Props) => {
|
||||
await utils.application.one.invalidate({
|
||||
applicationId: response?.applicationId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update the port");
|
||||
@@ -96,7 +98,7 @@ export const UpdatePort = ({ portId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
@@ -123,28 +125,14 @@ export const UpdatePort = ({ portId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Published Port</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1-65535"
|
||||
{...field}
|
||||
value={field.value?.toString() || ""}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "") {
|
||||
field.onChange(0);
|
||||
} else {
|
||||
const number = Number.parseInt(value, 10);
|
||||
if (!Number.isNaN(number)) {
|
||||
field.onChange(number);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<NumberInput placeholder="1-65535" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetPort"
|
||||
@@ -152,22 +140,7 @@ export const UpdatePort = ({ portId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Target Port</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1-65535"
|
||||
{...field}
|
||||
value={field.value?.toString() || ""}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "") {
|
||||
field.onChange(0);
|
||||
} else {
|
||||
const number = Number.parseInt(value, 10);
|
||||
if (!Number.isNaN(number)) {
|
||||
field.onChange(number);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<NumberInput placeholder="1-65535" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -11,22 +12,30 @@ import {
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { z } from "zod";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
const AddRedirectchema = z.object({
|
||||
regex: z.string().min(1, "Regex required"),
|
||||
@@ -36,6 +45,36 @@ const AddRedirectchema = z.object({
|
||||
|
||||
type AddRedirect = z.infer<typeof AddRedirectchema>;
|
||||
|
||||
// Default presets
|
||||
const redirectPresets = [
|
||||
// {
|
||||
// label: "Allow www & non-www.",
|
||||
// redirect: {
|
||||
// regex: "",
|
||||
// permanent: false,
|
||||
// replacement: "",
|
||||
// },
|
||||
// },
|
||||
{
|
||||
id: "to-www",
|
||||
label: "Redirect to www",
|
||||
redirect: {
|
||||
regex: "^https?://(?:www.)?(.+)",
|
||||
permanent: true,
|
||||
replacement: "https://www.$${1}",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "to-non-www",
|
||||
label: "Redirect to non-www",
|
||||
redirect: {
|
||||
regex: "^https?://www.(.+)",
|
||||
permanent: true,
|
||||
replacement: "https://$${1}",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
children?: React.ReactNode;
|
||||
@@ -43,8 +82,10 @@ interface Props {
|
||||
|
||||
export const AddRedirect = ({
|
||||
applicationId,
|
||||
children = <PlusIcon className="h-4 w-4" />,
|
||||
children = <PlusIcon className="w-4 h-4" />,
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [presetSelected, setPresetSelected] = useState("");
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
@@ -80,18 +121,36 @@ export const AddRedirect = ({
|
||||
await utils.application.readTraefikConfig.invalidate({
|
||||
applicationId,
|
||||
});
|
||||
onDialogToggle(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to create the redirect");
|
||||
});
|
||||
};
|
||||
|
||||
const onDialogToggle = (open: boolean) => {
|
||||
setIsOpen(open);
|
||||
// commented for the moment because not reseting the form if accidentally closed the dialog can be considered as a feature instead of a bug
|
||||
// setPresetSelected("");
|
||||
// form.reset();
|
||||
};
|
||||
|
||||
const onPresetSelect = (presetId: string) => {
|
||||
const redirectPreset = redirectPresets.find(
|
||||
(preset) => preset.id === presetId,
|
||||
)?.redirect;
|
||||
if (!redirectPreset) return;
|
||||
const { regex, permanent, replacement } = redirectPreset;
|
||||
form.reset({ regex, permanent, replacement }, { keepDefaultValues: true });
|
||||
setPresetSelected(presetId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={onDialogToggle}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>{children}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Redirects</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -100,6 +159,24 @@ export const AddRedirect = ({
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<Label>Presets</Label>
|
||||
<Select onValueChange={onPresetSelect} value={presetSelected}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="No preset selected" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{redirectPresets.map((preset) => (
|
||||
<SelectItem key={preset.label} value={preset.id}>
|
||||
{preset.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-redirect"
|
||||
@@ -140,7 +217,7 @@ export const AddRedirect = ({
|
||||
control={form.control}
|
||||
name="permanent"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Permanent</FormLabel>
|
||||
<FormDescription>
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { Split } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddRedirect } from "./add-redirect";
|
||||
import { DeleteRedirect } from "./delete-redirect";
|
||||
import { UpdateRedirect } from "./update-redirect";
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -11,22 +12,21 @@ import {
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
const UpdateRedirectSchema = z.object({
|
||||
regex: z.string().min(1, "Regex required"),
|
||||
permanent: z.boolean().default(false),
|
||||
@@ -41,6 +41,7 @@ interface Props {
|
||||
|
||||
export const UpdateRedirect = ({ redirectId }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { data } = api.redirects.one.useQuery(
|
||||
{
|
||||
redirectId,
|
||||
@@ -84,6 +85,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => {
|
||||
await utils.application.one.invalidate({
|
||||
applicationId: response?.applicationId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update the redirect");
|
||||
@@ -91,7 +93,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,12 +19,11 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { z } from "zod";
|
||||
|
||||
const AddSecuritychema = z.object({
|
||||
@@ -43,7 +43,7 @@ export const AddSecurity = ({
|
||||
children = <PlusIcon className="h-4 w-4" />,
|
||||
}: Props) => {
|
||||
const utils = api.useUtils();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.security.create.useMutation();
|
||||
|
||||
@@ -72,6 +72,7 @@ export const AddSecurity = ({
|
||||
await utils.application.readTraefikConfig.invalidate({
|
||||
applicationId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to create the security");
|
||||
@@ -79,7 +80,7 @@ export const AddSecurity = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>{children}</Button>
|
||||
</DialogTrigger>
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { LockKeyhole } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddSecurity } from "./add-security";
|
||||
import { DeleteSecurity } from "./delete-security";
|
||||
import { UpdateSecurity } from "./update-security";
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,10 +19,9 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -38,6 +38,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const UpdateSecurity = ({ securityId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { data } = api.security.one.useQuery(
|
||||
{
|
||||
@@ -79,6 +80,7 @@ export const UpdateSecurity = ({ securityId }: Props) => {
|
||||
await utils.application.one.invalidate({
|
||||
applicationId: response?.applicationId,
|
||||
});
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update the security");
|
||||
@@ -86,7 +88,7 @@ export const UpdateSecurity = ({ securityId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -21,7 +22,6 @@ import React, { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
const addResourcesApplication = z.object({
|
||||
memoryReservation: z.number().nullable().optional(),
|
||||
@@ -80,7 +80,7 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Resources</CardTitle>
|
||||
<CardDescription>
|
||||
If you want to decrease or increase the resources to a specific
|
||||
If you want to decrease or increase the resources to a specific.
|
||||
application or database
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user