Compare commits

...

251 Commits

Author SHA1 Message Date
Andras Bacsai
ed49d4e3a0 Merge pull request #1264 from coollabsio/next
v4.0.0-beta.51
2023-09-30 16:02:09 +02:00
Andras Bacsai
7811c75139 fix: deploykey branch 2023-09-30 15:59:39 +02:00
Andras Bacsai
068a1b4bc4 fix: preselect branc on private repos 2023-09-30 15:57:30 +02:00
Andras Bacsai
c618d912db fix: if public repository does not have a main branch 2023-09-30 15:51:01 +02:00
Andras Bacsai
3d43f2127a fix: respect server fqdn 2023-09-30 15:39:40 +02:00
Andras Bacsai
79fde593a9 fix: service volume read from filesystem
fix: edit compose moved to dialog
2023-09-30 15:08:40 +02:00
Andras Bacsai
64ce41df0f fix: file/dir based volumes are now read from the server 2023-09-29 21:38:11 +02:00
Andras Bacsai
23e205b6cd fix: docker cleanup should be a job by server 2023-09-29 14:26:42 +02:00
Andras Bacsai
4161ea7eb6 fix: show source on all type of applications 2023-09-29 14:26:31 +02:00
Andras Bacsai
77037f8933 ui: fix previews to preview 2023-09-29 14:26:19 +02:00
Andras Bacsai
bdcc0c8de5 fix: only show manually added private keys on server view 2023-09-29 11:01:58 +02:00
Andras Bacsai
ac133875fa fix: remove private key in case you removed a github app 2023-09-29 11:01:40 +02:00
Andras Bacsai
f4819a849b remove plausible for now 2023-09-29 10:53:33 +02:00
Andras Bacsai
2cd1785a92 version++ 2023-09-29 10:53:28 +02:00
Andras Bacsai
797352a5db Merge pull request #1263 from coollabsio/next
v4.0.0-beta.50
2023-09-29 10:36:25 +02:00
Andras Bacsai
03a3405524 fix: localhost privatekey update 2023-09-29 10:32:32 +02:00
Andras Bacsai
925326b885 change sentry dsn 2023-09-29 10:32:23 +02:00
Andras Bacsai
ffdf51a490 version++ 2023-09-29 10:32:13 +02:00
Andras Bacsai
272d5547c8 Merge pull request #1262 from coollabsio/next
v4.0.0-beta.49
2023-09-29 10:21:54 +02:00
Andras Bacsai
fbaec769f0 fix: reporting handler 2023-09-29 10:21:11 +02:00
Andras Bacsai
d93640a8d9 Merge pull request #1261 from coollabsio/next
v4.0.0-beta.48
2023-09-29 09:51:51 +02:00
Andras Bacsai
3749970831 fix: update process if server has been renamed 2023-09-29 09:48:20 +02:00
Andras Bacsai
7d45bb1335 version check 2023-09-28 22:55:03 +02:00
Andras Bacsai
da04e0c027 fix: sync:bunny 2023-09-28 22:42:10 +02:00
Andras Bacsai
c637d2d90d version++ 2023-09-28 22:31:53 +02:00
Andras Bacsai
1bb8860f37 Merge pull request #1260 from coollabsio/next
v4.0.0-beta.47
2023-09-28 22:25:12 +02:00
Andras Bacsai
38a22dcf4d fix: service templates 2023-09-28 22:20:49 +02:00
Andras Bacsai
91e1eb7664 fix 2023-09-28 21:56:34 +02:00
Andras Bacsai
c7946e7551 fix: next helper image 2023-09-28 21:52:50 +02:00
Andras Bacsai
958645e37f Merge branch 'main' into next 2023-09-28 21:48:03 +02:00
Andras Bacsai
acdfa89ec1 Update coolify-helper.yml 2023-09-28 21:32:53 +02:00
Andras Bacsai
62ec85ee2e Update coolify-helper.yml 2023-09-28 21:31:50 +02:00
Andras Bacsai
8f24a66456 Update coolify-helper.yml 2023-09-28 21:26:55 +02:00
Andras Bacsai
0dd0888775 Update coolify-helper.yml 2023-09-28 21:23:25 +02:00
Andras Bacsai
5040ddea28 fix: sentry 4502634789 2023-09-28 13:46:53 +02:00
Andras Bacsai
1a04a57c01 fix: sentry 4504136641 2023-09-28 13:40:58 +02:00
Andras Bacsai
9b6c162224 fix: sentry 4510197209 2023-09-28 13:15:14 +02:00
Andras Bacsai
e22c5d22f5 fix: instance fqdn setting 2023-09-28 13:13:21 +02:00
Andras Bacsai
84e5b39830 fix: prevent sync version (it needs an option) 2023-09-28 13:07:52 +02:00
Andras Bacsai
cdb6964b0b fix: install script drops an error 2023-09-28 13:05:17 +02:00
Andras Bacsai
df4ecd47a7 fix: sync command 2023-09-28 11:54:20 +02:00
Andras Bacsai
4a84c7238a fix: cannot delete env with available services 2023-09-28 11:51:01 +02:00
Andras Bacsai
2b3057e1b4 version++ 2023-09-28 11:50:50 +02:00
Andras Bacsai
ae65172946 Merge pull request #1256 from coollabsio/next
v4.0.0-beta.46
2023-09-28 11:38:16 +02:00
Andras Bacsai
3a2d17bc05 fix: containerStatusJob 2023-09-28 11:33:16 +02:00
Andras Bacsai
4c1067cf36 baseurl for docs 2023-09-28 11:01:00 +02:00
Andras Bacsai
b046a3e9f7 fix: sslip for localhost 2023-09-28 10:53:00 +02:00
Andras Bacsai
199881c596 fixes 2023-09-28 09:54:21 +02:00
Andras Bacsai
99c8607ff4 fix: disable early updates 2023-09-27 22:43:32 +02:00
Andras Bacsai
e61fcc77f9 ui 2023-09-27 22:39:10 +02:00
Andras Bacsai
20dca179fb add umami 2023-09-27 22:35:15 +02:00
Andras Bacsai
0f542c65ae fix: manually create network for services 2023-09-27 22:35:10 +02:00
Andras Bacsai
d609fcaee1 fix: services 2023-09-27 21:51:06 +02:00
Andras Bacsai
fe8a7fc54f fix: services view 2023-09-27 21:14:13 +02:00
Andras Bacsai
398f122593 fix: aaaaaaaaaaaaaaaaa 2023-09-27 15:48:19 +02:00
Andras Bacsai
f0abdcc2da okay, now it is way better 2023-09-27 12:45:53 +02:00
Andras Bacsai
c9a278b750 update service templates 2023-09-26 16:32:05 +02:00
Andras Bacsai
6990c593a4 update 2023-09-26 16:27:48 +02:00
Andras Bacsai
8f54b51ecd update templates 2023-09-26 16:21:55 +02:00
Andras Bacsai
3eb628b773 fix: containerstatusjob 2023-09-26 15:07:33 +02:00
Andras Bacsai
fabb97330a puh, fixes 2023-09-26 14:45:52 +02:00
Andras Bacsai
03c9793d11 Merge pull request #1252 from khrj/patch-1
Remove space before ? from onboarding flow
2023-09-25 21:02:53 +02:00
Andras Bacsai
a4320b7cee cloud: marketing emails 2023-09-25 20:57:52 +02:00
Andras Bacsai
cbf9bc99ea ui: help 2023-09-25 18:07:20 +02:00
Andras Bacsai
0fbc382467 feat: image tag for services 2023-09-25 17:51:04 +02:00
Andras Bacsai
0f8ccac775 fix: new service template layout 2023-09-25 17:41:35 +02:00
Andras Bacsai
ee20c3339e fix: show real storage name on services 2023-09-25 17:14:19 +02:00
Andras Bacsai
0b11093d18 feat: services 2023-09-25 15:48:43 +02:00
Khushraj Rathod
de8118b59d Remove space before ? from onboarding flow 2023-09-25 19:04:11 +05:30
Andras Bacsai
58522b59b7 - package updates
- feat: service fixes
- ui: help menu
2023-09-25 12:49:55 +02:00
Andras Bacsai
356394c03d ui: update help modal 2023-09-25 11:15:35 +02:00
Andras Bacsai
3e4db2f5b2 ui: more visible feedback button 2023-09-25 09:34:32 +02:00
Andras Bacsai
872981b8b4 ui: a bit better error 2023-09-25 09:20:29 +02:00
Andras Bacsai
51c468ae0b fix: proxy configuration + starter 2023-09-25 09:17:42 +02:00
Andras Bacsai
80a797aec8 version++ 2023-09-24 21:56:57 +02:00
Andras Bacsai
cfdab13d77 Merge pull request #1250 from coollabsio/next
v4.0.0-beta.45
2023-09-24 21:41:58 +02:00
Andras Bacsai
4fc8988ff4 fix: put back build pack chooser 2023-09-24 21:38:11 +02:00
Andras Bacsai
ab5619292e Merge pull request #1249 from chz/fix/1240
ui: Fixed z-index for magicbar
2023-09-24 21:18:48 +02:00
Andras Bacsai
b02e5d3f27 fix: applications with port mappins do a normal update (not rolling update) 2023-09-24 21:15:43 +02:00
Andras Bacsai
5f0c9c3a31 ui: Add source button 2023-09-24 21:15:21 +02:00
Chingiz Mammadov
ea6ec07a45 ui: Fixed z-index for magicbar 2023-09-24 23:03:11 +04:00
Andras Bacsai
36be325d0d Merge pull request #1248 from chz/ui/fix-version-zindex
ui: Fixed z-index for version link.
2023-09-24 20:42:35 +02:00
Andras Bacsai
6138ddcac6 revert sending notification 2023-09-24 17:51:01 +02:00
Andras Bacsai
0509da6730 fix: also check docker socks permission on validation 2023-09-24 17:48:40 +02:00
Andras Bacsai
5b877b84c2 fix: add expose port for containers 2023-09-24 17:48:25 +02:00
Andras Bacsai
0c35726a8d fix: add traefik labels no matter if traefik is selected or not 2023-09-24 17:47:43 +02:00
Andras Bacsai
e74899611b fix: links with path 2023-09-24 17:39:12 +02:00
Andras Bacsai
e9149e534d temporary disable containerstatusjob failed 2023-09-24 17:31:41 +02:00
Chingiz Mammadov
7cded7a36d ui: Fixed z-index for version link. 2023-09-24 14:12:07 +04:00
Andras Bacsai
ba74d55b4c fix: report livewire errors 2023-09-24 12:10:36 +02:00
Andras Bacsai
a1d13fc14e Merge pull request #1247 from coollabsio/next
v4.0.0-beta.44 - fix schema
2023-09-24 11:58:36 +02:00
Andras Bacsai
cdb2a3a8e5 version-- 2023-09-24 11:58:10 +02:00
Andras Bacsai
074c56edaa version++ 2023-09-24 11:57:11 +02:00
Andras Bacsai
66bc03b4cd fix: database schema 2023-09-24 11:56:32 +02:00
Andras Bacsai
2dcb7fec05 fix: services 2023-09-24 11:52:13 +02:00
Andras Bacsai
249bccb49e Merge pull request #1246 from coollabsio/next
v4.0.0-beta.44
2023-09-24 11:39:36 +02:00
Andras Bacsai
a55eaa10ac ui: fix typo 2023-09-24 11:32:26 +02:00
Andras Bacsai
ca2c75ce19 fix: only show traefik dashboard if its available 2023-09-24 11:10:50 +02:00
Andras Bacsai
250d7cbc53 ui: fixes 2023-09-24 10:55:15 +02:00
Andras Bacsai
92a53a151e fix: server validation with cf tunnels 2023-09-24 10:48:54 +02:00
Andras Bacsai
db3148c080 typos 2023-09-24 10:32:41 +02:00
Andras Bacsai
c46eeac4b5 feat: add cloudflare tunnel support 2023-09-23 13:34:40 +02:00
Andras Bacsai
19111ba059 fix: startProxy 2023-09-23 12:21:56 +02:00
Andras Bacsai
9bc61a0a17 fix: make sure proxy path created 2023-09-23 11:53:30 +02:00
Andras Bacsai
78b9166bdb fix: sentry issue 4478125289 2023-09-23 11:47:24 +02:00
Andras Bacsai
1752448050 wip: services 2023-09-22 21:31:47 +02:00
Andras Bacsai
3fc78bc760 wip: services 2023-09-22 20:39:56 +02:00
Andras Bacsai
0c30b6222d fix: mappings 2023-09-22 15:49:59 +02:00
Andras Bacsai
3f74609c7f remove debug 2023-09-22 15:47:21 +02:00
Andras Bacsai
31b0ccba99 fix: prevent overwrite already existing env variables in services 2023-09-22 15:46:30 +02:00
Andras Bacsai
3fc544e0b9 feat: healthcheck for apps 2023-09-22 15:29:19 +02:00
Andras Bacsai
67078fdc71 wip: services
feat: able to map port<->domain
2023-09-22 14:47:25 +02:00
Andras Bacsai
c91f426af3 wip: services 2023-09-22 12:08:51 +02:00
Andras Bacsai
9c2fea4b2e fix: delete persistent storages on resource deletion 2023-09-22 11:34:27 +02:00
Andras Bacsai
53d1fa0331 wip: ui for services 2023-09-22 11:23:49 +02:00
Andras Bacsai
4ae7e46e81 fix: proxy connections 2023-09-22 08:52:07 +02:00
Andras Bacsai
ebfc0bd1e1 fix: add proxy to network with periodic check 2023-09-22 08:42:27 +02:00
Andras Bacsai
e1a1490911 wip 2023-09-21 21:30:13 +02:00
Andras Bacsai
6b75ff7de4 wip: services 2023-09-21 17:48:31 +02:00
Andras Bacsai
301469de6b fix: save proxy configuration 2023-09-21 11:03:52 +02:00
Andras Bacsai
b4d69a22df wip: services 2023-09-20 15:42:41 +02:00
Andras Bacsai
a86e971020 wip: services 2023-09-19 15:51:13 +02:00
Andras Bacsai
145af41c82 fix: delete environment variables on app/db delete 2023-09-19 14:08:20 +02:00
Andras Bacsai
69c0b7240a fix: add github app change on new app view 2023-09-19 13:30:17 +02:00
Andras Bacsai
6543132bcb test 7 days trial 2023-09-19 09:03:27 +02:00
Andras Bacsai
9134437218 version++ 2023-09-18 15:43:14 +02:00
Andras Bacsai
b7acf99cde Merge pull request #1238 from coollabsio/next
Fixes
2023-09-18 15:31:54 +02:00
Andras Bacsai
21d52d7846 oops 2023-09-18 15:23:23 +02:00
Andras Bacsai
ab5929cc69 typo 2023-09-18 15:20:48 +02:00
Andras Bacsai
1452cdf5ad fix: send internal notifications of email errors 2023-09-18 15:19:27 +02:00
Andras Bacsai
3eb1a1f48c debug with ray 2023-09-18 15:06:37 +02:00
Andras Bacsai
5f7a97c31f debug job 2023-09-18 15:04:50 +02:00
Andras Bacsai
9cba0a6df3 fix: boarding again 2023-09-18 14:41:31 +02:00
Andras Bacsai
af57b2aa73 pricing change 2023-09-18 13:42:35 +02:00
Andras Bacsai
b9b9582601 version++ 2023-09-18 13:14:48 +02:00
Andras Bacsai
c8ba98b93d fix: try to use old docker-compose 2023-09-18 13:14:05 +02:00
Andras Bacsai
a50e1e2f0c Merge pull request #1237 from coollabsio/next
Lots of fixes
2023-09-18 13:06:43 +02:00
Andras Bacsai
1093294f06 minimum toaster 2023-09-18 13:06:02 +02:00
Andras Bacsai
c023be2348 fix: improve localhost boarding process 2023-09-18 13:01:01 +02:00
Andras Bacsai
deece51e83 fix: stop/start UI on apps and dbs 2023-09-18 12:38:11 +02:00
Andras Bacsai
e2ab569244 version++ 2023-09-18 12:30:49 +02:00
Andras Bacsai
93b202bde4 fix: convert startProxy to action 2023-09-18 12:29:50 +02:00
Andras Bacsai
6bb6de188c Merge pull request #1236 from coollabsio/next
v4.0.0-beta.41
2023-09-18 12:26:00 +02:00
Andras Bacsai
61f58fa30f fix: proxy configuration saving 2023-09-18 12:18:45 +02:00
Andras Bacsai
026f3fd72d fix 2023-09-18 12:09:31 +02:00
Andras Bacsai
9a706d55b4 fix: proxy container status 2023-09-18 12:08:19 +02:00
Andras Bacsai
9646969107 fix: errors 2023-09-18 11:49:26 +02:00
Andras Bacsai
df433efe62 fix: boarding 2023-09-18 11:21:10 +02:00
Andras Bacsai
efa7cab3e2 fix: installDocker id not found 2023-09-18 10:44:32 +02:00
Andras Bacsai
a386a1bde5 fix: allow non ip address (DNS) 2023-09-18 10:41:19 +02:00
Andras Bacsai
cc1bf023be fix: do not remove localhost in boarding 2023-09-18 10:41:06 +02:00
Andras Bacsai
edb06fa233 version++ 2023-09-18 10:06:35 +02:00
Andras Bacsai
abdb8074b1 Merge pull request #1235 from coollabsio/next
4.0.0-beta.40
2023-09-18 10:05:52 +02:00
Andras Bacsai
157da798dd fix: proxy start (if not proxy defined, use Traefik) 2023-09-18 09:58:13 +02:00
Andras Bacsai
9c5501326e Merge pull request #1230 from coollabsio/next
4.0.0-beta.39
2023-09-16 16:53:17 +02:00
Andras Bacsai
3ea462efc9 fix: localhost 2023-09-16 16:49:33 +02:00
Andras Bacsai
f77df5b732 feat: sentry add email for better support 2023-09-15 21:13:50 +02:00
Andras Bacsai
b77074fe4e Merge pull request #1228 from coollabsio/next
v4.0.0-beta.38
2023-09-15 18:05:59 +02:00
Andras Bacsai
a7b7d3fa32 version++ 2023-09-15 18:02:55 +02:00
Andras Bacsai
f4f3034389 fix: 4.0.0-beta.37 2023-09-15 17:57:17 +02:00
Andras Bacsai
4b2ffb456f fix: team error 2023-09-15 17:30:26 +02:00
Andras Bacsai
e17ff99c5b fix: missing upgrade js 2023-09-15 15:50:37 +02:00
Andras Bacsai
1cf036bbc6 fix: generate new key 2023-09-15 15:39:25 +02:00
Andras Bacsai
da4c2ee60f fix: boarding
fix: error handling
fix: restarting state
2023-09-15 15:34:25 +02:00
Andras Bacsai
fcf7c5ddd5 fix: restarting container state on ui 2023-09-15 13:44:41 +02:00
Andras Bacsai
27926322e1 version++ 2023-09-15 13:06:27 +02:00
Andras Bacsai
c735ff545e Merge pull request #1226 from coollabsio/next
v4.0.0-beta.37
2023-09-15 12:44:50 +02:00
Andras Bacsai
b4f048b028 fixes 2023-09-15 12:43:03 +02:00
Andras Bacsai
54a57d217f fix: ssh-agent revert 2023-09-15 12:30:25 +02:00
Andras Bacsai
cf28490acc feat: generate ssh key 2023-09-15 11:55:58 +02:00
Andras Bacsai
019670d5d1 fix: smtp view 2023-09-15 11:28:44 +02:00
Andras Bacsai
b07cc500e7 fix: invitation 2023-09-15 11:19:36 +02:00
Andras Bacsai
82c235d5af add features to pricing 2023-09-14 23:32:58 +02:00
Andras Bacsai
307e4a6990 fix: collect billing address 2023-09-14 20:42:12 +02:00
Andras Bacsai
ddb7af63a6 fix 2023-09-14 20:39:33 +02:00
Andras Bacsai
7a429ee5bb fix: rate limit 2023-09-14 18:49:15 +02:00
Andras Bacsai
bb591604ac workflow update 2023-09-14 18:44:55 +02:00
Andras Bacsai
81f7a65dd5 fix: help 2023-09-14 18:41:21 +02:00
Andras Bacsai
4b313bb1c6 fix: simply reply to help messages 2023-09-14 18:33:05 +02:00
Andras Bacsai
aba0b2f13c remove unnecessary jobs 2023-09-14 18:29:54 +02:00
Andras Bacsai
9a284e47da version++ 2023-09-14 18:22:25 +02:00
Andras Bacsai
9f2fbc661a fix: registration
fix: user deletion
2023-09-14 18:22:08 +02:00
Andras Bacsai
949407368e fix: uniqueips 2023-09-14 18:10:13 +02:00
Andras Bacsai
93c65f6a79 fix: ip check 2023-09-14 18:07:29 +02:00
Andras Bacsai
d9fe16a3ee fix: redirect on server not found 2023-09-14 17:45:00 +02:00
Andras Bacsai
adaca4d4e3 fix: sub for root 2023-09-14 17:32:33 +02:00
Andras Bacsai
a6d5f3038c fix: help uri 2023-09-14 17:28:58 +02:00
Andras Bacsai
4d49132821 hm 2023-09-14 17:22:21 +02:00
Andras Bacsai
c287276d0e temporary fix for proxy 2023-09-14 17:10:37 +02:00
Andras Bacsai
e89868c692 fix: SaveConfigurationSync 2023-09-14 16:55:13 +02:00
Andras Bacsai
f93317cd2e Merge pull request #1224 from coollabsio/next
v4.0.0-beta.36
2023-09-14 16:28:52 +02:00
Andras Bacsai
8412802f4d oh wow, it is cool! 2023-09-14 15:52:04 +02:00
Andras Bacsai
53c20e1e99 feat: new container status checks 2023-09-14 12:45:50 +02:00
Andras Bacsai
3c8c8e20b1 fix: editable ip
fix: traefik dashboard
2023-09-14 11:37:20 +02:00
Andras Bacsai
49f8abcd79 remove unnecessary things 2023-09-14 10:56:25 +02:00
Andras Bacsai
4a4d73b87b fix: plus boarding step about Coolify 2023-09-14 10:39:05 +02:00
Andras Bacsai
046eab3776 fix: processWithEnv()->run 2023-09-14 10:26:48 +02:00
Andras Bacsai
17c0e91a0d feat: ssh-agent instead of filesystem based ssh keys 2023-09-14 10:12:58 +02:00
Andras Bacsai
fe4a0ae166 fix: encrypt jobs 2023-09-14 10:12:44 +02:00
Andras Bacsai
52c84f8d22 fix: lower case email on waitlist 2023-09-13 20:48:13 +02:00
Andras Bacsai
b22fecb615 fix: lowercase email in forgot password 2023-09-13 20:27:58 +02:00
Andras Bacsai
23b7fc3c54 fix: prevent weird ui bug for validateServer 2023-09-13 13:00:43 +02:00
Andras Bacsai
1efb1235b4 fix: add timeout for ssh commands 2023-09-13 13:00:16 +02:00
Andras Bacsai
0924070a13 version++ 2023-09-13 12:42:59 +02:00
Andras Bacsai
12fc5a8f91 Merge pull request #1223 from coollabsio/next
v4.0.0-beta.35
2023-09-13 12:34:45 +02:00
Andras Bacsai
9eba058cf7 fix: disable dockerfile based healtcheck for now 2023-09-13 12:08:44 +02:00
Andras Bacsai
fa4f5fea8c fix: forgot password 2023-09-13 11:29:31 +02:00
Andras Bacsai
898563fe7c email: server lost connection 2023-09-13 11:05:10 +02:00
Andras Bacsai
c418a17161 ui: show trial instead of sub 2023-09-12 17:24:46 +02:00
Andras Bacsai
cd0da04ea2 wip: server check instead of app check 2023-09-12 15:47:30 +02:00
Andras Bacsai
01e942c6a0 fix: show help modal everywhere 2023-09-12 15:06:07 +02:00
Andras Bacsai
bb9abafa82 test gh runner 2023-09-12 15:00:05 +02:00
Andras Bacsai
d0cd926517 fix: sub type 2023-09-12 14:53:54 +02:00
Andras Bacsai
9baf0161c7 fix: help should send cc on email 2023-09-12 14:51:35 +02:00
Andras Bacsai
8ba18b2ce1 fix: confirm email before sending 2023-09-12 13:19:55 +02:00
Andras Bacsai
ab021ee535 fix: server is functional check 2023-09-12 13:14:01 +02:00
Andras Bacsai
c76a1b1ba5 fix: webhooks should not run if server is not functional 2023-09-12 13:10:39 +02:00
Andras Bacsai
6266a5e500 internal: trial emails 2023-09-12 12:03:17 +02:00
Andras Bacsai
5d27e89bfa feat: dynamic trial period 2023-09-12 11:23:31 +02:00
Andras Bacsai
6da4e78374 feat: trial 2023-09-12 11:19:21 +02:00
Andras Bacsai
be30651172 fix: remove nixpkgarchive from ui 2023-09-12 09:46:10 +02:00
Andras Bacsai
95764c2b76 fix: remove nixpkgarchive 2023-09-12 09:45:20 +02:00
Andras Bacsai
92a75685b5 version plus plus 2023-09-11 23:06:24 +02:00
Andras Bacsai
a1592373aa Merge pull request #1222 from coollabsio/next
v4.0.0-beta.34
2023-09-11 23:05:30 +02:00
Andras Bacsai
5747a87f66 ui: fix 2023-09-11 22:55:23 +02:00
Andras Bacsai
9cda671aef Add email to user 2023-09-11 22:45:07 +02:00
Andras Bacsai
2c9983046c fix 2023-09-11 22:43:07 +02:00
Andras Bacsai
11d33f328e fix: queue after commit 2023-09-11 22:39:18 +02:00
Andras Bacsai
f79c741d95 prepare: global env variables 2023-09-11 22:29:47 +02:00
Andras Bacsai
8a39a4469a fix: proxy check, reduce jobs, etc 2023-09-11 22:29:34 +02:00
Andras Bacsai
42daae10c6 feat: able to invite more people at once 2023-09-11 20:51:31 +02:00
Andras Bacsai
64a65e2018 fix: errors 2023-09-11 17:36:30 +02:00
Andras Bacsai
16c71f3647 fix: old docker version error 2023-09-11 17:19:30 +02:00
Andras Bacsai
363f525ad1 fix: sentry 4469575117 2023-09-11 16:51:38 +02:00
Andras Bacsai
f4fb519d55 internal: add Plausible analytics 2023-09-11 16:41:43 +02:00
Andras Bacsai
b7786504b8 wip: nixpacksarchive 2023-09-11 15:53:05 +02:00
Andras Bacsai
f0adf10e6a version++ 2023-09-11 14:11:22 +02:00
Andras Bacsai
cc8c6c5d16 Merge pull request #1221 from coollabsio/next
UI/UX fixes
2023-09-11 13:59:19 +02:00
Andras Bacsai
4782446f42 ui: show registered users on waitlist page 2023-09-11 13:53:08 +02:00
Andras Bacsai
230155312f ui: services are not availble yet 2023-09-11 13:52:54 +02:00
Andras Bacsai
3ab4365fca ui: user should know that the public key 2023-09-11 13:42:32 +02:00
Andras Bacsai
7349068b95 Merge pull request #1220 from coollabsio/next
Email issues fix
2023-09-11 12:55:52 +02:00
Andras Bacsai
da6cc151d1 fix: email sending error 2023-09-11 12:31:31 +02:00
Andras Bacsai
50527cf0a3 fix: testing email
fix: expired invitiation link
2023-09-11 12:16:26 +02:00
Andras Bacsai
26f490bb00 Merge pull request #1219 from coollabsio/next
Fixes
2023-09-11 10:50:18 +02:00
Andras Bacsai
dc4f412227 fix: recovery code 2023-09-11 10:41:39 +02:00
Andras Bacsai
ec4234e243 fix: only send internal notifcations to enabled channels 2023-09-11 10:19:46 +02:00
Andras Bacsai
f47fcb01ce Merge pull request #1218 from coollabsio/next
feat: generate public key from private keys
2023-09-11 10:16:48 +02:00
Andras Bacsai
02f6673345 feat: generate public key from private keys 2023-09-11 10:15:45 +02:00
Andras Bacsai
e6cd8702b5 Merge pull request #1216 from coollabsio/next
v4.0.0-beta.32
2023-09-10 15:34:08 +02:00
Andras Bacsai
fda4ea8cca fix: errors in views 2023-09-10 15:33:00 +02:00
309 changed files with 7371 additions and 2743 deletions

