Compare commits

...

172 Commits

Author SHA1 Message Date
Andras Bacsai
742df4f5df fix: select server view 2024-10-08 17:08:35 +02:00
Andras Bacsai
2522ecf832 chore: Update version to 4.0.0-beta.357 2024-10-08 17:08:26 +02:00
Andras Bacsai
40f2c7a1f9 Merge pull request #3714 from coollabsio/next
v4.0.0-beta.356
2024-10-07 14:08:08 +02:00
Andras Bacsai
3f8324d09e chore: Refactor loadServices2 method and remove unused code 2024-10-07 14:03:04 +02:00
Andras Bacsai
eee292b02f fix: directus 2024-10-07 13:45:02 +02:00
Andras Bacsai
2b4dc1f9c8 Merge pull request #3752 from peaklabs-dev/fix-directus
Fix: Directus template
2024-10-07 13:44:00 +02:00
Andras Bacsai
dd29827a14 Merge pull request #3749 from mkilinskidev/mki
Fix subtitle typo in server navbar
2024-10-07 13:23:40 +02:00
Andras Bacsai
dfe290b546 Merge pull request #3741 from coollabsio/dependabot/npm_and_yarn/cookie-0.7.0
chore(deps): bump cookie from 0.6.0 to 0.7.0
2024-10-07 13:15:16 +02:00
Andras Bacsai
4eeb7ee7c6 Merge pull request #3570 from katywings/nitropage
chore: add Nitropage service template and logo
2024-10-07 12:43:25 +02:00
Andras Bacsai
51813463b8 fix: chatwoot service 2024-10-07 12:36:33 +02:00
Andras Bacsai
da8a45391c Merge branch 'next' of github.com:coollabsio/coolify into next 2024-10-07 12:22:09 +02:00
Andras Bacsai
673081ffb3 chore: Update select.blade.php with improved search functionality 2024-10-07 12:22:00 +02:00
peaklabs-dev
8f6c01ba49 Update service-templates.json 2024-10-07 12:14:07 +02:00
peaklabs-dev
40852995b4 Merge branch 'next' of https://github.com/coollabsio/coolify into next 2024-10-07 12:13:53 +02:00
peaklabs-dev
e6bb1deaa9 Update getoutline.yaml 2024-10-07 12:13:52 +02:00
Andras Bacsai
2dc3177a7a feat: Refactor setType method to use slug value for type 2024-10-07 12:12:32 +02:00
Andras Bacsai
facbd9a50d Merge branch 'next' of github.com:coollabsio/coolify into next 2024-10-07 12:12:22 +02:00
Andras Bacsai
a0532afb24 feat: Refactor setType method to use slug value for type 2024-10-07 12:12:05 +02:00
Andras Bacsai
a725a6eaf2 feat: update setType method to use slug value for type 2024-10-07 12:11:56 +02:00
peaklabs-dev
3fd3660960 Update service-templates.json 2024-10-07 12:07:37 +02:00
peaklabs-dev
5898e0ac4a Merge branch 'next' of https://github.com/coollabsio/coolify into next 2024-10-07 12:07:18 +02:00
peaklabs-dev
4f4743cc7f Fix: Outline 2024-10-07 12:07:14 +02:00
Andras Bacsai
3b3362daf0 Merge pull request #3722 from danielalves96/add_ollama_with_web_ui
feat: add ollama service with open-webui and logo
2024-10-07 11:56:28 +02:00
Andras Bacsai
cd199ed81d Merge branch 'next' of github.com:coollabsio/coolify into next 2024-10-07 11:54:59 +02:00
Andras Bacsai
7e32a37729 fix: new services 2024-10-07 11:54:54 +02:00
🏔️ Peak
58c3cea0c0 Merge pull request #3127 from peaklabs-dev/service-getoutline
Feat: getoutline service
2024-10-07 11:29:57 +02:00
Andras Bacsai
1c10a43321 fix: new resource selection view
fix: new services
2024-10-07 11:19:14 +02:00
Andras Bacsai
370a0b1eec fix: use local service-templates in dev 2024-10-07 11:02:01 +02:00
peaklabs-dev
282ff4e670 Update service-templates.json 2024-10-07 10:39:21 +02:00
peaklabs-dev
af09472679 Update labelstudio.yaml 2024-10-07 10:36:08 +02:00
🏔️ Peak
053ee63606 Merge pull request #3729 from rennokki/service/labelstudio
Service: Label Studio
2024-10-07 10:21:03 +02:00
peaklabs-dev
a8d8df7d8b Fix: Directus 2024-10-06 19:42:09 +02:00
Andras Bacsai
283fe47f5c Merge pull request #3750 from coollabsio/helper-1.0.2
chore: Bump coolify-helper version to 1.0.2
2024-10-06 18:04:26 +02:00
Andras Bacsai
88367d8af1 chore: Bump coolify-helper version to 1.0.2 2024-10-06 18:04:03 +02:00
Andras Bacsai
506f733632 Merge pull request #3748 from peaklabs-dev/fix-helper-manifest
Fix: Broken helper manifests (for a short period a few hours)
2024-10-06 18:00:00 +02:00
Mateusz Kilinski
9f2e7851f7 Fix subtitle typo in server navbar 2024-10-06 17:43:22 +02:00
peaklabs-dev
8269e9d29d Fix: Update helper version 2024-10-06 17:39:16 +02:00
dependabot[bot]
8803cae54c chore(deps): bump cookie from 0.6.0 to 0.7.0
Bumps [cookie](https://github.com/jshttp/cookie) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-06 08:13:20 +00:00
peaklabs-dev
c1930e3dfc Revert "Update coolify-helper.yml"
This reverts commit c717b6859b.
2024-10-06 00:51:54 +02:00
🏔️ Peak
c717b6859b Update coolify-helper.yml 2024-10-06 00:51:28 +02:00
Andras Bacsai
4624a381b1 new search input on "new resource" view 2024-10-05 20:55:23 +02:00
Andras Bacsai
6928b0a2c8 chore: Update project query to order by name in lowercase 2024-10-05 15:04:47 +02:00
Andras Bacsai
2da6f66e85 chore: Update project query to order by name in uppercase 2024-10-05 15:04:31 +02:00
Andras Bacsai
a1124a885d feat: project search on frontend 2024-10-05 15:03:40 +02:00
Andras Bacsai
9448d0f0d2 feat: Add Invoice Ninja service configuration to Service model 2024-10-05 14:16:53 +02:00
Alex Renoki
e446354d97 Added Label Studio 2024-10-05 06:27:16 +03:00
Daniel Alves
c8aa09359f fix: remove not used extra host 2024-10-04 16:15:18 -03:00
Andras Bacsai
128d732438 fix: one-click services 2024-10-04 20:17:08 +02:00
Andras Bacsai
3b97bb1341 feat: Add Argilla service configuration to Service model 2024-10-04 20:16:58 +02:00
Andras Bacsai
a4c8f83d17 chore: Update password form submission in modal-confirmation component 2024-10-04 20:10:40 +02:00
Daniel Alves
0ca9885ddf feat: add ollama service with open webui and logo 2024-10-04 11:51:45 -03:00
Andras Bacsai
4f4453b17c chore: Update proxy configuration paths for Caddy and Nginx in dev 2024-10-04 13:46:33 +02:00
Andras Bacsai
d60d90de92 chore: Update anythingllm.yaml volumes configuration 2024-10-04 13:41:14 +02:00
Andras Bacsai
ce49f4806d fix: proxy conf in dev 2024-10-04 13:25:17 +02:00
Andras Bacsai
82daf6c420 Merge pull request #2980 from rennokki/feature/ai-templates
New services: LLM & AI-related one-click services
2024-10-04 13:24:34 +02:00
Andras Bacsai
f1eb43815e Merge branch 'next' into feature/ai-templates 2024-10-04 12:44:15 +02:00
Andras Bacsai
be9041d592 chore: Update MariaDB image to version 11 and fix service environment variable orders 2024-10-04 12:20:35 +02:00
Andras Bacsai
c2c0afa0ba fix: service env orders, application env orders 2024-10-04 12:08:57 +02:00
Andras Bacsai
611f70d79f chore: Remove commented code for shared variable type validation 2024-10-04 12:08:06 +02:00
Alexander G.
59c54b8aec Merge branch 'next' into feature/ai-templates 2024-10-04 12:29:30 +03:00
Alex Renoki
f0f685b78a Added anythingllm 2024-10-04 12:29:08 +03:00
Alex Renoki
c54d77515b Added unstructured 2024-10-04 11:54:32 +03:00
Alex Renoki
9435be7cce Added Argilla 2024-10-04 11:49:04 +03:00
Alex Renoki
f2ccc4059d Added prefect 2024-10-04 11:33:01 +03:00
Alex Renoki
21c6510e9d Improved healthchecks and generations 2024-10-04 11:26:45 +03:00
Andras Bacsai
2ca73695b3 fix: tag mass redeployments 2024-10-04 10:22:58 +02:00
Alex Renoki
31193a9f66 Fixed postgres healthchecks 2024-10-04 08:49:02 +03:00
Alex Renoki
9f5257bf8a Added infisical 2024-10-04 08:48:49 +03:00
Alex Renoki
78a34dbef1 Fixed langfuse 2024-10-04 07:21:20 +03:00
Alex Renoki
f4d650b03f Fixed litellm 2024-10-04 07:09:58 +03:00
Alex Renoki
97ee34f1a1 Added searxng 2024-10-04 06:53:14 +03:00
Alex Renoki
9014062b48 Added weaviate 2024-10-04 06:43:59 +03:00
Alex Renoki
a8f230b5eb Added qdrant 2024-10-04 06:36:25 +03:00
Alex Renoki
94ee525e0a Merge branch 'feature/ai-templates' of github.com:rennokki/coolify into feature/ai-templates 2024-10-04 05:46:26 +03:00
Alex Renoki
d4ace01633 Added litellm 2024-10-04 05:40:08 +03:00
Alex Renoki
49bcd7b8e6 Bump to v2 2024-10-04 05:40:08 +03:00
Alex Renoki
5f613df4e0 Added langfuse 2024-10-04 05:40:07 +03:00
Andras Bacsai
65aeebd9fa fix: Reset description and subject fields after submitting feedback 2024-10-03 23:27:23 +02:00
Andras Bacsai
f26d2e1d0b chore: Update version to 4.0.0-beta.356 2024-10-03 23:26:32 +02:00
Andras Bacsai
a1d395ff0c Merge pull request #3708 from coollabsio/next
v4.0.0-beta.355
2024-10-03 23:04:54 +02:00
Andras Bacsai
4e9126887f Merge pull request #3707 from coollabsio/custom-traefik-middlewares
fix: parser, espacing container labels
2024-10-03 23:00:17 +02:00
Andras Bacsai
5fa650122d Merge pull request #3637 from MarioZet23/custom-traefik-middlewares
feat: Allow custom traefik middlewares
2024-10-03 22:59:46 +02:00
Andras Bacsai
ee7f8200ac fix: parser, espacing container labels 2024-10-03 22:58:06 +02:00
Andras Bacsai
d2a8f31a1c Merge branch 'next' into custom-traefik-middlewares 2024-10-03 22:44:22 +02:00
Andras Bacsai
2468d0044b chore: Update version to 4.0.0-beta.355 2024-10-03 22:39:44 +02:00
Andras Bacsai
81b8a58415 fix: scheduled backup for services view 2024-10-03 22:38:37 +02:00
Andras Bacsai
7442d19611 Merge pull request #3705 from coollabsio/next
v4.0.0-beta.354
2024-10-03 22:04:36 +02:00
Andras Bacsai
8c024ddb57 chore: Update homarr service template and remove unnecessary code 2024-10-03 22:02:18 +02:00
Andras Bacsai
d990a5691d chore: Update homarr service template and remove unnecessary code 2024-10-03 21:47:06 +02:00
Andras Bacsai
2181ef381b Merge pull request #3697 from danielalves96/add_homarr_template
feat: add homarr service tamplate and logo
2024-10-03 21:39:14 +02:00
Andras Bacsai
c80f5be974 chore: Update it-tools service template and port configuration 2024-10-03 21:38:13 +02:00
Andras Bacsai
358f6575f8 chore: Refactor modal-confirmation component 2024-10-03 21:38:08 +02:00
Andras Bacsai
de0f34734b Merge pull request #3702 from danielalves96/add-it-tools
feat: add it-tools service template and logo
2024-10-03 21:33:33 +02:00
Andras Bacsai
33cb2d150d chore: Fix application deployment queue filter logic 2024-10-03 21:32:02 +02:00
Andras Bacsai
5f07b473e9 fix: parse proxy config and check the set ports usage 2024-10-03 21:29:55 +02:00
Andras Bacsai
5bcd813792 chore: Remove commented code in Server model 2024-10-03 21:29:04 +02:00
Andras Bacsai
a0bb523507 chore: Remove debug statement in Service model 2024-10-03 21:11:14 +02:00
Andras Bacsai
0b4fc38d6b chore: Update version to 4.0.0-beta.354 2024-10-03 21:10:52 +02:00
Andras Bacsai
82c834915d Merge pull request #3703 from coollabsio/next
v4.0.0-beta.353
2024-10-03 21:03:16 +02:00
Andras Bacsai
d637675ce3 chore: Update service application view 2024-10-03 21:02:42 +02:00
Andras Bacsai
e71c04a0c7 chore: Update version to 4.0.0-beta.353 2024-10-03 20:59:10 +02:00
Andras Bacsai
885b3bdea7 Merge pull request #3701 from coollabsio/next
v4.0.0-beta.352
2024-10-03 20:53:17 +02:00
Daniel Alves
9d6757aeb7 feat: add it-tools service template and logo 2024-10-03 15:52:33 -03:00
Andras Bacsai
d84d0a816b chore: Refactor DatabaseBackupJob to handle missing team 2024-10-03 20:51:18 +02:00
Andras Bacsai
0da31c34b5 fix: add new supported database images 2024-10-03 20:47:22 +02:00
Andras Bacsai
ccdaf59ecb fix: service application view 2024-10-03 20:47:02 +02:00
Andras Bacsai
3fc9cf90ab chore: update version to 4.0.0-beta.352 2024-10-03 20:46:45 +02:00
Daniel Alves
c4be720b20 fix: update FQDN 2024-10-03 15:00:52 -03:00
Daniel Alves
aabe27efd1 feat: add homarr service tamplate and logo 2024-10-03 12:58:35 -03:00
Andras Bacsai
a8982379c9 Merge pull request #3683 from coollabsio/next
v4.0.0-beta.351
2024-10-03 15:15:21 +02:00
Andras Bacsai
6dd0bd0742 fix: api useBuildServer 2024-10-03 15:09:56 +02:00
Andras Bacsai
1d3494a6ba fix: network handling
fix: environment variable handling
2024-10-03 15:04:40 +02:00
Andras Bacsai
ee5eb427c9 fix: bitcoin core template 2024-10-03 14:07:58 +02:00
Andras Bacsai
ad7b5e6e1c Merge pull request #3689 from ALsJourney/bitcoin_core_service
Basic Bitcoin Core node service
2024-10-03 14:00:52 +02:00
Andras Bacsai
73bd344147 fix: strapi template 2024-10-03 13:49:50 +02:00
Andras Bacsai
ef448280d8 fix: able to support more database dynamically from Coolify's UI 2024-10-03 13:49:43 +02:00
Andras Bacsai
5282248cb4 Merge pull request #3685 from statickidz/add-strapi-template
Add Strapi template
2024-10-03 13:18:40 +02:00
Andras Bacsai
14c9f25c57 feat: restart service without pulling the latest image 2024-10-03 13:17:35 +02:00
ALsJourney
44b3d08d86 Finished basic bitcoin core service 2024-10-03 13:13:25 +02:00
Andras Bacsai
5da1f48ae1 fix typo 2024-10-03 12:43:41 +02:00
Andras Bacsai
2bc1b9027c Merge pull request #3679 from alepouna/typo-fix
Update docker-registry.yaml
2024-10-03 12:43:46 +02:00
Andras Bacsai
1c7ca56756 feat: backup all databases for mysql,mariadb,postgresql 2024-10-03 12:39:45 +02:00
Adrian Barrio
d70faea845 Merge remote-tracking branch 'origin/next' into add-strapi-template 2024-10-03 11:33:35 +02:00
Adrian Barrio
fdb5cab875 feat: add strapi template 2024-10-03 11:28:27 +02:00
Andras Bacsai
bb6cb8edc9 improvement: show backup button on supported db service stacks 2024-10-03 10:48:25 +02:00
Andras Bacsai
a6ec2b92fb version++ 2024-10-03 10:23:38 +02:00
Andras Bacsai
436a1d945f Merge pull request #3663 from coollabsio/next
v4.0.0-beta.350
2024-10-03 10:09:05 +02:00
Andras Bacsai
a6a3abc273 chore: Update soketi service image to version 1.0.3 2024-10-03 10:05:54 +02:00
Andras Bacsai
c4e702f096 fix: able to select root permission easier 2024-10-03 09:57:37 +02:00
alepouna
31df222798 Update docker-registry.yaml 2024-10-03 04:04:37 +03:00
Andras Bacsai
e91939a4ea refactor: Remove unnecessary watch command from soketi service entrypoint 2024-10-02 21:24:09 +02:00
Andras Bacsai
ceccd093d2 refactor: Improve socket reconnection interval in terminal.js 2024-10-02 21:24:05 +02:00
Andras Bacsai
66bb4e0fc1 refactor: Remove inactivity timer in terminal-server.js 2024-10-02 21:23:59 +02:00
Andras Bacsai
dd3ff38df7 refactor: Encode delimiter in SshMultiplexingHelper 2024-10-02 21:23:46 +02:00
Andras Bacsai
69553ec314 refactor: Improve popup component styling and button behavior 2024-10-02 18:26:51 +02:00
Andras Bacsai
7bb1bf0ae3 refactor: Improve parsing of commands for sudo in parseCommandsByLineForSudo 2024-10-02 18:26:40 +02:00
Andras Bacsai
a1a8f1336a refactor: Fix indentation in modal-confirmation.blade.php 2024-10-02 17:35:48 +02:00
Andras Bacsai
207fe1d709 wtf wtf wtf 2024-10-02 17:27:02 +02:00
Andras Bacsai
059535a676 chore: Remove commented out code for uploading to S3 in DatabaseBackupJob 2024-10-02 16:43:01 +02:00
Andras Bacsai
765a74ca4f handle errors in databasebackupjob 2024-10-02 15:33:14 +02:00
Andras Bacsai
e03e4f2e91 refactor: Improve SSH command generation in Terminal.php and terminal-server.js 2024-10-02 15:16:55 +02:00
Andras Bacsai
0ab432d5e6 chore: Remove unnecessary command from SshMultiplexingHelper 2024-10-02 14:54:48 +02:00
Andras Bacsai
6f25b548c7 fix: realtime watch in development mode 2024-10-02 14:18:52 +02:00
Andras Bacsai
d55e4bf381 feat: Handle HTTPS domain in ConfigureCloudflareTunnels 2024-10-02 13:36:25 +02:00
Andras Bacsai
ec216254b5 chore: Update modal input in server form to prevent closing on outside click 2024-10-02 13:36:09 +02:00
Andras Bacsai
fbb36bfe8e revert modal-confirmation in dev 2024-10-02 12:01:12 +02:00
Andras Bacsai
dd782e75f5 fix: local dev s3 uploads
fix: hetzner s3 uploads (mc alias instead of mc host)
2024-10-02 11:45:30 +02:00
Andras Bacsai
2be2f0ac79 feat: support Hetzner S3 2024-10-02 10:25:45 +02:00
Andras Bacsai
97943db5f4 chore: Add missing import for Attribute class in ApplicationDeploymentQueue model 2024-10-02 09:21:54 +02:00
Andras Bacsai
bbd2748ad7 chore: Update command signature and description for cleanup application deployment queue 2024-10-02 09:21:50 +02:00
Andras Bacsai
a530804a71 feat: Add command to check application deployment queue 2024-10-02 09:21:28 +02:00
Andras Bacsai
8ca8ab82b0 refactor: Remove deployment queue when deleting an application 2024-10-02 09:20:49 +02:00
Andras Bacsai
024ad8e943 fix: cleanup stucked applicationdeploymentqueue 2024-10-02 09:20:08 +02:00
Andras Bacsai
4d86b556a4 fix: ipv6 scp should use -6 flag 2024-10-02 08:15:03 +02:00
Andras Bacsai
73e38ff951 chore: Update version numbers to 4.0.0-beta.350 in configuration files 2024-10-02 08:13:49 +02:00
Andras Bacsai
5354561c39 Merge pull request #3659 from coollabsio/next
v4.0.0-beta.349
2024-10-01 21:36:50 +02:00
Andras Bacsai
223cd37031 fix: remove autofocuses 2024-10-01 21:36:16 +02:00
Andras Bacsai
93a0725a4e Merge pull request #3613 from nfnot/main
refactor: Update search input placeholder in resource index view
2024-10-01 21:32:21 +02:00
Andras Bacsai
4d1d4598b6 chore: Update version numbers to 4.0.0-beta.349 and 4.0.0-beta.350 2024-10-01 21:31:54 +02:00
Norman
888e96448c Merge branch 'main' into main 2024-10-01 13:33:02 +03:00
MarioZet
1b0e2e1257 Feat. Apply all middlewares from labels to coolify router, instead of only basicauth and redirect 2024-09-29 20:08:39 +02:00
Norman
b84576ce87 Merge branch 'main' into main 2024-09-28 18:18:47 +03:00
Norman
f2a9a04461 refactor: Update search input placeholder in resource index view 2024-09-28 01:14:54 +00:00
Katja Lutz
2cf0f4facc chore: add Nitropage service template and logo 2024-09-25 21:23:23 +02:00
peaklabs-dev
0494f9a7e5 Merge branch 'coollabsio:main' into service-getoutline 2024-09-18 20:44:53 +02:00
peaklabs-dev
c402d7f543 Merge branch 'coollabsio:main' into service-getoutline 2024-09-12 13:05:27 +02:00
peaklabs-dev
565cb54dba Merge branch 'coollabsio:main' into service-getoutline 2024-09-09 12:07:00 +02:00
peaklabs-dev
eb5765979f Merge branch 'coollabsio:main' into service-getoutline 2024-09-05 18:05:58 +02:00
ayntk-ai
1e46b63041 Feat getoutline service 2024-08-20 18:32:35 +02:00
Alex Renoki
71bb1f5e17 Added litellm 2024-08-17 06:20:42 +03:00
Alex Renoki
ce926afdaa Bump to v2 2024-08-17 06:16:38 +03:00
Alex Renoki
18ae29ba99 Added langfuse 2024-07-31 07:11:08 +03:00
145 changed files with 2864 additions and 1205 deletions

View File

@@ -98,3 +98,4 @@ jobs:
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}