View File

@@ -4,7 +4,7 @@ on:
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- docker/coolify-helper/Dockerfile
env:

View File

@@ -77,7 +77,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -13,7 +13,7 @@ env:
jobs:
amd64:
runs-on: ubuntu-latest
runs-on: [self-hosted, x64]
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io

View File

@@ -10,7 +10,7 @@ env:
jobs:
amd64:
runs-on: ubuntu-latest
runs-on: [self-hosted, x64]
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io

View File

@@ -5,14 +5,12 @@ namespace App\Actions\CoolifyTask;
use App\Enums\ActivityTypes;
use App\Enums\ProcessStatus;
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Server;
use Illuminate\Process\ProcessResult;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
use Spatie\Activitylog\Models\Activity;
const TIMEOUT = 3600;
const IDLE_TIMEOUT = 3600;
class RunRemoteProcess
{
public Activity $activity;
@@ -76,8 +74,7 @@ class RunRemoteProcess
$this->time_start = hrtime(true);
$status = ProcessStatus::IN_PROGRESS;
$processResult = Process::timeout(TIMEOUT)->idleTimeout(IDLE_TIMEOUT)->run($this->getCommand(), $this->handleOutput(...));
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
$status = ProcessStatus::ERROR;
@@ -97,9 +94,8 @@ class RunRemoteProcess
'status' => $status->value,
]);
$this->activity->save();
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput());
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
}
return $processResult;
@@ -107,13 +103,11 @@ class RunRemoteProcess
protected function getCommand(): string
{
$user = $this->activity->getExtraProperty('user');
$server_ip = $this->activity->getExtraProperty('server_ip');
$private_key_location = $this->activity->getExtraProperty('private_key_location');
$port = $this->activity->getExtraProperty('port');
$server_uuid = $this->activity->getExtraProperty('server_uuid');
$command = $this->activity->getExtraProperty('command');
$server = Server::whereUuid($server_uuid)->firstOrFail();
return generate_ssh_command($private_key_location, $server_ip, $user, $port, $command);
return generateSshCommand($server, $command);
}
protected function handleOutput(string $type, string $output)

View File

@@ -21,6 +21,7 @@ class StartPostgresql
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
];
@@ -96,6 +97,7 @@ class StartPostgresql
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $server);
}

View File

@@ -57,13 +57,13 @@ class CheckResaleLicense
throw new \Exception('Invalid license key.');
}
throw new \Exception('Cannot activate license key.');
} catch (\Throwable $th) {
ray($th);
} catch (\Throwable $e) {
ray($e);
$settings->update([
'resale_license' => null,
'is_resale_license_active' => false,
]);
throw $th;
throw $e;
}
}
}

View File

@@ -2,24 +2,27 @@
namespace App\Actions\Proxy;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use Illuminate\Support\Str;
class CheckConfigurationSync
class CheckConfiguration
{
public function __invoke(Server $server, bool $reset = false)
use AsAction;
public function handle(Server $server, bool $reset = false)
{
$proxy_path = get_proxy_path();
$proxy_configuration = instant_remote_process([
"mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml",
], $server, false);
if ($reset || is_null($proxy_configuration)) {
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
return $proxy_configuration;
}
if (!$proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception("Could not generate proxy configuration");
}
return $proxy_configuration;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
class SaveConfiguration
{
use AsAction;
public function handle(Server $server, ?string $proxy_settings = null)
{
if (is_null($proxy_settings)) {
$proxy_settings = CheckConfiguration::run($server, true);
}
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($proxy_settings);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
return instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
class SaveConfigurationSync
{
public function __invoke(Server $server, string $configuration)
{
try {
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
} catch (\Throwable $th) {
ray($th);
}
}
}

View File

@@ -2,49 +2,64 @@
namespace App\Actions\Proxy;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
use Spatie\Activitylog\Models\Activity;
class StartProxy
{
public function __invoke(Server $server): Activity
use AsAction;
public function handle(Server $server, bool $async = true): Activity|string
{
$proxy_path = get_proxy_path();
$networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network'];
})->unique();
if ($networks->count() === 0) {
$networks = collect(['coolify']);
$commands = collect([]);
$proxyType = $server->proxyType();
if ($proxyType === 'none') {
return 'OK';
}
$create_networks_command = $networks->map(function ($network) {
return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1";
});
$configuration = resolve(CheckConfigurationSync::class)($server);
$proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
$activity = remote_process([
"echo '####### Creating required Docker networks...'",
...$create_networks_command,
"cd $proxy_path",
"echo '####### Creating Docker Compose file...'",
"echo '####### Pulling docker image...'",
$commands = $commands->merge([
"apt-get update > /dev/null 2>&1 || true",
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
"command -v lsof >/dev/null || apt install -y lsof",
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
"mkdir -p $proxy_path && cd $proxy_path",
"echo '####### Creating Docker Compose file.'",
"echo '####### Pulling docker image.'",
'docker compose pull',
"echo '####### Stopping existing proxy...'",
'docker compose down -v --remove-orphans',
"lsof -nt -i:80 | xargs -r kill -9",
"lsof -nt -i:443 | xargs -r kill -9",
"echo '####### Starting proxy...'",
"echo '####### Stopping existing coolify-proxy.'",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
"systemctl disable nginx > /dev/null 2>&1 || true",
"systemctl disable apache2 > /dev/null 2>&1 || true",
"systemctl disable apache > /dev/null 2>&1 || true",
"echo '####### Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo '####### Proxy installed successfully...'"
], $server);
return $activity;
"echo '####### Proxy installed successfully.'"
]);
$commands = $commands->merge(connectProxyToNetworks($server));
if ($async) {
$activity = remote_process($commands, $server);
return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
return 'OK';
}
}
}

View File

@@ -4,11 +4,10 @@ namespace App\Actions\Server;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
class InstallDocker
{
public function __invoke(Server $server, Team $team)
public function __invoke(Server $server)
{
$dockerVersion = '24.0';
$config = base64_encode('{
@@ -19,15 +18,16 @@ class InstallDocker
}
}');
$found = StandaloneDocker::where('server_id', $server->id);
if ($found->count() == 0) {
if ($found->count() == 0 && $server->id) {
StandaloneDocker::create([
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $server->id,
]);
}
if (isDev()) {
return remote_process([
if (isDev() && $server->id === 0) {
$command = [
"echo '####### Installing Prerequisites...'",
"sleep 1",
"echo '####### Installing/updating Docker Engine...'",
@@ -35,9 +35,9 @@ class InstallDocker
"sleep 4",
"echo '####### Restarting Docker Engine...'",
"ls -l /tmp"
], $server);
];
} else {
return remote_process([
$command = [
"echo '####### Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update",
"command -v jq >/dev/null || apt install -y jq",
@@ -53,7 +53,8 @@ class InstallDocker
"echo '####### Creating default Docker network (coolify)...'",
"docker network create --attachable coolify >/dev/null 2>&1 || true",
"echo '####### Done!'"
], $server);
];
}
return remote_process($command, $server);
}
}

View File

@@ -16,8 +16,7 @@ class UpdateCoolify
try {
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$localhost_name = 'localhost';
$this->server = Server::where('name', $localhost_name)->first();
$this->server = Server::find(0)->first();
if (!$this->server) {
return;
}
@@ -43,11 +42,11 @@ class UpdateCoolify
$this->update();
}
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
} catch (\Exception $th) {
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($th->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $th->getMessage());
throw $th;
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
class StartService
{
use AsAction;
public function handle(Service $service)
{
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo '####### Creating Docker network.'";
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
$commands[] = "echo '####### Pulling images.'";
$commands[] = "docker compose pull";
$commands[] = "echo '####### Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
$activity = remote_process($commands, $service->server);
return $activity;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
class StopService
{
use AsAction;
public function handle(Service $service)
{
$applications = $service->applications()->get();
foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
$application->update(['status' => 'exited']);
}
$dbs = $service->databases()->get();
foreach ($dbs as $db) {
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
$db->update(['status' => 'exited']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
}
}

View File

@@ -0,0 +1,280 @@
<?php
namespace App\Console\Commands;
use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
use App\Notifications\Test;
use App\Notifications\TransactionalEmails\InvitationLink;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Mail;
use Illuminate\Support\Str;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
class Emails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'emails';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send out test / prod emails';
/**
* Execute the console command.
*/
private ?MailMessage $mail = null;
private ?string $email = null;
public function handle()
{
$type = select(
'Which Email should be sent?',
options: [
'updates' => 'Send Update Email to all users',
'emails-test' => 'Test',
'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed',
'application-status-changed' => 'Application - Status Changed',
'backup-success' => 'Database - Backup Success',
'backup-failed' => 'Database - Backup Failed',
// 'invitation-link' => 'Invitation Link',
'waitlist-invitation-link' => 'Waitlist Invitation Link',
'waitlist-confirmation' => 'Waitlist Confirmation',
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
],
);
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to');
}
set_transanctional_email_settings();
$this->mail = new MailMessage();
$this->mail->subject("Test Email");
switch ($type) {
case 'updates':
$teams = Team::all();
if (!$teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL;
return;
}
$emails = [];
foreach ($teams as $team) {
foreach ($team->members as $member) {
if ($member->email && $member->marketing_emails) {
$emails[] = $member->email;
}
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) {
$this->info($email);
}
$confirmed = confirm('Are you sure?');
if ($confirmed) {
foreach ($emails as $email) {
$this->mail = new MailMessage();
$this->mail->subject('One-click Services, Docker Compose support');
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email),
]);
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
$this->sendEmail($email);
}
}
break;
case 'emails-test':
$this->mail = (new Test())->toMail();
$this->sendEmail();
break;
case 'application-deployment-success':
$application = Application::all()->first();
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
$this->sendEmail();
break;
case 'application-deployment-failed':
$application = Application::all()->first();
$preview = ApplicationPreview::all()->first();
if (!$preview) {
$preview = ApplicationPreview::create([
'application_id' => $application->id,
'pull_request_id' => 1,
'pull_request_html_url' => 'http://example.com',
'fqdn' => $application->fqdn,
]);
}
$this->mail = (new DeploymentFailed($application, 'test'))->toMail();
$this->sendEmail();
$this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail();
$this->sendEmail();
break;
case 'application-status-changed':
$application = Application::all()->first();
$this->mail = (new StatusChanged($application))->toMail();
$this->sendEmail();
break;
case 'backup-failed':
$backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first();
if (!$backup) {
$backup = ScheduledDatabaseBackup::create([
'enabled' => true,
'frequency' => 'daily',
'save_s3' => false,
'database_id' => $db->id,
'database_type' => $db->getMorphClass(),
'team_id' => 0,
]);
}
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
$this->sendEmail();
break;
case 'backup-success':
$backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first();
if (!$backup) {
$backup = ScheduledDatabaseBackup::create([
'enabled' => true,
'frequency' => 'daily',
'save_s3' => false,
'database_id' => $db->id,
'database_type' => $db->getMorphClass(),
'team_id' => 0,
]);
}
$this->mail = (new BackupSuccess($backup, $db))->toMail();
$this->sendEmail();
break;
// case 'invitation-link':
// $user = User::all()->first();
// $invitation = TeamInvitation::whereEmail($user->email)->first();
// if (!$invitation) {
// $invitation = TeamInvitation::create([
// 'uuid' => Str::uuid(),
// 'email' => $user->email,
// 'team_id' => 1,
// 'link' => 'http://example.com',
// ]);
// }
// $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail();
// break;
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [
'loginLink' => 'https://coolify.io',
]);
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
$this->sendEmail();
break;
case 'waitlist-confirmation':
$found = Waitlist::where('email', $this->email)->first();
if ($found) {
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
} else {
throw new Exception('Waitlist not found');
}
break;
case 'realusers-before-trial':
$this->mail = new MailMessage();
$this->mail->view('emails.before-trial-conversion');
$this->mail->subject('Trial period has been added for all subscription plans.');
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
if (!$teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL;
return;
}
$emails = [];
foreach ($teams as $team) {
foreach ($team->members as $member) {
if ($member->email) {
$emails[] = $member->email;
}
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) {
$this->info($email);
}
$confirmed = confirm('Are you sure?');
if ($confirmed) {
foreach ($emails as $email) {
$this->sendEmail($email);
}
}
break;
case 'realusers-server-lost-connection':
$serverId = text('Server Id');
$server = Server::find($serverId);
if (!$server) {
throw new Exception('Server not found');
}
$admins = [];
$members = $server->team->members;
foreach ($members as $member) {
if ($member->isAdmin()) {
$admins[] = $member->email;
}
}
$this->info('Sending to ' . count($admins) . ' admins.');
foreach ($admins as $admin) {
$this->info($admin);
}
$this->mail = new MailMessage();
$this->mail->view('emails.server-lost-connection', [
'name' => $server->name,
]);
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
foreach ($admins as $email) {
$this->sendEmail($email);
}
break;
}
}
private function sendEmail(string $email = null)
{
if ($email) {
$this->email = $email;
}
Mail::send(
[],
[],
fn (Message $message) => $message
->to($this->email)
->subject($this->mail->subject)
->html((string)$this->mail->render())
);
$this->info("Email sent to $this->email successfully. 📧");
}
}

View File

@@ -26,7 +26,7 @@ class Init extends Command
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
}

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
use function Laravel\Prompts\confirm;
class SyncBunny extends Command
{
/**
@@ -14,7 +16,7 @@ class SyncBunny extends Command
*
* @var string
*/
protected $signature = 'sync:bunny';
protected $signature = 'sync:bunny {--only-template} {--only-version}';
/**
* The console command description.
@@ -28,6 +30,9 @@ class SyncBunny extends Command
*/
public function handle()
{
$that = $this;
$only_template = $this->option('only-template');
$only_version = $this->option('only-version');
$bunny_cdn = "https://cdn.coollabs.io";
$bunny_cdn_path = "coolify";
$bunny_cdn_storage_name = "coolcdn";
@@ -39,51 +44,71 @@ class SyncBunny extends Command
$install_script = "install.sh";
$upgrade_script = "upgrade.sh";
$production_env = ".env.production";
$service_template = "service-templates.json";
$versions = "versions.json";
PendingRequest::macro('storage', function ($file) {
PendingRequest::macro('storage', function ($fileName) use($that) {
$headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream'
];
$fileStream = fopen($file, "r");
$file = fread($fileStream, filesize($file));
$fileStream = fopen($fileName, "r");
$file = fread($fileStream, filesize($fileName));
$that->info('Uploading: ' . $fileName);
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
});
PendingRequest::macro('purge', function ($url) {
PendingRequest::macro('purge', function ($url) use ($that) {
$headers = [
'AccessKey' => env('BUNNY_API_KEY'),
'Accept' => 'application/json',
];
ray('Purging: ' . $url);
$that->info('Purging: ' . $url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
"url" => $url,
"async" => false
]);
});
try {
$confirmed = confirm('Are you sure you want to sync?');
if (!$confirmed) {
return;
}
if ($only_template) {
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]);
$this->info('Service template uploaded & purged...');
return;
}
if ($only_version) {
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
$this->info('versions.json uploaded & purged...');
return;
}
Http::pool(fn (Pool $pool) => [
$pool->storage(file: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(file: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(file: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(file: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(file: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
$pool->storage(file: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
]);
ray("{$bunny_cdn}/{$bunny_cdn_path}");
Http::pool(fn (Pool $pool) => [
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
echo "All files uploaded & purged...\n";
} catch (\Exception $e) {
echo $e->getMessage();
$this->info("All files uploaded & purged...");
} catch (\Throwable $e) {
$this->error("Error: " . $e->getMessage());
}
}
}

View File

@@ -1,182 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
use App\Notifications\Test;
use App\Notifications\TransactionalEmails\InvitationLink;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Mail;
use Str;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
class TestEmail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'email:test';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a test email to the admin';
/**
* Execute the console command.
*/
private ?MailMessage $mail = null;
private string $email = 'andras.bacsai@protonmail.com';
public function handle()
{
$type = select(
'Which Email should be sent?',
options: [
'emails-test' => 'Test',
'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed',
'application-status-changed' => 'Application - Status Changed',
'backup-success' => 'Database - Backup Success',
'backup-failed' => 'Database - Backup Failed',
'invitation-link' => 'Invitation Link',
'waitlist-invitation-link' => 'Waitlist Invitation Link',
'waitlist-confirmation' => 'Waitlist Confirmation',
],
);
$this->email = text('Email Address to send to');
set_transanctional_email_settings();
$this->mail = new MailMessage();
$this->mail->subject("Test Email");
switch ($type) {
case 'emails-test':
$this->mail = (new Test())->toMail();
$this->sendEmail();
break;
case 'application-deployment-success':
$application = Application::all()->first();
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
$this->sendEmail();
break;
case 'application-deployment-failed':
$application = Application::all()->first();
$preview = ApplicationPreview::all()->first();
if (!$preview) {
$preview = ApplicationPreview::create([
'application_id' => $application->id,
'pull_request_id' => 1,
'pull_request_html_url' => 'http://example.com',
'fqdn' => $application->fqdn,
]);
}
$this->mail = (new DeploymentFailed($application, 'test'))->toMail();
$this->sendEmail();
$this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail();
$this->sendEmail();
break;
case 'application-status-changed':
$application = Application::all()->first();
$this->mail = (new StatusChanged($application))->toMail();
$this->sendEmail();
break;
case 'backup-failed':
$backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first();
if (!$backup) {
$backup = ScheduledDatabaseBackup::create([
'enabled' => true,
'frequency' => 'daily',
'save_s3' => false,
'database_id' => $db->id,
'database_type' => $db->getMorphClass(),
'team_id' => 0,
]);
}
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
$this->sendEmail();
break;
case 'backup-success':
$backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first();
if (!$backup) {
$backup = ScheduledDatabaseBackup::create([
'enabled' => true,
'frequency' => 'daily',
'save_s3' => false,
'database_id' => $db->id,
'database_type' => $db->getMorphClass(),
'team_id' => 0,
]);
}
$this->mail = (new BackupSuccess($backup, $db))->toMail();
$this->sendEmail();
break;
case 'invitation-link':
$user = User::all()->first();
$invitation = TeamInvitation::whereEmail($user->email)->first();
if (!$invitation) {
$invitation = TeamInvitation::create([
'uuid' => Str::uuid(),
'email' => $user->email,
'team_id' => 1,
'link' => 'http://example.com',
]);
}
$this->mail = (new InvitationLink($user))->toMail();
$this->sendEmail();
break;
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [
'email' => 'test2@example.com',
'password' => "supersecretpassword",
]);
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
$this->sendEmail();
break;
case 'waitlist-confirmation':
$this->mail = new MailMessage();
$this->mail->view(
'emails.waitlist-confirmation',
[
'confirmation_url' => 'http://example.com',
'cancel_url' => 'http://example.com',
]
);
$this->mail->subject('You are on the waitlist!');
$this->sendEmail();
break;
}
}
private function sendEmail()
{
Mail::send(
[],
[],
fn (Message $message) => $message
->to($this->email)
->subject($this->mail->subject)
->html((string)$this->mail->render())
);
}
}

View File

@@ -19,7 +19,7 @@ class WaitlistInvite extends Command
*
* @var string
*/
protected $signature = 'waitlist:invite {email?} {--only-email}';
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
/**
* The console command description.
@@ -33,6 +33,12 @@ class WaitlistInvite extends Command
*/
public function handle()
{
$people = $this->option('people');
for ($i = 0; $i < $people; $i++) {
$this->main();
}
}
private function main() {
if ($this->argument('email')) {
if ($this->option('only-email')) {
$this->next_patient = User::whereEmail($this->argument('email'))->first();
@@ -86,13 +92,10 @@ class WaitlistInvite extends Command
}
private function send_email()
{
ray($this->next_patient->email, $this->password);
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
$loginLink = route('auth.link', ['token' => $token]);
$mail = new MailMessage();
$mail->view('emails.waitlist-invitation', [
'email' => $this->next_patient->email,
'password' => $this->password,
'loginLink' => $loginLink,
]);
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');

View File

@@ -2,19 +2,15 @@
namespace App\Console;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DatabaseContainerStatusJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ProxyCheckJob;
use App\Jobs\ResourceStatusJob;
use App\Models\Application;
use App\Jobs\ContainerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\StandalonePostgresql;
use App\Models\Server;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -23,37 +19,42 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
if (isDev()) {
$schedule->command('horizon:snapshot')->everyMinute();
// $schedule->job(new ResourceStatusJob)->everyMinute();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
// $schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly();
$schedule->job(new DockerCleanupJob)->everyOddHour();
// $schedule->job(new DockerCleanupJob)->everyOddHour();
// $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
// $schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
// $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
}
}
private function cleanup_servers($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
}
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
}
private function check_resources($schedule)
{
$applications = Application::all();
foreach ($applications as $application) {
$schedule->job(new ApplicationContainerStatusJob($application))->everyMinute()->onOneServer();
}
$postgresqls = StandalonePostgresql::all();
foreach ($postgresqls as $postgresql) {
$schedule->job(new DatabaseContainerStatusJob($postgresql))->everyMinute()->onOneServer();
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule){
private function instance_auto_update($schedule)
{
if (isDev()) {
return;
}
@@ -74,7 +75,7 @@ class Kernel extends ConsoleKernel
if (!$scheduled_backup->enabled) {
continue;
}
if (is_null(data_get($scheduled_backup,'database'))) {
if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found');
$scheduled_backup->delete();
continue;

View File

@@ -12,16 +12,16 @@ use Spatie\LaravelData\Data;
class CoolifyTaskArgs extends Data
{
public function __construct(
public string $server_ip,
public string $private_key_location,
public string $server_uuid,
public string $command,
public int $port,
public string $user,
public string $type,
public ?string $type_uuid = null,
public ?Model $model = null,
public string $status = ProcessStatus::QUEUED->value,
public ?string $status = null ,
public bool $ignore_errors = false,
) {
if(is_null($status)){
$this->status = ProcessStatus::QUEUED->value;
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Exceptions;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Sentry\Laravel\Integration;
use Sentry\State\Scope;
@@ -25,7 +26,7 @@ class Handler extends ExceptionHandler
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
//
ProcessException::class
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
@@ -45,13 +46,23 @@ class Handler extends ExceptionHandler
public function register(): void
{
$this->reportable(function (Throwable $e) {
if (isDev()) {
return;
}
$this->settings = InstanceSettings::get();
if ($this->settings->do_not_track || isDev()) {
if ($this->settings->do_not_track) {
return;
}
app('sentry')->configureScope(
function (Scope $scope){
$scope->setUser(['id'=> config('sentry.server_name')]);
function (Scope $scope) {
$email = auth()?->user() ? auth()->user()->email : 'guest';
$instanceAdmin = User::find(0)->email ?? 'admin@localhost';
$scope->setUser(
[
'email' => $email,
'instanceAdmin' => $instanceAdmin
]
);
}
);
Integration::captureUnhandledException($e);

View File

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

View File

@@ -3,21 +3,17 @@
namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Throwable;
use Str;
use Illuminate\Support\Str;
class Controller extends BaseController
{
@@ -35,8 +31,15 @@ class Controller extends BaseController
return redirect()->route('login');
}
if (Hash::check($password, $user->password)) {
$invitation = TeamInvitation::whereEmail($email);
if ($invitation->exists()) {
$team = $invitation->first()->team;
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
$invitation->delete();
} else {
$team = $user->teams()->first();
}
Auth::login($user);
$team = $user->teams()->first();
session(['currentTeam' => $team]);
return redirect()->route('dashboard');
}
@@ -137,25 +140,21 @@ class Controller extends BaseController
try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
if (is_null(auth()->user())) {
return redirect()->route('login');
}
if (auth()->user()->id !== $user->id) {
abort(401);
}
$createdAt = $invitation->created_at;
$diff = $createdAt->diffInMinutes(now());
if ($diff <= config('constants.invitation.link.expiration')) {
$invitationValid = $invitation->isValid();
if ($invitationValid) {
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
refreshSession($invitation->team);
$invitation->delete();
return redirect()->route('team.index');
} else {
$invitation->delete();
abort(401);
}
} catch (Throwable $th) {
throw $th;
} catch (\Throwable $e) {
ray($e->getMessage());
throw $e;
}
}
@@ -172,8 +171,8 @@ class Controller extends BaseController
}
$invitation->delete();
return redirect()->route('team.index');
} catch (Throwable $th) {
throw $th;
} catch (\Throwable $e) {
throw $e;
}
}
}

View File

@@ -2,8 +2,12 @@
namespace App\Http\Controllers;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ProjectController extends Controller
{
@@ -41,9 +45,10 @@ class ProjectController extends Controller
public function new()
{
$type = request()->query('type');
$services = getServiceTemplates();
$type = Str::of(request()->query('type'));
$destination_uuid = request()->query('destination');
$server = requesT()->query('server');
$server_id = request()->query('server_id');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
@@ -61,8 +66,71 @@ class ProjectController extends Controller
'database_uuid' => $standalone_postgresql->uuid,
]);
}
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
ray($oneClickServiceName);
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
}
if ($oneClickService) {
$service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = Str::before($value, '=');
$value = Str::of(Str::after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
switch ($command->value()) {
case 'PASSWORD':
$generatedValue = Str::password(symbols: false);
break;
case 'PASSWORD_64':
$generatedValue = Str::password(length: 64, symbols: false);
break;
case 'BASE64_64':
$generatedValue = Str::random(64);
break;
case 'BASE64_128':
$generatedValue = Str::random(128);
break;
case 'BASE64':
$generatedValue = Str::random(32);
break;
case 'USER':
$generatedValue = Str::random(16);
break;
}
}
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
}
return view('project.new', [
'type' => $type
'type' => $type->value()
]);
}

View File

@@ -14,6 +14,7 @@ class Index extends Component
{
public string $currentState = 'welcome';
public ?string $selectedServerType = null;
public ?Collection $privateKeys = null;
public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null;
@@ -36,6 +37,11 @@ class Index extends Component
public ?int $selectedExistingProject = null;
public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false;
public string $serverPublicKey;
public bool $serverReachable = true;
public function mount()
{
$this->privateKeyName = generate_random_name();
@@ -53,20 +59,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerHost = 'coolify-testing-host';
}
}
public function welcome() {
public function explanation()
{
if (isCloud()) {
return $this->setServerType('remote');
}
$this->currentState = 'select-server-type';
}
public function restartBoarding()
{
if ($this->createdServer) {
$this->createdServer->delete();
}
if ($this->createdPrivateKey) {
$this->createdPrivateKey->delete();
}
return redirect()->route('boarding');
}
public function skipBoarding()
@@ -81,13 +83,15 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function setServerType(string $type)
{
if ($type === 'localhost') {
$this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0);
if (!$this->createdServer) {
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
}
return $this->validateServer();
} elseif ($type === 'remote') {
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
return $this->validateServer('localhost');
} elseif ($this->selectedServerType === 'remote') {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if ($this->privateKeys->count() > 0) {
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
@@ -110,11 +114,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
return;
}
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->validateServer();
$this->getProxyType();
$this->getProjects();
}
public function getProxyType() {
public function getProxyType()
{
$proxyTypeSet = $this->createdServer->proxy->type;
if (!$proxyTypeSet) {
$this->currentState = 'select-proxy';
@@ -124,6 +128,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
public function selectExistingPrivateKey()
{
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server';
}
public function createNewServer()
@@ -135,7 +141,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->selectedExistingPrivateKey = null;
$this->privateKeyType = $type;
if ($type === 'create' && !isDev()) {
if ($type === 'create') {
$this->createNewPrivateKey();
}
$this->currentState = 'create-private-key';
@@ -146,6 +152,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'privateKeyName' => 'required',
'privateKey' => 'required',
]);
$this->createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$this->createdPrivateKey->save();
$this->currentState = 'create-server';
}
public function saveServer()
@@ -153,16 +166,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->validate([
'remoteServerName' => 'required',
'remoteServerHost' => 'required',
'remoteServerPort' => 'required',
'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required',
]);
$this->privateKey = formatPrivateKey($this->privateKey);
$this->createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$foundServer = Server::whereIp($this->remoteServerHost)->first();
if ($foundServer) {
return $this->emit('error', 'IP address is already in use by another team.');
}
$this->createdServer = Server::create([
'name' => $this->remoteServerName,
'ip' => $this->remoteServerHost,
@@ -170,38 +181,52 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'user' => $this->remoteServerUser,
'description' => $this->remoteServerDescription,
'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id
'team_id' => currentTeam()->id,
]);
$this->createdServer->save();
$this->validateServer();
}
public function validateServer() {
public function validateServer()
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
if (!$uptime) {
throw new \Exception('Server is not reachable.');
} else {
$this->createdServer->settings->update([
'is_reachable' => true,
]);
$this->emit('success', 'Server is reachable.');
}
ray($dockerVersion, $uptime);
if (!$dockerVersion) {
$this->emit('error', 'Docker is not installed on the server.');
$this->currentState = 'install-docker';
return;
}
$this->getProxyType();
$customErrorMessage = "Server is not reachable:";
config()->set('coolify.mux_enabled', false);
} catch (\Exception $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
instant_remote_process(['uptime'], $this->createdServer, true);
$this->createdServer->settings()->update([
'is_reachable' => true,
]);
} catch (\Throwable $e) {
$this->serverReachable = false;
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
}
try {
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->currentState = 'install-docker';
throw new \Exception('Docker version is not supported or not installed.');
}
$this->createdServer->settings()->update([
'is_usable' => true,
]);
$this->getProxyType();
} catch (\Throwable $e) {
$this->dockerInstallationStarted = false;
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
}
}
public function installDocker()
{
$activity = resolve(InstallDocker::class)($this->createdServer, currentTeam());
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer);
$this->emit('newMonitorActivity', $activity->id);
$this->currentState = 'select-proxy';
}
public function dockerInstalledOrSkipped()
{
$this->validateServer();
}
public function selectProxy(string|null $proxyType = null)
{
@@ -214,14 +239,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->getProjects();
}
public function getProjects() {
public function getProjects()
{
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id;
}
$this->currentState = 'create-project';
}
public function selectExistingProject() {
public function selectExistingProject()
{
$this->createdProject = Project::find($this->selectedExistingProject);
$this->currentState = 'create-resource';
}
@@ -241,7 +268,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
[
'project_uuid' => $this->createdProject->uuid,
'environment_name' => 'production',
'server'=> $this->createdServer->id,
'server' => $this->createdServer->id,
]
);
}

View File

@@ -34,9 +34,9 @@ class CheckLicense extends Component
try {
resolve(CheckResaleLicense::class)();
$this->emit('reloadWindow');
} catch (\Throwable $th) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $th->getMessage());
ray($th->getMessage());
} catch (\Throwable $e) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
ray($e->getMessage());
return redirect()->to('/settings/license');
}
}

View File

@@ -37,8 +37,8 @@ class Form extends Component
}
$this->destination->delete();
return redirect()->route('dashboard');
} catch (\Exception $e) {
return general_error_handler(err: $e);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -71,14 +71,14 @@ class StandaloneDocker extends Component
}
$this->createNetworkAndAttachToProxy();
return redirect()->route('destination.show', $docker->uuid);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function createNetworkAndAttachToProxy()
{
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
}

View File

@@ -36,8 +36,8 @@ class ForcePasswordReset extends Component
send_internal_notification('First login for ' . auth()->user()->email);
}
return redirect()->route('dashboard');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -4,8 +4,8 @@ namespace App\Http\Livewire;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
use Route;
class Help extends Component
{
@@ -19,7 +19,7 @@ class Help extends Component
];
public function mount()
{
$this->path = Route::current()->uri();
$this->path = Route::current()?->uri() ?? null;
if (isDev()) {
$this->description = "I'm having trouble with {$this->path}";
$this->subject = "Help with {$this->path}";
@@ -28,9 +28,9 @@ class Help extends Component
public function submit()
{
try {
$this->rateLimit(1, 60);
$this->rateLimit(3, 60);
$this->validate();
$subscriptionType = auth()->user()?->subscription?->type() ?? 'unknown';
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
$debug = "Route: {$this->path}";
$mail = new MailMessage();
$mail->view(
@@ -41,10 +41,10 @@ class Help extends Component
]
);
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
} catch (\Exception $e) {
return general_error_handler($e, $this);
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()

View File

@@ -29,7 +29,7 @@ class DiscordSettings extends Component
{
try {
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
ray($e->getMessage());
$this->team->discord_enabled = false;
$this->validate();
@@ -46,9 +46,7 @@ class DiscordSettings extends Component
public function saveModel()
{
$this->team->save();
if (is_a($this->team, Team::class)) {
refreshSession();
}
refreshSession();
$this->emit('success', 'Settings saved.');
}

View File

@@ -62,9 +62,10 @@ class EmailSettings extends Component
'team.smtp_from_name' => 'required',
]);
$this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function sendTestNotification()
@@ -81,9 +82,10 @@ class EmailSettings extends Component
$this->team->smtp_enabled = false;
$this->team->resend_enabled = false;
$this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -92,9 +94,9 @@ class EmailSettings extends Component
try {
$this->team->smtp_enabled = false;
$this->submitResend();
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return general_error_handler($e, $this);
return handleError($e, $this);
}
}
public function instantSave()
@@ -102,17 +104,15 @@ class EmailSettings extends Component
try {
$this->team->resend_enabled = false;
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return general_error_handler($e, $this);
return handleError($e, $this);
}
}
public function saveModel()
{
$this->team->save();
if (is_a($this->team, Team::class)) {
refreshSession();
}
refreshSession();
$this->emit('success', 'Settings saved.');
}
public function submit()
@@ -130,10 +130,11 @@ class EmailSettings extends Component
'team.smtp_timeout' => 'nullable',
]);
$this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return general_error_handler($e, $this);
return handleError($e, $this);
}
}
public function submitResend()
@@ -141,14 +142,16 @@ class EmailSettings extends Component
try {
$this->resetErrorBag();
$this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
'team.resend_api_key' => 'required'
]);
$this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->resend_enabled = false;
return general_error_handler($e, $this);
return handleError($e, $this);
}
}
public function copyFromInstanceSettings()