View File

@@ -22,7 +22,7 @@ class CheckConfiguration
];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value();
}
if (! $proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception('Could not generate proxy configuration');

View File

@@ -2,14 +2,17 @@
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class CheckProxy
{
use AsAction;
public function handle(Server $server, $fromUI = false)
// It should return if the proxy should be started (true) or not (false)
public function handle(Server $server, $fromUI = false): bool
{
if (! $server->isFunctional()) {
return false;
@@ -62,22 +65,42 @@ class CheckProxy
$ip = 'host.docker.internal';
}
$connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
$portsToCheck = ['80', '443'];
try {
if ($server->proxyType() !== ProxyTypes::NONE->value) {
$proxyCompose = CheckConfiguration::run($server);
if (isset($proxyCompose)) {
$yaml = Yaml::parse($proxyCompose);
$portsToCheck = [];
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
$ports = data_get($yaml, 'services.traefik.ports');
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
$ports = data_get($yaml, 'services.caddy.ports');
}
if (isset($ports)) {
foreach ($ports as $port) {
$portsToCheck[] = str($port)->before(':')->value();
}
}
}
} else {
return false;
$portsToCheck = [];
}
} catch (\Exception $e) {
ray($e->getMessage());
}
if ($port443) {
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
if (count($portsToCheck) === 0) {
return false;
}
foreach ($portsToCheck as $port) {
$connection = @fsockopen($ip, $port);
if (is_resource($connection) && fclose($connection)) {
if ($fromUI) {
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
}

View File

@@ -26,7 +26,7 @@ class StartProxy
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;
class CheckApplicationDeploymentQueue extends Command
{
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
protected $description = 'Check application deployment queue.';
public function handle()
{
$seconds = $this->option('seconds');
$deployments = ApplicationDeploymentQueue::whereIn('status', [
ApplicationDeploymentStatus::IN_PROGRESS,
ApplicationDeploymentStatus::QUEUED,
])->where('created_at', '<=', now()->subSeconds($seconds))->get();
if ($deployments->isEmpty()) {
$this->info('No deployments found in the last '.$seconds.' seconds.');
return;
}
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
foreach ($deployments as $deployment) {
if ($this->option('force')) {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
$this->cancelDeployment($deployment);
} else {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
if ($this->confirm('Do you want to cancel this deployment?', true)) {
$this->cancelDeployment($deployment);
}
}
}
}
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
{
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
if ($deployment->server?->isFunctional()) {
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
}
}
}

View File

@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $signature = 'cleanup:deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
protected $description = 'Cleanup application deployment queue.';
public function handle()
{

View File

@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
@@ -47,6 +48,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) {
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
}
try {
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
if (is_null($applicationDeploymentQueue->application)) {
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
$applicationDeploymentQueue->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
}
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {

View File

@@ -78,7 +78,7 @@ class ServicesGenerate extends Command
if ($logo->count() > 0) {
$logo = str($logo[0])->after('# logo:')->trim()->value();
} else {
$logo = 'svgs/unknown.svg';
$logo = 'svgs/coolify.png';
}
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
if ($minversion->count() > 0) {

View File

@@ -94,7 +94,9 @@ class SshMultiplexingHelper
$muxPersistTime = config('constants.ssh.mux_persist_time');
$scp_command = "timeout $timeout scp ";
if ($server->isIpv6()) {
$scp_command .= '-6 ';
}
if (self::isMultiplexingEnabled()) {
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
@@ -136,8 +138,8 @@ class SshMultiplexingHelper
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command);
$delimiter = base64_encode($delimiter);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL

View File

@@ -744,8 +744,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -842,8 +844,10 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->source_type = $githubApp->getMorphClass();
$application->source_id = $githubApp->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -936,8 +940,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -1017,8 +1023,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1077,8 +1085,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1555,8 +1565,11 @@ class ApplicationsController extends Controller
$instantDeploy = $request->instant_deploy;
$use_build_server = $request->use_build_server;
$application->settings->is_build_server_enabled = $use_build_server;
$application->settings->save();
if (isset($use_build_server)) {
$application->settings->is_build_server_enabled = $use_build_server;
$application->settings->save();
}
removeUnnecessaryFieldsFromRequest($request);

View File

@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
@@ -24,7 +23,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -63,31 +61,32 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function __construct($backup)
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
}
public function handle(): void
{
try {
// Check if team is exists
if (is_null($this->team)) {
StopDatabase::run($this->database);
$this->database->delete();
$this->team = Team::find($this->backup->team_id);
if (! $this->team) {
$this->backup->delete();
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
if (is_null($this->server)) {
throw new \Exception('Server not found?!');
}
if (is_null($this->database)) {
throw new \Exception('Database not found?!');
}
BackupCreated::dispatch($this->team->id);
@@ -237,7 +236,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = 'coolify-db';
@@ -250,6 +248,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
try {
if (str($databaseType)->contains('postgres')) {
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/pg-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -278,6 +279,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mongodb($database);
} elseif (str($databaseType)->contains('mysql')) {
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/mysql-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -287,6 +291,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mysql($database);
} elseif (str($databaseType)->contains('mariadb')) {
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/mariadb-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -325,7 +332,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
throw $e;
} finally {
BackupCreated::dispatch($this->team->id);
if ($this->team) {
BackupCreated::dispatch($this->team->id);
}
}
}
@@ -384,7 +393,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->postgres_password) {
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
}
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
if ($this->backup->dump_all) {
$backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
} else {
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
}
$commands[] = $backupCommand;
ray($commands);
@@ -405,8 +418,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
ray($commands);
if ($this->backup->dump_all) {
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
}
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
@@ -424,7 +440,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
if ($this->backup->dump_all) {
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
}
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
@@ -466,34 +486,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
// private function upload_to_s3(): void
// {
// try {
// if (is_null($this->s3)) {
// return;
// }
// $key = $this->s3->key;
// $secret = $this->s3->secret;
// // $region = $this->s3->region;
// $bucket = $this->s3->bucket;
// $endpoint = $this->s3->endpoint;
// $this->s3->testConnection(shouldSave: true);
// $configName = new Cuid2;
// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
// instant_remote_process($commands, $this->server);
// $this->add_to_backup_output('Uploaded to S3.');
// } catch (\Throwable $e) {
// $this->add_to_backup_output($e->getMessage());
// throw $e;
// } finally {
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
// instant_remote_process($removeConfigCommands, $this->server, false);
// }
// }
private function upload_to_s3(): void
{
try {
@@ -515,10 +507,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->ensureHelperImageAvailable();
$fullImageName = $this->getFullImageName();
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
if (isDev()) {
if ($this->database->name === 'coolify-db') {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
} else {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
}
} else {
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
}
if ($this->s3->isHetzner()) {
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
} else {
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
}
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
$this->add_to_backup_output('Uploaded to S3.');
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());

View File

@@ -30,7 +30,7 @@ class Dashboard extends Component
public function cleanup_queue()
{
Artisan::queue('cleanup:application-deployment-queue', [
Artisan::queue('cleanup:deployment-queue', [
'--team-id' => currentTeam()->id,
]);
}

View File

@@ -61,6 +61,7 @@ class Help extends Component
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
}
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
$this->reset('description', 'subject');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -31,6 +31,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
'backup.dump_all' => 'required|boolean',
];
protected $validationAttributes = [
@@ -40,6 +41,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
'backup.dump_all' => 'Backup All Databases',
];
protected $messages = [

View File

@@ -26,7 +26,7 @@ class ScheduledBackups extends Component
public function mount(): void
{
if ($this->selectedBackupId) {
$this->setSelectedBackup($this->selectedBackupId);
$this->setSelectedBackup($this->selectedBackupId, true);
}
$this->parameters = get_route_parameters();
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
@@ -37,10 +37,13 @@ class ScheduledBackups extends Component
$this->s3s = currentTeam()->s3s;
}
public function setSelectedBackup($backupId)
public function setSelectedBackup($backupId, $force = false)
{
if ($this->selectedBackupId === $backupId && ! $force) {
return;
}
$this->selectedBackupId = $backupId;
$this->selectedBackup = $this->database->scheduledBackups->find($this->selectedBackupId);
$this->selectedBackup = $this->database->scheduledBackups->find($backupId);
if (is_null($this->selectedBackup)) {
$this->selectedBackupId = null;
}

File diff suppressed because one or more lines are too long

View File

@@ -108,6 +108,21 @@ class Navbar extends Component
return;
}
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id);
}
public function pullAndRestartEvent()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return;
}
PullImage::run($this->service);
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();

View File

@@ -48,14 +48,6 @@ class Add extends Component
public function submit()
{
$this->validate();
// if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
// $type = str($this->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
$this->dispatch('saveKey', [
'key' => $this->key,
'value' => $this->value,

View File

@@ -53,30 +53,16 @@ class All extends Component
public function sortEnvironmentVariables()
{
if ($this->resource->type() === 'application') {
$this->resource->load(['environment_variables', 'environment_variables_preview']);
} else {
$this->resource->load(['environment_variables']);
if (! data_get($this->resource, 'settings.is_env_sorting_enabled')) {
if ($this->resource->environment_variables) {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values();
}
if ($this->resource->environment_variables_preview) {
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('order')->values();
}
}
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
$sortFunction = function ($variables) use ($sortBy) {
if (! $variables) {
return $variables;
}
if ($sortBy === 'key') {
return $variables->sortBy(function ($item) {
return strtolower($item->key);
}, SORT_NATURAL | SORT_FLAG_CASE)->values();
} else {
return $variables->sortBy('order')->values();
}
};
$this->resource->environment_variables = $sortFunction($this->resource->environment_variables);
$this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview);
$this->getDevView();
}
@@ -121,6 +107,8 @@ class All extends Component
$this->sortEnvironmentVariables();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->refreshEnvs();
}
}

View File

@@ -34,9 +34,9 @@ class Terminal extends Component
if ($status !== 'running') {
return;
}
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
} else {
$command = SshMultiplexingHelper::generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
}
// ssh command is sent back to frontend then to websocket

View File

@@ -15,6 +15,8 @@ class ApiTokens extends Component
public bool $readOnly = true;
public bool $rootAccess = false;
public array $permissions = ['read-only'];
public $isApiEnabled;
@@ -35,12 +37,11 @@ class ApiTokens extends Component
if ($this->viewSensitiveData) {
$this->permissions[] = 'view:sensitive';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
}
if (count($this->permissions) == 0) {
$this->permissions = ['*'];
}
$this->makeSureOneIsSelected();
}
public function updatedReadOnly()
@@ -48,11 +49,30 @@ class ApiTokens extends Component
if ($this->readOnly) {
$this->permissions[] = 'read-only';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['read-only']);
}
if (count($this->permissions) == 0) {
$this->makeSureOneIsSelected();
}
public function updatedRootAccess()
{
if ($this->rootAccess) {
$this->permissions = ['*'];
$this->readOnly = false;
$this->viewSensitiveData = false;
} else {
$this->readOnly = true;
$this->permissions = ['read-only'];
}
}
public function makeSureOneIsSelected()
{
if (count($this->permissions) == 0) {
$this->permissions = ['read-only'];
$this->readOnly = true;
}
}
@@ -62,12 +82,6 @@ class ApiTokens extends Component
$this->validate([
'description' => 'required|min:3|max:255',
]);
// if ($this->viewSensitiveData) {
// $this->permissions[] = 'view:sensitive';
// }
// if ($this->readOnly) {
// $this->permissions[] = 'read-only';
// }
$token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);

View File

@@ -30,6 +30,11 @@ class ConfigureCloudflareTunnels extends Component
public function submit()
{
try {
if (str($this->ssh_domain)->contains('https://')) {
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
// remove / from the end
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
}
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
$server->settings->is_cloudflare_tunnel = true;

View File

@@ -4,7 +4,7 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Proxy\CheckProxy;
use App\Jobs\ContainerStatusJob;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -44,7 +44,10 @@ class Status extends Component
}
$this->numberOfPolls++;
}
CheckProxy::run($this->server, true);
$shouldStart = CheckProxy::run($this->server, true);
if ($shouldStart) {
StartProxy::run($this->server, false);
}
$this->dispatch('proxyStatusUpdated');
if ($this->server->proxy->status === 'running') {
$this->polling = false;

View File

@@ -43,15 +43,17 @@ class Create extends Component
'endpoint' => 'Endpoint',
];
public function mount()
public function updatedEndpoint($value)
{
if (isDev()) {
$this->name = 'Local MinIO';
$this->description = 'Local MinIO';
$this->key = 'minioadmin';
$this->secret = 'minioadmin';
$this->bucket = 'local';
$this->endpoint = 'http://coolify-minio:9000';
if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
$this->endpoint = 'https://'.$value;
$value = $this->endpoint;
}
if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
$this->bucket = str($value)->after('//')->before('.');
} elseif (str($value)->contains('your-objectstorage.com')) {
$this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
}
}

View File

@@ -143,6 +143,9 @@ class Application extends BaseModel
}
$application->tags()->detach();
$application->previews()->delete();
foreach ($application->deployment_queue as $deployment) {
$deployment->delete();
}
});
}
@@ -710,6 +713,11 @@ class Application extends BaseModel
return $this->hasMany(ApplicationPreview::class);
}
public function deployment_queue()
{
return $this->hasMany(ApplicationDeploymentQueue::class);
}
public function destination()
{
return $this->morphTo();

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use OpenApi\Attributes as OA;
@@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
public function application(): Attribute
{
return Attribute::make(
get: fn () => Application::find($this->application_id),
);
}
public function server(): Attribute
{
return Attribute::make(
get: fn () => Server::find($this->server_id),
);
}
public function setStatus(string $status)
{
$this->update([

View File

@@ -24,9 +24,11 @@ class Project extends BaseModel
{
protected $guarded = [];
protected $appends = ['default_environment'];
public static function ownedByCurrentTeam()
{
return Project::whereTeamId(currentTeam()->id)->orderBy('name');
return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)');
}
protected static function booted()
@@ -131,7 +133,7 @@ class Project extends BaseModel
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
}
public function default_environment()
public function getDefaultEnvironmentAttribute()
{
$default = $this->environments()->where('name', 'production')->first();
if ($default) {

View File

@@ -40,6 +40,16 @@ class S3Storage extends BaseModel
return "{$this->endpoint}/{$this->bucket}";
}
public function isHetzner()
{
return str($this->endpoint)->contains('your-objectstorage.com');
}
public function isDigitalOcean()
{
return str($this->endpoint)->contains('digitaloceanspaces.com');
}
public function testConnection(bool $shouldSave = false)
{
try {

View File

@@ -448,11 +448,19 @@ $schema://$host {
// Should move everything except /caddy and /nginx to /traefik
// The code needs to be modified as well, so maybe it does not worth it
if ($proxyType === ProxyTypes::TRAEFIK->value) {
$proxy_path = $proxy_path;
// Do nothing
} elseif ($proxyType === ProxyTypes::CADDY->value) {
$proxy_path = $proxy_path.'/caddy';
if (isDev()) {
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
} else {
$proxy_path = $proxy_path.'/caddy';
}
} elseif ($proxyType === ProxyTypes::NGINX->value) {
$proxy_path = $proxy_path.'/nginx';
if (isDev()) {
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
} else {
$proxy_path = $proxy_path.'/nginx';
}
}
return $proxy_path;
@@ -460,15 +468,6 @@ $schema://$host {
public function proxyType()
{
// $proxyType = $this->proxy->get('type');
// if ($proxyType === ProxyTypes::NONE->value) {
// return $proxyType;
// }
// if (is_null($proxyType)) {
// $this->proxy->type = ProxyTypes::TRAEFIK->value;
// $this->proxy->status = ProxyStatus::EXITED->value;
// $this->save();
// }
return data_get($this->proxy, 'type');
}
@@ -1221,4 +1220,9 @@ $schema://$host {
return instant_remote_process($commands, $this, false);
}
public function isIpv6(): bool
{
return str($this->ip)->contains(':');
}
}

View File

@@ -283,9 +283,147 @@ class Service extends BaseModel
$fields = collect([]);
$applications = $this->applications()->get();
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
$image = str($application->image)->before(':');
if ($image->isEmpty()) {
continue;
}
switch ($image) {
case str($image)?->contains('rabbitmq'):
case $image->contains('label-studio'):
$data = collect([]);
$username = $this->environment_variables()->where('key', 'LABEL_STUDIO_USERNAME')->first();
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LABELSTUDIO')->first();
if ($username) {
$data = $data->merge([
'Username' => [
'key' => 'LABEL_STUDIO_USERNAME',
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
}
if ($password) {
$data = $data->merge([
'Password' => [
'key' => 'LABEL_STUDIO_PASSWORD',
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Label Studio', $data->toArray());
break;
case $image->contains('litellm'):
$data = collect([]);
$username = $this->environment_variables()->where('key', 'SERVICE_USER_UI')->first();
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UI')->first();
if ($username) {
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
}
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Litellm', $data->toArray());
break;
case $image->contains('langfuse'):
$data = collect([]);
$email = $this->environment_variables()->where('key', 'LANGFUSE_INIT_USER_EMAIL')->first();
if ($email) {
$data = $data->merge([
'Admin Email' => [
'key' => 'LANGFUSE_INIT_USER_EMAIL',
'value' => data_get($email, 'value'),
'rules' => 'required|email',
],
]);
}
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LANGFUSE')->first();
ray('password', $password);
if ($password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'LANGFUSE_INIT_USER_PASSWORD',
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Langfuse', $data->toArray());
break;
case $image->contains('invoiceninja'):
$data = collect([]);
$email = $this->environment_variables()->where('key', 'IN_USER_EMAIL')->first();
$data = $data->merge([
'Email' => [
'key' => 'IN_USER_EMAIL',
'value' => data_get($email, 'value'),
'rules' => 'required|email',
],
]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_INVOICENINJAUSER')->first();
$data = $data->merge([
'Password' => [
'key' => 'IN_PASSWORD',
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
$fields->put('Invoice Ninja', $data->toArray());
break;
case $image->contains('argilla'):
$data = collect([]);
$api_key = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_APIKEY')->first();
$data = $data->merge([
'API Key' => [
'key' => data_get($api_key, 'key'),
'value' => data_get($api_key, 'value'),
'isPassword' => true,
'rules' => 'required',
],
]);
$data = $data->merge([
'API Key' => [
'key' => data_get($api_key, 'key'),
'value' => data_get($api_key, 'value'),
'isPassword' => true,
'rules' => 'required',
],
]);
$username = $this->environment_variables()->where('key', 'ARGILLA_USERNAME')->first();
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ARGILLA')->first();
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
$fields->put('Argilla', $data->toArray());
break;
case $image->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
@@ -320,7 +458,7 @@ class Service extends BaseModel
}
$fields->put('RabbitMQ', $data->toArray());
break;
case str($image)?->contains('tolgee'):
case $image->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
$data = $data->merge([
@@ -343,7 +481,7 @@ class Service extends BaseModel
}
$fields->put('Tolgee', $data->toArray());
break;
case str($image)?->contains('logto'):
case $image->contains('logto'):
$data = collect([]);
$logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first();
$logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
@@ -367,7 +505,7 @@ class Service extends BaseModel
}
$fields->put('Logto', $data->toArray());
break;
case str($image)?->contains('unleash-server'):
case $image->contains('unleash-server'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
$data = $data->merge([
@@ -390,7 +528,7 @@ class Service extends BaseModel
}
$fields->put('Unleash', $data->toArray());
break;
case str($image)?->contains('grafana'):
case $image->contains('grafana'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
$data = $data->merge([
@@ -413,7 +551,7 @@ class Service extends BaseModel
}
$fields->put('Grafana', $data->toArray());
break;
case str($image)?->contains('directus'):
case $image->contains('directus'):
$data = collect([]);
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
@@ -439,7 +577,7 @@ class Service extends BaseModel
}
$fields->put('Directus', $data->toArray());
break;
case str($image)?->contains('kong'):
case $image->contains('kong'):
$data = collect([]);
$dashboard_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
$dashboard_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
@@ -463,7 +601,7 @@ class Service extends BaseModel
]);
}
$fields->put('Supabase', $data->toArray());
case str($image)?->contains('minio'):
case $image->contains('minio'):
$data = collect([]);
$console_url = $this->environment_variables()->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
$s3_api_url = $this->environment_variables()->where('key', 'MINIO_SERVER_URL')->first();
@@ -516,7 +654,7 @@ class Service extends BaseModel
$fields->put('MinIO', $data->toArray());
break;
case str($image)?->contains('weblate'):
case $image->contains('weblate'):
$data = collect([]);
$admin_email = $this->environment_variables()->where('key', 'WEBLATE_ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_WEBLATE')->first();
@@ -542,7 +680,7 @@ class Service extends BaseModel
}
$fields->put('Weblate', $data->toArray());
break;
case str($image)?->contains('meilisearch'):
case $image->contains('meilisearch'):
$data = collect([]);
$SERVICE_PASSWORD_MEILISEARCH = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MEILISEARCH')->first();
if ($SERVICE_PASSWORD_MEILISEARCH) {
@@ -556,7 +694,7 @@ class Service extends BaseModel
}
$fields->put('Meilisearch', $data->toArray());
break;
case str($image)?->contains('ghost'):
case $image->contains('ghost'):
$data = collect([]);
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
$MAIL_OPTIONS_AUTH_USER = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_USER')->first();
@@ -616,45 +754,8 @@ class Service extends BaseModel
$fields->put('Ghost', $data->toArray());
break;
default:
$data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
// Chaskiq
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) {
$data = $data->merge([
'User' => [
'key' => 'SERVICE_USER_ADMIN',
'value' => data_get($admin_user, 'value', 'admin'),
'readonly' => true,
'rules' => 'required',
],
]);
}
if ($admin_password) {
$data = $data->merge([
'Password' => [
'key' => 'SERVICE_PASSWORD_ADMIN',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
if ($admin_email) {
$data = $data->merge([
'Email' => [
'key' => 'ADMIN_EMAIL',
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
]);
}
$fields->put('Admin', $data->toArray());
break;
case str($image)?->contains('vaultwarden'):
case $image->contains('vaultwarden'):
$data = collect([]);
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
@@ -720,7 +821,7 @@ class Service extends BaseModel
$fields->put('Vaultwarden', $data);
break;
case str($image)->contains('gitlab/gitlab'):
case $image->contains('gitlab/gitlab'):
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GITLAB')->first();
$data = collect([]);
if ($password) {
@@ -744,7 +845,7 @@ class Service extends BaseModel
$fields->put('GitLab', $data->toArray());
break;
case str($image)->contains('code-server'):
case $image->contains('code-server'):
$data = collect([]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first();
if ($password) {
@@ -770,14 +871,78 @@ class Service extends BaseModel
}
$fields->put('Code Server', $data->toArray());
break;
case $image->contains('elestio/strapi'):
$data = collect([]);
$license = $this->environment_variables()->where('key', 'STRAPI_LICENSE')->first();
if ($license) {
$data = $data->merge([
'License' => [
'key' => data_get($license, 'key'),
'value' => data_get($license, 'value'),
],
]);
}
$nodeEnv = $this->environment_variables()->where('key', 'NODE_ENV')->first();
if ($nodeEnv) {
$data = $data->merge([
'Node Environment' => [
'key' => data_get($nodeEnv, 'key'),
'value' => data_get($nodeEnv, 'value'),
],
]);
}
$fields->put('Strapi', $data->toArray());
break;
default:
$data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
// Chaskiq
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) {
$data = $data->merge([
'User' => [
'key' => 'SERVICE_USER_ADMIN',
'value' => data_get($admin_user, 'value', 'admin'),
'readonly' => true,
'rules' => 'required',
],
]);
}
if ($admin_password) {
$data = $data->merge([
'Password' => [
'key' => 'SERVICE_PASSWORD_ADMIN',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
if ($admin_email) {
$data = $data->merge([
'Email' => [
'key' => 'ADMIN_EMAIL',
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
]);
}
$fields->put('Admin', $data->toArray());
break;
}
}
$databases = $this->databases()->get();
foreach ($databases as $database) {
$image = str($database->image)->before(':')->value();
$image = str($database->image)->before(':');
if ($image->isEmpty()) {
continue;
}
switch ($image) {
case str($image)->contains('postgres'):
case $image->contains('postgres'):
$userVariables = ['SERVICE_USER_POSTGRES', 'SERVICE_USER_POSTGRESQL'];
$passwordVariables = ['SERVICE_PASSWORD_POSTGRES', 'SERVICE_PASSWORD_POSTGRESQL'];
$dbNameVariables = ['POSTGRESQL_DATABASE', 'POSTGRES_DB'];
@@ -815,7 +980,7 @@ class Service extends BaseModel
}
$fields->put('PostgreSQL', $data->toArray());
break;
case str($image)->contains('mysql'):
case $image->contains('mysql'):
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
@@ -865,7 +1030,7 @@ class Service extends BaseModel
}
$fields->put('MySQL', $data->toArray());
break;
case str($image)->contains('mariadb'):
case $image->contains('mariadb'):
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
@@ -1052,12 +1217,12 @@ class Service extends BaseModel
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function environment_variables_preview(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function workdir()
@@ -1108,7 +1273,6 @@ class Service extends BaseModel
$real_value = escapeEnvVariables($env->real_value);
}
}
ray("echo \"{$env->key}={$real_value}\" >> .env");
$commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
}
}

View File

@@ -112,4 +112,9 @@ class ServiceApplication extends BaseModel
{
getFilesystemVolumesFromServer($this, $isInit);
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -115,4 +115,13 @@ class ServiceDatabase extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function isBackupSolutionAvailable()
{
return str($this->databaseType())->contains('mysql') ||
str($this->databaseType())->contains('postgres') ||
str($this->databaseType())->contains('postgis') ||
str($this->databaseType())->contains('mariadb') ||
str($this->databaseType())->contains('mongodb');
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneClickhouse extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneDragonfly extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneKeydb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -294,4 +294,9 @@ class StandaloneMariadb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -314,4 +314,9 @@ class StandaloneMongodb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -295,4 +295,9 @@ class StandaloneMysql extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -296,4 +296,9 @@ class StandalonePostgresql extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}

View File

@@ -290,4 +290,9 @@ class StandaloneRedis extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}

View File

@@ -20,12 +20,16 @@ const RESTART_MODE = 'unless-stopped';
const DATABASE_DOCKER_IMAGES = [
'bitnami/mariadb',
'bitnami/mongodb',
'bitnami/mysql',
'bitnami/postgresql',
'bitnami/redis',
'mysql',
'bitnami/mysql',
'mysql/mysql-server',
'mariadb',
'postgis/postgis',
'postgres',
'bitnami/postgresql',
'supabase/postgres',
'elestio/postgres',
'mongo',
'redis',
'memcached',
@@ -33,7 +37,6 @@ const DATABASE_DOCKER_IMAGES = [
'neo4j',
'influxdb',
'clickhouse/clickhouse-server',
'supabase/postgres',
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',

View File

@@ -325,38 +325,16 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
$basic_auth = false;
$basic_auth_middleware = null;
$redirect = false;
$redirect_middleware = null;
$middlewares_from_labels = collect([]);
if ($serviceLabels) {
$basic_auth = $serviceLabels->contains(function ($value) {
return str_contains($value, 'basicauth');
});
if ($basic_auth) {
$basic_auth_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.basicauth\.users/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
$redirect = $serviceLabels->contains(function ($value) {
return str_contains($value, 'redirectregex');
});
if ($redirect) {
$redirect_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.redirectregex\.regex/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
$middlewares_from_labels = $serviceLabels->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)(\.|$)/', $item, $matches)) {
return $matches[1];
}
return null;
})->filter()
->unique();
}
foreach ($domains as $loop => $domain) {
try {
@@ -404,20 +382,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
}
if ($path !== '/') {
// Middleware handling
$middlewares = collect([]);
if ($is_stripprefix_enabled && ! str($image)->contains('ghost')) {
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -425,10 +398,13 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_non_www);
$middlewares->push($to_non_www_name);
}
if ($redirect_direction === 'www' && ! str($host)->startsWith('www.')) {
if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) {
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -437,13 +413,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$middlewares = collect([]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -455,6 +425,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -490,12 +463,6 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -507,6 +474,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@@ -516,12 +486,6 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -533,6 +497,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");

View File

@@ -1,14 +1,11 @@
<?php
use App\Models\S3Storage;
use Illuminate\Support\Str;
function set_s3_target(S3Storage $s3)
{
$is_digital_ocean = false;
if ($s3->endpoint) {
$is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com');
}
config()->set('filesystems.disks.custom-s3', [
'driver' => 's3',
'region' => $s3['region'],
@@ -17,7 +14,7 @@ function set_s3_target(S3Storage $s3)
'bucket' => $s3['bucket'],
'endpoint' => $s3['endpoint'],
'use_path_style_endpoint' => true,
'bucket_endpoint' => $is_digital_ocean,
'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(),
'aws_url' => $s3->awsUrl(),
]);
}

View File

@@ -522,6 +522,11 @@ function sslip(Server $server)
function get_service_templates(bool $force = false): Collection
{
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
return collect(json_decode($services))->sortKeys();
}
if ($force) {
try {
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
@@ -708,7 +713,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -758,7 +765,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -824,6 +833,31 @@ function convertToArray($collection)
return $collection;
}
function parseCommandFromMagicEnvVariable(Str|string $key): Stringable
{
$value = str($key);
$count = substr_count($value->value(), '_');
if ($count === 2) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
} else {
// SERVICE_BASE64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
if ($count === 3) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI_1000
$command = $value->after('SERVICE_')->before('_');
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
return str($command);
}
function parseEnvVariable(Str|string $value)
{
$value = str($value);
@@ -855,6 +889,7 @@ function parseEnvVariable(Str|string $value)
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
ray($command);
}
}
}
@@ -1184,14 +1219,16 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
if (! str(trim($line))->startsWith([
'cd',
'command',
'echo',
'true',
'if',
'fi',
])) {
if (
! str(trim($line))->startsWith([
'cd',
'command',
'echo',
'true',
'if',
'fi',
])
) {
return "sudo $line";
}
@@ -1606,7 +1643,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -2521,7 +2560,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -2982,11 +3023,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$predefinedPort = '8000';
}
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
$savedService = ServiceDatabase::firstOrCreate([
'name' => $applicationFound->name,
'image' => $applicationFound->image,
'service_id' => $applicationFound->service_id,
]);
$applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3096,7 +3148,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
foreach ($magicEnvironments as $key => $value) {
$key = str($key);
$value = replaceVariables($value);
$command = $key->after('SERVICE_')->before('_');
$command = parseCommandFromMagicEnvVariable($key);
$found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first();
if ($found) {
continue;
@@ -3207,12 +3259,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
$savedService = ServiceDatabase::firstOrCreate([
'name' => $applicationFound->name,
'image' => $applicationFound->image,
'service_id' => $applicationFound->service_id,
]);
$applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3643,6 +3707,18 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
});
}
$serviceLabels = $labels->merge($defaultLabels);
if ($serviceLabels->count() > 0) {
if ($isApplication) {
$isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled');
} else {
$isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled');
}
if ($isContainerLabelEscapeEnabled) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
}
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if ($isApplication) {
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
@@ -3863,14 +3939,19 @@ function convertComposeEnvironmentToArray($environment)
{
$convertedServiceVariables = collect([]);
if (isAssociativeArray($environment)) {
// Example: $environment = ['FOO' => 'bar', 'BAZ' => 'qux'];
if ($environment instanceof Collection) {
$changedEnvironment = collect([]);
$environment->each(function ($value, $key) use ($changedEnvironment) {
$parts = explode('=', $value, 2);
if (count($parts) === 2) {
$key = $parts[0];
$realValue = $parts[1] ?? '';
$changedEnvironment->put($key, $realValue);
if (is_numeric($key)) {
$parts = explode('=', $value, 2);
if (count($parts) === 2) {
$key = $parts[0];
$realValue = $parts[1] ?? '';
$changedEnvironment->put($key, $realValue);
} else {
$changedEnvironment->put($key, $value);
}
} else {
$changedEnvironment->put($key, $value);
}
@@ -3880,12 +3961,15 @@ function convertComposeEnvironmentToArray($environment)
}
$convertedServiceVariables = $environment;
} else {
// Example: $environment = ['FOO=bar', 'BAZ=qux'];
foreach ($environment as $value) {
$parts = explode('=', $value, 2);
$key = $parts[0];
$realValue = $parts[1] ?? '';
if ($key) {
$convertedServiceVariables->put($key, $realValue);
if (is_string($value)) {
$parts = explode('=', $value, 2);
$key = $parts[0];
$realValue = $parts[1] ?? '';
if ($key) {
$convertedServiceVariables->put($key, $realValue);
}
}
}
}

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.348',
'release' => '4.0.0-beta.357',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.348';
return '4.0.0-beta.357';

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('scheduled_database_backups', function (Blueprint $table) {
$table->boolean('dump_all')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('scheduled_database_backups', function (Blueprint $table) {
$table->dropColumn('dump_all');
});
}
};

View File

@@ -58,6 +58,7 @@ services:
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
vite:
image: node:20
pull_policy: always

View File

@@ -113,7 +113,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.2'
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"

View File

@@ -1,9 +1,27 @@
FROM quay.io/soketi/soketi:1.6-16-alpine
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2024.4.1
WORKDIR /terminal
RUN apk add --no-cache openssh-client make g++ python3
RUN apk add --no-cache openssh-client make g++ python3 curl
COPY docker/coolify-realtime/package.json ./
RUN npm i
RUN npm rebuild node-pty --update-binary
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
echo 'amd64' && \
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
echo 'arm64' && \
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]

View File

@@ -1,11 +1,19 @@
#!/bin/sh
# Function to timestamp logs
# Check if the first argument is 'watch'
if [ "$1" = "watch" ]; then
WATCH_MODE="--watch"
else
WATCH_MODE=""
fi
timestamp() {
date "+%Y-%m-%d %H:%M:%S"
}
# Start the terminal server in the background with logging
node /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
node $WATCH_MODE /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
TERMINAL_PID=$!
# Start the Soketi process in the background with logging

View File

@@ -61,9 +61,13 @@ wss.on('connection', (ws) => {
const userSession = { ws, userId, ptyProcess: null, isActive: false };
userSessions.set(userId, userSession);
ws.on('message', (message) => handleMessage(userSession, message));
ws.on('message', (message) => {
handleMessage(userSession, message);
});
ws.on('error', (err) => handleError(err, userId));
ws.on('close', () => handleClose(userId));
});
const messageHandlers = {
@@ -108,7 +112,6 @@ function parseMessage(message) {
async function handleCommand(ws, command, userId) {
const userSession = userSessions.get(userId);
if (userSession && userSession.isActive) {
const result = await killPtyProcess(userId);
if (!result) {
@@ -127,6 +130,7 @@ async function handleCommand(ws, command, userId) {
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: {},
};
// NOTE: - Initiates a process within the Terminal container
@@ -139,13 +143,16 @@ async function handleCommand(ws, command, userId) {
ws.send('pty-ready');
ptyProcess.onData((data) => ws.send(data));
ptyProcess.onData((data) => {
ws.send(data);
});
// when parent closes
ptyProcess.onExit(({ exitCode, signal }) => {
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
ws.send('pty-exited');
userSession.isActive = false;
});
if (timeout) {
@@ -179,7 +186,7 @@ async function killPtyProcess(userId) {
// session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098
// patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947
session.ptyProcess.write('kill -TERM -$$ && exit\n');
session.ptyProcess.write('set +o history\nkill -TERM -$$ && exit\nset -o history\n');
setTimeout(() => {
if (!session.isActive || !session.ptyProcess) {
@@ -228,5 +235,5 @@ function extractHereDocContent(commandString) {
}
server.listen(6002, () => {
console.log('Server listening on port 6002');
console.log('Coolify realtime terminal server listening on port 6002. Let the hacking begin!');
});

View File

@@ -46,6 +46,9 @@ services:
- PUSHER_APP_ID
- PUSHER_APP_KEY
- PUSHER_APP_SECRET
- TERMINAL_PROTOCOL
- TERMINAL_HOST
- TERMINAL_PORT
- AUTOUPDATE
- SELF_HOSTED
- SSH_MUX_ENABLED
@@ -110,7 +113,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.2'
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"

View File

@@ -1,16 +1,16 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.348"
"version": "4.0.0-beta.354"
},
"nightly": {
"version": "4.0.0-beta.349"
"version": "4.0.0-beta.355"
},
"helper": {
"version": "1.0.1"
"version": "1.0.2"
},
"realtime": {
"version": "1.0.2"
"version": "1.0.3"
}
}
}

9
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"alpinejs": "3.14.0",
"cookie": "^0.6.0",
"cookie": "^0.7.0",
"dotenv": "^16.4.5",
"ioredis": "5.4.1",
"node-pty": "^1.0.0",
@@ -960,10 +960,9 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"license": "MIT",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.0.tgz",
"integrity": "sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==",
"engines": {
"node": ">= 0.6"
}

View File

@@ -23,7 +23,7 @@
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"alpinejs": "3.14.0",
"cookie": "^0.6.0",
"cookie": "^0.7.0",
"dotenv": "^16.4.5",
"ioredis": "5.4.1",
"node-pty": "^1.0.0",

166
public/svgs/anythingllm.svg Normal file
View File

@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 245.00 245.00">
<path stroke="#919393" stroke-width="2.00" fill="none" stroke-linecap="butt" vector-effect="non-scaling-stroke" d="
M 187.03 159.00
L 161.49 159.00
A 1.24 1.24 0.0 0 1 160.48 158.48
Q 153.96 149.33 147.89 140.81
C 145.78 137.86 141.80 138.01 139.49 140.67
C 136.43 144.17 133.65 147.56 136.44 151.42
Q 142.22 159.39 148.22 167.98
Q 152.74 174.47 160.49 174.57
Q 173.94 174.73 188.79 174.60
C 196.64 174.52 203.24 168.72 203.23 160.63
Q 203.16 123.84 203.20 83.74
C 203.21 78.41 199.97 73.56 195.17 71.35
Q 191.90 69.85 186.23 69.92
Q 173.94 70.08 161.37 69.94
C 156.39 69.89 152.10 71.63 149.08 75.49
Q 117.03 116.46 84.45 158.09
A 2.37 2.35 -70.9 0 1 82.59 159.00
L 57.21 159.00
Q 56.65 159.00 56.65 158.45
L 56.66 85.95
Q 56.66 85.31 57.30 85.31
L 83.00 85.31
A 0.80 0.79 71.0 0 1 83.63 85.62
Q 90.51 94.60 97.61 104.71
C 99.47 107.36 103.07 108.21 105.48 105.83
Q 107.60 103.72 109.70 100.67
C 110.60 99.36 110.38 97.79 110.28 96.30
Q 110.23 95.62 109.82 95.07
Q 103.03 85.94 96.91 77.34
C 93.63 72.73 89.66 69.98 84.18 70.00
Q 71.60 70.05 56.56 69.96
C 50.37 69.93 45.44 72.61 42.63 78.14
Q 41.19 80.99 41.25 87.27
Q 41.55 120.87 41.27 158.11
Q 41.23 162.77 42.32 165.57
Q 44.32 170.75 49.37 173.22
Q 52.63 174.81 60.03 174.72
Q 72.58 174.56 82.84 174.65
Q 91.13 174.73 95.47 169.18
Q 127.35 128.40 160.51 86.00
A 1.81 1.80 18.9 0 1 161.93 85.31
L 187.42 85.31
A 0.40 0.40 0.0 0 1 187.82 85.71
L 187.82 158.21
Q 187.82 159.00 187.03 159.00"
/>
<path fill="#222627" d="
M 46.00 0.00
L 198.80 0.00
Q 221.03 2.81 233.86 19.40
Q 244.84 33.61 244.80 52.00
Q 244.65 120.20 244.83 188.95
Q 244.86 199.26 243.39 205.30
Q 241.12 214.72 235.86 222.46
C 234.56 224.37 232.98 226.08 231.56 227.86
Q 229.92 229.91 227.88 231.54
C 226.00 233.04 224.26 234.66 222.24 236.01
Q 214.15 241.39 204.78 243.49
Q 198.73 244.86 188.67 244.83
Q 119.30 244.65 52.55 244.80
Q 32.84 244.84 18.81 233.38
Q 2.64 220.18 0.00 198.69
L 0.00 45.76
Q 1.42 35.66 4.33 29.79
Q 17.17 3.86 46.00 0.00
Z
M 187.03 159.00
L 161.49 159.00
A 1.24 1.24 0.0 0 1 160.48 158.48
Q 153.96 149.33 147.89 140.81
C 145.78 137.86 141.80 138.01 139.49 140.67
C 136.43 144.17 133.65 147.56 136.44 151.42
Q 142.22 159.39 148.22 167.98
Q 152.74 174.47 160.49 174.57
Q 173.94 174.73 188.79 174.60
C 196.64 174.52 203.24 168.72 203.23 160.63
Q 203.16 123.84 203.20 83.74
C 203.21 78.41 199.97 73.56 195.17 71.35
Q 191.90 69.85 186.23 69.92
Q 173.94 70.08 161.37 69.94
C 156.39 69.89 152.10 71.63 149.08 75.49
Q 117.03 116.46 84.45 158.09
A 2.37 2.35 -70.9 0 1 82.59 159.00
L 57.21 159.00
Q 56.65 159.00 56.65 158.45
L 56.66 85.95
Q 56.66 85.31 57.30 85.31
L 83.00 85.31
A 0.80 0.79 71.0 0 1 83.63 85.62
Q 90.51 94.60 97.61 104.71
C 99.47 107.36 103.07 108.21 105.48 105.83
Q 107.60 103.72 109.70 100.67
C 110.60 99.36 110.38 97.79 110.28 96.30
Q 110.23 95.62 109.82 95.07
Q 103.03 85.94 96.91 77.34
C 93.63 72.73 89.66 69.98 84.18 70.00
Q 71.60 70.05 56.56 69.96
C 50.37 69.93 45.44 72.61 42.63 78.14
Q 41.19 80.99 41.25 87.27
Q 41.55 120.87 41.27 158.11
Q 41.23 162.77 42.32 165.57
Q 44.32 170.75 49.37 173.22
Q 52.63 174.81 60.03 174.72
Q 72.58 174.56 82.84 174.65
Q 91.13 174.73 95.47 169.18
Q 127.35 128.40 160.51 86.00
A 1.81 1.80 18.9 0 1 161.93 85.31
L 187.42 85.31
A 0.40 0.40 0.0 0 1 187.82 85.71
L 187.82 158.21
Q 187.82 159.00 187.03 159.00
Z"
/>
<path fill="#ffffff" d="
M 187.82 158.21
L 187.82 85.71
A 0.40 0.40 0.0 0 0 187.42 85.31
L 161.93 85.31
A 1.81 1.80 18.9 0 0 160.51 86.00
Q 127.35 128.40 95.47 169.18
Q 91.13 174.73 82.84 174.65
Q 72.58 174.56 60.03 174.72
Q 52.63 174.81 49.37 173.22
Q 44.32 170.75 42.32 165.57
Q 41.23 162.77 41.27 158.11
Q 41.55 120.87 41.25 87.27
Q 41.19 80.99 42.63 78.14
C 45.44 72.61 50.37 69.93 56.56 69.96
Q 71.60 70.05 84.18 70.00
C 89.66 69.98 93.63 72.73 96.91 77.34
Q 103.03 85.94 109.82 95.07
Q 110.23 95.62 110.28 96.30
C 110.38 97.79 110.60 99.36 109.70 100.67
Q 107.60 103.72 105.48 105.83
C 103.07 108.21 99.47 107.36 97.61 104.71
Q 90.51 94.60 83.63 85.62
A 0.80 0.79 71.0 0 0 83.00 85.31
L 57.30 85.31
Q 56.66 85.31 56.66 85.95
L 56.65 158.45
Q 56.65 159.00 57.21 159.00
L 82.59 159.00
A 2.37 2.35 -70.9 0 0 84.45 158.09
Q 117.03 116.46 149.08 75.49
C 152.10 71.63 156.39 69.89 161.37 69.94
Q 173.94 70.08 186.23 69.92
Q 191.90 69.85 195.17 71.35
C 199.97 73.56 203.21 78.41 203.20 83.74
Q 203.16 123.84 203.23 160.63
C 203.24 168.72 196.64 174.52 188.79 174.60
Q 173.94 174.73 160.49 174.57
Q 152.74 174.47 148.22 167.98
Q 142.22 159.39 136.44 151.42
C 133.65 147.56 136.43 144.17 139.49 140.67
C 141.80 138.01 145.78 137.86 147.89 140.81
Q 153.96 149.33 160.48 158.48
A 1.24 1.24 0.0 0 0 161.49 159.00
L 187.03 159.00
Q 187.82 159.00 187.82 158.21
Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
public/svgs/argilla.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

15
public/svgs/bitcoin.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"
viewBox="0 0 4091.27 4091.73"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1421344023328">
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg width="215" height="90" viewBox="0 0 100 43" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_378_10860)"><rect x="2.70837" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor" /><rect x="7.2085" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor" /><rect x="11.7086" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor" /><rect x="16.2076" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor" /><rect x="20.7087" y="10.7502" width="2.24992" height="4.49985" rx="0.236664" fill="currentColor" /></g></svg>

After

Width:  |  Height:  |  Size: 642 B

BIN
public/svgs/coolify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
public/svgs/getoutline.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

11
public/svgs/homarr.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="484px" height="329px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:0.947" fill="#f95251" d="M 157.5,-0.5 C 158.833,-0.5 160.167,-0.5 161.5,-0.5C 200.102,3.59921 222.268,24.9325 228,63.5C 228.667,88.5 228.667,113.5 228,138.5C 222.962,132.265 216.629,130.265 209,132.5C 208.667,109.167 208.333,85.8333 208,62.5C 202.332,36.3319 186.165,21.9986 159.5,19.5C 141.783,20.273 127.949,27.9397 118,42.5C 112.924,38.3791 107.757,34.3791 102.5,30.5C 115.97,11.5969 134.303,1.26361 157.5,-0.5 Z"/></g>
<g><path style="opacity:0.947" fill="#f95251" d="M 321.5,-0.5 C 322.833,-0.5 324.167,-0.5 325.5,-0.5C 348.697,1.26361 367.03,11.5969 380.5,30.5C 375.243,34.3791 370.076,38.3791 365,42.5C 349.005,21.3908 328.505,15.2242 303.5,24C 287.107,31.7273 277.607,44.5606 275,62.5C 274.667,85.8333 274.333,109.167 274,132.5C 266.371,130.265 260.038,132.265 255,138.5C 254.333,113.5 254.333,88.5 255,63.5C 260.732,24.9325 282.898,3.59921 321.5,-0.5 Z"/></g>
<g><path style="opacity:0.984" fill="#f95251" d="M -0.5,139.5 C -0.5,137.167 -0.5,134.833 -0.5,132.5C 19.5,132.5 39.5,132.5 59.5,132.5C 51.7127,98.1844 43.3793,64.0177 34.5,30C 83.3876,36.5512 118.554,62.0512 140,106.5C 159.513,155.397 153.846,201.064 123,243.5C 114.899,253.77 105.399,262.437 94.5,269.5C 90.7735,258.059 87.6068,246.392 85,234.5C 39.0428,218.384 10.5428,186.717 -0.5,139.5 Z"/></g>
<g><path style="opacity:0.984" fill="#f95251" d="M 483.5,132.5 C 483.5,134.833 483.5,137.167 483.5,139.5C 472.457,186.717 443.957,218.384 398,234.5C 395.393,246.392 392.226,258.059 388.5,269.5C 351.514,243.037 332.514,206.871 331.5,161C 333.865,105.236 359.865,64.9024 409.5,40C 421.97,34.3953 434.97,31.0619 448.5,30C 439.621,64.0177 431.287,98.1844 423.5,132.5C 443.5,132.5 463.5,132.5 483.5,132.5 Z"/></g>
<g><path style="opacity:0.95" fill="#f95251" d="M 211.5,170.5 C 225.127,170.958 231.627,177.958 231,191.5C 226.507,203.825 218.007,207.659 205.5,203C 197.535,196.871 195.369,189.037 199,179.5C 201.917,174.637 206.083,171.637 211.5,170.5 Z"/></g>
<g><path style="opacity:0.949" fill="#f95251" d="M 265.5,170.5 C 280.848,170.68 287.348,178.347 285,193.5C 279.06,204.76 270.227,207.593 258.5,202C 249.176,192.625 249.176,183.292 258.5,174C 260.925,172.787 263.259,171.621 265.5,170.5 Z"/></g>
<g><path style="opacity:0.987" fill="#f95251" d="M 388.5,328.5 C 386.5,328.5 384.5,328.5 382.5,328.5C 285.992,327.966 189.325,326.966 92.5,325.5C 120.96,261.579 170.294,227.913 240.5,224.5C 299.804,226.887 345.304,252.887 377,302.5C 381.676,310.846 385.509,319.513 388.5,328.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/svgs/infisical.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

6
public/svgs/it-tools.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512px" height="512px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:0.995" fill="#18a057" d="M 230.5,-0.5 C 247.5,-0.5 264.5,-0.5 281.5,-0.5C 290.12,3.28885 296.287,9.62218 300,18.5C 299.871,28.5889 300.704,38.4222 302.5,48C 326.717,53.6219 349.217,63.1219 370,76.5C 376.469,71.0323 382.636,65.199 388.5,59C 398.925,52.0844 409.591,51.7511 420.5,58C 432,68.1667 442.833,79 453,90.5C 459,100.5 459,110.5 453,120.5C 447.017,127.317 440.851,133.984 434.5,140.5C 447.998,161.821 457.498,184.821 463,209.5C 474.144,210.648 485.31,211.815 496.5,213C 503.496,216.822 508.496,222.322 511.5,229.5C 511.5,246.5 511.5,263.5 511.5,280.5C 508.841,288.664 503.508,294.497 495.5,298C 484.573,299.444 473.573,300.277 462.5,300.5C 457.369,325.4 448.036,348.566 434.5,370C 441.693,377.526 448.526,385.359 455,393.5C 458.667,402.825 458.001,411.825 453,420.5C 443.167,430.333 433.333,440.167 423.5,450C 414.895,457.565 405.229,459.232 394.5,455C 386.322,448.153 378.155,441.32 370,434.5C 348.501,447.828 325.334,457.161 300.5,462.5C 301.01,473.958 300.177,485.291 298,496.5C 294.006,504.342 287.839,509.342 279.5,511.5C 263.167,511.5 246.833,511.5 230.5,511.5C 222.336,508.841 216.503,503.508 213,495.5C 212,484.833 211,474.167 210,463.5C 198.505,459.749 187.005,455.915 175.5,452C 173.842,451.275 173.342,450.108 174,448.5C 192.195,429.971 210.695,411.805 229.5,394C 293.836,401.915 343.336,378.748 378,324.5C 408.025,263.569 400.691,207.569 356,156.5C 315.267,118.147 267.767,106.314 213.5,121C 164.374,138.458 132.874,172.291 119,222.5C 117.485,228.075 116.485,233.742 116,239.5C 115.261,253.906 115.594,268.239 117,282.5C 98.8333,300.667 80.6667,318.833 62.5,337C 61.552,337.483 60.552,337.649 59.5,337.5C 54.8294,325.486 51.1627,313.153 48.5,300.5C 37.4331,300.188 26.4331,299.355 15.5,298C 7.189,293.843 1.85567,287.343 -0.5,278.5C -0.5,262.833 -0.5,247.167 -0.5,231.5C 2.22646,223.578 7.22646,217.411 14.5,213C 25.7307,211.098 37.0641,210.265 48.5,210.5C 53.718,185.511 63.0513,162.178 76.5,140.5C 71.2189,133.716 65.3855,127.383 59,121.5C 51.8558,110.353 52.1892,99.353 60,88.5C 69.8333,78.6667 79.6667,68.8333 89.5,59C 98.3002,53.1199 107.633,52.1199 117.5,56C 125.629,62.4604 133.463,69.2938 141,76.5C 162.028,62.9274 184.861,53.4274 209.5,48C 210.93,37.2282 212.097,26.3949 213,15.5C 216.503,7.49214 222.336,2.15881 230.5,-0.5 Z"/></g>
<g><path style="opacity:0.99" fill="#1d1d1d" d="M 52.5,511.5 C 46.5,511.5 40.5,511.5 34.5,511.5C 16.5,506.167 4.83333,494.5 -0.5,476.5C -0.5,470.167 -0.5,463.833 -0.5,457.5C 1.14258,451.876 3.64258,446.542 7,441.5C 55.9723,391.528 105.306,341.861 155,292.5C 141.187,245.876 152.02,206.042 187.5,173C 216.657,150.235 248.991,143.902 284.5,154C 289.202,158.922 290.702,164.755 289,171.5C 275,186.167 261,200.833 247,215.5C 234.564,236.922 238.73,254.422 259.5,268C 272.178,272.999 284.178,271.666 295.5,264C 309.596,248.899 324.596,234.899 340.5,222C 353.68,220.169 360.513,226.003 361,239.5C 365.554,290.573 345.387,328.073 300.5,352C 273.591,363.046 246.258,364.379 218.5,356C 169.472,405.361 120.139,454.361 70.5,503C 64.8398,506.712 58.8398,509.545 52.5,511.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/svgs/labelstudio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/svgs/langfuse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

1
public/svgs/litellm.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#939598" d="M0 34h36v2H0z"/><path fill="#D1D3D4" d="M3 35h33V6H21c-4 0-5 2-5 2l-4 6S0 19 0 24c0 4 6 6 6 6z"/><path fill="#231F20" d="m14 35 2-3h20v3z"/><path fill="#3B88C3" d="M0 23.999c0 4 6 6 6 6V17.125C3 19 0 21.499 0 23.999M6 30v-.001z"/><path fill="#269" d="m6 30-3 5h33v-5z"/><path fill="#3B88C3" d="m20 30 4-6h12v6z"/><path fill="#55ACEE" d="M26 8H16l-4 6h-.001 10.843c.477 0 1.108-.448 1.412-1l2.197-4c.303-.552.102-1-.451-1"/><path fill="#3B88C3" d="m25.902 10 .549-1c.303-.552.102-1-.451-1H16l-1.333 2z"/></svg>

After

Width:  |  Height:  |  Size: 593 B

View File

@@ -0,0 +1,8 @@
<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
<rect transform="matrix(1 0 -.16885 .98564 0 0)" x="2.6887" y="1.2563" width="14.457" height="14.667" fill="#581c87" style="paint-order:stroke fill markers"/>
<text x="0.74027044" y="12.170821" display="none" fill="#ffffff" font-family="'Liberation Sans'" font-size="10.583px" font-weight="bold" letter-spacing="-.66146px" stroke-width="4.2333" word-spacing="0px" xml:space="preserve"><tspan x="0.74027044" y="12.170821" fill="#ffffff" font-family="'Jost*'" font-size="10.583px" font-style="italic" font-weight="900" stroke-width="4.2333" style="paint-order:stroke fill markers">NP</tspan></text>
<g fill="#fff" stroke-width="4.2333" aria-label="NP">
<path d="m7.1008 4.7625-0.59266 3.3655-4.0322-3.7147-1.3229 7.7576h2.1907l0.58208-3.3655 4.0428 3.7147 1.3229-7.7576z" style="paint-order:stroke fill markers"/>
<path d="m10.027 4.7625-1.27 7.4083h2.2966l0.39158-2.3283h0.62442c1.9156-0.021167 3.2385-0.89958 3.3867-2.54 0.14817-1.6192-0.79375-2.5188-2.6564-2.54zm2.5717 1.8415c0.48683 0.010583 0.75141 0.26458 0.67733 0.68791-0.08467 0.45508-0.508 0.6985-1.016 0.6985h-0.49742l0.24342-1.3864z" style="paint-order:stroke fill markers"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

14
public/svgs/ollama.svg Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="4096px" height="4096px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:0.999" fill="#fefefe" d="M 1984.5,-0.5 C 2026.5,-0.5 2068.5,-0.5 2110.5,-0.5C 2537.4,15.5079 2922.4,149.008 3265.5,400C 3560.04,620.244 3781.2,898.744 3929,1235.5C 4031.4,1474.76 4086.9,1724.43 4095.5,1984.5C 4095.5,2026.5 4095.5,2068.5 4095.5,2110.5C 4079.61,2533.7 3948.11,2916.03 3701,3257.5C 3593.06,3404.12 3467.89,3534.62 3325.5,3649C 3314.27,3657.74 3302.94,3666.24 3291.5,3674.5C 3294.9,3533.77 3270.4,3398.11 3218,3267.5C 3213.27,3256.38 3208.27,3245.38 3203,3234.5C 3199.86,3228.11 3198.2,3221.44 3198,3214.5C 3233.37,3166.09 3260.04,3113.09 3278,3055.5C 3315.81,2931.9 3329.15,2805.57 3318,2676.5C 3312.19,2588.11 3293.86,2502.44 3263,2419.5C 3248.74,2385.4 3232.58,2352.23 3214.5,2320C 3228.7,2298.44 3242.54,2276.6 3256,2254.5C 3306.49,2163.1 3334.16,2065.1 3339,1960.5C 3347.09,1770.12 3293.75,1599.45 3179,1448.5C 3152.35,1414.84 3123.18,1383.34 3091.5,1354C 3073.96,1338.79 3056.13,1323.96 3038,1309.5C 3037.33,1303.83 3037.33,1298.17 3038,1292.5C 3071.33,1118.01 3070.66,943.681 3036,769.5C 3022.49,704.637 3000.82,642.637 2971,583.5C 2948.49,539.65 2918.65,501.484 2881.5,469C 2825.8,423.008 2762.8,409.008 2692.5,427C 2651.4,440.793 2615.9,463.293 2586,494.5C 2546.73,537.991 2515.73,586.991 2493,641.5C 2466.37,705.666 2447.04,771.999 2435,840.5C 2428.17,878.941 2423.17,917.608 2420,956.5C 2419.88,958.573 2418.88,959.907 2417,960.5C 2399.1,950.801 2381.27,940.967 2363.5,931C 2153.62,824.176 1943.62,823.842 1733.5,930C 1715.06,940.301 1696.56,950.467 1678,960.5C 1676.35,960.18 1675.35,959.18 1675,957.5C 1667.31,849.014 1643.31,744.347 1603,643.5C 1581.9,592.477 1553.57,546.144 1518,504.5C 1488.61,471.396 1453.44,446.562 1412.5,430C 1388.07,422.647 1363.07,419.147 1337.5,419.5C 1322.44,419.049 1307.78,421.215 1293.5,426C 1244.84,441.668 1204.68,469.502 1173,509.5C 1141.62,549.221 1116.62,592.887 1098,640.5C 1066.87,721.373 1047.53,805.04 1040,891.5C 1026.67,1023.93 1032.01,1155.6 1056,1286.5C 1057.33,1293.8 1058,1301.14 1058,1308.5C 887.035,1442.34 787.369,1617.67 759,1834.5C 747.069,1944.53 758.735,2051.86 794,2156.5C 807.02,2192.21 823.02,2226.54 842,2259.5C 854.715,2279.77 867.548,2299.93 880.5,2320C 836.781,2396.64 806.948,2478.47 791,2565.5C 783.711,2603.19 779.045,2641.19 777,2679.5C 776.333,2731.83 776.333,2784.17 777,2836.5C 782.248,2934.22 803.581,3028.22 841,3118.5C 856.12,3152.76 874.787,3184.76 897,3214.5C 897.611,3218.68 896.944,3222.68 895,3226.5C 830.268,3368.77 799.768,3518.1 803.5,3674.5C 556.783,3485.22 362.616,3252.55 221,2976.5C 83.5748,2704.13 9.74149,2415.47 -0.5,2110.5C -0.5,2068.5 -0.5,2026.5 -0.5,1984.5C 15.5079,1557.6 149.008,1172.6 400,829.5C 620.244,534.963 898.744,313.796 1235.5,166C 1474.76,63.6006 1724.43,8.10056 1984.5,-0.5 Z"/></g>
<g><path style="opacity:1" fill="#000000" d="M 3291.5,3674.5 C 3219.32,3730.61 3143.32,3781.28 3063.5,3826.5C 3086.34,3778.48 3098.17,3727.82 3099,3674.5C 3102.6,3547.82 3078.26,3426.82 3026,3311.5C 3012.26,3283.68 2998.26,3256.01 2984,3228.5C 2983.33,3211.83 2983.33,3195.17 2984,3178.5C 2986.77,3167.3 2991.43,3156.97 2998,3147.5C 3011.32,3131.52 3025.32,3116.18 3040,3101.5C 3055.7,3082.12 3068.37,3060.78 3078,3037.5C 3102.95,2974.82 3118.62,2909.82 3125,2842.5C 3135.67,2731.91 3124.67,2623.58 3092,2517.5C 3079.64,2478.12 3061.97,2441.45 3039,2407.5C 3031.38,2397.54 3023.38,2387.88 3015,2378.5C 2984.39,2332.41 2986.72,2288.07 3022,2245.5C 3066.42,2200.01 3099.09,2147.01 3120,2086.5C 3154.85,1979.03 3156.52,1871.03 3125,1762.5C 3089.97,1643.55 3024.14,1545.72 2927.5,1469C 2862.72,1419.8 2789.72,1390.13 2708.5,1380C 2686.83,1378.03 2665.16,1378.03 2643.5,1380C 2623.56,1383.05 2603.56,1385.55 2583.5,1387.5C 2544.15,1385.92 2515.31,1367.92 2497,1333.5C 2488.47,1313.24 2478.81,1293.57 2468,1274.5C 2451.43,1251.93 2434.1,1229.93 2416,1208.5C 2343.7,1135.91 2257.86,1085.74 2158.5,1058C 2140.99,1053.57 2123.33,1049.9 2105.5,1047C 2033.1,1038.03 1962.77,1046.03 1894.5,1071C 1825.56,1096.11 1763.56,1132.45 1708.5,1180C 1668.06,1216.42 1635.23,1258.58 1610,1306.5C 1603.43,1325.64 1593.77,1342.98 1581,1358.5C 1561.48,1377.23 1538.15,1386.57 1511,1386.5C 1493.39,1385.7 1475.89,1383.87 1458.5,1381C 1393.37,1374.82 1330.71,1384.49 1270.5,1410C 1181.85,1448.98 1109.68,1508.15 1054,1587.5C 957.404,1727.48 927.071,1880.82 963,2047.5C 983.321,2126.03 1021.65,2194.03 1078,2251.5C 1102.43,2283.23 1107.43,2317.89 1093,2355.5C 1089.44,2363.62 1085.11,2371.29 1080,2378.5C 1052.63,2408.91 1031.3,2443.25 1016,2481.5C 996.087,2532.48 982.42,2585.15 975,2639.5C 958.127,2755.12 965.46,2869.12 997,2981.5C 1005.9,3012.21 1017.9,3041.54 1033,3069.5C 1039.84,3080.51 1047.17,3091.18 1055,3101.5C 1069.68,3116.18 1083.68,3131.52 1097,3147.5C 1103.4,3157.04 1108.07,3167.37 1111,3178.5C 1111.67,3195.17 1111.67,3211.83 1111,3228.5C 1085.66,3274.84 1063.33,3322.51 1044,3371.5C 1005.13,3479.61 989.794,3590.94 998,3705.5C 1001.44,3737.36 1008.44,3768.36 1019,3798.5C 1022.7,3808.23 1027.2,3817.56 1032.5,3826.5C 952.208,3781.54 875.874,3730.87 803.5,3674.5C 799.768,3518.1 830.268,3368.77 895,3226.5C 896.944,3222.68 897.611,3218.68 897,3214.5C 874.787,3184.76 856.12,3152.76 841,3118.5C 803.581,3028.22 782.248,2934.22 777,2836.5C 776.333,2784.17 776.333,2731.83 777,2679.5C 779.045,2641.19 783.711,2603.19 791,2565.5C 806.948,2478.47 836.781,2396.64 880.5,2320C 867.548,2299.93 854.715,2279.77 842,2259.5C 823.02,2226.54 807.02,2192.21 794,2156.5C 758.735,2051.86 747.069,1944.53 759,1834.5C 787.369,1617.67 887.035,1442.34 1058,1308.5C 1058,1301.14 1057.33,1293.8 1056,1286.5C 1032.01,1155.6 1026.67,1023.93 1040,891.5C 1047.53,805.04 1066.87,721.373 1098,640.5C 1116.62,592.887 1141.62,549.221 1173,509.5C 1204.68,469.502 1244.84,441.668 1293.5,426C 1307.78,421.215 1322.44,419.049 1337.5,419.5C 1363.07,419.147 1388.07,422.647 1412.5,430C 1453.44,446.562 1488.61,471.396 1518,504.5C 1553.57,546.144 1581.9,592.477 1603,643.5C 1643.31,744.347 1667.31,849.014 1675,957.5C 1675.35,959.18 1676.35,960.18 1678,960.5C 1696.56,950.467 1715.06,940.301 1733.5,930C 1943.62,823.842 2153.62,824.176 2363.5,931C 2381.27,940.967 2399.1,950.801 2417,960.5C 2418.88,959.907 2419.88,958.573 2420,956.5C 2423.17,917.608 2428.17,878.941 2435,840.5C 2447.04,771.999 2466.37,705.666 2493,641.5C 2515.73,586.991 2546.73,537.991 2586,494.5C 2615.9,463.293 2651.4,440.793 2692.5,427C 2762.8,409.008 2825.8,423.008 2881.5,469C 2918.65,501.484 2948.49,539.65 2971,583.5C 3000.82,642.637 3022.49,704.637 3036,769.5C 3070.66,943.681 3071.33,1118.01 3038,1292.5C 3037.33,1298.17 3037.33,1303.83 3038,1309.5C 3056.13,1323.96 3073.96,1338.79 3091.5,1354C 3123.18,1383.34 3152.35,1414.84 3179,1448.5C 3293.75,1599.45 3347.09,1770.12 3339,1960.5C 3334.16,2065.1 3306.49,2163.1 3256,2254.5C 3242.54,2276.6 3228.7,2298.44 3214.5,2320C 3232.58,2352.23 3248.74,2385.4 3263,2419.5C 3293.86,2502.44 3312.19,2588.11 3318,2676.5C 3329.15,2805.57 3315.81,2931.9 3278,3055.5C 3260.04,3113.09 3233.37,3166.09 3198,3214.5C 3198.2,3221.44 3199.86,3228.11 3203,3234.5C 3208.27,3245.38 3213.27,3256.38 3218,3267.5C 3270.4,3398.11 3294.9,3533.77 3291.5,3674.5 Z"/></g>
<g><path style="opacity:1" fill="#fefefe" d="M 1343.5,613.5 C 1345.86,613.337 1348.19,613.503 1350.5,614C 1363.25,622.08 1374.08,632.247 1383,644.5C 1406.08,678.657 1424.41,715.323 1438,754.5C 1469.67,849.867 1486.01,947.867 1487,1048.5C 1487.67,1077.83 1487.67,1107.17 1487,1136.5C 1475.83,1153 1464.67,1169.5 1453.5,1186C 1407.83,1184.32 1362.5,1187.32 1317.5,1195C 1293.64,1199.88 1269.98,1205.55 1246.5,1212C 1244.5,1212.67 1242.5,1212.67 1240.5,1212C 1237.69,1200.67 1235.52,1189.17 1234,1177.5C 1220.98,1067.87 1222.98,958.541 1240,849.5C 1250.18,786.973 1269.18,727.306 1297,670.5C 1306.8,651.693 1319.3,634.86 1334.5,620C 1337.57,617.812 1340.57,615.645 1343.5,613.5 Z"/></g>
<g><path style="opacity:1" fill="#fefefe" d="M 2743.5,613.5 C 2746.98,613.184 2750.31,613.684 2753.5,615C 2767.13,624.964 2778.3,637.13 2787,651.5C 2807.31,685.784 2822.97,722.118 2834,760.5C 2858.32,848.257 2870.16,937.757 2869.5,1029C 2869.08,1081 2865.91,1132.83 2860,1184.5C 2858.99,1193.9 2857.16,1203.06 2854.5,1212C 2852.5,1212.67 2850.5,1212.67 2848.5,1212C 2813.95,1202.49 2778.95,1195.16 2743.5,1190C 2709.57,1187.05 2675.57,1185.71 2641.5,1186C 2630.33,1169.5 2619.17,1153 2608,1136.5C 2606.33,1076.74 2607.99,1017.07 2613,957.5C 2620.37,882.341 2637.04,809.341 2663,738.5C 2676.24,703.018 2693.9,670.018 2716,639.5C 2724.1,629.559 2733.27,620.893 2743.5,613.5 Z"/></g>
<g><path style="opacity:1" fill="#fefefe" d="M 3063.5,3826.5 C 2768.17,3994.26 2450.5,4083.93 2110.5,4095.5C 2068.5,4095.5 2026.5,4095.5 1984.5,4095.5C 1663.69,4084.63 1361.69,4003.8 1078.5,3853C 1062.94,3844.39 1047.61,3835.56 1032.5,3826.5C 1027.2,3817.56 1022.7,3808.23 1019,3798.5C 1008.44,3768.36 1001.44,3737.36 998,3705.5C 989.794,3590.94 1005.13,3479.61 1044,3371.5C 1063.33,3322.51 1085.66,3274.84 1111,3228.5C 1111.67,3211.83 1111.67,3195.17 1111,3178.5C 1108.07,3167.37 1103.4,3157.04 1097,3147.5C 1083.68,3131.52 1069.68,3116.18 1055,3101.5C 1047.17,3091.18 1039.84,3080.51 1033,3069.5C 1017.9,3041.54 1005.9,3012.21 997,2981.5C 965.46,2869.12 958.127,2755.12 975,2639.5C 982.42,2585.15 996.087,2532.48 1016,2481.5C 1031.3,2443.25 1052.63,2408.91 1080,2378.5C 1085.11,2371.29 1089.44,2363.62 1093,2355.5C 1107.43,2317.89 1102.43,2283.23 1078,2251.5C 1021.65,2194.03 983.321,2126.03 963,2047.5C 927.071,1880.82 957.404,1727.48 1054,1587.5C 1109.68,1508.15 1181.85,1448.98 1270.5,1410C 1330.71,1384.49 1393.37,1374.82 1458.5,1381C 1475.89,1383.87 1493.39,1385.7 1511,1386.5C 1538.15,1386.57 1561.48,1377.23 1581,1358.5C 1593.77,1342.98 1603.43,1325.64 1610,1306.5C 1635.23,1258.58 1668.06,1216.42 1708.5,1180C 1763.56,1132.45 1825.56,1096.11 1894.5,1071C 1962.77,1046.03 2033.1,1038.03 2105.5,1047C 2123.33,1049.9 2140.99,1053.57 2158.5,1058C 2257.86,1085.74 2343.7,1135.91 2416,1208.5C 2434.1,1229.93 2451.43,1251.93 2468,1274.5C 2478.81,1293.57 2488.47,1313.24 2497,1333.5C 2515.31,1367.92 2544.15,1385.92 2583.5,1387.5C 2603.56,1385.55 2623.56,1383.05 2643.5,1380C 2665.16,1378.03 2686.83,1378.03 2708.5,1380C 2789.72,1390.13 2862.72,1419.8 2927.5,1469C 3024.14,1545.72 3089.97,1643.55 3125,1762.5C 3156.52,1871.03 3154.85,1979.03 3120,2086.5C 3099.09,2147.01 3066.42,2200.01 3022,2245.5C 2986.72,2288.07 2984.39,2332.41 3015,2378.5C 3023.38,2387.88 3031.38,2397.54 3039,2407.5C 3061.97,2441.45 3079.64,2478.12 3092,2517.5C 3124.67,2623.58 3135.67,2731.91 3125,2842.5C 3118.62,2909.82 3102.95,2974.82 3078,3037.5C 3068.37,3060.78 3055.7,3082.12 3040,3101.5C 3025.32,3116.18 3011.32,3131.52 2998,3147.5C 2991.43,3156.97 2986.77,3167.3 2984,3178.5C 2983.33,3195.17 2983.33,3211.83 2984,3228.5C 2998.26,3256.01 3012.26,3283.68 3026,3311.5C 3078.26,3426.82 3102.6,3547.82 3099,3674.5C 3098.17,3727.82 3086.34,3778.48 3063.5,3826.5 Z"/></g>
<g><path style="opacity:1" fill="#000000" d="M 2012.5,1851.5 C 2112.37,1846.89 2208.04,1864.39 2299.5,1904C 2385.62,1942.45 2455.79,2000.28 2510,2077.5C 2542.75,2125.73 2563.75,2178.73 2573,2236.5C 2586.91,2380.14 2536.41,2494.97 2421.5,2581C 2340.82,2637.34 2251.15,2668.01 2152.5,2673C 2079.17,2674.97 2005.83,2674.97 1932.5,2673C 1826.08,2665.77 1731.75,2628.77 1649.5,2562C 1545.11,2470.64 1503.61,2356.14 1525,2218.5C 1543.42,2138.98 1581.08,2070.31 1638,2012.5C 1724.73,1929.05 1827.89,1877.89 1947.5,1859C 1969.31,1856.53 1990.98,1854.03 2012.5,1851.5 Z"/></g>
<g><path style="opacity:1" fill="#000000" d="M 1379.5,1875.5 C 1434.55,1872.44 1476.05,1894.44 1504,1941.5C 1507.73,1949.96 1511.73,1958.29 1516,1966.5C 1522.92,2031.46 1501.09,2084.29 1450.5,2125C 1425.26,2142.77 1397.26,2149.44 1366.5,2145C 1313.36,2136.83 1277.86,2107.33 1260,2056.5C 1249.49,1997.03 1266.32,1946.86 1310.5,1906C 1330.89,1889.8 1353.89,1879.64 1379.5,1875.5 Z"/></g>
<g><path style="opacity:1" fill="#000000" d="M 2687.5,1875.5 C 2742.89,1873.11 2785.39,1895.11 2815,1941.5C 2832.9,1972.49 2840.57,2005.82 2838,2041.5C 2828.41,2090.75 2799.91,2123.59 2752.5,2140C 2699.54,2155.81 2655.04,2142.98 2619,2101.5C 2597.87,2076.91 2584.54,2048.58 2579,2016.5C 2578.33,1999.83 2578.33,1983.17 2579,1966.5C 2593.13,1927.71 2618.96,1899.87 2656.5,1883C 2666.74,1879.52 2677.07,1877.02 2687.5,1875.5 Z"/></g>
<g><path style="opacity:1" fill="#fefefe" d="M 2018.5,1983.5 C 2118.82,1978 2211.82,2001.16 2297.5,2053C 2340.25,2081.75 2376.41,2117.25 2406,2159.5C 2433.84,2203.86 2446.84,2252.19 2445,2304.5C 2439.12,2354.58 2419.45,2398.25 2386,2435.5C 2362.54,2461.31 2335.37,2482.15 2304.5,2498C 2251.11,2526.6 2194.11,2541.6 2133.5,2543C 2075.5,2543.67 2017.5,2543.67 1959.5,2543C 1884.97,2540.6 1816.97,2518.6 1755.5,2477C 1725.83,2455.33 1701,2429.16 1681,2398.5C 1637.86,2320.42 1639.19,2243.09 1685,2166.5C 1733.44,2091.82 1799.94,2039.99 1884.5,2011C 1927.96,1995.22 1972.62,1986.05 2018.5,1983.5 Z"/></g>
<g><path style="opacity:1" fill="#000000" d="M 1964.5,2160.5 C 1974.84,2160.33 1985.17,2160.5 1995.5,2161C 1999.17,2162 2002.83,2163 2006.5,2164C 2019.36,2170.6 2032.03,2177.43 2044.5,2184.5C 2051.61,2180.57 2058.61,2176.4 2065.5,2172C 2084.44,2160.58 2104.77,2157.25 2126.5,2162C 2154.24,2173.76 2165.74,2194.6 2161,2224.5C 2159.39,2229.33 2157.39,2234 2155,2238.5C 2141.4,2256.86 2124.57,2271.36 2104.5,2282C 2095.83,2289.2 2093,2298.36 2096,2309.5C 2097.52,2320.59 2099.52,2331.59 2102,2342.5C 2102.67,2352.17 2102.67,2361.83 2102,2371.5C 2097.45,2391.05 2085.28,2402.88 2065.5,2407C 2048.08,2407.98 2030.75,2407.31 2013.5,2405C 1996.6,2395.27 1986.6,2380.77 1983.5,2361.5C 1986.88,2342.54 1990.05,2323.54 1993,2304.5C 1994.81,2296.58 1992.98,2289.75 1987.5,2284C 1971.14,2274.9 1956.31,2263.73 1943,2250.5C 1918.23,2221.25 1920.06,2193.75 1948.5,2168C 1953.54,2164.64 1958.88,2162.14 1964.5,2160.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/svgs/prefect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
public/svgs/qdrant.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

56
public/svgs/searxng.svg Normal file
View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 92 92"
height="92mm"
width="92mm">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-40.921303,-17.416526)"
id="layer1">
<circle
r="0"
style="fill:none;stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cy="92"
cx="75"
id="path3713" />
<circle
r="30"
cy="53.902557"
cx="75.921303"
id="path834"
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 67.514849,37.91524 a 18,18 0 0 1 21.051475,3.312407 18,18 0 0 1 3.137312,21.078282"
id="path852"
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
transform="rotate(-46.234709)"
ry="1.8669105e-13"
y="122.08995"
x="3.7063529"
height="39.963303"
width="18.846331"
id="rect912"
style="opacity:1;fill:#3050ff;fill-opacity:1;stroke:none;stroke-width:8;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

8
public/svgs/strapi.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 22.1867C0 11.7278 0 6.49832 3.24916 3.24916C6.49832 0 11.7278 0 22.1867 0H41.8133C52.2722 0 57.5017 0 60.7508 3.24916C64 6.49832 64 11.7278 64 22.1867V41.8133C64 52.2722 64 57.5017 60.7508 60.7508C57.5017 64 52.2722 64 41.8133 64H22.1867C11.7278 64 6.49832 64 3.24916 60.7508C0 57.5017 0 52.2722 0 41.8133V22.1867Z" fill="#4945FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.156 19.4131H22.6094V30.4004H33.596V41.3864H44.5827V19.8398C44.5827 19.6041 44.3917 19.4131 44.156 19.4131Z" fill="white"/>
<rect x="33.1719" y="30.4004" width="0.426667" height="0.426667" fill="white"/>
<path d="M22.6172 30.4004H33.1772C33.4128 30.4004 33.6039 30.5914 33.6039 30.8271V41.3871H23.0439C22.8082 41.3871 22.6172 41.196 22.6172 40.9604V30.4004Z" fill="#9593FF"/>
<path d="M33.6016 41.3867H44.5882L33.9657 52.0092C33.8314 52.1436 33.6016 52.0484 33.6016 51.8584V41.3867Z" fill="#9593FF"/>
<path d="M22.6151 30.3998H12.1434C11.9534 30.3998 11.8582 30.17 11.9926 30.0356L22.6151 19.4131V30.3998Z" fill="#9593FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

5
public/svgs/tolgee.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg"
style="fill:#EC407A;fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M97.16,7.27a16.94,16.94,0,0,0-1.9,24.47,16.36,16.36,0,0,0,5,3.83,3.23,3.23,0,0,1-2.9,5.77,23.14,23.14,0,0,1-11.41-13C73.83,31.1,63.46,37.09,52.82,46.51c-27.44,24.3-34.35,61.74-16.38,85.26-4.57,5.79-8,12.22-8.9,18.69a20.88,20.88,0,0,0,5.62,18c9.18,9.61,21.42,7.13,31.26,5.14,6.58-1.34,12.8-2.6,16.5-.23,3.22,2.07,3.47,3.87,3.61,4.45,2.1,9.32-5.79,13.89-7.67,16.27a1.48,1.48,0,0,0,1.13,2.4c3.48,0,9-1.18,12.34-4.08s7.16-7.9,5.89-16.32c-.08-.5-.18-1-.32-1.58-.86-3.35-3.1-7.57-8.61-11.09-7.72-4.95-17-3.07-25.22-1.41-9.76,2-16,2.85-20.37-1.71a9.13,9.13,0,0,1-2.46-8.19c.54-3.77,2.65-7.89,5.62-11.86,21.71,16.89,56.87,13.47,82.67-9.39a75.34,75.34,0,0,0,20.81-28.09A23.14,23.14,0,0,1,134.8,89a3.23,3.23,0,0,1,6.08-2.19,16.37,16.37,0,0,0,3.2,5.39,16.85,16.85,0,1,0,11.48-28,3.23,3.23,0,0,1-.51-6.44,23.41,23.41,0,0,1,12.88,2.69c2.6-14.08,3.34-31.41-2.06-37.51-4.08-4.61-20.62-8-35.18-7.76A23.48,23.48,0,0,1,130.8,25a3.23,3.23,0,0,1-6.33-1.28A16.94,16.94,0,0,0,97.16,7.27Zm63.25,21a5.29,5.29,0,0,1-.57,6.19c-1.29,1.14-2.72-.51-4.1-2.06s-3.1-3.42-1.81-4.56A5.74,5.74,0,0,1,160.41,28.27Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/svgs/weaviate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -16,6 +16,7 @@ export function initializeTerminalComponent() {
paused: false,
MAX_PENDING_WRITES: 5,
keepAliveInterval: null,
reconnectInterval: null,
init() {
this.setupTerminal();
@@ -48,6 +49,9 @@ export function initializeTerminalComponent() {
document.addEventListener(event, () => {
this.checkIfProcessIsRunningAndKillIt();
clearInterval(this.keepAliveInterval);
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
}
}, { once: true });
});
@@ -103,11 +107,27 @@ export function initializeTerminalComponent() {
};
this.socket.onclose = () => {
console.log('WebSocket connection closed');
this.reconnect();
};
}
},
reconnect() {
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
}
this.reconnectInterval = setInterval(() => {
console.log('Attempting to reconnect...');
this.initializeWebSocket();
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
console.log('Reconnected successfully');
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
window.location.reload();
}
}, 2000);
},
handleSocketMessage(event) {
this.message = '(connection closed)';
if (event.data === 'pty-ready') {

View File

@@ -8,7 +8,7 @@
<div class="w-96">
<form action="/user/confirm-password" method="POST" class="flex flex-col gap-2">
@csrf
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" autofocus />
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
<x-forms.button type="submit">{{ __('auth.confirm_password') }}</x-forms.button>
</form>
@if ($errors->any())

View File

@@ -12,7 +12,7 @@
@if (is_transactional_emails_active())
<form action="/forgot-password" method="POST" class="flex flex-col gap-2">
@csrf
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
</form>
@else

View File

@@ -10,7 +10,7 @@
@csrf
@env('local')
<x-forms.input value="test@example.com" type="email" autocomplete="email" name="email"
required label="{{ __('input.email') }}" autofocus />
required label="{{ __('input.email') }}" />
<x-forms.input value="password" type="password" autocomplete="current-password" name="password"
required label="{{ __('input.password') }}" />
@@ -20,7 +20,7 @@
</a>
@else
<x-forms.input type="email" name="email" autocomplete="email" required
label="{{ __('input.email') }}" autofocus />
label="{{ __('input.email') }}" />
<x-forms.input type="password" name="password" autocomplete="current-password" required
label="{{ __('input.password') }}" />
<a href="/forgot-password" class="text-xs">

View File

@@ -16,7 +16,7 @@
label="{{ __('input.email') }}" />
<div class="flex flex-col gap-2">
<x-forms.input required type="password" id="password" name="password"
label="{{ __('input.password') }}" autofocus />
label="{{ __('input.password') }}" />
<x-forms.input required type="password" id="password_confirmation"
name="password_confirmation" label="{{ __('input.password.again') }}" />
</div>

View File

@@ -9,7 +9,7 @@
<form action="/two-factor-challenge" method="POST" class="flex flex-col gap-2">
@csrf
<div>
<x-forms.input type="number" name="code" autocomplete="one-time-code" label="{{ __('input.code') }}" autofocus />
<x-forms.input type="number" name="code" autocomplete="one-time-code" label="{{ __('input.code') }}" />
<div x-show="!showRecovery"
class="pt-2 text-xs cursor-pointer hover:underline hover:dark:text-white"
x-on:click="showRecovery = !showRecovery">Enter

View File

@@ -222,12 +222,12 @@
</template>
@endforeach
</ul>
@if ($confirmWithText && $confirmationText)
@if ($confirmWithText)
<div class="mb-4">
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
<div class="relative mb-2">
<input autocomplete="off" type="text" x-model="confirmationText"
<input type="text" x-model="confirmationText"
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
<button @click="copyConfirmationText()"
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
@@ -255,7 +255,7 @@
class="block mt-4 text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $shortConfirmationLabel }}
</label>
<input autocomplete="off" type="text" x-model="userConfirmationText"
<input type="text" x-model="userConfirmationText"
class="p-2 mt-1 w-full text-black rounded input">
</div>
@endif
@@ -272,8 +272,10 @@
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Your Password
</label>
<input autocomplete="off" type="password" id="password-confirm" x-model="password" class="w-full input"
placeholder="Enter your password">
<form @submit.prevent="return false" @keydown.enter.prevent>
<input type="password" id="password-confirm" x-model="password" class="w-full input"
placeholder="Enter your password">
</form>
<p x-show="passwordError" x-text="passwordError" class="mt-1 text-sm text-red-500"></p>
@error('password')
<p class="mt-1 text-sm text-red-500">{{ $message }}</p>
@@ -296,20 +298,13 @@
</template>
<template x-if="step === 1">
@if(isDev() && $submitAction === 'delete')
<x-forms.button class="w-auto" isError
@click="$wire.delete('hello')">
<span x-text="step3ButtonText"></span>
</x-forms.button>
@else
<x-forms.button @click="step++" class="w-auto" isError>
<span x-text="step1ButtonText"></span>
</x-forms.button>
@endif
<x-forms.button @click="step++" class="w-auto" isError>
<span x-text="step1ButtonText"></span>
</x-forms.button>
</template>
<template x-if="step === 2">
<x-forms.button x-bind:disabled="confirmationText !== '' && confirmWithText && userConfirmationText !== confirmationText"
<x-forms.button x-bind:disabled="confirmWithText && userConfirmationText !== confirmationText"
class="w-auto" isError
@click="
if (dispatchEvent) {

View File

@@ -6,10 +6,10 @@
x-transition:enter-start="translate-y-full" x-transition:enter-end="translate-y-0"
x-transition:leave="transition ease-in duration-300" x-transition:leave-start="translate-y-0"
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
class="fixed bottom-0 right-0 w-full h-auto duration-300 ease-out sm:px-5 sm:pb-5 sm:w-[26rem] lg:w-full z-[999]"
class="fixed bottom-0 right-0 w-full h-auto duration-300 ease-out sm:px-5 sm:pb-5 w-full z-[999]"
x-cloak>
<div
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
class="flex items-center flex-col justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
<div
class="flex flex-col items-start h-full pb-6 text-xs lg:items-center lg:flex-row lg:pb-0 lg:pr-6 lg:space-x-5 dark:text-neutral-300">
@if (isset($icon))
@@ -23,14 +23,12 @@
<p class="">{{ $description }}</span></p>
</div>
</div>
<div class="flex items-end justify-end w-full pl-3 space-x-3 lg:flex-shrink-0 lg:w-auto">
<button
@if ($buttonText->attributes->whereStartsWith('@click')->first()) @click="bannerVisible=false;{{ $buttonText->attributes->get('@click') }}"
@else
@else
@click="bannerVisible=false;" @endif
class="inline-flex items-center justify-center flex-shrink-0 w-1/2 px-4 py-2 text-sm font-medium tracking-wide transition-colors duration-200 rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-coolgray-200 lg:w-auto dark:text-neutral-200 dark:hover:bg-coolgray-300 focus:shadow-outline focus:outline-none">
class="w-full px-4 py-2 text-sm font-medium tracking-wide transition-colors duration-200 rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-coolgray-200 lg:w-auto dark:text-neutral-200 dark:hover:bg-coolgray-300 focus:shadow-outline focus:outline-none">
{{ $buttonText }}
</button>
</div>
</div>
</div>

View File

@@ -2,7 +2,7 @@
'transition-all duration-150 box-without-bg dark:bg-coolgray-100 bg-white group',
'hover:border-l-coollabs cursor-pointer' => !$upgrade,
'hover:border-l-red-500 cursor-not-allowed' => $upgrade,
]) @if (!$upgrade) wire:click={{ $wire }} @endif>
])>
<div class="flex items-center">
{{ $logo }}
<div class="flex flex-col pl-2 ">
@@ -20,11 +20,6 @@
</div>
@isset($documentation)
<div class="flex-1"></div>
<div class="flex items-center px-2 " title="Read the documentation.">
<a class="p-2 rounded hover:bg-coolgray-200 hover:no-underline group-hover:dark:text-white text-neutral-600"
onclick="event.stopPropagation()" href="{{ $documentation }}" target="_blank">
Docs
</a>
</div>
{{ $documentation }}
@endisset
</div>

View File

@@ -6,7 +6,7 @@
<livewire:server.proxy.status :server="$server" />
@endif
</div>
<div class="subtitle">{{ data_get($server, 'name') }}.</div>
<div class="subtitle">{{ data_get($server, 'name') }}</div>
<div class="navbar-main">
<nav class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"

View File

@@ -2,9 +2,12 @@
'status' => 'Restarting',
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
'noLoading' => false,
])
<div class="flex items-center">
<x-loading wire:loading.delay.longer />
@if (!$noLoading)
<x-loading wire:loading.delay.longer />
@endif
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-warning "></div>
<div class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning" @if($lastDeploymentInfo) title="{{$lastDeploymentInfo}}" @endif>

View File

@@ -2,9 +2,12 @@
'status' => 'Running',
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
'noLoading' => false,
])
<div class="flex items-center">
<x-loading wire:loading.delay.longer />
@if (!$noLoading)
<x-loading wire:loading.delay.longer />
@endif
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-success "></div>
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success" @if($lastDeploymentInfo) title="{{$lastDeploymentInfo}}" @endif>

View File

@@ -1,8 +1,11 @@
@props([
'status' => 'Stopped',
'noLoading' => false,
])
<div class="flex items-center">
<x-loading wire:loading.delay.longer />
@if (!$noLoading)
<x-loading wire:loading.delay.longer />
@endif
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-error "></div>
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-error">{{ str($status)->before(':')->headline() }}</div>

View File

@@ -23,7 +23,7 @@
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group"
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment }}')">
<div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1">
<div class="box-title">{{ $project->name }}</div>
@@ -33,7 +33,7 @@
</div>
<div class="flex items-center justify-center gap-2 text-xs font-bold">
<a class="hover:underline"
href="{{ route('project.resource.create', ['project_uuid' => $project->uuid, 'environment_name' => data_get($project, 'default_environment()', 'production')]) }}">
href="{{ route('project.resource.create', ['project_uuid' => $project->uuid, 'environment_name' => data_get($project, 'default_environment', 'production')]) }}">
<span class="p-2 font-bold">+ Add Resource</span>
</a>
<a class="hover:underline"
@@ -122,7 +122,7 @@
@if (count($deployments_per_server) > 0)
<x-loading />
@endif
<x-modal-confirmation
<x-modal-confirmation
title="Confirm Cleanup Queues?"
buttonTitle="Cleanup Queues"
isErrorButton

View File

@@ -32,21 +32,16 @@
@if (!isCloud())
<x-popup>
<x-slot:title>
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
<span class="font-bold text-left text-red-500">WARNING: </span> Cannot connect to real-time service
</x-slot:title>
<x-slot:description>
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the
UI
if
not fixed! <br><br>
<div>This will cause unusual problems on the
UI! <br><br>
Please ensure that you have opened the
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall'
target='_blank'>required ports</a>,
check the
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
target='_blank'>documentation</a> or get
target='_blank'>required ports</a> or get
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>.
</span>
</div>
</x-slot:description>
<x-slot:button-text @click="disableRealtime()">
Acknowledge & Disable This Popup

View File

@@ -1,5 +1,5 @@
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
<x-forms.input autofocus placeholder="Your Cool Project" id="name" label="Name" required />
<x-forms.input placeholder="Your Cool Project" id="name" label="Name" required />
<x-forms.input placeholder="This is my cool project everyone knows about" id="description" label="Description" />
<div class="subtitle">New project will have a default production environment.</div>
<x-forms.button type="submit" @click="slideOverOpen=false">

View File

@@ -1,5 +1,5 @@
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
<x-forms.input autofocus placeholder="production" id="name" label="Name" required />
<x-forms.input placeholder="production" id="name" label="Name" required />
<x-forms.button type="submit" @click="slideOverOpen=false">
Save
</x-forms.button>

View File

@@ -8,17 +8,14 @@
<livewire:project.database.backup-now :backup="$backup" />
@endif
@if ($backup->database_id !== 0)
<x-modal-confirmation
title="Confirm Backup Schedule Deletion?"
buttonTitle="Delete Backups and Schedule"
isErrorButton
submitAction="delete"
:checkboxes="$checkboxes"
:actions="['The selected backup schedule will be deleted.', 'Scheduled backups for this database will be stopped (if this is the only backup schedule for this database).']"
confirmationText="{{ $backup->database->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Database Name of the scheduled backups below"
shortConfirmationLabel="Database Name"
/>
<x-modal-confirmation title="Confirm Backup Schedule Deletion?" buttonTitle="Delete Backups and Schedule"
isErrorButton submitAction="delete" :checkboxes="$checkboxes" :actions="[
'The selected backup schedule will be deleted.',
'Scheduled backups for this database will be stopped (if this is the only backup schedule for this database).',
]"
confirmationText="{{ $backup->database->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Database Name of the scheduled backups below"
shortConfirmationLabel="Database Name" />
@endif
</div>
<div class="w-48 pb-2">
@@ -36,23 +33,39 @@
</div>
@endif
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<h3>Settings</h3>
<div class="flex gap-2 flex-col ">
@if ($backup->database_type === 'App\Models\StandalonePostgresql')
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
<div class="w-48">
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
</div>
@if (!$backup->dump_all)
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
@endif
@elseif($backup->database_type === 'App\Models\StandaloneMongodb')
<x-forms.input label="Databases To Include"
helper="A list of databases to backup. You can specify which collection(s) per database to exclude from the backup. Empty will include all databases and collections.<br><br>Example:<br><br>database1:collection1,collection2|database2:collection3,collection4<br><br> database1 will include all collections except collection1 and collection2. <br>database2 will include all collections except collection3 and collection4.<br><br>Another Example:<br><br>database1:collection1|database2<br><br> database1 will include all collections except collection1.<br>database2 will include ALL collections."
id="backup.databases_to_backup" />
@elseif($backup->database_type === 'App\Models\StandaloneMysql')
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
<div class="w-48">
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
</div>
@if (!$backup->dump_all)
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
@endif
@elseif($backup->database_type === 'App\Models\StandaloneMariadb')
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
<div class="w-48">
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
</div>
@if (!$backup->dump_all)
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
@endif
@endif
</div>
<div class="flex gap-2">

View File

@@ -45,20 +45,11 @@
<x-forms.button class="dark:hover:bg-coolgray-400"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif
<x-modal-confirmation
title="Confirm Backup Deletion?"
buttonTitle="Delete"
isErrorButton
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
{{-- :checkboxes="$checkboxes" --}}
:actions="[
'This backup will be permanently deleted from local storage.'
]"
confirmationText="{{ data_get($execution, 'filename') }}"
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
shortConfirmationLabel="Backup Filename"
step3ButtonText="Permanently Delete"
/>
<x-modal-confirmation title="Confirm Backup Deletion?" buttonTitle="Delete" isErrorButton
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
:actions="['This backup will be permanently deleted from local storage.']" confirmationText="{{ data_get($execution, 'filename') }}"
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
shortConfirmationLabel="Backup Filename" step3ButtonText="Permanently Delete" />
</div>
</div>
@empty

View File

@@ -1,17 +1,19 @@
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
<x-forms.input autofocus placeholder="0 0 * * * or daily" id="frequency"
<x-forms.input placeholder="0 0 * * * or daily" id="frequency"
helper="You can use every_minute, hourly, daily, weekly, monthly, yearly or a cron expression." label="Frequency"
required />
<x-forms.checkbox id="save_s3" label="Save to S3" />
<x-forms.select id="selected_storage_id">
@if ($s3s->count() === 0)
<option value="0">No S3 Storages found.</option>
@else
@foreach ($s3s as $s3)
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
@endforeach
@if ($s3s->count() === 0)
<div class="text-red-500">No validated S3 Storages found.</div>
@else
<x-forms.checkbox wire:model.live="save_s3" label="Save to S3" />
@if ($save_s3)
<x-forms.select id="selected_storage_id" label="Select a validated S3 storage">
@foreach ($s3s as $s3)
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
@endforeach
</x-forms.select>
@endif
</x-forms.select>
@endif
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>

View File

@@ -102,7 +102,7 @@
<h3>Initialization scripts</h3>
<x-modal-input buttonTitle="+ Add" title="New Init Script">
<form class="flex flex-col w-full gap-2 rounded" wire:submit='save_new_init_script'>
<x-forms.input autofocus placeholder="create_test_db.sql" id="new_filename" label="Filename"
<x-forms.input placeholder="create_test_db.sql" id="new_filename" label="Filename"
required />
<x-forms.textarea rows="20" placeholder="CREATE DATABASE test;" id="new_content"
label="Content" required />

View File

@@ -1,37 +1,38 @@
<div>
<div class="flex flex-col gap-2">
@forelse($database->scheduledBackups as $backup)
@if ($type == 'database')
<a class="box"
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
<div class="flex flex-col">
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</a>
@else
<div class="box" wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')">
<div @class([ 'border-coollabs'=>
data_get($backup, 'id') === data_get($selectedBackup, 'id'),
'flex flex-col border-l-2 border-transparent',
])>
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</div>
@endif
@if ($type == 'database')
<a class="box"
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
<div class="flex flex-col">
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</a>
@else
<div class="box" wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')">
<div @class([
'border-coollabs' =>
data_get($backup, 'id') === data_get($selectedBackup, 'id'),
'flex flex-col border-l-2 border-transparent',
])>
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</div>
@endif
@empty
<div>No scheduled backups configured.</div>
<div>No scheduled backups configured.</div>
@endforelse
</div>
@if ($type === 'service-database' && $selectedBackup)
<div class="pt-10">
<livewire:project.database.backup-edit wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup"
:s3s="$s3s" :status="data_get($database, 'status')" />
<h3 class="py-4">Executions</h3>
<livewire:project.database.backup-executions wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup" :database="$database" />
</div>
<div class="pt-10">
<livewire:project.database.backup-edit wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup"
:s3s="$s3s" :status="data_get($database, 'status')" />
<livewire:project.database.backup-executions wire:key="{{ $selectedBackup->uuid }}" :backup="$selectedBackup"
:database="$database" />
</div>
@endif
</div>

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