View File

@@ -35,7 +35,7 @@ class TelegramSettings extends Component
{
try {
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
ray($e->getMessage());
$this->team->telegram_enabled = false;
$this->validate();
@@ -52,9 +52,7 @@ class TelegramSettings extends Component
public function saveModel()
{
$this->team->save();
if (is_a($this->team, Team::class)) {
refreshSession();
}
refreshSession();
$this->emit('success', 'Settings saved.');
}

View File

@@ -8,7 +8,7 @@ use Livewire\Component;
class Change extends Component
{
public PrivateKey $private_key;
public $public_key;
protected $rules = [
'private_key.name' => 'required|string',
'private_key.description' => 'nullable|string',
@@ -21,6 +21,14 @@ class Change extends Component
'private_key.private_key' => 'private key'
];
public function mount()
{
try {
$this->public_key = $this->private_key->publicKey();
}catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function delete()
{
try {
@@ -30,8 +38,8 @@ class Change extends Component
return redirect()->route('security.private-key.index');
}
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -41,8 +49,8 @@ class Change extends Component
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
$this->private_key->save();
refresh_server_connection($this->private_key);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -3,14 +3,20 @@
namespace App\Http\Livewire\PrivateKey;
use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component;
use phpseclib3\Crypt\PublicKeyLoader;
class Create extends Component
{
public string|null $from = null;
use WithRateLimiting;
public string $name;
public string|null $description = null;
public string $value;
public ?string $from = null;
public ?string $description = null;
public ?string $publicKey = null;
protected $rules = [
'name' => 'required|string',
'value' => 'required|string',
@@ -20,6 +26,32 @@ class Create extends Component
'value' => 'private Key',
];
public function generateNewKey()
{
try {
$this->rateLimit(10);
$this->name = generate_random_name();
$this->description = 'Created by Coolify';
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function updated($updateProperty)
{
if ($updateProperty === 'value') {
try {
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
} catch (\Throwable $e) {
if ($this->$updateProperty === "") {
$this->publicKey = "";
} else {
$this->publicKey = "Invalid private key";
}
}
}
$this->validateOnly($updateProperty);
}
public function createPrivateKey()
{
$this->validate();
@@ -38,8 +70,8 @@ class Create extends Component
return redirect()->route('server.create');
}
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -34,7 +34,7 @@ class Form extends Component
'name' => $this->name,
]);
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
}

View File

@@ -28,8 +28,8 @@ class AddEmpty extends Component
'team_id' => currentTeam()->id,
]);
return redirect()->route('project.show', $project->uuid);
} catch (\Exception $e) {
general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->name = '';
}

View File

@@ -31,8 +31,8 @@ class AddEnvironment extends Component
'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name,
]);
} catch (\Exception $e) {
general_error_handler($e, $this);
} catch (\Throwable $e) {
handleError($e, $this);
} finally {
$this->name = '';
}

View File

@@ -65,7 +65,7 @@ class DeploymentNavbar extends Component
]);
}
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
}

View File

@@ -4,24 +4,24 @@ namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use App\Models\InstanceSettings;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
class General extends Component
{
public string $applicationId;
public Application $application;
public Collection $services;
public string $name;
public string|null $fqdn;
public string $git_repository;
public string $git_branch;
public string|null $git_commit_sha;
public string $build_pack;
public string|null $wildcard_domain = null;
public string|null $server_wildcard_domain = null;
public string|null $global_wildcard_domain = null;
public bool $is_static;
public bool $is_git_submodules_enabled;
@@ -31,6 +31,7 @@ class General extends Component
public bool $is_auto_deploy_enabled;
public bool $is_force_https_enabled;
protected $rules = [
'application.name' => 'required',
'application.description' => 'nullable',
@@ -66,6 +67,7 @@ class General extends Component
'application.ports_exposes' => 'Ports exposes',
'application.ports_mappings' => 'Ports mappings',
'application.dockerfile' => 'Dockerfile',
];
public function instantSave()
@@ -87,17 +89,19 @@ class General extends Component
$this->application->save();
$this->application->refresh();
$this->emit('success', 'Application settings updated!');
$this->checkWildCardDomain();
}
protected function checkWildCardDomain()
{
$coolify_instance_settings = InstanceSettings::get();
$this->server_wildcard_domain = data_get($this->application, 'destination.server.settings.wildcard_domain');
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain');
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null;
}
public function getWildcardDomain() {
$server = data_get($this->application, 'destination.server');
if ($server) {
$fqdn = generateFqdn($server, $this->application->uuid);
ray($fqdn);
$this->application->fqdn = $fqdn;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
}
public function mount()
{
$this->is_static = $this->application->settings->is_static;
@@ -107,45 +111,19 @@ class General extends Component
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
$this->checkWildCardDomain();
}
public function generateGlobalRandomDomain()
{
// Set wildcard domain based on Global wildcard domain
$url = Url::fromString($this->global_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function generateServerRandomDomain()
{
// Set wildcard domain based on Server wildcard domain
$url = Url::fromString($this->server_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function submit()
{
ray($this->application);
try {
$this->validate();
if (data_get($this->application,'fqdn')) {
if (data_get($this->application, 'fqdn')) {
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return Str::of($domain)->trim()->lower();
});
$this->application->fqdn = $domains->implode(',');
}
if ($this->application->dockerfile) {
if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port) {
$this->application->ports_exposes = $port;
@@ -159,8 +137,8 @@ class General extends Component
}
$this->application->save();
$this->emit('success', 'Application settings updated!');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,9 +2,8 @@
namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -22,10 +21,11 @@ class Heading extends Component
public function check_status()
{
dispatch_sync(new ApplicationContainerStatusJob(
application: $this->application,
));
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
}
public function force_deploy_without_cache()
@@ -64,14 +64,15 @@ class Heading extends Component
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
remote_process(
instant_remote_process(
["docker rm -f {$containerName}"],
$this->application->destination->server
);
$this->application->status = 'stopped';
$this->application->status = 'exited';
$this->application->save();
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
}
}
$this->application->refresh();
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Support\Collection;
@@ -23,14 +22,6 @@ class Previews extends Component
$this->parameters = get_route_parameters();
}
public function loadStatus($pull_request_id)
{
dispatch(new ApplicationContainerStatusJob(
application: $this->application,
pullRequestId: $pull_request_id
));
}
public function load_prs()
{
try {
@@ -39,7 +30,7 @@ class Previews extends Component
$this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $e) {
$this->rate_limit_remaining = 0;
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
@@ -68,7 +59,7 @@ class Previews extends Component
'environment_name' => $this->parameters['environment_name'],
]);
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
@@ -81,14 +72,14 @@ class Previews extends Component
public function stop(int $pull_request_id)
{
try {
$container_name = generateApplicationContainerName($this->application->uuid, $pull_request_id);
$container_name = generateApplicationContainerName($this->application);
ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
$this->application->refresh();
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}

View File

@@ -65,7 +65,7 @@ class Rollback extends Component
];
})->toArray();
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
}

View File

@@ -51,7 +51,7 @@ class BackupEdit extends Component
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->emit('error', $e->getMessage());
}
}
@@ -76,7 +76,7 @@ class BackupEdit extends Component
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->emit('error', $e->getMessage());
}
}

View File

@@ -42,8 +42,8 @@ class CreateScheduledBackup extends Component
'team_id' => currentTeam()->id,
]);
$this->emit('refreshScheduledBackups');
} catch (\Exception $e) {
general_error_handler($e, $this);
} catch (\Throwable $e) {
handleError($e, $this);
} finally {
$this->frequency = '';
$this->save_s3 = true;

View File

@@ -3,8 +3,7 @@
namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartPostgresql;
use App\Jobs\DatabaseContainerStatusJob;
use App\Notifications\Application\StatusChanged;
use App\Jobs\ContainerStatusJob;
use Livewire\Component;
class Heading extends Component
@@ -25,9 +24,7 @@ class Heading extends Component
public function check_status()
{
dispatch_sync(new DatabaseContainerStatusJob(
database: $this->database,
));
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
$this->database->refresh();
}
@@ -38,7 +35,7 @@ class Heading extends Component
public function stop()
{
remote_process(
instant_remote_process(
["docker rm -f {$this->database->uuid}"],
$this->database->destination->server
);
@@ -46,9 +43,9 @@ class Heading extends Component
stopPostgresProxy($this->database);
$this->database->is_public = false;
}
$this->database->status = 'stopped';
$this->database->status = 'exited';
$this->database->save();
$this->emit('refresh');
$this->check_status();
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
}

View File

@@ -36,7 +36,7 @@ class InitScript extends Component
$this->script['filename'] = $this->filename;
$this->emitUp('save_init_script', $this->script);
} catch (Exception $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Models\StandalonePostgresql;
use Exception;
use Livewire\Component;
use function Aws\filter;
class General extends Component
@@ -73,9 +74,9 @@ class General extends Component
}
$this->getDbUrl();
$this->database->save();
} catch(Exception $e) {
} catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
@@ -140,7 +141,7 @@ class General extends Component
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
}

View File

@@ -19,8 +19,8 @@ class Edit extends Component
try {
$this->project->save();
$this->emit('saved');
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Service;
use Livewire\Component;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class DockerCompose extends Component
{
public string $dockerComposeRaw = '';
public string $envFile = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
$this->dockerComposeRaw = 'services:
ghost:
image: ghost:5
volumes:
- ~/configs:/etc/configs/:ro
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
- /var/lib/ghost/content:/tmp/ghost/content:rw
- ghost-content-data:/var/lib/ghost/content
- type: volume
source: mydata
target: /data
volume:
nocopy: true
- type: bind
source: ./var/lib/ghost/data
target: /data
- type: bind
source: /tmp
target: /tmp
labels:
- "test.label=true"
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
- "127.0.0.1::5000"
- "6060:6060/udp"
- "12400-12500:1240"
- target: 80
published: 8080
protocol: tcp
mode: host
networks:
- some-network
- other-network
environment:
- database__client=${DATABASE_CLIENT:-mysql}
- database__connection__database=${MYSQL_DATABASE:-ghost}
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
- test=${TEST:?true}
- url=$SERVICE_FQDN_GHOST
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
depends_on:
- mysql
mysql:
image: mysql:8.0
volumes:
- ghost-mysql-data:/var/lib/mysql
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=$MYSQL_DATABASE
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
- SESSION_SECRET
minio:
image: minio/minio
environment:
RACK_ENV: development
A: $A
SHOW: ${SHOW}
SHOW1: ${SHOW2-show1}
SHOW2: ${SHOW3:-show2}
SHOW3: ${SHOW4?show3}
SHOW4: ${SHOW5:?show4}
SHOW5: ${SERVICE_USER_MINIO}
SHOW6: ${SERVICE_PASSWORD_MINIO}
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
SHOW8: ${SERVICE_BASE64_64_MINIO}
SHOW9: ${SERVICE_BASE64_128_MINIO}
SHOW10: ${SERVICE_BASE64_MINIO}
SHOW11:
';
}
}
public function submit()
{
try {
$this->validate([
'dockerComposeRaw' => 'required'
]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$server_id = $this->query['server_id'];
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$service = Service::create([
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) {
EnvironmentVariable::create([
'key' => $key,
'value' => $variable,
'is_build_time' => false,
'is_preview' => false,
'service_id' => $service->id,
]);
}
$service->name = "service-$service->uuid";
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -9,8 +9,9 @@ use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use App\Traits\SaveFromRedirect;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
use Route;
use Spatie\Url\Url;
class GithubPrivateRepository extends Component
{
@@ -40,21 +41,6 @@ class GithubPrivateRepository extends Component
public string|null $publish_directory = null;
protected int $page = 1;
// public function saveFromRedirect(string $route, ?Collection $parameters = null){
// session()->forget('from');
// if (!$parameters || $parameters->count() === 0) {
// $parameters = $this->parameters;
// }
// $parameters = collect($parameters) ?? collect([]);
// $queries = collect($this->query) ?? collect([]);
// $parameters = $parameters->merge($queries);
// session(['from'=> [
// 'back'=> $this->currentRoute,
// 'route' => $route,
// 'parameters' => $parameters
// ]]);
// return redirect()->route($route);
// }
public function mount()
{
@@ -110,6 +96,7 @@ class GithubPrivateRepository extends Component
$this->loadBranchByPage();
}
}
$this->selected_branch_name = data_get($this->branches,'0.name');
}
protected function loadBranchByPage()
@@ -159,13 +146,19 @@ class GithubPrivateRepository extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
$application->save();
redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

View File

@@ -46,7 +46,6 @@ class GithubPrivateRepositoryDeployKey extends Component
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private string $git_repository;
private string $git_branch;
public function mount()
{
@@ -96,7 +95,7 @@ class GithubPrivateRepositoryDeployKey extends Component
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'git_branch' => $this->branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
@@ -112,13 +111,18 @@ class GithubPrivateRepositoryDeployKey extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_random_name($application->uuid);
$application->save();
return redirect()->route('project.application.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'application_uuid' => $application->uuid,
]);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -127,11 +131,6 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
if ($this->branch) {
$this->git_branch = $this->branch;
} else {
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
}
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();

View File

@@ -69,21 +69,22 @@ class PublicGitRepository extends Component
{
try {
$this->branch_found = false;
$this->validate([
'repository_url' => 'required|url'
]);
$this->get_git_source();
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
$this->validate([
'repository_url' => 'required|url'
]);
$this->get_git_source();
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Throwable $e) {
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} else {
return handleError($e, $this);
}
}
}
@@ -137,7 +138,6 @@ class PublicGitRepository extends Component
$project = Project::where('uuid', $project_uuid)->first();
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
@@ -153,16 +153,22 @@ class PublicGitRepository extends Component
];
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$application->save();
return redirect()->route('project.application.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'application_uuid' => $application->uuid,
]);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -3,12 +3,12 @@
namespace App\Http\Livewire\Project\New;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Countable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Livewire\Component;
use Route;
class Select extends Component
{
@@ -17,16 +17,20 @@ class Select extends Component
public string $type;
public string $server_id;
public string $destination_uuid;
public Countable|array|Server $servers;
public Countable|array|Server $servers = [];
public Collection|array $standaloneDockers = [];
public Collection|array $swarmDockers = [];
public array $parameters;
public Collection|array $services = [];
public bool $loadingServices = true;
public bool $loading = false;
public ?string $existingPostgresqlUrl = null;
protected $queryString = [
'server',
];
public function mount()
{
$this->parameters = get_route_parameters();
@@ -40,13 +44,35 @@ class Select extends Component
// try {
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
// $this->emit('success', 'Successfully connected to the database.');
// } catch (\Exception $e) {
// return general_error_handler($e, $this);
// } catch (\Throwable $e) {
// return handleError($e, $this);
// }
// }
public function loadThings()
{
$this->loadServices();
$this->loadServers();
}
public function loadServices(bool $forceReload = false)
{
try {
if ($forceReload) {
Cache::forget('services');
}
$this->services = getServiceTemplates();
$this->emit('success', 'Successfully loaded services.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->loadingServices = false;
}
}
public function setType(string $type)
{
$this->type = $type;
if ($this->loading) return;
$this->loading = true;
if ($type === "existing-postgresql") {
$this->current_step = $type;
return;
@@ -83,10 +109,11 @@ class Select extends Component
'environment_name' => $this->parameters['environment_name'],
'type' => $this->type,
'destination' => $this->destination_uuid,
'server_id' => $this->server_id,
]);
}
public function load_servers()
public function loadServers()
{
$this->servers = Server::isUsable()->get();
}

View File

@@ -59,8 +59,11 @@ CMD ["nginx", "-g", "daemon off;"]
'source_id' => 0,
'source_type' => GithubApp::class
]);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'dockerfile-' . $application->id
'name' => 'dockerfile-' . $application->uuid,
'fqdn' => $fqdn
]);
redirect()->route('project.application.configuration', [

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\ServiceApplication;
use Livewire\Component;
class Application extends Component
{
public ServiceApplication $application;
public $parameters;
protected $rules = [
'application.human_name' => 'nullable',
'application.description' => 'nullable',
'application.fqdn' => 'nullable',
'application.image' => 'required',
'application.exclude_from_status' => 'required|boolean',
'application.required_fqdn' => 'required|boolean',
];
public function render()
{
return view('livewire.project.service.application');
}
public function instantSave()
{
$this->submit();
}
public function delete()
{
try {
$this->application->delete();
$this->emit('success', 'Application deleted successfully.');
return redirect()->route('project.service', $this->parameters);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->parameters = get_route_parameters();
}
public function submit()
{
try {
$this->validate();
$this->application->save();
updateCompose($this->application);
$this->emit('success', 'Application saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->emit('generateDockerCompose');
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class ComposeModal extends Component
{
public string $raw;
public string $actual;
public function render()
{
return view('livewire.project.service.compose-modal');
}
public function submit() {
$this->emit('warning', "Saving new docker compose...");
$this->emit('saveCompose', $this->raw);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\ServiceDatabase;
use Livewire\Component;
class Database extends Component
{
public ServiceDatabase $database;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [
'database.human_name' => 'nullable',
'database.description' => 'nullable',
'database.image' => 'required',
'database.exclude_from_status' => 'required|boolean',
];
public function render()
{
return view('livewire.project.service.database');
}
public function mount() {
$this->refreshFileStorages();
}
public function instantSave() {
$this->submit();
}
public function refreshFileStorages()
{
$this->fileStorages = $this->database->fileStorages()->get();
}
public function submit()
{
try {
$this->validate();
$this->database->save();
updateCompose($this->database);
$this->emit('success', 'Database saved successfully.');
} catch (\Throwable $e) {
ray($e);
} finally {
$this->emit('generateDockerCompose');
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Livewire\Component;
use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase $service;
public string $fs_path;
public ?string $workdir = null;
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable',
];
public function mount()
{
$this->service = $this->fileStorage->service;
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
$this->workdir = $this->service->service->workdir();
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
} else {
$this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();
try {
$this->validate();
if ($this->fileStorage->is_directory) {
$this->fileStorage->content = null;
}
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer($this->service);
$this->emit('success', 'File updated successfully.');
} catch (\Throwable $e) {
$this->fileStorage->setRawAttributes($original);
$this->fileStorage->save();
return handleError($e, $this);
}
}
public function instantSave()
{
$this->submit();
}
public function render()
{
return view('livewire.project.service.file-storage');
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Index extends Component
{
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
protected $listeners = ["saveCompose"];
public function render()
{
return view('livewire.project.service.index');
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->refreshStack();
}
public function saveCompose($raw)
{
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStack();
}
public function refreshStack()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
public function submit()
{
try {
$this->validate();
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->refreshStack();
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class Modal extends Component
{
public function serviceStatusUpdated() {
$this->emit('serviceStatusUpdated');
}
public function render()
{
return view('livewire.project.service.modal');
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Navbar extends Component
{
public Service $service;
public array $parameters;
public array $query;
protected $listeners = ['serviceStatusUpdated'];
public function render()
{
return view('livewire.project.service.navbar');
}
public function serviceStatusUpdated()
{
ray('serviceStatusUpdated');
$this->check_status();
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->service->refresh();
}
public function deploy()
{
$this->service->parse();
$activity = StartService::run($this->service);
$this->emit('newMonitorActivity', $activity->id);
}
public function stop()
{
StopService::run($this->service);
$this->service->refresh();
$this->emit('success', 'Service stopped successfully.');
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Collection;
use Livewire\Component;
class Show extends Component
{
public Service $service;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
public array $query;
public Collection $services;
protected $listeners = ['generateDockerCompose'];
public function mount()
{
try {
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
if (is_null($service)) {
throw new \Exception("Service not found.");
}
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{
$this->service->parse();
}
public function render()
{
return view('livewire.project.service.show');
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Project\Shared;
use App\Actions\Service\StopService;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -19,13 +20,26 @@ class Danger extends Component
public function delete()
{
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server);
$this->resource->delete();
return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name']
]);
// Should be queued
try {
if ($this->resource->type() === 'service') {
$server = $this->resource->server;
StopService::run($this->resource);
} else {
$destination = data_get($this->resource, 'destination');
if ($destination) {
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
$server = $destination->server;
}
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
}
$this->resource->delete();
return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name']
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -9,13 +9,13 @@ class Add extends Component
public $parameters;
public bool $is_preview = false;
public string $key;
public string $value;
public ?string $value = null;
public bool $is_build_time = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
'key' => 'required|string',
'value' => 'required|string',
'value' => 'nullable',
'is_build_time' => 'required|boolean',
];
protected $validationAttributes = [
@@ -31,8 +31,8 @@ class Add extends Component
public function submit()
{
ray('submitting');
$this->validate();
ray($this->key, $this->value, $this->is_build_time);
$this->emitUp('submit', [
'key' => $this->key,
'value' => $this->value,

View File

@@ -5,7 +5,7 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Str;
use Illuminate\Support\Str;
class All extends Component
{
@@ -68,15 +68,25 @@ class All extends Component
$environment->value = $variable;
$environment->is_build_time = false;
$environment->is_preview = $isPreview ? true : false;
if ($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if ($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
switch ($this->resource->type()) {
case 'application':
$environment->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
}
$environment->save();
}
}
if ($isPreview) {
$this->emit('success', 'Preview environment variables updated successfully.');
} else {
$this->emit('success', 'Environment variables updated successfully.');
}
$this->refreshEnvs();
}
public function refreshEnvs()
@@ -99,17 +109,22 @@ class All extends Component
$environment->is_build_time = $data['is_build_time'];
$environment->is_preview = $data['is_preview'];
if ($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if ($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
switch ($this->resource->type()) {
case 'application':
$environment->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
}
$environment->save();
$this->refreshEnvs();
$this->emit('success', 'Environment variable added successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -5,15 +5,19 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class Show extends Component
{
public $parameters;
public ModelsEnvironmentVariable $env;
public string|null $modalId = null;
public ?string $modalId = null;
public bool $isDisabled = false;
public string $type;
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
];
protected $validationAttributes = [
@@ -24,6 +28,10 @@ class Show extends Component
public function mount()
{
$this->isDisabled = false;
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
$this->isDisabled = true;
}
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
}
@@ -37,6 +45,7 @@ class Show extends Component
$this->validate();
$this->env->save();
$this->emit('success', 'Environment variable updated successfully.');
$this->emit('refreshEnvs');
}
public function delete()

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;
class HealthChecks extends Component
{
public $resource;
protected $rules = [
'resource.health_check_path' => 'string',
'resource.health_check_port' => 'nullable|string',
'resource.health_check_host' => 'string',
'resource.health_check_method' => 'string',
'resource.health_check_return_code' => 'integer',
'resource.health_check_scheme' => 'string',
'resource.health_check_response_text' => 'nullable|string',
'resource.health_check_interval' => 'integer',
'resource.health_check_timeout' => 'integer',
'resource.health_check_retries' => 'integer',
'resource.health_check_start_period' => 'integer',
];
public function submit()
{
try {
$this->validate();
$this->resource->save();
$this->emit('saved');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.shared.health-checks');
}
}

View File

@@ -53,8 +53,8 @@ class ResourceLimits extends Component
$this->validate();
$this->resource->save();
$this->emit('success', 'Resource limits updated successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -28,8 +28,8 @@ class All extends Component
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,13 +2,17 @@
namespace App\Http\Livewire\Project\Shared\Storages;
use App\Models\LocalPersistentVolume;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
public $storage;
public string|null $modalId = null;
public LocalPersistentVolume $storage;
public bool $isReadOnly = false;
public ?string $modalId = null;
public ?string $realName = null;
protected $rules = [
'storage.name' => 'required|string',
'storage.mount_path' => 'required|string',
@@ -22,6 +26,11 @@ class Show extends Component
public function mount()
{
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
} else {
$this->realName = $this->storage->name;
}
$this->modalId = new Cuid2(7);
}

View File

@@ -32,8 +32,8 @@ class RunCommand extends Component
try {
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
$this->emit('newMonitorActivity', $activity->id);
} catch (\Exception $e) {
return general_error_handler(err: $e);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -15,6 +15,7 @@ class Form extends Component
public $dockerVersion;
public string|null $wildcard_domain = null;
public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
protected $rules = [
'server.name' => 'required|min:6',
@@ -22,6 +23,7 @@ class Form extends Component
'server.ip' => 'required',
'server.user' => 'required',
'server.port' => 'required',
'server.settings.is_cloudflare_tunnel' => 'required',
'server.settings.is_reachable' => 'required',
'server.settings.is_part_of_swarm' => 'required',
'wildcard_domain' => 'nullable|url',
@@ -32,6 +34,7 @@ class Form extends Component
'server.ip' => 'ip',
'server.user' => 'user',
'server.port' => 'port',
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
'server.settings.is_reachable' => 'is reachable',
'server.settings.is_part_of_swarm' => 'is part of swarm'
];
@@ -41,32 +44,39 @@ class Form extends Component
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
}
public function instantSave() {
refresh_server_connection($this->server->privateKey);
$this->validateServer();
$this->server->settings->save();
}
public function installDocker()
{
$activity = resolve(InstallDocker::class)($this->server, currentTeam());
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function validateServer()
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
if ($uptime) {
$this->uptime = $uptime;
$this->emit('success', 'Server is reachable!');
$this->emit('success', 'Server is reachable.');
} else {
throw new \Exception('Server is not rachable');
$this->emit('error', 'Server is not reachable.');
return;
}
if ($dockerVersion) {
$this->dockerVersion = $dockerVersion;
$this->emit('proxyStatusUpdated');
$this->emit('success', 'Docker Engine 23+ is installed!');
} else {
throw new \Exception('Old Docker version detected (lower than 23).');
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
}
} catch (\Exception $e) {
return general_error_handler($e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
} finally {
$this->emit('proxyStatusUpdated');
}
}
@@ -80,24 +90,20 @@ class Form extends Component
}
$this->server->delete();
return redirect()->route('server.all');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
$this->validate();
// $validation = Validator::make($this->server->toArray(), [
// 'ip' => [
// 'ip'
// ],
// ]);
// if ($validation->fails()) {
// foreach ($validation->errors()->getMessages() as $key => $value) {
// $this->addError("server.{$key}", $value[0]);
// }
// return;
// }
$uniqueIPs = Server::all()->reject(function (Server $server) {
return $server->id === $this->server->id;
})->pluck('ip')->toArray();
if (in_array($this->server->ip, $uniqueIPs)) {
$this->emit('error', 'IP address is already in use by another team.');
return;
}
$this->server->settings->wildcard_domain = $this->wildcard_domain;
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
$this->server->settings->save();

View File

@@ -26,7 +26,7 @@ class ByIp extends Component
protected $rules = [
'name' => 'required|string',
'description' => 'nullable|string',
'ip' => 'required|ip',
'ip' => 'required',
'user' => 'required|string',
'port' => 'required|integer',
];
@@ -58,7 +58,7 @@ class ByIp extends Component
{
$this->validate();
try {
if (!$this->private_key_id) {
if (is_null($this->private_key_id)) {
return $this->emit('error', 'You must select a private key');
}
$server = Server::create([
@@ -78,8 +78,8 @@ class ByIp extends Component
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
$server->settings->save();
return redirect()->route('server.show', $server->uuid);
} catch (\Exception $e) {
return general_error_handler(err: $e);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,11 +2,11 @@
namespace App\Http\Livewire\Server;
use App\Actions\Proxy\CheckConfigurationSync;
use App\Actions\Proxy\SaveConfigurationSync;
use App\Enums\ProxyTypes;
use App\Actions\Proxy\CheckConfiguration;
use App\Actions\Proxy\SaveConfiguration;
use App\Models\Server;
use Livewire\Component;
use Illuminate\Support\Str;
class Proxy extends Component
{
@@ -14,14 +14,14 @@ class Proxy extends Component
public ?string $selectedProxy = null;
public $proxy_settings = null;
public string|null $redirect_url = null;
public ?string $redirect_url = null;
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
public function mount()
{
$this->selectedProxy = $this->server->proxy->type;
$this->redirect_url = $this->server->proxy->redirect_url;
$this->selectedProxy = data_get($this->server, 'proxy.type');
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
}
public function proxyStatusUpdated()
@@ -38,8 +38,8 @@ class Proxy extends Component
public function select_proxy($proxy_type)
{
$this->server->proxy->type = $proxy_type;
$this->server->proxy->status = 'exited';
$this->server->proxy->set('status', 'exited');
$this->server->proxy->set('type', $proxy_type);
$this->server->save();
$this->selectedProxy = $this->server->proxy->type;
$this->emit('proxyStatusUpdated');
@@ -48,33 +48,38 @@ class Proxy extends Component
public function submit()
{
try {
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
SaveConfiguration::run($this->server, $this->proxy_settings);
$this->server->proxy->redirect_url = $this->redirect_url;
$this->server->save();
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
$this->emit('success', 'Proxy configuration saved.');
} catch (\Exception $e) {
return general_error_handler(err: $e);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function reset_proxy_configuration()
{
try {
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true);
} catch (\Exception $e) {
return general_error_handler(err: $e);
$this->proxy_settings = CheckConfiguration::run($this->server, true);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function load_proxy_configuration()
public function loadProxyConfiguration()
{
try {
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
} catch (\Exception $e) {
return general_error_handler(err: $e);
$this->proxy_settings = CheckConfiguration::run($this->server);
if (Str::of($this->proxy_settings)->contains('--api.dashboard=true') && Str::of($this->proxy_settings)->contains('--api.insecure=true')) {
$this->emit('traefikDashboardAvailable', true);
} else {
$this->emit('traefikDashboardAvailable', false);
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -9,18 +9,28 @@ use Livewire\Component;
class Deploy extends Component
{
public Server $server;
public $proxy_settings = null;
public bool $traefikDashboardAvailable = false;
public ?string $currentRoute = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
public function start_proxy()
public function mount() {
$this->currentRoute = request()->route()->getName();
}
public function traefikDashboardAvailable(bool $data) {
$this->traefikDashboardAvailable = $data;
}
public function proxyStatusUpdated()
{
if (
$this->server->proxy->last_applied_settings &&
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
) {
$this->emit('saveConfiguration', $this->server);
$this->server->refresh();
}
public function startProxy()
{
try {
$activity = StartProxy::run($this->server);
$this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
}
$activity = resolve(StartProxy::class)($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function stop()

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Modal extends Component
{
public Server $server;
public function proxyStatusUpdated()
{
$this->server->proxy->set('status', 'running');
$this->server->save();
$this->emit('proxyStatusUpdated');
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Livewire\Server\Proxy;
use App\Jobs\ProxyContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
use Livewire\Component;
@@ -10,14 +10,23 @@ class Status extends Component
{
public Server $server;
public function get_status()
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated()
{
if (data_get($this->server,'settings.is_usable')) {
dispatch_sync(new ProxyContainerStatusJob(
server: $this->server
));
$this->server->refresh();
$this->server->refresh();
}
public function getProxyStatus()
{
try {
dispatch_sync(new ContainerStatusJob($this->server));
$this->emit('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function getProxyStatusWithNoti()
{
$this->emit('success', 'Refreshed proxy status.');
$this->getProxyStatus();
}
}

View File

@@ -13,9 +13,12 @@ class Show extends Component
public function mount()
{
try {
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail();
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
return handleError($e, $this);
}
}
public function render()

View File

@@ -15,38 +15,53 @@ class ShowPrivateKey extends Component
{
try {
$oldPrivateKeyId = $this->server->private_key_id;
refresh_server_connection($this->server->privateKey);
$this->server->update([
'private_key_id' => $newPrivateKeyId
]);
$this->server->refresh();
refresh_server_connection($this->server->privateKey);
$this->checkConnection();
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->server->update([
'private_key_id' => $oldPrivateKeyId
]);
$this->server->refresh();
refresh_server_connection($this->server->privateKey);
return general_error_handler($e, that: $this);
refresh_server_connection($this->server->privateKey);
return handleError($e, $this);
}
}
public function checkConnection()
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
if ($uptime) {
$this->server->settings->update([
'is_reachable' => true
]);
$this->emit('success', 'Server is reachable with this private key.');
} else {
throw new \Exception('Server is not reachable with this private key.');
$this->server->settings->update([
'is_reachable' => false,
'is_usable' => false
]);
$this->emit('error', 'Server is not reachable with this private key.');
return;
}
if ($dockerVersion) {
$this->server->settings->update([
'is_usable' => true
]);
$this->emit('success', 'Server is usable for Coolify.');
} else {
throw new \Exception('Old Docker version detected (lower than 23).');
$this->server->settings->update([
'is_usable' => false
]);
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Livewire\Settings;
use App\Jobs\ProxyStartJob;
use App\Jobs\ContainerStatusJob;
use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Models\Server;
use Livewire\Component;
@@ -45,7 +45,12 @@ class Configuration extends Component
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->next_channel = $this->next_channel;
if ($this->next_channel) {
$this->settings->next_channel = false;
$this->next_channel = false;
} else {
$this->settings->next_channel = $this->next_channel;
}
$this->settings->save();
$this->emit('success', 'Settings updated!');
}
@@ -68,7 +73,7 @@ class Configuration extends Component
{
$file = "$this->dynamic_config_path/coolify.yaml";
if (empty($this->settings->fqdn)) {
remote_process([
instant_remote_process([
"rm -f $file",
], $this->server);
} else {
@@ -124,7 +129,6 @@ class Configuration extends Component
];
}
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
dispatch(new ProxyStartJob($this->server));
}
}
@@ -137,7 +141,7 @@ class Configuration extends Component
$yaml;
$base64 = base64_encode($yaml);
remote_process([
instant_remote_process([
"mkdir -p $this->dynamic_config_path",
"echo '$base64' | base64 -d > $file",
], $this->server);

View File

@@ -50,8 +50,8 @@ class Email extends Component
]);
$this->settings->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitResend() {
@@ -62,17 +62,17 @@ class Email extends Component
]);
$this->settings->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->settings->resend_enabled = false;
return general_error_handler($e, $this);
return handleError($e, $this);
}
}
public function instantSaveResend() {
try {
$this->settings->smtp_enabled = false;
$this->submitResend();
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave()
@@ -80,8 +80,8 @@ class Email extends Component
try {
$this->settings->resend_enabled = false;
$this->submit();
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -99,8 +99,8 @@ class Email extends Component
]);
$this->settings->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

View File

@@ -51,8 +51,8 @@ class Change extends Component
try {
$this->validate();
$this->github_app->save();
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -65,8 +65,8 @@ class Change extends Component
try {
$this->github_app->delete();
redirect()->route('source.all');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -49,8 +49,8 @@ class Create extends Component
session(['from' => session('from') + ['source_id' => $github_app->id]]);
}
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -30,8 +30,8 @@ class Actions extends Component
$this->emit('success', 'Subscription cancelled successfully. Reloading in 5s.');
$this->emit('reloadWindow', 5000);
}
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function resume()
@@ -65,8 +65,8 @@ class Actions extends Component
$this->emit('success', 'Subscription resumed successfully. Reloading in 5s.');
$this->emit('reloadWindow', 5000);
}
} catch (\Exception $e) {
return general_error_handler($e, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function stripeCustomerPortal() {

View File

@@ -8,8 +8,13 @@ use Stripe\Checkout\Session;
class PricingPlans extends Component
{
public bool $isTrial = false;
public function mount() {
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
}
public function subscribeStripe($type)
{
$team = currentTeam();
Stripe::setApiKey(config('subscription.stripe_api_key'));
switch ($type) {
case 'basic-monthly':
@@ -39,6 +44,7 @@ class PricingPlans extends Component
return;
}
$payload = [
'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
'line_items' => [[
'price' => $priceId,
@@ -50,10 +56,23 @@ class PricingPlans extends Component
'automatic_tax' => [
'enabled' => true,
],
'mode' => 'subscription',
'success_url' => route('dashboard', ['success' => true]),
'cancel_url' => route('subscription.index', ['cancelled' => true]),
];
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
$payload['subscription_data'] = [
'trial_period_days' => config('constants.limits.trial_period'),
'trial_settings' => [
'end_behavior' => [
'missing_payment_method' => 'cancel',
]
],
];
$payload['payment_method_collection'] = 'if_required';
}
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
if ($customer) {
$payload['customer'] = $customer;

View File

@@ -31,8 +31,8 @@ class Create extends Component
auth()->user()->teams()->attach($team, ['role' => 'admin']);
refreshSession();
return redirect()->route('team.index');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -12,7 +12,6 @@ class Delete extends Component
$currentTeam = currentTeam();
$currentTeam->delete();
$team = auth()->user()->teams()->first();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;

View File

@@ -28,8 +28,8 @@ class Form extends Component
try {
$this->team->save();
refreshSession();
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Notifications\TransactionalEmails\InvitationLink;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Artisan;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class InviteLink extends Component
{
@@ -20,64 +24,74 @@ class InviteLink extends Component
public function viaEmail()
{
$this->generate_invite_link(isEmail: true);
$this->generate_invite_link(sendEmail: true);
}
private function generate_invite_link(bool $isEmail = false)
public function viaLink()
{
$this->generate_invite_link(sendEmail: false);
}
private function generate_invite_link(bool $sendEmail = false)
{
try {
$uuid = new Cuid2(32);
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
$user = User::whereEmail($this->email);
if (!$user->exists()) {
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
}
$member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
}
$uuid = new Cuid2(32);
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
$user = User::whereEmail($this->email)->first();
$invitation = TeamInvitation::whereEmail($this->email);
if ($invitation->exists()) {
$created_at = $invitation->first()->created_at;
$diff = $created_at->diffInMinutes(now());
if ($diff <= config('constants.invitation.link.expiration')) {
return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action.");
if (is_null($user)) {
$password = Str::password();
$user = User::create([
'name' => Str::of($this->email)->before('@'),
'email' => $this->email,
'password' => Hash::make($password),
'force_password_reset' => true,
]);
$token = Crypt::encryptString("{$user->email}@@@$password");
$link = route('auth.link', ['token' => $token]);
}
$invitation = TeamInvitation::whereEmail($this->email)->first();
if (!is_null($invitation)) {
$invitationValid = $invitation->isValid();
if ($invitationValid) {
return handleError(livewire: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
} else {
$invitation->delete();
}
}
TeamInvitation::firstOrCreate([
$invitation = TeamInvitation::firstOrCreate([
'team_id' => currentTeam()->id,
'uuid' => $uuid,
'email' => $this->email,
'role' => $this->role,
'link' => $link,
'via' => $isEmail ? 'email' : 'link',
'via' => $sendEmail ? 'email' : 'link',
]);
if ($isEmail) {
$user->first()->notify(new InvitationLink);
if ($sendEmail) {
$mail = new MailMessage();
$mail->view('emails.invitation-link', [
'team' => currentTeam()->name,
'invitation_link' => $link,
]);
$mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.');
send_user_an_email($mail, $this->email);
$this->emit('success', 'Invitation sent via email successfully.');
$this->emit('refreshInvitations');
return;
} else {
$this->emit('success', 'Invitation link generated.');
$this->emit('refreshInvitations');
}
$this->emit('refreshInvitations');
} catch (\Throwable $e) {
$error_message = $e->getMessage();
if ($e->getCode() === '23505') {
$error_message = 'Invitation already sent.';
}
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
return handleError(error: $e, livewire: $this, customErrorMessage: $error_message);
}
}
public function viaLink()
{
$this->generate_invite_link();
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Http\Livewire\Team;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class Member extends Component
@@ -24,6 +25,10 @@ class Member extends Component
public function remove()
{
$this->member->teams()->detach(currentTeam());
Cache::forget("team:{$this->member->id}");
Cache::remember('team:' . $this->member->id, 3600, function() {
return $this->member->teams()->first();
});
$this->emit('reloadWindow');
}
}

View File

@@ -67,8 +67,8 @@ class Create extends Component
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -77,8 +77,8 @@ class Create extends Component
try {
$this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -32,8 +32,8 @@ class Form extends Component
try {
$this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -42,8 +42,8 @@ class Form extends Component
try {
$this->storage->delete();
return redirect()->route('team.storages.all');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
@@ -55,8 +55,8 @@ class Form extends Component
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->save();
$this->emit('success', 'Storage settings saved.');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -36,9 +36,9 @@ class Upgrade extends Component
}
$this->showProgress = true;
resolve(UpdateCoolify::class)(true);
Toaster::success("Upgrading to {$this->latestVersion} version...");
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
$this->emit('success', "Upgrading to {$this->latestVersion} version...");
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -6,10 +6,12 @@ use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\User;
use App\Models\Waitlist;
use Livewire\Component;
use Illuminate\Support\Str;
class Index extends Component
{
public string $email;
public int $users = 0;
public int $waitingInLine = 0;
protected $rules = [
@@ -22,6 +24,7 @@ class Index extends Component
public function mount()
{
$this->waitingInLine = Waitlist::whereVerified(true)->count();
$this->users = User::count();
if (isDev()) {
$this->email = 'waitlist@example.com';
}
@@ -44,14 +47,14 @@ class Index extends Component
return;
}
$waitlist = Waitlist::create([
'email' => $this->email,
'email' => Str::lower($this->email),
'type' => 'registration',
]);
$this->emit('success', 'Check your email to verify your email address.');
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsBoardingFlow
{
@@ -15,8 +16,11 @@ class IsBoardingFlow
*/
public function handle(Request $request, Closure $next): Response
{
ray()->showQueries()->color('orange');
// ray()->showQueries()->color('orange');
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('boarding');
}
return $next($request);

View File

@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsSubscriptionValid
{
@@ -31,6 +32,9 @@ class IsSubscriptionValid
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
// ray('SubscriptionValid Middleware');
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('subscription');
} else {
return $next($request);

View File

@@ -1,54 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Notifications\Application\StatusChanged;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $containerName;
public function __construct(
public Application $application,
public int $pullRequestId = 0)
{
$this->containerName = generateApplicationContainerName($application->uuid, $pullRequestId);
}
public function uniqueId(): string
{
return $this->containerName;
}
public function handle(): void
{
try {
$status = getApplicationContainerStatus(application: $this->application);
if ($this->application->status === 'running' && $status !== 'running') {
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
}
if ($this->pullRequestId !== 0) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pullRequestId);
$preview->status = $status;
$preview->save();
} else {
$this->application->status = $status;
$this->application->save();
}
} catch (\Exception $th) {
ray($th->getMessage());
throw $th;
}
}
}

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