Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5360c60f3d | ||
|
|
f60c640dc6 | ||
|
|
91bb323e84 | ||
|
|
cf9e122bd2 | ||
|
|
7528ca18d8 | ||
|
|
05ee35b6bc | ||
|
|
0e8b069781 | ||
|
|
3b95d7278d | ||
|
|
7691706295 | ||
|
|
2af65ee946 | ||
|
|
279e1fd9c5 | ||
|
|
0f8f33e9fe | ||
|
|
12e91f1c6b | ||
|
|
f4f605867b | ||
|
|
ac40abba2e | ||
|
|
224604f2e7 | ||
|
|
ee4360de3a | ||
|
|
0af59b9602 | ||
|
|
ff8fe68f14 | ||
|
|
71c15e0ff5 | ||
|
|
6be1fbacde | ||
|
|
be594dd49e | ||
|
|
6b65d435fb | ||
|
|
0dc5212066 | ||
|
|
7c8ffd510e | ||
|
|
8e6423e873 | ||
|
|
c99f74b351 | ||
|
|
57b462223c | ||
|
|
055db97273 | ||
|
|
c80ebf73ee | ||
|
|
9b613294ae | ||
|
|
8cb73e1680 | ||
|
|
27dfa24cfb | ||
|
|
c53f0dbb30 | ||
|
|
db16a357e8 | ||
|
|
01e71958b2 | ||
|
|
f379519d40 | ||
|
|
54a09958d5 | ||
|
|
3421de06d5 | ||
|
|
e44eb01396 | ||
|
|
313143586b | ||
|
|
c8ae72893a | ||
|
|
cbc3735ca0 | ||
|
|
31fdbdf8c9 | ||
|
|
2741d0ab2a | ||
|
|
79b0187b58 | ||
|
|
bf5659d0e2 | ||
|
|
f6314cab69 | ||
|
|
4f5fe3d383 | ||
|
|
1c720d587c | ||
|
|
c46dc99224 | ||
|
|
5e43d4f20d | ||
|
|
359434bfd3 | ||
|
|
e755a2d4ec | ||
|
|
0b416cd03e | ||
|
|
857e0f251b | ||
|
|
f040c7c742 | ||
|
|
2e82c9d312 | ||
|
|
126923c33e | ||
|
|
26528d8bec | ||
|
|
f99da111f7 | ||
|
|
8c30472472 | ||
|
|
11131ebe06 | ||
|
|
8dd80589d6 | ||
|
|
51e27146f3 | ||
|
|
70717dcbe5 | ||
|
|
51cba32d8d | ||
|
|
b9076714cf | ||
|
|
b76caabd32 | ||
|
|
0922fd66a4 | ||
|
|
4e7e9b2cfc | ||
|
|
0c24134ac2 | ||
|
|
f96e418dd6 | ||
|
|
1627415cca | ||
|
|
d047c91399 | ||
|
|
8bec5550cf | ||
|
|
7a61ade4a0 | ||
|
|
0239be69e6 | ||
|
|
883bdc2879 | ||
|
|
c3457a4c8a | ||
|
|
d93506a18c | ||
|
|
0bb77a671b | ||
|
|
028f883499 | ||
|
|
727133e28b | ||
|
|
1bd08cb2db | ||
|
|
2962aa6166 | ||
|
|
bac55cd90d | ||
|
|
9b51936131 | ||
|
|
692665d0da | ||
|
|
14cdf33473 | ||
|
|
17f4f83ad6 | ||
|
|
c981606da7 | ||
|
|
e21d1bffe8 | ||
|
|
32706092f3 | ||
|
|
a7fe446550 | ||
|
|
bb01cfb330 | ||
|
|
2590efba4b | ||
|
|
186c5897a0 | ||
|
|
6150a008d5 | ||
|
|
fb2a86ba3f | ||
|
|
91fa762985 | ||
|
|
d80f760c92 | ||
|
|
ce2c887469 | ||
|
|
8b5c7c94cd | ||
|
|
3dd2a059bf | ||
|
|
2f724ffba2 | ||
|
|
0586ec3f42 | ||
|
|
4908463722 | ||
|
|
26d0ef9ac9 | ||
|
|
c2c9d992e7 | ||
|
|
ce9aa636c4 | ||
|
|
68d06dcd19 | ||
|
|
03bf93eb12 | ||
|
|
163eabb76c | ||
|
|
bed1bb2429 | ||
|
|
bc802b6f19 | ||
|
|
9b67a253f1 | ||
|
|
29dc5a8bb4 | ||
|
|
b63e516274 | ||
|
|
db9d5a0fb0 | ||
|
|
3f5e6faac5 | ||
|
|
eef6b95e24 | ||
|
|
96b76aea6b | ||
|
|
0745a12e7d | ||
|
|
645d5e19db | ||
|
|
45e2c7bd03 | ||
|
|
9001f34fab | ||
|
|
4d3dd2052f | ||
|
|
ff8849a907 | ||
|
|
adecdc8788 | ||
|
|
36cbf7f0fb | ||
|
|
1262f6b11b | ||
|
|
edbc34e7e5 | ||
|
|
e2b4ec47b0 | ||
|
|
e9565c3602 | ||
|
|
8d3ca92fe9 | ||
|
|
272b2bb65d | ||
|
|
5d5a478cd1 | ||
|
|
ddd09412cd | ||
|
|
d6cfc2624f | ||
|
|
e92d0914c2 | ||
|
|
0a44867240 |
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
build
|
build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
|
|||||||
2
.gitpod.Dockerfile
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
FROM gitpod/workspace-full:2022-08-17-18-37-55
|
||||||
|
RUN brew install buildpacks/tap/pack
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
# This configuration file was automatically generated by Gitpod.
|
# This configuration file was automatically generated by Gitpod.
|
||||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||||
# and commit this file to your remote git repository to share the goodness with others.
|
# and commit this file to your remote git repository to share the goodness with others.
|
||||||
image: gitpod/workspace-node:2022-06-20-19-54-55
|
#image:
|
||||||
tasks:
|
# file: .gitpod.Dockerfile
|
||||||
- init: pnpm install && pnpm db:push && pnpm db:seed
|
#tasks:
|
||||||
command: pnpm dev
|
# - init: pnpm install && pnpm db:push && pnpm db:seed
|
||||||
|
# command: pnpm dev
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- port: 3001
|
- port: 3001
|
||||||
|
|||||||
@@ -23,14 +23,15 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/app/prisma-engines/query-engine \
|
|||||||
|
|
||||||
COPY --from=coollabsio/prisma-engine:3.15 /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
COPY --from=coollabsio/prisma-engine:3.15 /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
||||||
|
|
||||||
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl
|
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl psmisc
|
||||||
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
||||||
|
|
||||||
RUN mkdir -p ~/.docker/cli-plugins/
|
RUN mkdir -p ~/.docker/cli-plugins/
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.7.0 -o ~/.docker/cli-plugins/docker-compose
|
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.6.1 -o ~/.docker/cli-plugins/docker-compose
|
||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||||
|
|
||||||
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
||||||
|
|||||||
231
README.md
@@ -1,7 +1,109 @@
|
|||||||
# Coolify
|
# Coolify
|
||||||
|
|
||||||
An open-source & self-hostable Heroku / Netlify alternative
|
An open-source & self-hostable Heroku / Netlify alternative.
|
||||||
(ARM support is in beta).
|
|
||||||
|
## Live Demo
|
||||||
|
|
||||||
|
https://demo.coolify.io/
|
||||||
|
|
||||||
|
(If it is unresponsive, that means someone overloaded the server. 😄)
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
|
||||||
|
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
For more details goto the [docs](https://docs.coollabs.io/coolify/installation.html).
|
||||||
|
|
||||||
|
Installation is automated with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like no questions during installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Git Sources
|
||||||
|
|
||||||
|
Self-hosted versions also!
|
||||||
|
|
||||||
|
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
|
||||||
|
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
|
||||||
|
|
||||||
|
### Destinations
|
||||||
|
|
||||||
|
Deploy your resource to:
|
||||||
|
|
||||||
|
- Local Docker Engine
|
||||||
|
- Remote Docker Engine
|
||||||
|
|
||||||
|
### Applications
|
||||||
|
|
||||||
|
<a href="https://heroku.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/heroku.com"></a>
|
||||||
|
<a href="https://html5.org/">
|
||||||
|
<svg style="width:40px;height:40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" ><g clip-path="url(#HTML5_Clip0_4)" ><path d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z" fill="#E44D26" /><path d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z" fill="#F16529" /><path d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z" fill="#EBEBEB" /><path d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z" fill="#EBEBEB" /><path d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z" fill="white" /><path d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z" fill="white" /></g><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs></svg></a>
|
||||||
|
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
|
||||||
|
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
|
||||||
|
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
|
||||||
|
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
|
||||||
|
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
|
||||||
|
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
|
||||||
|
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
|
||||||
|
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
|
||||||
|
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
|
||||||
|
<a href="https://laravel.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/laravel.com"></a>
|
||||||
|
<a href="https://python.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/python.org"></a>
|
||||||
|
<a href="https://deno.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/deno.com"></a>
|
||||||
|
<a href="https://docker.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/docker.com"></a>
|
||||||
|
|
||||||
|
### Databases
|
||||||
|
|
||||||
|
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
|
||||||
|
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
|
||||||
|
<a href="https://mysql.com"><svg style="width:40px;height:40px" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 25.6 25.6" ><path d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z" transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)" fill-rule="evenodd" fill="#00678c" /></svg></a>
|
||||||
|
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
|
||||||
|
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
|
||||||
|
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
|
||||||
|
|
||||||
|
### Services
|
||||||
|
- [Appwrite](https://appwrite.io)
|
||||||
|
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
||||||
|
- [Ghost](https://ghost.org)
|
||||||
|
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
||||||
|
- [NocoDB](https://nocodb.com)
|
||||||
|
- [VSCode Server](https://github.com/cdr/code-server)
|
||||||
|
- [MinIO](https://min.io)
|
||||||
|
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
||||||
|
- [LanguageTool](https://languagetool.org)
|
||||||
|
- [n8n](https://n8n.io)
|
||||||
|
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
|
||||||
|
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
|
||||||
|
- [Umami](https://github.com/mikecao/umami)
|
||||||
|
- [Fider](https://fider.io)
|
||||||
|
- [Hasura](https://hasura.io)
|
||||||
|
- [GlitchTip](https://glitchtip.com)
|
||||||
|
|
||||||
|
## Migration from v1
|
||||||
|
|
||||||
|
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||||
|
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
||||||
|
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
||||||
|
- Discord: [Invitation](https://coollabs.io/discord)
|
||||||
|
|
||||||
## Financial Contributors
|
## Financial Contributors
|
||||||
|
|
||||||
@@ -24,127 +126,4 @@ Support this project with your organization. Your logo will show up here with a
|
|||||||
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Live Demo
|
|
||||||
|
|
||||||
https://demo.coolify.io/
|
|
||||||
|
|
||||||
(If it is unresponsive, that means someone overloaded the server. 😄)
|
|
||||||
|
|
||||||
## Feedback
|
|
||||||
|
|
||||||
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to install
|
|
||||||
|
|
||||||
Installation is automated with the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like no questions during installation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f
|
|
||||||
```
|
|
||||||
|
|
||||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### Git Sources
|
|
||||||
|
|
||||||
You can use the following Git Sources to be auto-deployed to your Coolify instance! (Self-hosted versions are also supported.)
|
|
||||||
|
|
||||||
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
|
|
||||||
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
|
|
||||||
|
|
||||||
### Destinations
|
|
||||||
|
|
||||||
You can deploy your applications to the following destinations:
|
|
||||||
|
|
||||||
- Local Docker Engine
|
|
||||||
- Remote Docker Engine
|
|
||||||
|
|
||||||
### Applications
|
|
||||||
|
|
||||||
Predefined build packs to cover the basic needs to deploy applications.
|
|
||||||
|
|
||||||
If you have an advanced use case, you can use the Docker build pack that allows you to deploy your application based on your custom Dockerfile.
|
|
||||||
|
|
||||||
<a href="https://html5.org/">
|
|
||||||
<svg style="width:40px;height:40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" ><g clip-path="url(#HTML5_Clip0_4)" ><path d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z" fill="#E44D26" /><path d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z" fill="#F16529" /><path d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z" fill="#EBEBEB" /><path d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z" fill="#EBEBEB" /><path d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z" fill="white" /><path d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z" fill="white" /></g><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs></svg></a>
|
|
||||||
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
|
|
||||||
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
|
|
||||||
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
|
|
||||||
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
|
|
||||||
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
|
|
||||||
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
|
|
||||||
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
|
|
||||||
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
|
|
||||||
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
|
|
||||||
<a href="https://laravel.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/laravel.com"></a>
|
|
||||||
<a href="https://python.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/python.org"></a>
|
|
||||||
<a href="https://deno.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/deno.com"></a>
|
|
||||||
<a href="https://docker.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/docker.com"></a>
|
|
||||||
|
|
||||||
If you have a new build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
|
||||||
|
|
||||||
### Databases
|
|
||||||
|
|
||||||
One-click database is ready to be used internally or shared over the internet:
|
|
||||||
|
|
||||||
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
|
|
||||||
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
|
|
||||||
<a href="https://mysql.com"><svg style="width:40px;height:40px" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 25.6 25.6" ><path d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z" transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)" fill-rule="evenodd" fill="#00678c" /></svg></a>
|
|
||||||
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
|
|
||||||
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
|
|
||||||
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
|
|
||||||
|
|
||||||
If you have a new database you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
|
||||||
|
|
||||||
|
|
||||||
### Services
|
|
||||||
|
|
||||||
You quickly need to host a self-hostable, open-source service? You can do it with a few clicks!
|
|
||||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
|
||||||
- [Ghost](https://ghost.org)
|
|
||||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
|
||||||
- [NocoDB](https://nocodb.com)
|
|
||||||
- [VSCode Server](https://github.com/cdr/code-server)
|
|
||||||
- [MinIO](https://min.io)
|
|
||||||
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
|
||||||
- [LanguageTool](https://languagetool.org)
|
|
||||||
- [n8n](https://n8n.io)
|
|
||||||
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
|
|
||||||
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
|
|
||||||
- [Umami](https://github.com/mikecao/umami)
|
|
||||||
- [Fider](https://fider.io)
|
|
||||||
- [Hasura](https://hasura.io)
|
|
||||||
|
|
||||||
|
|
||||||
If you have a new service you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
|
||||||
|
|
||||||
## Migration from v1
|
|
||||||
|
|
||||||
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
|
||||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
|
||||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
|
||||||
- Discord: [Invitation](https://discord.gg/xhBCC7eGKw)
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.
|
|
||||||
|
|||||||
@@ -16,26 +16,27 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/autoload": "5.2.0",
|
"@fastify/autoload": "5.2.0",
|
||||||
"@fastify/cookie": "7.3.1",
|
"@fastify/cookie": "8.0.0",
|
||||||
"@fastify/cors": "8.1.0",
|
"@fastify/cors": "8.1.0",
|
||||||
"@fastify/env": "4.1.0",
|
"@fastify/env": "4.1.0",
|
||||||
"@fastify/jwt": "6.3.2",
|
"@fastify/jwt": "6.3.2",
|
||||||
"@fastify/static": "6.5.0",
|
"@fastify/static": "6.5.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
|
"@ladjs/graceful": "3.0.2",
|
||||||
"@prisma/client": "3.15.2",
|
"@prisma/client": "3.15.2",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bree": "9.1.2",
|
"bree": "9.1.2",
|
||||||
"cabin": "9.1.2",
|
"cabin": "9.1.2",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.4",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.4",
|
"dayjs": "1.11.5",
|
||||||
"dockerode": "3.3.3",
|
"dockerode": "3.3.4",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"fastify": "4.4.0",
|
"execa": "6.1.0",
|
||||||
"fastify-plugin": "4.1.0",
|
"fastify": "4.5.2",
|
||||||
|
"fastify-plugin": "4.2.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.1.2",
|
|
||||||
"got": "12.3.1",
|
"got": "12.3.1",
|
||||||
"is-ip": "5.0.0",
|
"is-ip": "5.0.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"is-port-reachable": "4.0.0",
|
||||||
@@ -43,19 +44,20 @@
|
|||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"node-os-utils": "1.3.7",
|
"node-os-utils": "1.3.7",
|
||||||
"p-queue": "7.3.0",
|
"p-all": "4.0.0",
|
||||||
|
"p-throttle": "5.0.0",
|
||||||
"public-ip": "6.0.1",
|
"public-ip": "6.0.1",
|
||||||
"ssh-config": "4.1.6",
|
"ssh-config": "4.1.6",
|
||||||
"strip-ansi": "7.0.1",
|
"strip-ansi": "7.0.1",
|
||||||
"unique-names-generator": "4.7.1"
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.6.5",
|
"@types/node": "18.7.13",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.33.0",
|
"@typescript-eslint/eslint-plugin": "5.35.1",
|
||||||
"@typescript-eslint/parser": "5.33.0",
|
"@typescript-eslint/parser": "5.35.1",
|
||||||
"esbuild": "0.15.0",
|
"esbuild": "0.15.5",
|
||||||
"eslint": "8.21.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"nodemon": "2.0.19",
|
"nodemon": "2.0.19",
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GlitchTip" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"postgresqlUser" TEXT NOT NULL,
|
||||||
|
"postgresqlPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlDatabase" TEXT NOT NULL,
|
||||||
|
"postgresqlPublicPort" INTEGER,
|
||||||
|
"secretKeyBase" TEXT,
|
||||||
|
"defaultEmail" TEXT NOT NULL,
|
||||||
|
"defaultUsername" TEXT NOT NULL,
|
||||||
|
"defaultPassword" TEXT NOT NULL,
|
||||||
|
"defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl',
|
||||||
|
"emailSmtpHost" TEXT DEFAULT 'domain.tdl',
|
||||||
|
"emailSmtpPort" INTEGER DEFAULT 25,
|
||||||
|
"emailSmtpUser" TEXT,
|
||||||
|
"emailSmtpPassword" TEXT,
|
||||||
|
"emailSmtpUseTls" BOOLEAN DEFAULT false,
|
||||||
|
"emailSmtpUseSsl" BOOLEAN DEFAULT false,
|
||||||
|
"emailBackend" TEXT,
|
||||||
|
"mailgunApiKey" TEXT,
|
||||||
|
"sendgridApiKey" TEXT,
|
||||||
|
"enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId");
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Appwrite" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"opensslKeyV1" TEXT NOT NULL,
|
||||||
|
"executorSecret" TEXT NOT NULL,
|
||||||
|
"redisPassword" TEXT NOT NULL,
|
||||||
|
"mariadbHost" TEXT,
|
||||||
|
"mariadbPort" INTEGER NOT NULL DEFAULT 3306,
|
||||||
|
"mariadbUser" TEXT NOT NULL,
|
||||||
|
"mariadbPassword" TEXT NOT NULL,
|
||||||
|
"mariadbRootUser" TEXT NOT NULL,
|
||||||
|
"mariadbRootUserPassword" TEXT NOT NULL,
|
||||||
|
"mariadbDatabase" TEXT NOT NULL,
|
||||||
|
"mariadbPublicPort" INTEGER,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Appwrite_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Appwrite_serviceId_key" ON "Appwrite"("serviceId");
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_ApplicationSettings" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||||
|
DROP TABLE "ApplicationSettings";
|
||||||
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "DNSServers" TEXT;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_GitSource" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"type" TEXT,
|
||||||
|
"apiUrl" TEXT,
|
||||||
|
"htmlUrl" TEXT,
|
||||||
|
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||||
|
"organization" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"githubAppId" TEXT,
|
||||||
|
"gitlabAppId" TEXT,
|
||||||
|
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||||
|
DROP TABLE "GitSource";
|
||||||
|
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||||
|
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||||
|
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||||
|
CREATE TABLE "new_ApplicationSettings" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||||
|
DROP TABLE "ApplicationSettings";
|
||||||
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Searxng" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"secretKey" TEXT NOT NULL,
|
||||||
|
"redisPassword" TEXT NOT NULL,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Searxng_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Searxng_serviceId_key" ON "Searxng"("serviceId");
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"proxyPassword" TEXT NOT NULL,
|
||||||
|
"proxyUser" TEXT NOT NULL,
|
||||||
|
"proxyHash" TEXT,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"DNSServers" TEXT,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "arch", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "DNSServers", "arch", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Build" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"applicationId" TEXT,
|
||||||
|
"destinationDockerId" TEXT,
|
||||||
|
"gitSourceId" TEXT,
|
||||||
|
"githubAppId" TEXT,
|
||||||
|
"gitlabAppId" TEXT,
|
||||||
|
"commit" TEXT,
|
||||||
|
"pullmergeRequestId" TEXT,
|
||||||
|
"forceRebuild" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"sourceBranch" TEXT,
|
||||||
|
"branch" TEXT,
|
||||||
|
"status" TEXT DEFAULT 'queued',
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Build" ("applicationId", "branch", "commit", "createdAt", "destinationDockerId", "gitSourceId", "githubAppId", "gitlabAppId", "id", "status", "type", "updatedAt") SELECT "applicationId", "branch", "commit", "createdAt", "destinationDockerId", "gitSourceId", "githubAppId", "gitlabAppId", "id", "status", "type", "updatedAt" FROM "Build";
|
||||||
|
DROP TABLE "Build";
|
||||||
|
ALTER TABLE "new_Build" RENAME TO "Build";
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -20,12 +20,14 @@ model Setting {
|
|||||||
proxyHash String?
|
proxyHash String?
|
||||||
isAutoUpdateEnabled Boolean @default(false)
|
isAutoUpdateEnabled Boolean @default(false)
|
||||||
isDNSCheckEnabled Boolean @default(true)
|
isDNSCheckEnabled Boolean @default(true)
|
||||||
|
DNSServers String?
|
||||||
isTraefikUsed Boolean @default(true)
|
isTraefikUsed Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
ipv4 String?
|
ipv4 String?
|
||||||
ipv6 String?
|
ipv6 String?
|
||||||
arch String?
|
arch String?
|
||||||
|
concurrentBuilds Int @default(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
@@ -118,15 +120,17 @@ model Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ApplicationSettings {
|
model ApplicationSettings {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
applicationId String @unique
|
applicationId String @unique
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
debug Boolean @default(false)
|
debug Boolean @default(false)
|
||||||
previews Boolean @default(false)
|
previews Boolean @default(false)
|
||||||
autodeploy Boolean @default(true)
|
autodeploy Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
isBot Boolean @default(false)
|
||||||
updatedAt DateTime @updatedAt
|
isPublicRepository Boolean @default(false)
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
@@ -194,6 +198,9 @@ model Build {
|
|||||||
githubAppId String?
|
githubAppId String?
|
||||||
gitlabAppId String?
|
gitlabAppId String?
|
||||||
commit String?
|
commit String?
|
||||||
|
pullmergeRequestId String?
|
||||||
|
forceRebuild Boolean @default(false)
|
||||||
|
sourceBranch String?
|
||||||
branch String?
|
branch String?
|
||||||
status String? @default("queued")
|
status String? @default("queued")
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -236,6 +243,7 @@ model SshKey {
|
|||||||
model GitSource {
|
model GitSource {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
|
forPublic Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
apiUrl String?
|
apiUrl String?
|
||||||
htmlUrl String?
|
htmlUrl String?
|
||||||
@@ -323,19 +331,23 @@ model Service {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
fider Fider?
|
|
||||||
ghost Ghost?
|
|
||||||
hasura Hasura?
|
|
||||||
meiliSearch MeiliSearch?
|
|
||||||
minio Minio?
|
|
||||||
moodle Moodle?
|
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
|
||||||
persistentStorage ServicePersistentStorage[]
|
persistentStorage ServicePersistentStorage[]
|
||||||
serviceSecret ServiceSecret[]
|
serviceSecret ServiceSecret[]
|
||||||
umami Umami?
|
|
||||||
vscodeserver Vscodeserver?
|
|
||||||
wordpress Wordpress?
|
|
||||||
teams Team[]
|
teams Team[]
|
||||||
|
|
||||||
|
fider Fider?
|
||||||
|
ghost Ghost?
|
||||||
|
glitchTip GlitchTip?
|
||||||
|
hasura Hasura?
|
||||||
|
meiliSearch MeiliSearch?
|
||||||
|
minio Minio?
|
||||||
|
moodle Moodle?
|
||||||
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
|
umami Umami?
|
||||||
|
vscodeserver Vscodeserver?
|
||||||
|
wordpress Wordpress?
|
||||||
|
appwrite Appwrite?
|
||||||
|
searxng Searxng?
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@@ -491,3 +503,59 @@ model Moodle {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Appwrite {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
serviceId String @unique
|
||||||
|
opensslKeyV1 String
|
||||||
|
executorSecret String
|
||||||
|
redisPassword String
|
||||||
|
mariadbHost String?
|
||||||
|
mariadbPort Int @default(3306)
|
||||||
|
mariadbUser String
|
||||||
|
mariadbPassword String
|
||||||
|
mariadbRootUser String
|
||||||
|
mariadbRootUserPassword String
|
||||||
|
mariadbDatabase String
|
||||||
|
mariadbPublicPort Int?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model GlitchTip {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
postgresqlUser String
|
||||||
|
postgresqlPassword String
|
||||||
|
postgresqlDatabase String
|
||||||
|
postgresqlPublicPort Int?
|
||||||
|
secretKeyBase String?
|
||||||
|
defaultEmail String
|
||||||
|
defaultUsername String
|
||||||
|
defaultPassword String
|
||||||
|
defaultEmailFrom String @default("glitchtip@domain.tdl")
|
||||||
|
emailSmtpHost String? @default("domain.tdl")
|
||||||
|
emailSmtpPort Int? @default(25)
|
||||||
|
emailSmtpUser String?
|
||||||
|
emailSmtpPassword String?
|
||||||
|
emailSmtpUseTls Boolean? @default(false)
|
||||||
|
emailSmtpUseSsl Boolean? @default(false)
|
||||||
|
emailBackend String?
|
||||||
|
mailgunApiKey String?
|
||||||
|
sendgridApiKey String?
|
||||||
|
enableOpenUserRegistration Boolean @default(true)
|
||||||
|
serviceId String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Searxng {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
secretKey String
|
||||||
|
redisPassword String
|
||||||
|
serviceId String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,34 @@ async function main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const github = await prisma.gitSource.findFirst({
|
||||||
|
where: { htmlUrl: 'https://github.com', forPublic: true }
|
||||||
|
});
|
||||||
|
const gitlab = await prisma.gitSource.findFirst({
|
||||||
|
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
||||||
|
});
|
||||||
|
if (!github) {
|
||||||
|
await prisma.gitSource.create({
|
||||||
|
data: {
|
||||||
|
apiUrl: 'https://api.github.com',
|
||||||
|
htmlUrl: 'https://github.com',
|
||||||
|
forPublic: true,
|
||||||
|
name: 'Github Public',
|
||||||
|
type: 'github'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!gitlab) {
|
||||||
|
await prisma.gitSource.create({
|
||||||
|
data: {
|
||||||
|
apiUrl: 'https://gitlab.com/api/v4',
|
||||||
|
htmlUrl: 'https://gitlab.com',
|
||||||
|
forPublic: true,
|
||||||
|
name: 'Gitlab Public',
|
||||||
|
type: 'gitlab'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import env from '@fastify/env';
|
|||||||
import cookie from '@fastify/cookie';
|
import cookie from '@fastify/cookie';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
|
import { asyncExecShell, asyncSleep, isDev, listSettings, prisma, version } from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
import { scheduler } from './lib/scheduler';
|
||||||
|
import compareVersions from 'compare-versions';
|
||||||
|
import Graceful from '@ladjs/graceful'
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
config: {
|
config: {
|
||||||
@@ -102,34 +103,39 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
|
|||||||
}
|
}
|
||||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||||
await initServer();
|
await initServer();
|
||||||
await scheduler.start('deployApplication');
|
|
||||||
await scheduler.start('cleanupStorage');
|
|
||||||
await scheduler.start('checkProxies');
|
|
||||||
|
|
||||||
// Check if no build is running
|
const graceful = new Graceful({ brees: [scheduler] });
|
||||||
|
graceful.listen();
|
||||||
// Check for update
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
if (!scheduler.workers.has('deployApplication')) {
|
||||||
if (isAutoUpdateEnabled) {
|
scheduler.run('deployApplication');
|
||||||
if (scheduler.workers.has('deployApplication')) {
|
|
||||||
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 60000 * 15)
|
if (!scheduler.workers.has('infrastructure')) {
|
||||||
|
scheduler.run('infrastructure');
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
// Cleanup storage
|
// autoUpdater
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (scheduler.workers.has('deployApplication')) {
|
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:autoUpdater")
|
||||||
scheduler.workers.get('deployApplication').postMessage("status:cleanupStorage");
|
}, isDev ? 5000 : 60000 * 15)
|
||||||
}
|
|
||||||
}, 60000 * 10)
|
// cleanupStorage
|
||||||
|
setInterval(async () => {
|
||||||
|
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage")
|
||||||
|
}, isDev ? 6000 : 60000 * 10)
|
||||||
|
|
||||||
|
// checkProxies
|
||||||
|
setInterval(async () => {
|
||||||
|
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies")
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
// cleanupPrismaEngines
|
||||||
|
// setInterval(async () => {
|
||||||
|
// scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupPrismaEngines")
|
||||||
|
// }, 60000)
|
||||||
|
|
||||||
scheduler.on('worker deleted', async (name) => {
|
|
||||||
if (name === 'autoUpdater' || name === 'cleanupStorage') {
|
|
||||||
if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await getArch();
|
await getArch();
|
||||||
await getIPAddress();
|
await getIPAddress();
|
||||||
});
|
});
|
||||||
@@ -153,6 +159,12 @@ async function initServer() {
|
|||||||
try {
|
try {
|
||||||
await asyncExecShell(`docker network create --attachable coolify`);
|
await asyncExecShell(`docker network create --attachable coolify`);
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
|
try {
|
||||||
|
const isOlder = compareVersions('3.8.1', version);
|
||||||
|
if (isOlder === -1) {
|
||||||
|
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
async function getArch() {
|
async function getArch() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import compareVersions from 'compare-versions';
|
|
||||||
import { parentPort } from 'node:worker_threads';
|
|
||||||
import { asyncExecShell, asyncSleep, isDev, prisma, version } from '../lib/common';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
if (parentPort) {
|
|
||||||
try {
|
|
||||||
const currentVersion = version;
|
|
||||||
const { data: versions } = await axios
|
|
||||||
.get(
|
|
||||||
`https://get.coollabs.io/versions.json`
|
|
||||||
, {
|
|
||||||
params: {
|
|
||||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
|
||||||
version: currentVersion
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const latestVersion = versions['coolify'].main.version;
|
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
|
||||||
if (isUpdateAvailable === 1) {
|
|
||||||
const activeCount = 0
|
|
||||||
if (activeCount === 0) {
|
|
||||||
if (!isDev) {
|
|
||||||
console.log(`Updating Coolify to ${latestVersion}.`);
|
|
||||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
|
||||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
|
||||||
await asyncExecShell(
|
|
||||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log('Updating (not really in dev mode).');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
} finally {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else process.exit(0);
|
|
||||||
})();
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import { parentPort } from 'node:worker_threads';
|
|
||||||
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd } from '../lib/common';
|
|
||||||
import { checkContainer } from '../lib/docker';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
if (parentPort) {
|
|
||||||
// Coolify Proxy local
|
|
||||||
const engine = '/var/run/docker.sock';
|
|
||||||
const localDocker = await prisma.destinationDocker.findFirst({
|
|
||||||
where: { engine, network: 'coolify' }
|
|
||||||
});
|
|
||||||
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
|
||||||
// Remove HAProxy
|
|
||||||
const found = await checkContainer({ dockerId: localDocker.id, container: 'coolify-haproxy' });
|
|
||||||
if (found) {
|
|
||||||
await executeDockerCmd({
|
|
||||||
dockerId: localDocker.id,
|
|
||||||
command: `docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await startTraefikProxy(localDocker.id);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP Proxies
|
|
||||||
const databasesWithPublicPort = await prisma.database.findMany({
|
|
||||||
where: { publicPort: { not: null } },
|
|
||||||
include: { settings: true, destinationDocker: true }
|
|
||||||
});
|
|
||||||
for (const database of databasesWithPublicPort) {
|
|
||||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
|
||||||
const { privatePort } = generateDatabaseConfiguration(database);
|
|
||||||
// Remove HAProxy
|
|
||||||
const found = await checkContainer({
|
|
||||||
dockerId: localDocker.id, container: `haproxy-for-${publicPort}`
|
|
||||||
});
|
|
||||||
if (found) {
|
|
||||||
await executeDockerCmd({
|
|
||||||
dockerId: localDocker.id,
|
|
||||||
command: `docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
|
||||||
where: { ftpPublicPort: { not: null } },
|
|
||||||
include: { service: { include: { destinationDocker: true } } }
|
|
||||||
});
|
|
||||||
for (const ftp of wordpressWithFtp) {
|
|
||||||
const { service, ftpPublicPort } = ftp;
|
|
||||||
const { destinationDockerId, destinationDocker, id } = service;
|
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
|
||||||
// Remove HAProxy
|
|
||||||
const found = await checkContainer({ dockerId: localDocker.id, container: `haproxy-for-${ftpPublicPort}` });
|
|
||||||
if (found) {
|
|
||||||
await executeDockerCmd({
|
|
||||||
dockerId: localDocker.id,
|
|
||||||
command: `docker stop -t 0 haproxy -for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Proxies
|
|
||||||
const minioInstances = await prisma.minio.findMany({
|
|
||||||
where: { publicPort: { not: null } },
|
|
||||||
include: { service: { include: { destinationDocker: true } } }
|
|
||||||
});
|
|
||||||
for (const minio of minioInstances) {
|
|
||||||
const { service, publicPort } = minio;
|
|
||||||
const { destinationDockerId, destinationDocker, id } = service;
|
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
|
||||||
// Remove HAProxy
|
|
||||||
const found = await checkContainer({ dockerId: localDocker.id, container: `${id}-${publicPort}` });
|
|
||||||
if (found) {
|
|
||||||
await executeDockerCmd({
|
|
||||||
dockerId: localDocker.id,
|
|
||||||
command: `docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort} `
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await prisma.$disconnect();
|
|
||||||
} else process.exit(0);
|
|
||||||
})();
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { parentPort } from 'node:worker_threads';
|
|
||||||
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, version } from '../lib/common';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
if (parentPort) {
|
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
|
||||||
let enginesDone = new Set()
|
|
||||||
for (const destination of destinationDockers) {
|
|
||||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
|
||||||
if (destination.engine) enginesDone.add(destination.engine)
|
|
||||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
|
||||||
|
|
||||||
let lowDiskSpace = false;
|
|
||||||
try {
|
|
||||||
let stdout = null
|
|
||||||
if (!isDev) {
|
|
||||||
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
|
||||||
stdout = output.stdout;
|
|
||||||
} else {
|
|
||||||
const output = await asyncExecShell(
|
|
||||||
`df -kPT /`
|
|
||||||
);
|
|
||||||
stdout = output.stdout;
|
|
||||||
}
|
|
||||||
let lines = stdout.trim().split('\n');
|
|
||||||
let header = lines[0];
|
|
||||||
let regex =
|
|
||||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
|
||||||
const boundaries = [];
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(header))) {
|
|
||||||
boundaries.push(match[0].length);
|
|
||||||
}
|
|
||||||
|
|
||||||
boundaries[boundaries.length - 1] = -1;
|
|
||||||
const data = lines.slice(1).map((line) => {
|
|
||||||
const cl = boundaries.map((boundary) => {
|
|
||||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
|
||||||
line = line.slice(boundary);
|
|
||||||
return column.trim();
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
capacity: Number.parseInt(cl[5], 10) / 100
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (data.length > 0) {
|
|
||||||
const { capacity } = data[0];
|
|
||||||
if (capacity > 0.8) {
|
|
||||||
lowDiskSpace = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
|
||||||
}
|
|
||||||
await prisma.$disconnect();
|
|
||||||
} else process.exit(0);
|
|
||||||
})();
|
|
||||||
@@ -4,203 +4,275 @@ import fs from 'fs/promises';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
||||||
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
|
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma } from '../lib/common';
|
||||||
import * as importers from '../lib/importers';
|
import * as importers from '../lib/importers';
|
||||||
import * as buildpacks from '../lib/buildPacks';
|
import * as buildpacks from '../lib/buildPacks';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
const concurrency = 1
|
|
||||||
const PQueue = await import('p-queue');
|
|
||||||
const queue = new PQueue.default({ concurrency });
|
|
||||||
parentPort.on('message', async (message) => {
|
parentPort.on('message', async (message) => {
|
||||||
if (parentPort) {
|
if (message === 'error') throw new Error('oops');
|
||||||
if (message === 'error') throw new Error('oops');
|
if (message === 'cancel') {
|
||||||
if (message === 'cancel') {
|
parentPort.postMessage('cancelled');
|
||||||
parentPort.postMessage('cancelled');
|
await prisma.$disconnect()
|
||||||
return;
|
process.exit(0);
|
||||||
}
|
}
|
||||||
if (message === 'status:autoUpdater') {
|
});
|
||||||
parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'autoUpdater' });
|
const pThrottle = await import('p-throttle')
|
||||||
return;
|
const throttle = pThrottle.default({
|
||||||
}
|
limit: 1,
|
||||||
if (message === 'status:cleanupStorage') {
|
interval: 2000
|
||||||
parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'cleanupStorage' });
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await queue.add(async () => {
|
|
||||||
const {
|
|
||||||
id: applicationId,
|
|
||||||
repository,
|
|
||||||
name,
|
|
||||||
destinationDocker,
|
|
||||||
destinationDockerId,
|
|
||||||
gitSource,
|
|
||||||
build_id: buildId,
|
|
||||||
configHash,
|
|
||||||
fqdn,
|
|
||||||
projectId,
|
|
||||||
secrets,
|
|
||||||
phpModules,
|
|
||||||
type,
|
|
||||||
pullmergeRequestId = null,
|
|
||||||
sourceBranch = null,
|
|
||||||
settings,
|
|
||||||
persistentStorage,
|
|
||||||
pythonWSGI,
|
|
||||||
pythonModule,
|
|
||||||
pythonVariable,
|
|
||||||
denoOptions,
|
|
||||||
exposePort,
|
|
||||||
baseImage,
|
|
||||||
baseBuildImage,
|
|
||||||
deploymentType,
|
|
||||||
} = message
|
|
||||||
let {
|
|
||||||
branch,
|
|
||||||
buildPack,
|
|
||||||
port,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
baseDirectory,
|
|
||||||
publishDirectory,
|
|
||||||
dockerFileLocation,
|
|
||||||
denoMainFile
|
|
||||||
} = message
|
|
||||||
try {
|
|
||||||
const { debug } = settings;
|
|
||||||
if (concurrency === 1) {
|
|
||||||
await prisma.build.updateMany({
|
|
||||||
where: {
|
|
||||||
status: { in: ['queued', 'running'] },
|
|
||||||
id: { not: buildId },
|
|
||||||
applicationId,
|
|
||||||
createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) }
|
|
||||||
},
|
|
||||||
data: { status: 'failed' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let imageId = applicationId;
|
|
||||||
let domain = getDomain(fqdn);
|
|
||||||
const volumes =
|
|
||||||
persistentStorage?.map((storage) => {
|
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
|
||||||
}${storage.path}`;
|
|
||||||
}) || [];
|
|
||||||
// Previews, we need to get the source branch and set subdomain
|
|
||||||
if (pullmergeRequestId) {
|
|
||||||
branch = sourceBranch;
|
|
||||||
domain = `${pullmergeRequestId}.${domain}`;
|
|
||||||
imageId = `${applicationId}-${pullmergeRequestId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let deployNeeded = true;
|
const th = throttle(async () => {
|
||||||
let destinationType;
|
try {
|
||||||
|
const queuedBuilds = await prisma.build.findMany({ where: { status: 'queued' }, orderBy: { createdAt: 'asc' } });
|
||||||
|
const { concurrentBuilds } = await prisma.setting.findFirst({})
|
||||||
|
if (queuedBuilds.length > 0) {
|
||||||
|
parentPort.postMessage({ deploying: true });
|
||||||
|
const concurrency = concurrentBuilds;
|
||||||
|
const pAll = await import('p-all');
|
||||||
|
const actions = []
|
||||||
|
|
||||||
if (destinationDockerId) {
|
for (const queueBuild of queuedBuilds) {
|
||||||
destinationType = 'docker';
|
actions.push(async () => {
|
||||||
}
|
const application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
|
||||||
if (destinationType === 'docker') {
|
const { id: buildId, type, sourceBranch = null, pullmergeRequestId = null, forceRebuild } = queueBuild
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
const {
|
||||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
id: applicationId,
|
||||||
const configuration = await setDefaultConfiguration(message);
|
|
||||||
|
|
||||||
buildPack = configuration.buildPack;
|
|
||||||
port = configuration.port;
|
|
||||||
installCommand = configuration.installCommand;
|
|
||||||
startCommand = configuration.startCommand;
|
|
||||||
buildCommand = configuration.buildCommand;
|
|
||||||
publishDirectory = configuration.publishDirectory;
|
|
||||||
baseDirectory = configuration.baseDirectory;
|
|
||||||
dockerFileLocation = configuration.dockerFileLocation;
|
|
||||||
denoMainFile = configuration.denoMainFile;
|
|
||||||
const commit = await importers[gitSource.type]({
|
|
||||||
applicationId,
|
|
||||||
debug,
|
|
||||||
workdir,
|
|
||||||
repodir,
|
|
||||||
githubAppId: gitSource.githubApp?.id,
|
|
||||||
gitlabAppId: gitSource.gitlabApp?.id,
|
|
||||||
customPort: gitSource.customPort,
|
|
||||||
repository,
|
repository,
|
||||||
branch,
|
name,
|
||||||
buildId,
|
destinationDocker,
|
||||||
apiUrl: gitSource.apiUrl,
|
destinationDockerId,
|
||||||
htmlUrl: gitSource.htmlUrl,
|
gitSource,
|
||||||
|
configHash,
|
||||||
|
fqdn,
|
||||||
projectId,
|
projectId,
|
||||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
secrets,
|
||||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
|
phpModules,
|
||||||
});
|
settings,
|
||||||
if (!commit) {
|
persistentStorage,
|
||||||
throw new Error('No commit found?');
|
pythonWSGI,
|
||||||
}
|
pythonModule,
|
||||||
let tag = commit.slice(0, 7);
|
pythonVariable,
|
||||||
if (pullmergeRequestId) {
|
denoOptions,
|
||||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
exposePort,
|
||||||
}
|
baseImage,
|
||||||
|
baseBuildImage,
|
||||||
|
deploymentType,
|
||||||
|
} = application
|
||||||
|
let {
|
||||||
|
branch,
|
||||||
|
buildPack,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile
|
||||||
|
} = application
|
||||||
|
const currentHash = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(
|
||||||
|
JSON.stringify({
|
||||||
|
pythonWSGI,
|
||||||
|
pythonModule,
|
||||||
|
pythonVariable,
|
||||||
|
deploymentType,
|
||||||
|
denoOptions,
|
||||||
|
baseImage,
|
||||||
|
baseBuildImage,
|
||||||
|
buildPack,
|
||||||
|
port,
|
||||||
|
exposePort,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
secrets,
|
||||||
|
branch,
|
||||||
|
repository,
|
||||||
|
fqdn
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.digest('hex');
|
||||||
try {
|
try {
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
const { debug } = settings;
|
||||||
} catch (err) {
|
if (concurrency === 1) {
|
||||||
console.log(err);
|
await prisma.build.updateMany({
|
||||||
}
|
where: {
|
||||||
if (!pullmergeRequestId) {
|
status: { in: ['queued', 'running'] },
|
||||||
const currentHash = crypto
|
id: { not: buildId },
|
||||||
//@ts-ignore
|
applicationId,
|
||||||
.createHash('sha256')
|
createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) }
|
||||||
.update(
|
},
|
||||||
JSON.stringify({
|
data: { status: 'failed' }
|
||||||
buildPack,
|
|
||||||
port,
|
|
||||||
exposePort,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
secrets,
|
|
||||||
branch,
|
|
||||||
repository,
|
|
||||||
fqdn
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.digest('hex');
|
|
||||||
|
|
||||||
if (configHash !== currentHash) {
|
|
||||||
await prisma.application.update({
|
|
||||||
where: { id: applicationId },
|
|
||||||
data: { configHash: currentHash }
|
|
||||||
});
|
});
|
||||||
deployNeeded = true;
|
|
||||||
if (configHash) {
|
|
||||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deployNeeded = false;
|
|
||||||
}
|
}
|
||||||
} else {
|
let imageId = applicationId;
|
||||||
deployNeeded = true;
|
let domain = getDomain(fqdn);
|
||||||
}
|
const volumes =
|
||||||
|
persistentStorage?.map((storage) => {
|
||||||
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||||
|
}${storage.path}`;
|
||||||
|
}) || [];
|
||||||
|
// Previews, we need to get the source branch and set subdomain
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
branch = sourceBranch;
|
||||||
|
domain = `${pullmergeRequestId}.${domain}`;
|
||||||
|
imageId = `${applicationId}-${pullmergeRequestId}`;
|
||||||
|
}
|
||||||
|
|
||||||
let imageFound = false;
|
let deployNeeded = true;
|
||||||
try {
|
let destinationType;
|
||||||
await executeDockerCmd({
|
|
||||||
dockerId: destinationDocker.id,
|
if (destinationDockerId) {
|
||||||
command: `docker image inspect ${applicationId}:${tag}`
|
destinationType = 'docker';
|
||||||
})
|
}
|
||||||
imageFound = true;
|
if (destinationType === 'docker') {
|
||||||
} catch (error) {
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||||
//
|
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||||
}
|
const configuration = await setDefaultConfiguration(application);
|
||||||
if (!imageFound || deployNeeded) {
|
|
||||||
// if (true) {
|
buildPack = configuration.buildPack;
|
||||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
port = configuration.port;
|
||||||
if (buildpacks[buildPack])
|
installCommand = configuration.installCommand;
|
||||||
await buildpacks[buildPack]({
|
startCommand = configuration.startCommand;
|
||||||
dockerId: destinationDocker.id,
|
buildCommand = configuration.buildCommand;
|
||||||
buildId,
|
publishDirectory = configuration.publishDirectory;
|
||||||
|
baseDirectory = configuration.baseDirectory;
|
||||||
|
dockerFileLocation = configuration.dockerFileLocation;
|
||||||
|
denoMainFile = configuration.denoMainFile;
|
||||||
|
const commit = await importers[gitSource.type]({
|
||||||
applicationId,
|
applicationId,
|
||||||
domain,
|
debug,
|
||||||
|
workdir,
|
||||||
|
repodir,
|
||||||
|
githubAppId: gitSource.githubApp?.id,
|
||||||
|
gitlabAppId: gitSource.gitlabApp?.id,
|
||||||
|
customPort: gitSource.customPort,
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
buildId,
|
||||||
|
apiUrl: gitSource.apiUrl,
|
||||||
|
htmlUrl: gitSource.htmlUrl,
|
||||||
|
projectId,
|
||||||
|
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||||
|
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null,
|
||||||
|
forPublic: gitSource.forPublic
|
||||||
|
});
|
||||||
|
if (!commit) {
|
||||||
|
throw new Error('No commit found?');
|
||||||
|
}
|
||||||
|
let tag = commit.slice(0, 7);
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pullmergeRequestId) {
|
||||||
|
if (configHash !== currentHash) {
|
||||||
|
deployNeeded = true;
|
||||||
|
if (configHash) {
|
||||||
|
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deployNeeded = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deployNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageFound = false;
|
||||||
|
try {
|
||||||
|
await executeDockerCmd({
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker image inspect ${applicationId}:${tag}`
|
||||||
|
})
|
||||||
|
imageFound = true;
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||||
|
|
||||||
|
if (forceRebuild) deployNeeded = true
|
||||||
|
if (!imageFound || deployNeeded) {
|
||||||
|
// if (true) {
|
||||||
|
if (buildpacks[buildPack])
|
||||||
|
await buildpacks[buildPack]({
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
domain,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
pullmergeRequestId,
|
||||||
|
buildPack,
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
projectId,
|
||||||
|
publishDirectory,
|
||||||
|
debug,
|
||||||
|
commit,
|
||||||
|
tag,
|
||||||
|
workdir,
|
||||||
|
port: exposePort ? `${exposePort}:${port}` : port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
phpModules,
|
||||||
|
pythonWSGI,
|
||||||
|
pythonModule,
|
||||||
|
pythonVariable,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile,
|
||||||
|
denoOptions,
|
||||||
|
baseImage,
|
||||||
|
baseBuildImage,
|
||||||
|
deploymentType
|
||||||
|
});
|
||||||
|
else {
|
||||||
|
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||||
|
throw new Error(`Build pack ${buildPack} not found.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const envs = [
|
||||||
|
`PORT=${port}`
|
||||||
|
];
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
const labels = makeLabelForStandaloneApplication({
|
||||||
|
applicationId,
|
||||||
|
fqdn,
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
@@ -208,151 +280,93 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
projectId,
|
projectId,
|
||||||
publishDirectory,
|
|
||||||
debug,
|
|
||||||
commit,
|
|
||||||
tag,
|
|
||||||
workdir,
|
|
||||||
port: exposePort ? `${exposePort}:${port}` : port,
|
port: exposePort ? `${exposePort}:${port}` : port,
|
||||||
|
commit,
|
||||||
installCommand,
|
installCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
startCommand,
|
startCommand,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets,
|
publishDirectory
|
||||||
phpModules,
|
|
||||||
pythonWSGI,
|
|
||||||
pythonModule,
|
|
||||||
pythonVariable,
|
|
||||||
dockerFileLocation,
|
|
||||||
denoMainFile,
|
|
||||||
denoOptions,
|
|
||||||
baseImage,
|
|
||||||
baseBuildImage,
|
|
||||||
deploymentType
|
|
||||||
});
|
});
|
||||||
else {
|
let envFound = false;
|
||||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
try {
|
||||||
throw new Error(`Build pack ${buildPack} not found.`);
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
}
|
} catch (error) {
|
||||||
} else {
|
//
|
||||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
const envs = [];
|
|
||||||
if (secrets.length > 0) {
|
|
||||||
secrets.forEach((secret) => {
|
|
||||||
if (pullmergeRequestId) {
|
|
||||||
if (secret.isPRMRSecret) {
|
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
try {
|
||||||
}
|
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
const composeVolumes = volumes.map((volume) => {
|
||||||
const labels = makeLabelForStandaloneApplication({
|
return {
|
||||||
applicationId,
|
[`${volume.split(':')[0]}`]: {
|
||||||
fqdn,
|
name: volume.split(':')[0]
|
||||||
name,
|
|
||||||
type,
|
|
||||||
pullmergeRequestId,
|
|
||||||
buildPack,
|
|
||||||
repository,
|
|
||||||
branch,
|
|
||||||
projectId,
|
|
||||||
port: exposePort ? `${exposePort}:${port}` : port,
|
|
||||||
commit,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
baseDirectory,
|
|
||||||
publishDirectory
|
|
||||||
});
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
|
||||||
const composeVolumes = volumes.map((volume) => {
|
|
||||||
return {
|
|
||||||
[`${volume.split(':')[0]}`]: {
|
|
||||||
name: volume.split(':')[0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const composeFile = {
|
|
||||||
version: '3.8',
|
|
||||||
services: {
|
|
||||||
[imageId]: {
|
|
||||||
image: `${applicationId}:${tag}`,
|
|
||||||
container_name: imageId,
|
|
||||||
volumes,
|
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
|
||||||
networks: [destinationDocker.network],
|
|
||||||
labels,
|
|
||||||
depends_on: [],
|
|
||||||
restart: 'always',
|
|
||||||
expose: [port],
|
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
|
||||||
// logging: {
|
|
||||||
// driver: 'fluentd',
|
|
||||||
// },
|
|
||||||
deploy: {
|
|
||||||
restart_policy: {
|
|
||||||
condition: 'on-failure',
|
|
||||||
delay: '5s',
|
|
||||||
max_attempts: 3,
|
|
||||||
window: '120s'
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
});
|
||||||
},
|
const composeFile = {
|
||||||
networks: {
|
version: '3.8',
|
||||||
[destinationDocker.network]: {
|
services: {
|
||||||
external: true
|
[imageId]: {
|
||||||
}
|
image: `${applicationId}:${tag}`,
|
||||||
},
|
container_name: imageId,
|
||||||
volumes: Object.assign({}, ...composeVolumes)
|
volumes,
|
||||||
};
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
labels,
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
depends_on: [],
|
||||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
expose: [port],
|
||||||
} catch (error) {
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
// logging: {
|
||||||
await prisma.build.update({
|
// driver: 'fluentd',
|
||||||
where: { id: message.build_id },
|
// },
|
||||||
|
...defaultComposeConfiguration(destinationDocker.network),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[destinationDocker.network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
|
};
|
||||||
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
|
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||||
|
} catch (error) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
await prisma.build.updateMany({
|
||||||
|
where: { id: buildId, status: { in: ['queued', 'running'] } },
|
||||||
|
data: { status: 'failed' }
|
||||||
|
});
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||||
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||||
|
if (!pullmergeRequestId) await prisma.application.update({
|
||||||
|
where: { id: applicationId },
|
||||||
|
data: { configHash: currentHash }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
await prisma.build.updateMany({
|
||||||
|
where: { id: buildId, status: { in: ['queued', 'running'] } },
|
||||||
data: { status: 'failed' }
|
data: { status: 'failed' }
|
||||||
});
|
});
|
||||||
throw new Error(error);
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
}
|
}
|
||||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
|
||||||
await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
await prisma.build.update({
|
|
||||||
where: { id: message.build_id },
|
|
||||||
data: { status: 'failed' }
|
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
|
||||||
} finally {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
}
|
||||||
});
|
await pAll.default(actions, { concurrency })
|
||||||
await prisma.$disconnect();
|
}
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
await th()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} else process.exit(0);
|
} else process.exit(0);
|
||||||
})();
|
})();
|
||||||
|
|||||||
216
apps/api/src/jobs/infrastructure.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import { parentPort } from 'node:worker_threads';
|
||||||
|
import axios from 'axios';
|
||||||
|
import compareVersions from 'compare-versions';
|
||||||
|
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version } from '../lib/common';
|
||||||
|
|
||||||
|
async function disconnect() {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
async function autoUpdater() {
|
||||||
|
try {
|
||||||
|
const currentVersion = version;
|
||||||
|
const { data: versions } = await axios
|
||||||
|
.get(
|
||||||
|
`https://get.coollabs.io/versions.json`
|
||||||
|
, {
|
||||||
|
params: {
|
||||||
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
|
version: currentVersion
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const latestVersion = versions['coolify'].main.version;
|
||||||
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
|
if (isUpdateAvailable === 1) {
|
||||||
|
const activeCount = 0
|
||||||
|
if (activeCount === 0) {
|
||||||
|
if (!isDev) {
|
||||||
|
console.log(`Updating Coolify to ${latestVersion}.`);
|
||||||
|
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||||
|
await asyncExecShell(`env | grep COOLIFY > .env`);
|
||||||
|
await asyncExecShell(
|
||||||
|
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('Updating (not really in dev mode).');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function checkProxies() {
|
||||||
|
try {
|
||||||
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
|
let portReachable;
|
||||||
|
|
||||||
|
const { arch, ipv4, ipv6 } = await listSettings();
|
||||||
|
// Coolify Proxy local
|
||||||
|
const engine = '/var/run/docker.sock';
|
||||||
|
const localDocker = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { engine, network: 'coolify' }
|
||||||
|
});
|
||||||
|
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||||
|
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
||||||
|
if (!portReachable) {
|
||||||
|
await startTraefikProxy(localDocker.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP Proxies
|
||||||
|
const databasesWithPublicPort = await prisma.database.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { settings: true, destinationDocker: true }
|
||||||
|
});
|
||||||
|
for (const database of databasesWithPublicPort) {
|
||||||
|
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||||
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
|
const { privatePort } = generateDatabaseConfiguration(database, arch);
|
||||||
|
portReachable = await isReachable(publicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 })
|
||||||
|
if (!portReachable) {
|
||||||
|
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||||
|
where: { ftpPublicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const ftp of wordpressWithFtp) {
|
||||||
|
const { service, ftpPublicPort } = ftp;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
|
portReachable = await isReachable(ftpPublicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 })
|
||||||
|
if (!portReachable) {
|
||||||
|
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Proxies
|
||||||
|
const minioInstances = await prisma.minio.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const minio of minioInstances) {
|
||||||
|
const { service, publicPort } = minio;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
|
portReachable = await isReachable(publicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 })
|
||||||
|
if (!portReachable) {
|
||||||
|
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function cleanupPrismaEngines() {
|
||||||
|
if (!isDev) {
|
||||||
|
try {
|
||||||
|
const { stdout } = await asyncExecShell(`ps -ef | grep /app/prisma-engines/query-engine | grep -v grep | wc -l | xargs`)
|
||||||
|
if (stdout.trim() != null && stdout.trim() != '' && Number(stdout.trim()) > 1) {
|
||||||
|
await asyncExecShell(`killall -q -e /app/prisma-engines/query-engine -o 1m`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function cleanupStorage() {
|
||||||
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
|
let enginesDone = new Set()
|
||||||
|
for (const destination of destinationDockers) {
|
||||||
|
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
||||||
|
if (destination.engine) enginesDone.add(destination.engine)
|
||||||
|
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
||||||
|
|
||||||
|
let lowDiskSpace = false;
|
||||||
|
try {
|
||||||
|
let stdout = null
|
||||||
|
if (!isDev) {
|
||||||
|
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
||||||
|
stdout = output.stdout;
|
||||||
|
} else {
|
||||||
|
const output = await asyncExecShell(
|
||||||
|
`df -kPT /`
|
||||||
|
);
|
||||||
|
stdout = output.stdout;
|
||||||
|
}
|
||||||
|
let lines = stdout.trim().split('\n');
|
||||||
|
let header = lines[0];
|
||||||
|
let regex =
|
||||||
|
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||||
|
const boundaries = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(header))) {
|
||||||
|
boundaries.push(match[0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
boundaries[boundaries.length - 1] = -1;
|
||||||
|
const data = lines.slice(1).map((line) => {
|
||||||
|
const cl = boundaries.map((boundary) => {
|
||||||
|
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||||
|
line = line.slice(boundary);
|
||||||
|
return column.trim();
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
capacity: Number.parseInt(cl[5], 10) / 100
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (data.length > 0) {
|
||||||
|
const { capacity } = data[0];
|
||||||
|
if (capacity > 0.8) {
|
||||||
|
lowDiskSpace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let status = {
|
||||||
|
cleanupStorage: false,
|
||||||
|
autoUpdater: false
|
||||||
|
}
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.on('message', async (message) => {
|
||||||
|
if (parentPort) {
|
||||||
|
if (message === 'error') throw new Error('oops');
|
||||||
|
if (message === 'cancel') {
|
||||||
|
parentPort.postMessage('cancelled');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (message === 'action:cleanupStorage') {
|
||||||
|
if (!status.autoUpdater) {
|
||||||
|
status.cleanupStorage = true
|
||||||
|
await cleanupStorage();
|
||||||
|
status.cleanupStorage = false
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message === 'action:cleanupPrismaEngines') {
|
||||||
|
await cleanupPrismaEngines();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message === 'action:checkProxies') {
|
||||||
|
await checkProxies();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message === 'action:autoUpdater') {
|
||||||
|
if (!status.cleanupStorage) {
|
||||||
|
status.autoUpdater = true
|
||||||
|
await autoUpdater();
|
||||||
|
status.autoUpdater = false
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else process.exit(0);
|
||||||
|
})();
|
||||||
@@ -252,6 +252,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
label: 'python:3.7-slim-bullseye'
|
label: 'python:3.7-slim-bullseye'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const herokuVersions = [
|
||||||
|
{
|
||||||
|
value: 'heroku/builder:22',
|
||||||
|
label: 'heroku/builder:22'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'heroku/buildpacks:20',
|
||||||
|
label: 'heroku/buildpacks:20'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'heroku/builder-classic:22',
|
||||||
|
label: 'heroku/builder-classic:22'
|
||||||
|
},
|
||||||
|
]
|
||||||
let payload: any = {
|
let payload: any = {
|
||||||
baseImage: null,
|
baseImage: null,
|
||||||
baseBuildImage: null,
|
baseBuildImage: null,
|
||||||
@@ -299,6 +313,11 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
payload.baseBuildImage = 'node:18';
|
payload.baseBuildImage = 'node:18';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
}
|
}
|
||||||
|
if (buildPack === 'heroku') {
|
||||||
|
payload.baseImage = 'heroku/buildpacks:20';
|
||||||
|
payload.baseImages = herokuVersions;
|
||||||
|
|
||||||
|
}
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,9 +541,6 @@ export async function buildImage({
|
|||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||||
}
|
}
|
||||||
if (debug) {
|
|
||||||
await saveBuildLog({ line: `\n###############\nIMPORTANT: Due to some issues during implementing Remote Docker Engine, the builds logs are not streamed at the moment. You will see the full build log when the build is finished!\n###############`, buildId, applicationId });
|
|
||||||
}
|
|
||||||
if (!debug && isCache) {
|
if (!debug && isCache) {
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
||||||
@@ -534,54 +550,11 @@ export async function buildImage({
|
|||||||
}
|
}
|
||||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
||||||
if (debug) {
|
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
|
||||||
const array = stderr.split('\n')
|
if (status === 'canceled') {
|
||||||
for (const line of array) {
|
throw new Error('Deployment canceled.')
|
||||||
if (line !== '\n') {
|
|
||||||
await saveBuildLog({
|
|
||||||
line: `${line.replace('\n', '')}`,
|
|
||||||
buildId,
|
|
||||||
applicationId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// await new Promise((resolve, reject) => {
|
|
||||||
// const command = spawn(`docker`, ['build', '-f', `${workdir}${dockerFile}`, '-t', `${cache}`,`${workdir}`], {
|
|
||||||
// env: {
|
|
||||||
// DOCKER_HOST: 'ssh://root@95.217.178.202',
|
|
||||||
// DOCKER_BUILDKIT: '1'
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// command.stdout.on('data', function (data) {
|
|
||||||
// console.log('stdout: ' + data);
|
|
||||||
// });
|
|
||||||
// command.stderr.on('data', function (data) {
|
|
||||||
// console.log('stderr: ' + data);
|
|
||||||
// });
|
|
||||||
// command.on('error', function (error) {
|
|
||||||
// console.log(error)
|
|
||||||
// reject(error)
|
|
||||||
// })
|
|
||||||
// command.on('exit', function (code) {
|
|
||||||
// console.log('exit code: ' + code);
|
|
||||||
// resolve(code)
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
// console.log({ stdout, stderr })
|
|
||||||
// const stream = await docker.engine.buildImage(
|
|
||||||
// { src: ['.'], context: workdir },
|
|
||||||
// {
|
|
||||||
// dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
|
||||||
// t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// await streamEvents({ stream, docker, buildId, applicationId, debug });
|
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { executeDockerCmd, prisma } from "../common"
|
|||||||
import { saveBuildLog } from "./common";
|
import { saveBuildLog } from "./common";
|
||||||
|
|
||||||
export default async function (data: any): Promise<void> {
|
export default async function (data: any): Promise<void> {
|
||||||
|
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
|
||||||
try {
|
try {
|
||||||
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
|
|
||||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||||
const { stdout } = await executeDockerCmd({
|
const { stdout } = await executeDockerCmd({
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `pack build -p ${workdir} ${applicationId}:${tag} --builder heroku/buildpacks:20`
|
command: `pack build -p ${workdir} ${applicationId}:${tag} --builder heroku/buildpacks:20`
|
||||||
})
|
})
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
const array = stdout.split('\n')
|
const array = stdout.split('\n')
|
||||||
for (const line of array) {
|
for (const line of array) {
|
||||||
@@ -24,6 +24,16 @@ export default async function (data: any): Promise<void> {
|
|||||||
}
|
}
|
||||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const array = error.stdout.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import child from 'child_process';
|
import { exec } from 'node:child_process'
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
@@ -16,8 +16,10 @@ import sshConfig from 'ssh-config'
|
|||||||
import { checkContainer, removeContainer } from './docker';
|
import { checkContainer, removeContainer } from './docker';
|
||||||
import { day } from './dayjs';
|
import { day } from './dayjs';
|
||||||
import * as serviceFields from './serviceFields'
|
import * as serviceFields from './serviceFields'
|
||||||
|
import { saveBuildLog } from './buildPacks/common';
|
||||||
|
import { scheduler } from './scheduler';
|
||||||
|
|
||||||
export const version = '3.2.2';
|
export const version = '3.8.2';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
@@ -38,8 +40,8 @@ export function getAPIUrl() {
|
|||||||
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '')
|
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '')
|
||||||
return newURL
|
return newURL
|
||||||
}
|
}
|
||||||
if (process.env.CODESANDBOX_HOST) {
|
if (process.env.CODESANDBOX_HOST) {
|
||||||
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/,'3001')}`
|
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`
|
||||||
}
|
}
|
||||||
return isDev ? 'http://localhost:3001' : 'http://localhost:3000';
|
return isDev ? 'http://localhost:3001' : 'http://localhost:3000';
|
||||||
}
|
}
|
||||||
@@ -50,8 +52,8 @@ export function getUIUrl() {
|
|||||||
const newURL = href.replace('https://', 'https://3000-').replace(/\/$/, '')
|
const newURL = href.replace('https://', 'https://3000-').replace(/\/$/, '')
|
||||||
return newURL
|
return newURL
|
||||||
}
|
}
|
||||||
if (process.env.CODESANDBOX_HOST) {
|
if (process.env.CODESANDBOX_HOST) {
|
||||||
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/,'3000')}`
|
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3000')}`
|
||||||
}
|
}
|
||||||
return 'http://localhost:3000';
|
return 'http://localhost:3000';
|
||||||
}
|
}
|
||||||
@@ -78,16 +80,87 @@ export const include: any = {
|
|||||||
umami: true,
|
umami: true,
|
||||||
hasura: true,
|
hasura: true,
|
||||||
fider: true,
|
fider: true,
|
||||||
|
moodle: true,
|
||||||
|
appwrite: true,
|
||||||
|
glitchTip: true,
|
||||||
|
searxng: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
||||||
export const asyncExecShell = util.promisify(child.exec);
|
export const asyncExecShell = util.promisify(exec);
|
||||||
|
export const asyncExecShellStream = async ({ debug, buildId, applicationId, command, engine }: { debug: boolean, buildId: string, applicationId: string, command: string, engine: string }) => {
|
||||||
|
return await new Promise(async (resolve, reject) => {
|
||||||
|
const { execaCommand } = await import('execa')
|
||||||
|
const subprocess = execaCommand(command, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine } })
|
||||||
|
if (debug) {
|
||||||
|
subprocess.stdout.on('data', async (data) => {
|
||||||
|
const stdout = data.toString();
|
||||||
|
const array = stdout.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n' && line !== '') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
subprocess.stderr.on('data', async (data) => {
|
||||||
|
const stderr = data.toString();
|
||||||
|
const array = stderr.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n' && line !== '') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
subprocess.on('exit', async (code) => {
|
||||||
|
await asyncSleep(1000);
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(code)
|
||||||
|
} else {
|
||||||
|
reject(code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const asyncSleep = (delay: number): Promise<unknown> =>
|
export const asyncSleep = (delay: number): Promise<unknown> =>
|
||||||
new Promise((resolve) => setTimeout(resolve, delay));
|
new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
export const prisma = new PrismaClient({
|
export const prisma = new PrismaClient({
|
||||||
errorFormat: 'minimal'
|
errorFormat: 'minimal',
|
||||||
|
// log: [
|
||||||
|
// {
|
||||||
|
// emit: 'event',
|
||||||
|
// level: 'query',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// emit: 'stdout',
|
||||||
|
// level: 'error',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// emit: 'stdout',
|
||||||
|
// level: 'info',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// emit: 'stdout',
|
||||||
|
// level: 'warn',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// prisma.$on('query', (e) => {
|
||||||
|
// console.log({e})
|
||||||
|
// console.log('Query: ' + e.query)
|
||||||
|
// console.log('Params: ' + e.params)
|
||||||
|
// console.log('Duration: ' + e.duration + 'ms')
|
||||||
|
// })
|
||||||
export const base64Encode = (text: string): string => {
|
export const base64Encode = (text: string): string => {
|
||||||
return Buffer.from(text).toString('base64');
|
return Buffer.from(text).toString('base64');
|
||||||
};
|
};
|
||||||
@@ -96,17 +169,23 @@ export const base64Decode = (text: string): string => {
|
|||||||
};
|
};
|
||||||
export const decrypt = (hashString: string) => {
|
export const decrypt = (hashString: string) => {
|
||||||
if (hashString) {
|
if (hashString) {
|
||||||
const hash = JSON.parse(hashString);
|
try {
|
||||||
const decipher = crypto.createDecipheriv(
|
const hash = JSON.parse(hashString);
|
||||||
algorithm,
|
const decipher = crypto.createDecipheriv(
|
||||||
process.env['COOLIFY_SECRET_KEY'],
|
algorithm,
|
||||||
Buffer.from(hash.iv, 'hex')
|
process.env['COOLIFY_SECRET_KEY'],
|
||||||
);
|
Buffer.from(hash.iv, 'hex')
|
||||||
const decrpyted = Buffer.concat([
|
);
|
||||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
const decrpyted = Buffer.concat([
|
||||||
decipher.final()
|
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||||
]);
|
decipher.final()
|
||||||
return decrpyted.toString();
|
]);
|
||||||
|
return decrpyted.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ decryptionError: error.message })
|
||||||
|
return hashString
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const encrypt = (text: string) => {
|
export const encrypt = (text: string) => {
|
||||||
@@ -252,8 +331,8 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
fancyName: 'Hasura',
|
fancyName: 'Hasura',
|
||||||
baseImage: 'hasura/graphql-engine',
|
baseImage: 'hasura/graphql-engine',
|
||||||
images: ['postgres:12-alpine'],
|
images: ['postgres:12-alpine'],
|
||||||
versions: ['latest', 'v2.8.4', 'v2.5.1'],
|
versions: ['latest', 'v2.10.0', 'v2.5.1'],
|
||||||
recommendedVersion: 'v2.8.4',
|
recommendedVersion: 'v2.10.0',
|
||||||
ports: {
|
ports: {
|
||||||
main: 8080
|
main: 8080
|
||||||
}
|
}
|
||||||
@@ -269,6 +348,17 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
main: 3000
|
main: 3000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'appwrite',
|
||||||
|
fancyName: 'Appwrite',
|
||||||
|
baseImage: 'appwrite/appwrite',
|
||||||
|
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
|
||||||
|
versions: ['latest', '0.15.3'],
|
||||||
|
recommendedVersion: '0.15.3',
|
||||||
|
ports: {
|
||||||
|
main: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@@ -280,6 +370,28 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// main: 8080
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
{
|
||||||
|
name: 'glitchTip',
|
||||||
|
fancyName: 'GlitchTip',
|
||||||
|
baseImage: 'glitchtip/glitchtip',
|
||||||
|
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'searxng',
|
||||||
|
fancyName: 'SearXNG',
|
||||||
|
baseImage: 'searxng/searxng',
|
||||||
|
images: [],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
||||||
@@ -288,6 +400,10 @@ export async function checkDoubleBranch(branch: string, projectId: number): Prom
|
|||||||
}
|
}
|
||||||
export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
||||||
const { isIP } = await import('is-ip');
|
const { isIP } = await import('is-ip');
|
||||||
|
const { DNSServers } = await listSettings();
|
||||||
|
if (DNSServers) {
|
||||||
|
dns.setServers([DNSServers]);
|
||||||
|
}
|
||||||
let resolves = [];
|
let resolves = [];
|
||||||
try {
|
try {
|
||||||
if (isIP(hostname)) {
|
if (isIP(hostname)) {
|
||||||
@@ -301,7 +417,6 @@ export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let ipDomainFound = false;
|
let ipDomainFound = false;
|
||||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
|
||||||
const dnsResolve = await dns.resolve4(domain);
|
const dnsResolve = await dns.resolve4(domain);
|
||||||
if (dnsResolve.length > 0) {
|
if (dnsResolve.length > 0) {
|
||||||
for (const ip of dnsResolve) {
|
for (const ip of dnsResolve) {
|
||||||
@@ -393,7 +508,12 @@ export async function checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }): P
|
|||||||
const { isIP } = await import('is-ip');
|
const { isIP } = await import('is-ip');
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
|
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
|
||||||
dns.setServers(['1.1.1.1', '8.8.8.8']);
|
|
||||||
|
const { DNSServers } = await listSettings();
|
||||||
|
if (DNSServers) {
|
||||||
|
dns.setServers([DNSServers]);
|
||||||
|
}
|
||||||
|
|
||||||
let resolves = [];
|
let resolves = [];
|
||||||
try {
|
try {
|
||||||
if (isIP(hostname)) {
|
if (isIP(hostname)) {
|
||||||
@@ -460,45 +580,84 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
name: 'mongodb',
|
name: 'mongodb',
|
||||||
fancyName: 'MongoDB',
|
fancyName: 'MongoDB',
|
||||||
baseImage: 'bitnami/mongodb',
|
baseImage: 'bitnami/mongodb',
|
||||||
versions: ['5.0', '4.4', '4.2']
|
baseImageARM: 'mongo',
|
||||||
|
versions: ['5.0', '4.4', '4.2'],
|
||||||
|
versionsARM: ['5.0', '4.4', '4.2']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mysql',
|
||||||
|
fancyName: 'MySQL',
|
||||||
|
baseImage: 'bitnami/mysql',
|
||||||
|
baseImageARM: 'mysql',
|
||||||
|
versions: ['8.0', '5.7'],
|
||||||
|
versionsARM: ['8.0', '5.7']
|
||||||
},
|
},
|
||||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
|
|
||||||
{
|
{
|
||||||
name: 'mariadb',
|
name: 'mariadb',
|
||||||
fancyName: 'MariaDB',
|
fancyName: 'MariaDB',
|
||||||
baseImage: 'bitnami/mariadb',
|
baseImage: 'bitnami/mariadb',
|
||||||
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
baseImageARM: 'mariadb',
|
||||||
|
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
|
||||||
|
versionsARM: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'postgresql',
|
name: 'postgresql',
|
||||||
fancyName: 'PostgreSQL',
|
fancyName: 'PostgreSQL',
|
||||||
baseImage: 'bitnami/postgresql',
|
baseImage: 'bitnami/postgresql',
|
||||||
versions: ['14.4.0', '13.6.0', '12.10.0', '11.15.0', '10.20.0']
|
baseImageARM: 'postgres',
|
||||||
|
versions: ['14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
|
||||||
|
versionsARM: ['14.5', '13.8', '12.12', '11.17', '10.22']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'redis',
|
name: 'redis',
|
||||||
fancyName: 'Redis',
|
fancyName: 'Redis',
|
||||||
baseImage: 'bitnami/redis',
|
baseImage: 'bitnami/redis',
|
||||||
versions: ['7.0', '6.2', '6.0', '5.0']
|
baseImageARM: 'redis',
|
||||||
|
versions: ['7.0', '6.2', '6.0', '5.0'],
|
||||||
|
versionsARM: ['7.0', '6.2', '6.0', '5.0']
|
||||||
},
|
},
|
||||||
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.2'] }
|
{
|
||||||
|
name: 'couchdb',
|
||||||
|
fancyName: 'CouchDB',
|
||||||
|
baseImage: 'bitnami/couchdb',
|
||||||
|
baseImageARM: 'couchdb',
|
||||||
|
versions: ['3.2.2', '3.1.2', '2.3.1'],
|
||||||
|
versionsARM: ['3.2.2', '3.1.2', '2.3.1']
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getFreeSSHLocalPort(id: string): Promise<number> {
|
export async function getFreeSSHLocalPort(id: string): Promise<number | boolean> {
|
||||||
const { default: getPort, portNumbers } = await import('get-port');
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } })
|
const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||||
if (sshLocalPort) {
|
if (sshLocalPort) {
|
||||||
return Number(sshLocalPort)
|
return Number(sshLocalPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = await prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } })
|
const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } })
|
||||||
const alreadyConfigured = await prisma.destinationDocker.findFirst({ where: { remoteIpAddress, id: { not: id }, sshLocalPort: { not: null } } })
|
|
||||||
|
const alreadyConfigured = await prisma.destinationDocker.findFirst({
|
||||||
|
where: {
|
||||||
|
remoteIpAddress, id: { not: id }, sshLocalPort: { not: null }
|
||||||
|
}
|
||||||
|
})
|
||||||
if (alreadyConfigured?.sshLocalPort) {
|
if (alreadyConfigured?.sshLocalPort) {
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } })
|
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } })
|
||||||
return Number(alreadyConfigured.sshLocalPort)
|
return Number(alreadyConfigured.sshLocalPort)
|
||||||
}
|
}
|
||||||
const availablePort = await getPort({ port: portNumbers(10000, 10100), exclude: ports.map(p => p.sshLocalPort) })
|
const range = generateRangeArray(minPort, maxPort)
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(availablePort) } })
|
console.log({ ports })
|
||||||
return Number(availablePort)
|
const availablePorts = range.filter(port => !ports.map(p => p.sshLocalPort).includes(port))
|
||||||
|
for (const port of availablePorts) {
|
||||||
|
const found = await isReachable(port, { host: 'localhost' })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(port) } })
|
||||||
|
return Number(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRemoteEngineConfiguration(id: string) {
|
export async function createRemoteEngineConfiguration(id: string) {
|
||||||
@@ -530,7 +689,7 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
config.append({
|
config.append({
|
||||||
Host: remoteIpAddress,
|
Host: remoteIpAddress,
|
||||||
Hostname: 'localhost',
|
Hostname: 'localhost',
|
||||||
Port: Number(localPort),
|
Port: localPort.toString(),
|
||||||
User: remoteUser,
|
User: remoteUser,
|
||||||
IdentityFile: sshKeyFile,
|
IdentityFile: sshKeyFile,
|
||||||
StrictHostKeyChecking: 'no'
|
StrictHostKeyChecking: 'no'
|
||||||
@@ -543,7 +702,7 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
}
|
}
|
||||||
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
||||||
}
|
}
|
||||||
export async function executeDockerCmd({ dockerId, command }: { dockerId: string, command: string }) {
|
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
|
||||||
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||||
if (remoteEngine) {
|
if (remoteEngine) {
|
||||||
await createRemoteEngineConfiguration(dockerId)
|
await createRemoteEngineConfiguration(dockerId)
|
||||||
@@ -551,6 +710,14 @@ export async function executeDockerCmd({ dockerId, command }: { dockerId: string
|
|||||||
} else {
|
} else {
|
||||||
engine = 'unix:///var/run/docker.sock'
|
engine = 'unix:///var/run/docker.sock'
|
||||||
}
|
}
|
||||||
|
if (process.env.CODESANDBOX_HOST) {
|
||||||
|
if (command.startsWith('docker compose')) {
|
||||||
|
command = command.replace(/docker compose/gi, 'docker-compose')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (command.startsWith(`docker build --progress plain`)) {
|
||||||
|
return await asyncExecShellStream({ debug, buildId, applicationId, command, engine });
|
||||||
|
}
|
||||||
return await asyncExecShell(
|
return await asyncExecShell(
|
||||||
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
||||||
);
|
);
|
||||||
@@ -562,6 +729,11 @@ export async function startTraefikProxy(id: string): Promise<void> {
|
|||||||
const { id: settingsId, ipv4, ipv6 } = await listSettings();
|
const { id: settingsId, ipv4, ipv6 } = await listSettings();
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
const { stdout: coolifyNetwork } = await executeDockerCmd({ dockerId: id, command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"` })
|
||||||
|
|
||||||
|
if (!coolifyNetwork) {
|
||||||
|
await executeDockerCmd({ dockerId: id, command: `docker network create --attachable coolify-infra` })
|
||||||
|
}
|
||||||
const { stdout: Config } = await executeDockerCmd({ dockerId: id, command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` })
|
const { stdout: Config } = await executeDockerCmd({ dockerId: id, command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` })
|
||||||
const ip = JSON.parse(Config)[0].Gateway;
|
const ip = JSON.parse(Config)[0].Gateway;
|
||||||
let traefikUrl = mainTraefikEndpoint
|
let traefikUrl = mainTraefikEndpoint
|
||||||
@@ -665,19 +837,25 @@ export async function listSettings(): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function generatePassword(length = 24, symbols = false): string {
|
export function generatePassword({ length = 24, symbols = false, isHex = false }: { length?: number, symbols?: boolean, isHex?: boolean } | null): string {
|
||||||
return generator.generate({
|
if (isHex) {
|
||||||
|
return crypto.randomBytes(length).toString("hex");
|
||||||
|
}
|
||||||
|
const password = generator.generate({
|
||||||
length,
|
length,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
strict: true,
|
strict: true,
|
||||||
symbols
|
symbols
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDatabaseConfiguration(database: any):
|
export function generateDatabaseConfiguration(database: any, arch: string):
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
@@ -691,16 +869,20 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MONGODB_ROOT_USER: string;
|
MONGO_INITDB_ROOT_USERNAME?: string;
|
||||||
MONGODB_ROOT_PASSWORD: string;
|
MONGO_INITDB_ROOT_PASSWORD?: string;
|
||||||
|
MONGODB_ROOT_USER?: string;
|
||||||
|
MONGODB_ROOT_PASSWORD?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
@@ -714,6 +896,7 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
@@ -726,6 +909,19 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
command?: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
POSTGRES_USER: string;
|
||||||
|
POSTGRES_PASSWORD: string;
|
||||||
|
POSTGRES_DB: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
@@ -736,6 +932,7 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
@@ -754,9 +951,9 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
type,
|
type,
|
||||||
settings: { appendOnly }
|
settings: { appendOnly }
|
||||||
} = database;
|
} = database;
|
||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type, arch);
|
||||||
if (type === 'mysql') {
|
if (type === 'mysql') {
|
||||||
return {
|
const configuration = {
|
||||||
privatePort: 3306,
|
privatePort: 3306,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MYSQL_USER: dbUser,
|
MYSQL_USER: dbUser,
|
||||||
@@ -768,9 +965,13 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
image: `${baseImage}:${version}`,
|
image: `${baseImage}:${version}`,
|
||||||
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
}
|
||||||
|
if (isARM(arch)) {
|
||||||
|
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
} else if (type === 'mariadb') {
|
} else if (type === 'mariadb') {
|
||||||
return {
|
const configuration = {
|
||||||
privatePort: 3306,
|
privatePort: 3306,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MARIADB_ROOT_USER: rootUser,
|
MARIADB_ROOT_USER: rootUser,
|
||||||
@@ -783,8 +984,12 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
volume: `${id}-${type}-data:/bitnami/mariadb`,
|
volume: `${id}-${type}-data:/bitnami/mariadb`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
|
if (isARM(arch)) {
|
||||||
|
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
} else if (type === 'mongodb') {
|
} else if (type === 'mongodb') {
|
||||||
return {
|
const configuration = {
|
||||||
privatePort: 27017,
|
privatePort: 27017,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MONGODB_ROOT_USER: rootUser,
|
MONGODB_ROOT_USER: rootUser,
|
||||||
@@ -794,8 +999,16 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
volume: `${id}-${type}-data:/bitnami/mongodb`,
|
volume: `${id}-${type}-data:/bitnami/mongodb`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
|
if (isARM(arch)) {
|
||||||
|
configuration.environmentVariables = {
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: rootUser,
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
|
||||||
|
}
|
||||||
|
configuration.volume = `${id}-${type}-data:/data/db`;
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
} else if (type === 'postgresql') {
|
} else if (type === 'postgresql') {
|
||||||
return {
|
const configuration = {
|
||||||
privatePort: 5432,
|
privatePort: 5432,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
|
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
|
||||||
@@ -806,10 +1019,20 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
image: `${baseImage}:${version}`,
|
image: `${baseImage}:${version}`,
|
||||||
volume: `${id}-${type}-data:/bitnami/postgresql`,
|
volume: `${id}-${type}-data:/bitnami/postgresql`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
}
|
||||||
|
if (isARM(arch)) {
|
||||||
|
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
|
||||||
|
configuration.environmentVariables = {
|
||||||
|
POSTGRES_PASSWORD: dbUserPassword,
|
||||||
|
POSTGRES_USER: dbUser,
|
||||||
|
POSTGRES_DB: defaultDatabase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
} else if (type === 'redis') {
|
} else if (type === 'redis') {
|
||||||
return {
|
const configuration = {
|
||||||
privatePort: 6379,
|
privatePort: 6379,
|
||||||
|
command: undefined,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
REDIS_PASSWORD: dbUserPassword,
|
REDIS_PASSWORD: dbUserPassword,
|
||||||
REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no'
|
REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no'
|
||||||
@@ -818,8 +1041,13 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
volume: `${id}-${type}-data:/bitnami/redis/data`,
|
volume: `${id}-${type}-data:/bitnami/redis/data`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
|
if (isARM(arch)) {
|
||||||
|
configuration.volume = `${id}-${type}-data:/data`;
|
||||||
|
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'} --requirepass ${dbUserPassword}`;
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
} else if (type === 'couchdb') {
|
} else if (type === 'couchdb') {
|
||||||
return {
|
const configuration = {
|
||||||
privatePort: 5984,
|
privatePort: 5984,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
COUCHDB_PASSWORD: dbUserPassword,
|
COUCHDB_PASSWORD: dbUserPassword,
|
||||||
@@ -829,20 +1057,35 @@ export function generateDatabaseConfiguration(database: any):
|
|||||||
volume: `${id}-${type}-data:/bitnami/couchdb`,
|
volume: `${id}-${type}-data:/bitnami/couchdb`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
|
if (isARM(arch)) {
|
||||||
|
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
|
||||||
|
}
|
||||||
|
return configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function isARM(arch: string) {
|
||||||
export function getDatabaseImage(type: string): string {
|
if (arch === 'arm' || arch === 'arm64') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
export function getDatabaseImage(type: string, arch: string): string {
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
|
if (isARM(arch)) {
|
||||||
|
return found.baseImageARM || found.baseImage
|
||||||
|
}
|
||||||
return found.baseImage;
|
return found.baseImage;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDatabaseVersions(type: string): string[] {
|
export function getDatabaseVersions(type: string, arch: string): string[] {
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
|
if (isARM(arch)) {
|
||||||
|
return found.versionsARM || found.versions
|
||||||
|
}
|
||||||
return found.versions;
|
return found.versions;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -1051,8 +1294,27 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }: { id: string, configuredPort?: number, exposePort: number, dockerId: string, remoteIpAddress?: string }) {
|
||||||
|
if (exposePort < 1024 || exposePort > 65535) {
|
||||||
|
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuredPort) {
|
||||||
|
if (configuredPort !== exposePort) {
|
||||||
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
|
if (availablePort.toString() !== exposePort.toString()) {
|
||||||
|
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
|
if (availablePort.toString() !== exposePort.toString()) {
|
||||||
|
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
|
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
|
||||||
const { default: getPort } = await import('get-port');
|
const { default: checkPort } = await import('is-port-reachable');
|
||||||
const applicationUsed = await (
|
const applicationUsed = await (
|
||||||
await prisma.application.findMany({
|
await prisma.application.findMany({
|
||||||
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
||||||
@@ -1066,22 +1328,23 @@ export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddre
|
|||||||
})
|
})
|
||||||
).map((a) => a.exposePort);
|
).map((a) => a.exposePort);
|
||||||
const usedPorts = [...applicationUsed, ...serviceUsed];
|
const usedPorts = [...applicationUsed, ...serviceUsed];
|
||||||
if (remoteIpAddress) {
|
if (usedPorts.includes(exposePort)) {
|
||||||
const { default: checkPort } = await import('is-port-reachable');
|
|
||||||
const found = await checkPort(exposePort, { host: remoteIpAddress });
|
|
||||||
if (!found) {
|
|
||||||
return exposePort
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return await getPort({ port: Number(exposePort), exclude: usedPorts });
|
const found = await checkPort(exposePort, { host: remoteIpAddress || 'localhost' });
|
||||||
|
if (!found) {
|
||||||
|
return exposePort
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
export function generateRangeArray(start, end) {
|
||||||
|
return Array.from({ length: (end - start) }, (v, k) => k + start);
|
||||||
|
}
|
||||||
export async function getFreePublicPort(id, dockerId) {
|
export async function getFreePublicPort(id, dockerId) {
|
||||||
const { default: getPort, portNumbers } = await import('get-port');
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
const data = await prisma.setting.findFirst();
|
const data = await prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const dbUsed = await (
|
const dbUsed = await (
|
||||||
await prisma.database.findMany({
|
await prisma.database.findMany({
|
||||||
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
||||||
@@ -1107,7 +1370,15 @@ export async function getFreePublicPort(id, dockerId) {
|
|||||||
})
|
})
|
||||||
).map((a) => a.publicPort);
|
).map((a) => a.publicPort);
|
||||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
||||||
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
const range = generateRangeArray(minPort, maxPort)
|
||||||
|
const availablePorts = range.filter(port => !usedPorts.includes(port))
|
||||||
|
for (const port of availablePorts) {
|
||||||
|
const found = await isReachable(port, { host: 'localhost' })
|
||||||
|
if (!found) {
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startTraefikTCPProxy(
|
export async function startTraefikTCPProxy(
|
||||||
@@ -1143,7 +1414,6 @@ export async function startTraefikTCPProxy(
|
|||||||
}
|
}
|
||||||
traefikUrl = `${ip}/webhooks/traefik/other.json`
|
traefikUrl = `${ip}/webhooks/traefik/other.json`
|
||||||
}
|
}
|
||||||
console.log(traefikUrl)
|
|
||||||
const tcpProxy = {
|
const tcpProxy = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -1208,6 +1478,7 @@ export async function getServiceFromDB({ id, teamId }: { id: string; teamId: str
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) }
|
body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) }
|
||||||
return { ...body, settings };
|
return { ...body, settings };
|
||||||
}
|
}
|
||||||
@@ -1236,11 +1507,11 @@ export async function configureServiceType({
|
|||||||
type: string;
|
type: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (type === 'plausibleanalytics') {
|
if (type === 'plausibleanalytics') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword({}));
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'plausibleanalytics';
|
const postgresqlDatabase = 'plausibleanalytics';
|
||||||
const secretKeyBase = encrypt(generatePassword(64));
|
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||||
|
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1264,22 +1535,22 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'minio') {
|
} else if (type === 'minio') {
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
||||||
});
|
});
|
||||||
} else if (type === 'vscodeserver') {
|
} else if (type === 'vscodeserver') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type, vscodeserver: { create: { password } } }
|
data: { type, vscodeserver: { create: { password } } }
|
||||||
});
|
});
|
||||||
} else if (type === 'wordpress') {
|
} else if (type === 'wordpress') {
|
||||||
const mysqlUser = cuid();
|
const mysqlUser = cuid();
|
||||||
const mysqlPassword = encrypt(generatePassword());
|
const mysqlPassword = encrypt(generatePassword({}));
|
||||||
const mysqlRootUser = cuid();
|
const mysqlRootUser = cuid();
|
||||||
const mysqlRootUserPassword = encrypt(generatePassword());
|
const mysqlRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1317,11 +1588,11 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'ghost') {
|
} else if (type === 'ghost') {
|
||||||
const defaultEmail = `${cuid()}@example.com`;
|
const defaultEmail = `${cuid()}@example.com`;
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
|
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1340,7 +1611,7 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'meilisearch') {
|
} else if (type === 'meilisearch') {
|
||||||
const masterKey = encrypt(generatePassword(32));
|
const masterKey = encrypt(generatePassword({ length: 32 }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1349,11 +1620,11 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'umami') {
|
} else if (type === 'umami') {
|
||||||
const umamiAdminPassword = encrypt(generatePassword());
|
const umamiAdminPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'umami';
|
const postgresqlDatabase = 'umami';
|
||||||
const hashSalt = encrypt(generatePassword(64));
|
const hashSalt = encrypt(generatePassword({ length: 64 }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1371,9 +1642,9 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'hasura') {
|
} else if (type === 'hasura') {
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'hasura';
|
const postgresqlDatabase = 'hasura';
|
||||||
const graphQLAdminPassword = encrypt(generatePassword());
|
const graphQLAdminPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1390,9 +1661,9 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'fider') {
|
} else if (type === 'fider') {
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'fider';
|
const postgresqlDatabase = 'fider';
|
||||||
const jwtSecret = encrypt(generatePassword(64, true));
|
const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1409,13 +1680,13 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'moodle') {
|
} else if (type === 'moodle') {
|
||||||
const defaultUsername = cuid();
|
const defaultUsername = cuid();
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
const defaultEmail = `${cuid()} @example.com`;
|
const defaultEmail = `${cuid()} @example.com`;
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbDatabase = 'moodle_db';
|
const mariadbDatabase = 'moodle_db';
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1434,6 +1705,76 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (type === 'appwrite') {
|
||||||
|
const opensslKeyV1 = encrypt(generatePassword({}));
|
||||||
|
const executorSecret = encrypt(generatePassword({}));
|
||||||
|
const redisPassword = encrypt(generatePassword({}));
|
||||||
|
const mariadbHost = `${id}-mariadb`
|
||||||
|
const mariadbUser = cuid();
|
||||||
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
|
const mariadbDatabase = 'appwrite';
|
||||||
|
const mariadbRootUser = cuid();
|
||||||
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
appwrite: {
|
||||||
|
create: {
|
||||||
|
opensslKeyV1,
|
||||||
|
executorSecret,
|
||||||
|
redisPassword,
|
||||||
|
mariadbHost,
|
||||||
|
mariadbUser,
|
||||||
|
mariadbPassword,
|
||||||
|
mariadbDatabase,
|
||||||
|
mariadbRootUser,
|
||||||
|
mariadbRootUserPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (type === 'glitchTip') {
|
||||||
|
const defaultUsername = cuid();
|
||||||
|
const defaultEmail = `${defaultUsername}@example.com`;
|
||||||
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
|
const postgresqlUser = cuid();
|
||||||
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
|
const postgresqlDatabase = 'glitchTip';
|
||||||
|
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||||
|
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
glitchTip: {
|
||||||
|
create: {
|
||||||
|
postgresqlDatabase,
|
||||||
|
postgresqlUser,
|
||||||
|
postgresqlPassword,
|
||||||
|
secretKeyBase,
|
||||||
|
defaultEmail,
|
||||||
|
defaultUsername,
|
||||||
|
defaultPassword,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (type === 'searxng') {
|
||||||
|
const secretKey = encrypt(generatePassword({ length: 32, isHex: true }))
|
||||||
|
const redisPassword = encrypt(generatePassword({}));
|
||||||
|
await prisma.service.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
searxng: {
|
||||||
|
create: {
|
||||||
|
secretKey,
|
||||||
|
redisPassword,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1445,6 +1786,7 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||||
|
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||||
@@ -1455,8 +1797,10 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
|||||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1521,9 +1865,9 @@ export const getServiceMainPort = (service: string) => {
|
|||||||
export function makeLabelForServices(type) {
|
export function makeLabelForServices(type) {
|
||||||
return [
|
return [
|
||||||
'coolify.managed=true',
|
'coolify.managed=true',
|
||||||
`coolify.version = ${version} `,
|
`coolify.version = ${version}`,
|
||||||
`coolify.type = service`,
|
`coolify.type = service`,
|
||||||
`coolify.service.type = ${type} `
|
`coolify.service.type = ${type}`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) {
|
export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) {
|
||||||
@@ -1549,18 +1893,22 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
await new Promise<void>(async (resolve, reject) => {
|
await new Promise<void>(async (resolve, reject) => {
|
||||||
const { destinationDockerId, status } = await prisma.build.findFirst({ where: { id: buildId } });
|
const { destinationDockerId, status } = await prisma.build.findFirst({ where: { id: buildId } });
|
||||||
const { engine, id: dockerId } = await prisma.destinationDocker.findFirst({ where: { id: destinationDockerId } });
|
const { id: dockerId } = await prisma.destinationDocker.findFirst({ where: { id: destinationDockerId } });
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
if (status === 'failed') {
|
if (status === 'failed' || status === 'canceled') {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
if (count > 100) {
|
if (count > 15) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return reject(new Error('Build canceled'));
|
if (scheduler.workers.has('deployApplication')) {
|
||||||
|
scheduler.workers.get('deployApplication').postMessage('cancel')
|
||||||
|
}
|
||||||
|
await cleanupDB(buildId, applicationId);
|
||||||
|
return reject(new Error('Deployment canceled.'));
|
||||||
}
|
}
|
||||||
const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls--filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` })
|
const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` })
|
||||||
if (buildContainers) {
|
if (buildContainers) {
|
||||||
const containersArray = buildContainers.trim().split('\n');
|
const containersArray = buildContainers.trim().split('\n');
|
||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
@@ -1568,8 +1916,11 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
const id = containerObj.ID;
|
const id = containerObj.ID;
|
||||||
if (!containerObj.Names.startsWith(`${applicationId} `)) {
|
if (!containerObj.Names.startsWith(`${applicationId} `)) {
|
||||||
await removeContainer({ id, dockerId });
|
await removeContainer({ id, dockerId });
|
||||||
await cleanupDB(buildId);
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
if (scheduler.workers.has('deployApplication')) {
|
||||||
|
scheduler.workers.get('deployApplication').postMessage('cancel')
|
||||||
|
}
|
||||||
|
await cleanupDB(buildId, applicationId);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1580,11 +1931,12 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupDB(buildId: string) {
|
async function cleanupDB(buildId: string, applicationId: string) {
|
||||||
const data = await prisma.build.findUnique({ where: { id: buildId } });
|
const data = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
if (data?.status === 'queued' || data?.status === 'running') {
|
if (data?.status === 'queued' || data?.status === 'running') {
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'failed' } });
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'canceled' } });
|
||||||
}
|
}
|
||||||
|
await saveBuildLog({ line: 'Deployment canceled.', buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertTolOldVolumeNames(type) {
|
export function convertTolOldVolumeNames(type) {
|
||||||
@@ -1592,10 +1944,7 @@ export function convertTolOldVolumeNames(type) {
|
|||||||
return 'nc'
|
return 'nc'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// export async function getAvailableServices(): Promise<any> {
|
|
||||||
// const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
|
|
||||||
// return data
|
|
||||||
//
|
|
||||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
||||||
// Cleanup old coolify images
|
// Cleanup old coolify images
|
||||||
try {
|
try {
|
||||||
@@ -1637,7 +1986,7 @@ export function persistentVolumes(id, persistentStorage, config) {
|
|||||||
return `${id}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
return `${id}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
let volumes = [ ...persistentVolume]
|
let volumes = [...persistentVolume]
|
||||||
if (config.volume) volumes = [config.volume, ...volumes]
|
if (config.volume) volumes = [config.volume, ...volumes]
|
||||||
|
|
||||||
const composeVolumes = volumes.length > 0 && volumes.map((volume) => {
|
const composeVolumes = volumes.length > 0 && volumes.map((volume) => {
|
||||||
@@ -1658,4 +2007,18 @@ export function persistentVolumes(id, persistentStorage, config) {
|
|||||||
...composeVolumes
|
...composeVolumes
|
||||||
) || {}
|
) || {}
|
||||||
return { volumes, volumeMounts }
|
return { volumes, volumeMounts }
|
||||||
}
|
}
|
||||||
|
export function defaultComposeConfiguration(network: string): any {
|
||||||
|
return {
|
||||||
|
networks: [network],
|
||||||
|
restart: 'on-failure',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 10,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export async function removeContainer({
|
|||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||||
|
|
||||||
if (JSON.parse(stdout).Running) {
|
if (JSON.parse(stdout).Running) {
|
||||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export default async function ({
|
|||||||
htmlUrl,
|
htmlUrl,
|
||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
customPort
|
customPort,
|
||||||
|
forPublic
|
||||||
}: {
|
}: {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
@@ -23,41 +24,55 @@ export default async function ({
|
|||||||
branch: string;
|
branch: string;
|
||||||
buildId: string;
|
buildId: string;
|
||||||
customPort: number;
|
customPort: number;
|
||||||
|
forPublic?: boolean;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got')
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||||
|
if (forPublic) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Cloning ${repository}:${branch} branch.`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
await asyncExecShell(
|
||||||
|
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
|
);
|
||||||
|
|
||||||
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
} else {
|
||||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||||
const { privateKey, appId, installationId } = body
|
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||||
|
const { privateKey, appId, installationId } = body
|
||||||
|
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||||
|
|
||||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
const payload = {
|
||||||
|
iat: Math.round(new Date().getTime() / 1000),
|
||||||
const payload = {
|
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||||
iat: Math.round(new Date().getTime() / 1000),
|
iss: appId
|
||||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
};
|
||||||
iss: appId
|
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||||
};
|
algorithm: 'RS256'
|
||||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
});
|
||||||
algorithm: 'RS256'
|
const { token } = await got
|
||||||
});
|
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||||
const { token } = await got
|
headers: {
|
||||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
headers: {
|
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||||
Authorization: `Bearer ${jwtToken}`,
|
}
|
||||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
})
|
||||||
}
|
.json();
|
||||||
})
|
await saveBuildLog({
|
||||||
.json();
|
line: `Cloning ${repository}:${branch} branch.`,
|
||||||
await saveBuildLog({
|
buildId,
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
applicationId
|
||||||
buildId,
|
});
|
||||||
applicationId
|
await asyncExecShell(
|
||||||
});
|
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
await asyncExecShell(
|
);
|
||||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
}
|
||||||
);
|
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
|
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,51 +2,29 @@ import Bree from 'bree';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Cabin from 'cabin';
|
import Cabin from 'cabin';
|
||||||
import TSBree from '@breejs/ts-worker';
|
import TSBree from '@breejs/ts-worker';
|
||||||
import { isDev } from './common';
|
|
||||||
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
Bree.extend(TSBree);
|
Bree.extend(TSBree);
|
||||||
|
|
||||||
const options: any = {
|
const options: any = {
|
||||||
defaultExtension: 'js',
|
defaultExtension: 'js',
|
||||||
|
// logger: new Cabin(),
|
||||||
logger: false,
|
logger: false,
|
||||||
workerMessageHandler: async ({ name, message }) => {
|
workerMessageHandler: async ({ name, message }) => {
|
||||||
if (name === 'deployApplication') {
|
if (name === 'deployApplication' && message?.deploying) {
|
||||||
if (message.pending === 0 && message.size === 0) {
|
if (scheduler.workers.has('autoUpdater') || scheduler.workers.has('cleanupStorage')) {
|
||||||
if (message.caller === 'autoUpdater') {
|
scheduler.workers.get('deployApplication').postMessage('cancel')
|
||||||
if (!scheduler.workers.has('autoUpdater')) {
|
|
||||||
await scheduler.stop('deployApplication');
|
|
||||||
await scheduler.run('autoUpdater')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (message.caller === 'cleanupStorage') {
|
|
||||||
if (!scheduler.workers.has('cleanupStorage')) {
|
|
||||||
await scheduler.stop('deployApplication');
|
|
||||||
await scheduler.run('cleanupStorage')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
jobs: [
|
jobs: [
|
||||||
{
|
{ name: 'infrastructure' },
|
||||||
name: 'deployApplication'
|
{ name: 'deployApplication' },
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cleanupStorage',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'checkProxies',
|
|
||||||
interval: '10s'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'autoUpdater',
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
if (isDev) options.root = path.join(__dirname, '../jobs');
|
if (isDev) options.root = path.join(__dirname, '../jobs');
|
||||||
|
|
||||||
|
|
||||||
export const scheduler = new Bree(options);
|
export const scheduler = new Bree(options);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export const fider = [{
|
|||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: true
|
isEncrypted: true
|
||||||
}, {
|
}, {
|
||||||
name: 'postgreslUser',
|
name: 'postgresqlUser',
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
isLowerCase: false,
|
isLowerCase: false,
|
||||||
isNumber: false,
|
isNumber: false,
|
||||||
@@ -476,4 +476,215 @@ export const moodle = [{
|
|||||||
isNumber: false,
|
isNumber: false,
|
||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: false
|
isEncrypted: false
|
||||||
|
}]
|
||||||
|
|
||||||
|
export const appwrite = [{
|
||||||
|
name: 'opensslKeyV1',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'executorSecret',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redisPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbHost',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbPort',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbRootUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbRootUserPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mariadbDatabase',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
}]
|
||||||
|
|
||||||
|
export const glitchTip = [{
|
||||||
|
name: 'postgresqlUser',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlDatabase',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postgresqlPublicPort',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: true,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'secretKeyBase',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultEmail',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultUsername',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultFromEmail',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailUrl',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailBackend',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mailgunApiKey',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sendgridApiKey',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableOpenUserRegistration',
|
||||||
|
isEditable: true,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: true,
|
||||||
|
isEncrypted: false
|
||||||
|
}]
|
||||||
|
|
||||||
|
export const searxng = [{
|
||||||
|
name: 'secretKey',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redisPassword',
|
||||||
|
isEditable: false,
|
||||||
|
isLowerCase: false,
|
||||||
|
isNumber: false,
|
||||||
|
isBoolean: false,
|
||||||
|
isEncrypted: true
|
||||||
}]
|
}]
|
||||||
20
apps/api/src/lib/services.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createDirectories, getServiceFromDB, getServiceImage, getServiceMainPort, makeLabelForServices } from "./common";
|
||||||
|
|
||||||
|
export async function defaultServiceConfigurations({ id, teamId }) {
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const { destinationDockerId, destinationDocker, type, serviceSecret } = service;
|
||||||
|
|
||||||
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
const port = getServiceMainPort(type);
|
||||||
|
|
||||||
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
|
||||||
|
const image = getServiceImage(type);
|
||||||
|
let secrets = [];
|
||||||
|
if (serviceSecret.length > 0) {
|
||||||
|
serviceSecret.forEach((secret) => {
|
||||||
|
secrets.push(`${secret.name}=${secret.value}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { ...service, network, port, workdir, image, secrets }
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import axios from 'axios';
|
|||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||||
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||||
import { scheduler } from '../../../../lib/scheduler';
|
import { scheduler } from '../../../../lib/scheduler';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
|
|||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const applications = await prisma.application.findMany({
|
const applications = await prisma.application.findMany({
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { teams: true, destinationDocker: true }
|
include: { teams: true, destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
const settings = await prisma.setting.findFirst()
|
const settings = await prisma.setting.findFirst()
|
||||||
return {
|
return {
|
||||||
@@ -75,7 +75,6 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
isExited = await isContainerExited(application.destinationDocker.id, id);
|
isExited = await isContainerExited(application.destinationDocker.id, id);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isQueueActive: scheduler.workers.has('deployApplication'),
|
|
||||||
isRunning,
|
isRunning,
|
||||||
isExited,
|
isExited,
|
||||||
};
|
};
|
||||||
@@ -90,10 +89,11 @@ export async function getApplication(request: FastifyRequest<OnlyId>) {
|
|||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
const settings = await listSettings();
|
||||||
return {
|
return {
|
||||||
application,
|
application,
|
||||||
appId
|
appId,
|
||||||
|
settings
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -237,6 +237,9 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
exposePort = Number(exposePort);
|
exposePort = Number(exposePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { destinationDocker: { id: dockerId, remoteIpAddress } } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
if (exposePort) await checkExposedPort({ id, exposePort, dockerId, remoteIpAddress })
|
||||||
if (denoOptions) denoOptions = denoOptions.trim();
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
const defaultConfiguration = await setDefaultConfiguration({
|
const defaultConfiguration = await setDefaultConfiguration({
|
||||||
buildPack,
|
buildPack,
|
||||||
@@ -275,7 +278,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
|
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { debug, previews, dualCerts, autodeploy, branch, projectId } = request.body
|
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body
|
||||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
const isDouble = await checkDoubleBranch(branch, projectId);
|
||||||
if (isDouble && autodeploy) {
|
if (isDouble && autodeploy) {
|
||||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
||||||
@@ -283,7 +286,7 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
|
|||||||
}
|
}
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
@@ -391,18 +394,7 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
|||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||||
}
|
}
|
||||||
if (exposePort) {
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||||
if (exposePort < 1024 || exposePort > 65535) {
|
|
||||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuredPort !== exposePort) {
|
|
||||||
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
|
||||||
if (availablePort.toString() !== exposePort.toString()) {
|
|
||||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
let hostname = request.hostname.split(':')[0];
|
let hostname = request.hostname.split(':')[0];
|
||||||
if (remoteEngine) hostname = remoteIpAddress;
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
@@ -435,7 +427,7 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const { pullmergeRequestId = null, branch } = request.body
|
const { pullmergeRequestId = null, branch, forceRebuild } = request.body
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
const application = await getApplicationFromDB(id, teamId);
|
const application = await getApplicationFromDB(id, teamId);
|
||||||
if (application) {
|
if (application) {
|
||||||
@@ -460,6 +452,8 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
id: buildId,
|
id: buildId,
|
||||||
applicationId: id,
|
applicationId: id,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
|
pullmergeRequestId,
|
||||||
|
forceRebuild,
|
||||||
destinationDockerId: application.destinationDocker?.id,
|
destinationDockerId: application.destinationDocker?.id,
|
||||||
gitSourceId: application.gitSource?.id,
|
gitSourceId: application.gitSource?.id,
|
||||||
githubAppId: application.gitSource?.githubApp?.id,
|
githubAppId: application.gitSource?.githubApp?.id,
|
||||||
@@ -468,22 +462,6 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
type: 'manual'
|
type: 'manual'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (pullmergeRequestId) {
|
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
|
||||||
build_id: buildId,
|
|
||||||
type: 'manual',
|
|
||||||
...application,
|
|
||||||
sourceBranch: branch,
|
|
||||||
pullmergeRequestId
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
|
||||||
build_id: buildId,
|
|
||||||
type: 'manual',
|
|
||||||
...application
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
buildId
|
buildId
|
||||||
};
|
};
|
||||||
@@ -498,11 +476,20 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { gitSourceId } = request.body
|
const { gitSourceId, forPublic, type } = request.body
|
||||||
await prisma.application.update({
|
if (forPublic) {
|
||||||
where: { id },
|
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
|
||||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
await prisma.application.update({
|
||||||
});
|
where: { id },
|
||||||
|
data: { gitSource: { connect: { id: publicGit.id } } }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id },
|
||||||
|
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -556,7 +543,7 @@ export async function checkRepository(request: FastifyRequest<CheckRepository>)
|
|||||||
export async function saveRepository(request, reply) {
|
export async function saveRepository(request, reply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
let { repository, branch, projectId, autodeploy, webhookToken } = request.body
|
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
|
||||||
|
|
||||||
repository = repository.toLowerCase();
|
repository = repository.toLowerCase();
|
||||||
branch = branch.toLowerCase();
|
branch = branch.toLowerCase();
|
||||||
@@ -564,17 +551,19 @@ export async function saveRepository(request, reply) {
|
|||||||
if (webhookToken) {
|
if (webhookToken) {
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy } } }
|
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy, isPublicRepository } } }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
|
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
if (!isPublicRepository) {
|
||||||
if (isDouble) {
|
const isDouble = await checkDoubleBranch(branch, projectId);
|
||||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
if (isDouble) {
|
||||||
|
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -606,7 +595,8 @@ export async function getBuildPack(request) {
|
|||||||
projectId: application.projectId,
|
projectId: application.projectId,
|
||||||
repository: application.repository,
|
repository: application.repository,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
apiUrl: application.gitSource.apiUrl
|
apiUrl: application.gitSource.apiUrl,
|
||||||
|
isPublicRepository: application.settings.isPublicRepository
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -656,13 +646,13 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
|
|||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Secret ${name} already exists.` }
|
throw { status: 500, message: `Secret ${name} already exists.` }
|
||||||
} else {
|
} else {
|
||||||
value = encrypt(value);
|
value = encrypt(value.trim());
|
||||||
await prisma.secret.create({
|
await prisma.secret.create({
|
||||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value = encrypt(value);
|
value = encrypt(value.trim());
|
||||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export interface SaveApplication extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; };
|
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; };
|
||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
@@ -44,13 +44,13 @@ export interface CheckDNS extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface DeployApplication {
|
export interface DeployApplication {
|
||||||
Querystring: { domain: string }
|
Querystring: { domain: string }
|
||||||
Body: { pullmergeRequestId: string | null, branch: string }
|
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
||||||
}
|
}
|
||||||
export interface GetImages {
|
export interface GetImages {
|
||||||
Body: { buildPack: string, deploymentType: string }
|
Body: { buildPack: string, deploymentType: string }
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSource extends OnlyId {
|
export interface SaveApplicationSource extends OnlyId {
|
||||||
Body: { gitSourceId: string }
|
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
|
||||||
}
|
}
|
||||||
export interface CheckRepository extends OnlyId {
|
export interface CheckRepository extends OnlyId {
|
||||||
Querystring: { repository: string, branch: string }
|
Querystring: { repository: string, branch: string }
|
||||||
@@ -115,7 +115,8 @@ export interface CancelDeployment {
|
|||||||
export interface DeployApplication extends OnlyId {
|
export interface DeployApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null,
|
||||||
branch: string
|
branch: string,
|
||||||
|
forceRebuild?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { FastifyReply } from 'fastify';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
|
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
|
||||||
import { checkContainer } from '../../../../lib/docker';
|
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
|
|
||||||
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||||
@@ -30,9 +29,9 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
|
|||||||
|
|
||||||
const name = uniqueName();
|
const name = uniqueName();
|
||||||
const dbUser = cuid();
|
const dbUser = cuid();
|
||||||
const dbUserPassword = encrypt(generatePassword());
|
const dbUserPassword = encrypt(generatePassword({}));
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword({}));
|
||||||
const defaultDatabase = cuid();
|
const defaultDatabase = cuid();
|
||||||
|
|
||||||
const { id } = await prisma.database.create({
|
const { id } = await prisma.database.create({
|
||||||
@@ -93,14 +92,15 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
if (!database) {
|
if (!database) {
|
||||||
throw { status: 404, message: 'Database not found.' }
|
throw { status: 404, message: 'Database not found.' }
|
||||||
}
|
}
|
||||||
|
const { arch } = await listSettings();
|
||||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
const configuration = generateDatabaseConfiguration(database);
|
const configuration = generateDatabaseConfiguration(database, arch);
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
return {
|
return {
|
||||||
privatePort: configuration?.privatePort,
|
privatePort: configuration?.privatePort,
|
||||||
database,
|
database,
|
||||||
versions: await getDatabaseVersions(database.type),
|
versions: await getDatabaseVersions(database.type, arch),
|
||||||
settings
|
settings
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -137,8 +137,10 @@ export async function getVersions(request: FastifyRequest<OnlyId>) {
|
|||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
|
const { arch } = await listSettings();
|
||||||
|
const versions = getDatabaseVersions(type, arch);
|
||||||
return {
|
return {
|
||||||
versions: supportedDatabaseTypesAndVersions.find((name) => name.name === type).versions
|
versions
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -219,6 +221,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
|
const { arch } = await listSettings();
|
||||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
const {
|
const {
|
||||||
@@ -228,8 +231,8 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
publicPort,
|
publicPort,
|
||||||
settings: { isPublic }
|
settings: { isPublic }
|
||||||
} = database;
|
} = database;
|
||||||
const { privatePort, environmentVariables, image, volume, ulimits } =
|
const { privatePort, command, environmentVariables, image, volume, ulimits } =
|
||||||
generateDatabaseConfiguration(database);
|
generateDatabaseConfiguration(database, arch);
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const volumeName = volume.split(':')[0];
|
const volumeName = volume.split(':')[0];
|
||||||
@@ -243,6 +246,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
[id]: {
|
[id]: {
|
||||||
container_name: id,
|
container_name: id,
|
||||||
image,
|
image,
|
||||||
|
command,
|
||||||
networks: [network],
|
networks: [network],
|
||||||
environment: environmentVariables,
|
environment: environmentVariables,
|
||||||
volumes: [volume],
|
volumes: [volume],
|
||||||
@@ -270,6 +274,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
try {
|
try {
|
||||||
@@ -282,6 +287,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
return {};
|
return {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
throw {
|
throw {
|
||||||
error
|
error
|
||||||
};
|
};
|
||||||
@@ -427,9 +433,13 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { isPublic, appendOnly = true } = request.body;
|
const { isPublic, appendOnly = true } = request.body;
|
||||||
|
|
||||||
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
let publicPort = null
|
||||||
const publicPort = await getFreePublicPort(id, dockerId);
|
|
||||||
|
|
||||||
|
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
|
||||||
|
if (isPublic) {
|
||||||
|
publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
}
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -440,11 +450,12 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
|
const { arch } = await listSettings();
|
||||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
|
|
||||||
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
|
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
|
||||||
const { privatePort } = generateDatabaseConfiguration(database);
|
const { privatePort } = generateDatabaseConfiguration(database, arch);
|
||||||
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
|||||||
|
|
||||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
console.log(engine)
|
|
||||||
if (engine) {
|
if (engine) {
|
||||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
||||||
if (stdout === '') {
|
if (stdout === '') {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import axios from 'axios';
|
|||||||
import compare from 'compare-versions';
|
import compare from 'compare-versions';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, prisma, uniqueName, version } from '../../../lib/common';
|
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, listSettings, prisma, uniqueName, version } from '../../../lib/common';
|
||||||
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import type { Login, Update } from '.';
|
import type { Login, Update } from '.';
|
||||||
@@ -31,7 +31,7 @@ export async function checkUpdate(request: FastifyRequest) {
|
|||||||
const { data: versions } = await axios.get(
|
const { data: versions } = await axios.get(
|
||||||
`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}`
|
`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}`
|
||||||
);
|
);
|
||||||
const latestVersion = versions['coolify'].main.version
|
const latestVersion = versions['coolify'].main.version
|
||||||
const isUpdateAvailable = compare(latestVersion, currentVersion);
|
const isUpdateAvailable = compare(latestVersion, currentVersion);
|
||||||
if (isStaging) {
|
if (isStaging) {
|
||||||
return {
|
return {
|
||||||
@@ -73,6 +73,23 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function restartCoolify(request: FastifyRequest<any>) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
if (teamId === '0') {
|
||||||
|
if (!isDev) {
|
||||||
|
await asyncExecShell(`docker restart coolify`);
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
console.log('Restarting Coolify')
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw { status: 500, message: 'You are not authorized to restart Coolify.' };
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function showUsage() {
|
export async function showUsage() {
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
@@ -96,34 +113,23 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
try {
|
try {
|
||||||
const userId = request.user.userId;
|
const userId = request.user.userId;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const applicationsCount = await prisma.application.count({
|
const applications = await prisma.application.findMany({
|
||||||
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { settings: true }
|
||||||
|
});
|
||||||
|
const databases = await prisma.database.findMany({
|
||||||
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { settings: true }
|
||||||
|
});
|
||||||
|
const services = await prisma.service.findMany({
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const sourcesCount = await prisma.gitSource.count({
|
const settings = await listSettings();
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
|
||||||
});
|
|
||||||
const destinationsCount = await prisma.destinationDocker.count({
|
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
|
||||||
});
|
|
||||||
const teamsCount = await prisma.permission.count({ where: { userId } });
|
|
||||||
const databasesCount = await prisma.database.count({
|
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
|
||||||
});
|
|
||||||
const servicesCount = await prisma.service.count({
|
|
||||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
|
||||||
});
|
|
||||||
const teams = await prisma.permission.findMany({
|
|
||||||
where: { userId },
|
|
||||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
teams,
|
applications,
|
||||||
applicationsCount,
|
databases,
|
||||||
sourcesCount,
|
services,
|
||||||
destinationsCount,
|
settings,
|
||||||
teamsCount,
|
|
||||||
databasesCount,
|
|
||||||
servicesCount,
|
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually } from './handlers';
|
import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually, restartCoolify } from './handlers';
|
||||||
import { GetCurrentUser } from './types';
|
import { GetCurrentUser } from './types';
|
||||||
|
|
||||||
export interface Update {
|
export interface Update {
|
||||||
@@ -47,6 +47,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
onRequest: [fastify.authenticate]
|
onRequest: [fastify.authenticate]
|
||||||
}, async () => await showUsage());
|
}, async () => await showUsage());
|
||||||
|
|
||||||
|
fastify.post('/internal/restart', {
|
||||||
|
onRequest: [fastify.authenticate]
|
||||||
|
}, async (request) => await restartCoolify(request));
|
||||||
|
|
||||||
fastify.post('/internal/cleanup', {
|
fastify.post('/internal/cleanup', {
|
||||||
onRequest: [fastify.authenticate]
|
onRequest: [fastify.authenticate]
|
||||||
}, async () => await cleanupManually());
|
}, async () => await cleanupManually());
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
minPort,
|
minPort,
|
||||||
maxPort,
|
maxPort,
|
||||||
isAutoUpdateEnabled,
|
isAutoUpdateEnabled,
|
||||||
isDNSCheckEnabled
|
isDNSCheckEnabled,
|
||||||
|
DNSServers
|
||||||
} = request.body
|
} = request.body
|
||||||
const { id } = await listSettings();
|
const { id } = await listSettings();
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled }
|
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers }
|
||||||
});
|
});
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||||
@@ -54,6 +55,10 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { fqdn } = request.body
|
const { fqdn } = request.body
|
||||||
|
const { DNSServers } = await listSettings();
|
||||||
|
if (DNSServers) {
|
||||||
|
dns.setServers([DNSServers]);
|
||||||
|
}
|
||||||
let ip;
|
let ip;
|
||||||
try {
|
try {
|
||||||
ip = await dns.resolve(fqdn);
|
ip = await dns.resolve(fqdn);
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export interface SaveSettings {
|
|||||||
minPort: number,
|
minPort: number,
|
||||||
maxPort: number,
|
maxPort: number,
|
||||||
isAutoUpdateEnabled: boolean,
|
isAutoUpdateEnabled: boolean,
|
||||||
isDNSCheckEnabled: boolean
|
isDNSCheckEnabled: boolean,
|
||||||
|
DNSServers: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface DeleteDomain {
|
export interface DeleteDomain {
|
||||||
|
|||||||
@@ -142,12 +142,6 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
type: 'webhook_commit'
|
type: 'webhook_commit'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
|
||||||
build_id: buildId,
|
|
||||||
type: 'webhook_commit',
|
|
||||||
...applicationFound
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Queued. Thank you!'
|
message: 'Queued. Thank you!'
|
||||||
};
|
};
|
||||||
@@ -183,6 +177,8 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
await prisma.build.create({
|
await prisma.build.create({
|
||||||
data: {
|
data: {
|
||||||
id: buildId,
|
id: buildId,
|
||||||
|
pullmergeRequestId,
|
||||||
|
sourceBranch,
|
||||||
applicationId: applicationFound.id,
|
applicationId: applicationFound.id,
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
destinationDockerId: applicationFound.destinationDocker.id,
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
gitSourceId: applicationFound.gitSource.id,
|
||||||
@@ -192,14 +188,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
type: 'webhook_pr'
|
type: 'webhook_pr'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
|
||||||
build_id: buildId,
|
|
||||||
type: 'webhook_pr',
|
|
||||||
...applicationFound,
|
|
||||||
sourceBranch,
|
|
||||||
pullmergeRequestId
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Queued. Thank you!'
|
message: 'Queued. Thank you!'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,12 +89,6 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
|
||||||
build_id: buildId,
|
|
||||||
type: 'webhook_commit',
|
|
||||||
...applicationFound
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Queued. Thank you!'
|
message: 'Queued. Thank you!'
|
||||||
};
|
};
|
||||||
@@ -141,6 +135,8 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
await prisma.build.create({
|
await prisma.build.create({
|
||||||
data: {
|
data: {
|
||||||
id: buildId,
|
id: buildId,
|
||||||
|
pullmergeRequestId,
|
||||||
|
sourceBranch,
|
||||||
applicationId: applicationFound.id,
|
applicationId: applicationFound.id,
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
destinationDockerId: applicationFound.destinationDocker.id,
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
gitSourceId: applicationFound.gitSource.id,
|
||||||
@@ -150,14 +146,6 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
type: 'webhook_mr'
|
type: 'webhook_mr'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
scheduler.workers.get('deployApplication').postMessage({
|
|
||||||
build_id: buildId,
|
|
||||||
type: 'webhook_mr',
|
|
||||||
...applicationFound,
|
|
||||||
sourceBranch,
|
|
||||||
pullmergeRequestId
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Queued. Thank you!'
|
message: 'Queued. Thank you!'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -484,7 +484,6 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
|
|||||||
}
|
}
|
||||||
throw { status: 500 }
|
throw { status: 500 }
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
console.log(status, message);
|
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,20 +14,20 @@
|
|||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write --plugin-search-dir=. ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.24.2",
|
"@playwright/test": "1.25.1",
|
||||||
"@sveltejs/kit": "1.0.0-next.405",
|
"@sveltejs/kit": "1.0.0-next.405",
|
||||||
"@types/js-cookie": "3.0.2",
|
"@types/js-cookie": "3.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.33.0",
|
"@typescript-eslint/eslint-plugin": "5.35.1",
|
||||||
"@typescript-eslint/parser": "5.33.0",
|
"@typescript-eslint/parser": "5.35.1",
|
||||||
"autoprefixer": "10.4.8",
|
"autoprefixer": "10.4.8",
|
||||||
"eslint": "8.21.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-svelte3": "4.0.0",
|
"eslint-plugin-svelte3": "4.0.0",
|
||||||
"postcss": "8.4.16",
|
"postcss": "8.4.16",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"prettier-plugin-svelte": "2.7.0",
|
"prettier-plugin-svelte": "2.7.0",
|
||||||
"svelte": "3.49.0",
|
"svelte": "3.49.0",
|
||||||
"svelte-check": "2.8.0",
|
"svelte-check": "2.8.1",
|
||||||
"svelte-preprocess": "4.10.7",
|
"svelte-preprocess": "4.10.7",
|
||||||
"tailwindcss": "3.1.8",
|
"tailwindcss": "3.1.8",
|
||||||
"tailwindcss-scrollbar": "0.1.0",
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
@@ -38,8 +38,9 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.39",
|
"@sveltejs/adapter-static": "1.0.0-next.39",
|
||||||
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"daisyui": "2.22.0",
|
"daisyui": "2.24.0",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
"p-limit": "4.0.0",
|
"p-limit": "4.0.0",
|
||||||
"svelte-select": "4.4.7",
|
"svelte-select": "4.4.7",
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
fancyName: 'Hasura',
|
fancyName: 'Hasura',
|
||||||
baseImage: 'hasura/graphql-engine',
|
baseImage: 'hasura/graphql-engine',
|
||||||
images: ['postgres:12-alpine'],
|
images: ['postgres:12-alpine'],
|
||||||
versions: ['latest', 'v2.5.1'],
|
versions: ['latest', 'v2.10.0', 'v2.5.1'],
|
||||||
recommendedVersion: 'v2.5.1',
|
recommendedVersion: 'v2.10.0',
|
||||||
ports: {
|
ports: {
|
||||||
main: 8080
|
main: 8080
|
||||||
}
|
}
|
||||||
@@ -148,6 +148,17 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
main: 3000
|
main: 3000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'appwrite',
|
||||||
|
fancyName: 'Appwrite',
|
||||||
|
baseImage: 'appwrite/appwrite',
|
||||||
|
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
|
||||||
|
versions: ['latest', '0.15.3'],
|
||||||
|
recommendedVersion: '0.15.3',
|
||||||
|
ports: {
|
||||||
|
main: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@@ -159,6 +170,28 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// main: 8080
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
{
|
||||||
|
name: 'glitchTip',
|
||||||
|
fancyName: 'GlitchTip',
|
||||||
|
baseImage: 'glitchtip/glitchtip',
|
||||||
|
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'searxng',
|
||||||
|
fancyName: 'SearXNG',
|
||||||
|
baseImage: 'searxng/searxng',
|
||||||
|
images: ['redis:6.2-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const asyncSleep = (delay: number) =>
|
export const asyncSleep = (delay: number) =>
|
||||||
@@ -218,7 +251,7 @@ export const staticDeployments = [
|
|||||||
'astro',
|
'astro',
|
||||||
'eleventy'
|
'eleventy'
|
||||||
];
|
];
|
||||||
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel'];
|
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel', 'heroku'];
|
||||||
|
|
||||||
|
|
||||||
export function generateRemoteEngine(destination: any) {
|
export function generateRemoteEngine(destination: any) {
|
||||||
@@ -232,35 +265,6 @@ export function changeQueryParams(buildId: string) {
|
|||||||
return history.pushState(null, null, '?' + queryParams.toString());
|
return history.pushState(null, null, '?' + queryParams.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const supportedDatabaseTypesAndVersions = [
|
|
||||||
{
|
|
||||||
name: 'mongodb',
|
|
||||||
fancyName: 'MongoDB',
|
|
||||||
baseImage: 'bitnami/mongodb',
|
|
||||||
versions: ['5.0', '4.4', '4.2']
|
|
||||||
},
|
|
||||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
|
|
||||||
{
|
|
||||||
name: 'mariadb',
|
|
||||||
fancyName: 'MariaDB',
|
|
||||||
baseImage: 'bitnami/mariadb',
|
|
||||||
versions: ['10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'postgresql',
|
|
||||||
fancyName: 'PostgreSQL',
|
|
||||||
baseImage: 'bitnami/postgresql',
|
|
||||||
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'redis',
|
|
||||||
fancyName: 'Redis',
|
|
||||||
baseImage: 'bitnami/redis',
|
|
||||||
versions: ['6.2', '6.0', '5.0']
|
|
||||||
},
|
|
||||||
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getServiceMainPort = (service: string) => {
|
export const getServiceMainPort = (service: string) => {
|
||||||
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
|
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
|
||||||
if (serviceType) {
|
if (serviceType) {
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
export let type = 'info';
|
export let type = 'info';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="alert shadow-lg text-white rounded"
|
on:click={() => dispatch('click')}
|
||||||
class:alert-success={type === 'success'}
|
on:mouseover={() => dispatch('pause')}
|
||||||
|
on:focus={() => dispatch('pause')}
|
||||||
|
on:mouseout={() => dispatch('resume')}
|
||||||
|
on:blur={() => dispatch('resume')}
|
||||||
|
class="alert shadow-lg text-white rounded hover:scale-105 transition-all duration-100 cursor-pointer"
|
||||||
|
class:bg-coollabs={type === 'success'}
|
||||||
class:alert-error={type === 'error'}
|
class:alert-error={type === 'error'}
|
||||||
class:alert-info={type === 'info'}
|
class:alert-info={type === 'info'}
|
||||||
>
|
>
|
||||||
<!-- {#if type === 'success'}
|
{#if type === 'success'}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
class="stroke-current flex-shrink-0 h-6 w-6"
|
||||||
@@ -47,6 +54,6 @@
|
|||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
/></svg
|
/></svg
|
||||||
>
|
>
|
||||||
{/if} -->
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import Toast from './Toast.svelte';
|
import Toast from './Toast.svelte';
|
||||||
|
|
||||||
import { toasts } from '$lib/store';
|
import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $toasts}
|
{#if $toasts}
|
||||||
<section>
|
<section>
|
||||||
<article class="toast toast-bottom toast-end rounded-none" role="alert" transition:fade>
|
<article class="toast toast-top toast-end rounded-none" role="alert" transition:fade>
|
||||||
{#each $toasts as toast (toast.id)}
|
{#each $toasts as toast (toast.id)}
|
||||||
<Toast type={toast.type}>{@html toast.message}</Toast>
|
<Toast
|
||||||
|
type={toast.type}
|
||||||
|
on:resume={() => resumeToast(toast.id)}
|
||||||
|
on:pause={() => pauseToast(toast.id)}
|
||||||
|
on:click={() => dismissToast(toast.id)}>{@html toast.message}</Toast
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@@ -17,6 +22,6 @@
|
|||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
section {
|
section {
|
||||||
@apply fixed top-0 left-0 right-0 w-full flex flex-col mt-4 justify-center z-[1000];
|
@apply fixed top-0 left-0 right-0 w-full flex flex-col mt-4 justify-center z-[1000];
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
disabled={updateStatus.success === false}
|
disabled={updateStatus.success === false}
|
||||||
on:click={update}
|
on:click={update}
|
||||||
class="icons tooltip tooltip-right tooltip-primary bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
class="icons tooltip tooltip-right tooltip-primary bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
||||||
data-tip="Update available!"
|
data-tip="Update Available!"
|
||||||
>
|
>
|
||||||
{#if updateStatus.loading}
|
{#if updateStatus.loading}
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
let usageInterval: any;
|
let usageInterval: any;
|
||||||
let loading = {
|
let loading = {
|
||||||
usage: false,
|
usage: false,
|
||||||
cleanup: false
|
cleanup: false,
|
||||||
|
restart: false
|
||||||
};
|
};
|
||||||
import { addToast, appSession } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import Trend from './Trend.svelte';
|
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if (loading.usage) return;
|
if (loading.usage) return;
|
||||||
loading.usage = true;
|
loading.usage = true;
|
||||||
@@ -34,6 +34,25 @@
|
|||||||
usage = data.usage;
|
usage = data.usage;
|
||||||
loading.usage = false;
|
loading.usage = false;
|
||||||
}
|
}
|
||||||
|
async function restartCoolify() {
|
||||||
|
const sure = confirm(
|
||||||
|
'Are you sure you would like to restart Coolify? Currently running deployments will be stopped and restarted.'
|
||||||
|
);
|
||||||
|
if (sure) {
|
||||||
|
loading.restart = true;
|
||||||
|
try {
|
||||||
|
await post(`/internal/restart`, {});
|
||||||
|
addToast({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Coolify restarted successfully. It will take a moment.'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.restart = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
clearInterval(usageInterval);
|
clearInterval(usageInterval);
|
||||||
});
|
});
|
||||||
@@ -49,118 +68,107 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let warning = {
|
|
||||||
memory: false,
|
|
||||||
cpu: false,
|
|
||||||
disk: false
|
|
||||||
};
|
|
||||||
let trends = {
|
|
||||||
memory: 'stable',
|
|
||||||
cpu: 'stable',
|
|
||||||
disk: 'stable'
|
|
||||||
};
|
|
||||||
async function manuallyCleanupStorage() {
|
async function manuallyCleanupStorage() {
|
||||||
try {
|
try {
|
||||||
loading.cleanup = true
|
loading.cleanup = true;
|
||||||
await post('/internal/cleanup', {});
|
await post('/internal/cleanup', {});
|
||||||
return addToast({
|
return addToast({
|
||||||
message: "Cleanup done.",
|
message: 'Cleanup done.',
|
||||||
type:"success"
|
type: 'success'
|
||||||
})
|
});
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading.cleanup = false
|
loading.cleanup = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $appSession.teamId === '0'}
|
<div class="w-full">
|
||||||
<div class="px-6 text-2xl font-bold">Server Usage</div>
|
<div class="flex items-center">
|
||||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
<h1 class="title text-4xl">Hardware Details</h1>
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
<div class="flex space-x-4">
|
||||||
<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
|
{#if $appSession.teamId === '0'}
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
||||||
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
>Cleanup Storage</button
|
||||||
</dd>
|
>
|
||||||
|
<button
|
||||||
|
on:click={restartCoolify}
|
||||||
|
class:loading={loading.restart}
|
||||||
|
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider" />
|
||||||
|
<div class="grid grid-flow-col gap-4 grid-rows-3 lg:grid-rows-1">
|
||||||
|
<div class="stats stats-vertical lg:stats-horizontal w-full mb-5 bg-transparent rounded">
|
||||||
|
<div class="font-bold flex lg:justify-center">Memory</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Total</div>
|
||||||
|
<div class="stat-value text-2xl">
|
||||||
|
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Used</div>
|
||||||
|
<div class="stat-value text-2xl">
|
||||||
|
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Free</div>
|
||||||
|
<div class="stat-value text-2xl">
|
||||||
|
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
<div class="stats stats-vertical lg:stats-horizontal w-full mb-5 bg-transparent rounded">
|
||||||
<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
|
<div class="font-bold flex lg:justify-center">CPU</div>
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white ">
|
<div class="stat">
|
||||||
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
<div class="stat-title">Total</div>
|
||||||
</dd>
|
<div class="stat-value text-2xl">
|
||||||
</div>
|
{usage?.cpu.count}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="stat">
|
||||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
<div class="stat-title">Usage</div>
|
||||||
class:bg-red-500={warning.memory}
|
<div class="stat-value text-2xl">
|
||||||
>
|
{usage?.cpu.usage}<span class="text-sm">%</span>
|
||||||
<dt class="truncate text-sm font-medium text-white">Free Memory</dt>
|
</div>
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
</div>
|
||||||
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
|
|
||||||
{#if !warning.memory}
|
|
||||||
<Trend trend={trends.memory} />
|
|
||||||
{/if}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
|
||||||
<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
|
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
|
||||||
{usage?.cpu.count}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
|
||||||
class:bg-red-500={warning.cpu}
|
|
||||||
>
|
|
||||||
<dt class="truncate text-sm font-medium text-white">CPU Usage</dt>
|
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
|
||||||
{usage?.cpu.usage}<span class="text-sm">%</span>
|
|
||||||
{#if !warning.cpu}
|
|
||||||
<Trend trend={trends.cpu} />
|
|
||||||
{/if}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
|
||||||
<dt class="truncate text-sm font-medium text-white">Load Average (5/10/30mins)</dt>
|
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
|
||||||
{usage?.cpu.load.join('/')}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
|
||||||
<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
|
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
|
||||||
{usage?.disk.totalGb}<span class="text-sm">GB</span>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
|
||||||
<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
|
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
|
||||||
{usage?.disk.usedGb}<span class="text-sm">GB</span>
|
|
||||||
</dd>
|
|
||||||
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
|
||||||
>Cleanup Storage</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
|
||||||
class:bg-red-500={warning.disk}
|
|
||||||
>
|
|
||||||
<dt class="truncate text-sm font-medium text-white">Free Disk</dt>
|
|
||||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
|
||||||
{usage?.disk.freePercentage}<span class="text-sm">%</span>
|
|
||||||
{#if !warning.disk}
|
|
||||||
<Trend trend={trends.disk} />
|
|
||||||
{/if}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<div class="px-6 pt-20 text-2xl font-bold">Resources</div>
|
<div class="stat">
|
||||||
{/if}
|
<div class="stat-title">Load Average (5,10,30mins)</div>
|
||||||
|
<div class="stat-value text-2xl">{usage?.cpu.load}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats stats-vertical lg:stats-horizontal w-full mb-5 bg-transparent rounded">
|
||||||
|
<div class="font-bold flex lg:justify-center">Disk</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Total</div>
|
||||||
|
<div class="stat-value text-2xl">
|
||||||
|
{usage?.disk.totalGb}<span class="text-sm">GB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Used</div>
|
||||||
|
<div class="stat-value text-2xl">
|
||||||
|
{usage?.disk.usedGb}<span class="text-sm">GB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Free</div>
|
||||||
|
<div class="stat-value text-2xl">
|
||||||
|
{usage?.disk.freePercentage}<span class="text-sm">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Icons from '$lib/components/svg/applications';
|
||||||
|
export let application: any;
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if application.buildPack?.toLowerCase() === 'rust'}
|
||||||
|
<Icons.Rust {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'node'}
|
||||||
|
<Icons.Nodejs {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'react'}
|
||||||
|
<Icons.React {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'svelte'}
|
||||||
|
<Icons.Svelte {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'vuejs'}
|
||||||
|
<Icons.Vuejs {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'php'}
|
||||||
|
<Icons.Php {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'python'}
|
||||||
|
<Icons.Python {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'static'}
|
||||||
|
<Icons.Static {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'nestjs'}
|
||||||
|
<Icons.Nestjs {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'nuxtjs'}
|
||||||
|
<Icons.Nuxtjs {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'nextjs'}
|
||||||
|
<Icons.Nextjs {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'gatsby'}
|
||||||
|
<Icons.Gatsby {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'docker'}
|
||||||
|
<Icons.Docker {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'astro'}
|
||||||
|
<Icons.Astro {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'eleventy'}
|
||||||
|
<Icons.Eleventy {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'deno'}
|
||||||
|
<Icons.Deno {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'laravel'}
|
||||||
|
<Icons.Laravel {isAbsolute} />
|
||||||
|
{:else if application.buildPack?.toLowerCase() === 'heroku'}
|
||||||
|
<Icons.Heroku {isAbsolute} />
|
||||||
|
{/if}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="absolute top-0 left-0 -m-6 h-14 w-14"
|
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
||||||
viewBox="0 0 256 256"
|
viewBox="0 0 256 256"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="text-white-500 absolute top-0 left-0 -m-4 h-10 w-10"
|
class={isAbsolute
|
||||||
|
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white-500'
|
||||||
|
: 'mx-auto w-8 h-8 text-white-500'}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,73 +1,9 @@
|
|||||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
|
<script lang="ts">
|
||||||
<g
|
export let isAbsolute = true;
|
||||||
><path
|
</script>
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#3A4D54"
|
<svg viewBox="0 0 128 128" class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-10 h-10'}>
|
||||||
d="M73.8 50.8h11.3v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6v-11.4h11.3v-11.2h22.5v-11.3h13.5v22.5z"
|
<path d="M124.8 52.1c-4.3-2.5-10-2.8-14.8-1.4-.6-5.2-4-9.7-8-12.9l-1.6-1.3-1.4 1.6c-2.7 3.1-3.5 8.3-3.1 12.3.3 2.9 1.2 5.9 3 8.3-1.4.8-2.9 1.9-4.3 2.4-2.8 1-5.9 2-8.9 2H79V49H66V24H51v12H26v13H13v14H1.8l-.2 1.5c-.5 6.4.3 12.6 3 18.5l1.1 2.2.1.2c7.9 13.4 21.7 19 36.8 19 29.2 0 53.3-13.1 64.3-40.6 7.4.4 15-1.8 18.6-8.9l.9-1.8-1.6-1zM28 39h10v11H28V39zm13.1 44.2c0 1.7-1.4 3.1-3.1 3.1-1.7 0-3.1-1.4-3.1-3.1 0-1.7 1.4-3.1 3.1-3.1 1.7.1 3.1 1.4 3.1 3.1zM28 52h10v11H28V52zm-13 0h11v11H15V52zm27.7 50.2c-15.8-.1-24.3-5.4-31.3-12.4 2.1.1 4.1.2 5.9.2 1.6 0 3.2 0 4.7-.1 3.9-.2 7.3-.7 10.1-1.5 2.3 5.3 6.5 10.2 14 13.8h-3.4zM51 63H40V52h11v11zm0-13H40V39h11v11zm13 13H53V52h11v11zm0-13H53V39h11v11zm0-13H53V26h11v11zm13 26H66V52h11v11zM38.8 81.2c-.2-.1-.5-.2-.8-.2-1.2 0-2.2 1-2.2 2.2 0 1.2 1 2.2 2.2 2.2s2.2-1 2.2-2.2c0-.3-.1-.6-.2-.8-.2.3-.4.5-.8.5-.5 0-.9-.4-.9-.9.1-.4.3-.7.5-.8z" fill="#019BC6"></path>
|
||||||
/><path
|
</svg>
|
||||||
fill="#00AADA"
|
|
||||||
d="M110.4 55.1c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-72.2c-.6 6.2.5 11.9 3 16.8l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5 1.1-8.3 1.3h-.6000000000000001c-1.3.1-2.7.1-4.2.1-1.6 0-3.1 0-4.9-.1 6 6.8 15.4 10.8 27.2 10.8 25 0 46.2-11.1 55.5-35.9 6.7.7 13.1-1 16-6.7-4.5-2.7-10.5-1.8-13.9-.1z"
|
|
||||||
/><path
|
|
||||||
fill="#28B8EB"
|
|
||||||
d="M110.4 55.1c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-68c-.3 9.5 3.2 16.7 9.5 21 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4l-.1-.1c8.5 4.4 20.8 4.3 35-1.1 15.8-6.1 30.6-17.7 40.9-30.9-.2.1-.4.1-.5.2z"
|
|
||||||
/><path
|
|
||||||
fill="#028BB8"
|
|
||||||
d="M18.7 71.8c.4 3.3 1.4 6.4 2.9 9.3l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4h-.4c-1.3.1-2.7.1-4.1.1-1.6 0-3.2 0-4.9-.1 6 6.8 15.5 10.8 27.3 10.8 21.4 0 40-8.1 50.8-26h-85.1v-.1z"
|
|
||||||
/><path
|
|
||||||
fill="#019BC6"
|
|
||||||
d="M23.5 71.8c1.3 5.8 4.3 10.4 8.8 13.5 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.6 1.4 8.5 4.4 20.8 4.3 34.9-1.1 8.5-3.3 16.8-8.2 24.2-14.1h-70.6z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#00ACD3"
|
|
||||||
d="M28.4 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zM39.6 41.5h9.8v9.8h-9.8v-9.8zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#23C2EE"
|
|
||||||
d="M39.6 52.7h9.8v9.8h-9.8v-9.8zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#00ACD3"
|
|
||||||
d="M50.9 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#23C2EE"
|
|
||||||
d="M50.9 41.5h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zM62.2 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#00ACD3"
|
|
||||||
d="M62.2 41.5h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#23C2EE"
|
|
||||||
d="M62.2 30.2h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#00ACD3"
|
|
||||||
d="M73.5 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#D4EEF1"
|
|
||||||
d="M48.8 78.3c1.5 0 2.7 1.2 2.7 2.7 0 1.5-1.2 2.7-2.7 2.7-1.5 0-2.7-1.2-2.7-2.7 0-1.5 1.2-2.7 2.7-2.7"
|
|
||||||
/><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
fill="#3A4D54"
|
|
||||||
d="M48.8 79.1c.2 0 .5 0 .7.1-.2.1-.4.4-.4.7 0 .4.4.8.8.8.3 0 .6-.2.7-.4.1.2.1.5.1.7 0 1.1-.9 1.9-1.9 1.9-1.1 0-1.9-.9-1.9-1.9 0-1 .8-1.9 1.9-1.9M1.1 72.8h125.4c-2.7-.7-8.6-1.6-7.7-5.2-5 5.7-16.9 4-20 1.2-3.4 4.9-23 3-24.3-.8-4.2 5-17.3 5-21.5 0-1.4 3.8-21 5.7-24.3.8-3 2.8-15 4.5-20-1.2 1.1 3.5-4.9 4.5-7.6 5.2"
|
|
||||||
/><path
|
|
||||||
fill="#BFDBE0"
|
|
||||||
d="M56 97.8c-6.7-3.2-10.3-7.5-12.4-12.2-2.5.7-5.5 1.2-8.9 1.4-1.3.1-2.7.1-4.1.1-1.7 0-3.4 0-5.2-.1 6 6 13.6 10.7 27.5 10.8h3.1z"
|
|
||||||
/><path
|
|
||||||
fill="#D4EEF1"
|
|
||||||
d="M46.1 89.9c-.9-1.3-1.8-2.8-2.5-4.3-2.5.7-5.5 1.2-8.9 1.4 2.3 1.2 5.7 2.4 11.4 2.9z"
|
|
||||||
/></g
|
|
||||||
>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +1,11 @@
|
|||||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
|
||||||
|
>
|
||||||
<path fill="transparent" d="M18 0h92v128H18z" /><path
|
<path fill="transparent" d="M18 0h92v128H18z" /><path
|
||||||
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
|
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
|
||||||
fill="#FFF"
|
fill="#FFF"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,11 @@
|
|||||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10" viewBox="0 0 128 128">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
fill="#64328B"
|
fill="#64328B"
|
||||||
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
|
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 593 B |
15
apps/ui/src/lib/components/svg/applications/Heroku.svelte
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8 '}
|
||||||
|
viewBox="0 0 72 80"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="#430098"
|
||||||
|
d="M64.8,0 L7.2,0 C3.224,0 0,3.224 0,7.2 L0,72.8 C0,76.776 3.224,80 7.2,80 L64.8,80 C68.776,80 72,76.776 72,72.8 L72,7.2 C72,3.224 68.776,0 64.8,0 Z M18,68 L18,52 L27,60 L18,68 Z M46,68 L46,44.11 C45.961,42.243 45.062,40 41,40 C32.866,40 23.742,44.091 23.651,44.132 L18,46.692 L18,12 L26,12 L26,34.711 C29.994,33.411 35.577,32 41,32 C45.945,32 48.905,33.944 50.517,35.575 C53.958,39.055 54.005,43.488 54.0002258,44 L54.0002258,68 L46,68 Z M48,25 L40,25 C43.144,20.875 45.118,16.534 46,12 L54,12 C53.46,16.544 51.618,20.9 48,25 Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="absolute top-0 left-0 -m-4 h-10 w-10"
|
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||||
viewBox="0 0 50 52"
|
viewBox="0 0 50 52"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
><title>Logomark</title><path
|
><title>Logomark</title><path
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,4 +1,13 @@
|
|||||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500" viewBox="0 0 128 128">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute
|
||||||
|
? 'absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500'
|
||||||
|
: 'mx-auto w-8 h-8 fill-current text-blue-500'}
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
|
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
|
||||||
/>
|
/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 442 B |
@@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500"
|
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500' : 'mx-auto w-8 h-8 text-green-500'}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="absolute top-0 left-0 -m-4 h-10 w-10"
|
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 400 400"
|
viewBox="0 0 400 400"
|
||||||
>
|
>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,4 +1,13 @@
|
|||||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-6 h-14 w-14 text-white">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
class={isAbsolute
|
||||||
|
? 'absolute top-0 left-0 -m-6 h-14 w-14 text-white'
|
||||||
|
: 'mx-auto w-8 h-8 text-white'}
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
fill="#6181B6"
|
fill="#6181B6"
|
||||||
d="M64 33.039c-33.74 0-61.094 13.862-61.094 30.961s27.354 30.961 61.094 30.961 61.094-13.862 61.094-30.961-27.354-30.961-61.094-30.961zm-15.897 36.993c-1.458 1.364-3.077 1.927-4.86 2.507-1.783.581-4.052.461-6.811.461h-6.253l-1.733 10h-7.301l6.515-34h14.04c4.224 0 7.305 1.215 9.242 3.432 1.937 2.217 2.519 5.364 1.747 9.337-.319 1.637-.856 3.159-1.614 4.515-.759 1.357-1.75 2.624-2.972 3.748zm21.311 2.968l2.881-14.42c.328-1.688.208-2.942-.361-3.555-.57-.614-1.782-1.025-3.635-1.025h-5.79l-3.731 19h-7.244l6.515-33h7.244l-1.732 9h6.453c4.061 0 6.861.815 8.402 2.231s2.003 3.356 1.387 6.528l-3.031 15.241h-7.358zm40.259-11.178c-.318 1.637-.856 3.133-1.613 4.488-.758 1.357-1.748 2.598-2.971 3.722-1.458 1.364-3.078 1.927-4.86 2.507-1.782.581-4.053.461-6.812.461h-6.253l-1.732 10h-7.301l6.514-34h14.041c4.224 0 7.305 1.215 9.241 3.432 1.935 2.217 2.518 5.418 1.746 9.39zM95.919 54h-5.001l-2.727 14h4.442c2.942 0 5.136-.29 6.576-1.4 1.442-1.108 2.413-2.828 2.918-5.421.484-2.491.264-4.434-.66-5.458-.925-1.024-2.774-1.721-5.548-1.721zM38.934 54h-5.002l-2.727 14h4.441c2.943 0 5.136-.29 6.577-1.4 1.441-1.108 2.413-2.828 2.917-5.421.484-2.491.264-4.434-.66-5.458s-2.772-1.721-5.546-1.721z"
|
d="M64 33.039c-33.74 0-61.094 13.862-61.094 30.961s27.354 30.961 61.094 30.961 61.094-13.862 61.094-30.961-27.354-30.961-61.094-30.961zm-15.897 36.993c-1.458 1.364-3.077 1.927-4.86 2.507-1.783.581-4.052.461-6.811.461h-6.253l-1.733 10h-7.301l6.515-34h14.04c4.224 0 7.305 1.215 9.242 3.432 1.937 2.217 2.519 5.364 1.747 9.337-.319 1.637-.856 3.159-1.614 4.515-.759 1.357-1.75 2.624-2.972 3.748zm21.311 2.968l2.881-14.42c.328-1.688.208-2.942-.361-3.555-.57-.614-1.782-1.025-3.635-1.025h-5.79l-3.731 19h-7.244l6.515-33h7.244l-1.732 9h6.453c4.061 0 6.861.815 8.402 2.231s2.003 3.356 1.387 6.528l-3.031 15.241h-7.358zm40.259-11.178c-.318 1.637-.856 3.133-1.613 4.488-.758 1.357-1.748 2.598-2.971 3.722-1.458 1.364-3.078 1.927-4.86 2.507-1.782.581-4.053.461-6.812.461h-6.253l-1.732 10h-7.301l6.514-34h14.041c4.224 0 7.305 1.215 9.241 3.432 1.935 2.217 2.518 5.418 1.746 9.39zM95.919 54h-5.001l-2.727 14h4.442c2.942 0 5.136-.29 6.576-1.4 1.442-1.108 2.413-2.828 2.918-5.421.484-2.491.264-4.434-.66-5.458-.925-1.024-2.774-1.721-5.548-1.721zM38.934 54h-5.002l-2.727 14h4.441c2.943 0 5.136-.29 6.577-1.4 1.441-1.108 2.413-2.828 2.917-5.421.484-2.491.264-4.434-.66-5.458s-2.772-1.721-5.546-1.721z"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,4 +1,11 @@
|
|||||||
<svg class="absolute top-0 left-0 -m-6 h-14 w-14" viewBox="0 0 128 128">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="a"
|
id="a"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,4 +1,13 @@
|
|||||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500" viewBox="0 0 128 128">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute
|
||||||
|
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500'
|
||||||
|
: 'mx-auto w-8 h-8 text-blue-500'}
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
>
|
||||||
<g fill="#61DAFB"
|
<g fill="#61DAFB"
|
||||||
><circle cx="64" cy="64" r="11.4" /><path
|
><circle cx="64" cy="64" r="11.4" /><path
|
||||||
d="M107.3 45.2c-2.2-.8-4.5-1.6-6.9-2.3.6-2.4 1.1-4.8 1.5-7.1 2.1-13.2-.2-22.5-6.6-26.1-1.9-1.1-4-1.6-6.4-1.6-7 0-15.9 5.2-24.9 13.9-9-8.7-17.9-13.9-24.9-13.9-2.4 0-4.5.5-6.4 1.6-6.4 3.7-8.7 13-6.6 26.1.4 2.3.9 4.7 1.5 7.1-2.4.7-4.7 1.4-6.9 2.3-12.5 4.8-19.3 11.4-19.3 18.8s6.9 14 19.3 18.8c2.2.8 4.5 1.6 6.9 2.3-.6 2.4-1.1 4.8-1.5 7.1-2.1 13.2.2 22.5 6.6 26.1 1.9 1.1 4 1.6 6.4 1.6 7.1 0 16-5.2 24.9-13.9 9 8.7 17.9 13.9 24.9 13.9 2.4 0 4.5-.5 6.4-1.6 6.4-3.7 8.7-13 6.6-26.1-.4-2.3-.9-4.7-1.5-7.1 2.4-.7 4.7-1.4 6.9-2.3 12.5-4.8 19.3-11.4 19.3-18.8s-6.8-14-19.3-18.8zm-14.8-30.5c4.1 2.4 5.5 9.8 3.8 20.3-.3 2.1-.8 4.3-1.4 6.6-5.2-1.2-10.7-2-16.5-2.5-3.4-4.8-6.9-9.1-10.4-13 7.4-7.3 14.9-12.3 21-12.3 1.3 0 2.5.3 3.5.9zm-11.2 59.3c-1.8 3.2-3.9 6.4-6.1 9.6-3.7.3-7.4.4-11.2.4-3.9 0-7.6-.1-11.2-.4-2.2-3.2-4.2-6.4-6-9.6-1.9-3.3-3.7-6.7-5.3-10 1.6-3.3 3.4-6.7 5.3-10 1.8-3.2 3.9-6.4 6.1-9.6 3.7-.3 7.4-.4 11.2-.4 3.9 0 7.6.1 11.2.4 2.2 3.2 4.2 6.4 6 9.6 1.9 3.3 3.7 6.7 5.3 10-1.7 3.3-3.4 6.6-5.3 10zm8.3-3.3c1.5 3.5 2.7 6.9 3.8 10.3-3.4.8-7 1.4-10.8 1.9 1.2-1.9 2.5-3.9 3.6-6 1.2-2.1 2.3-4.2 3.4-6.2zm-25.6 27.1c-2.4-2.6-4.7-5.4-6.9-8.3 2.3.1 4.6.2 6.9.2 2.3 0 4.6-.1 6.9-.2-2.2 2.9-4.5 5.7-6.9 8.3zm-18.6-15c-3.8-.5-7.4-1.1-10.8-1.9 1.1-3.3 2.3-6.8 3.8-10.3 1.1 2 2.2 4.1 3.4 6.1 1.2 2.2 2.4 4.1 3.6 6.1zm-7-25.5c-1.5-3.5-2.7-6.9-3.8-10.3 3.4-.8 7-1.4 10.8-1.9-1.2 1.9-2.5 3.9-3.6 6-1.2 2.1-2.3 4.2-3.4 6.2zm25.6-27.1c2.4 2.6 4.7 5.4 6.9 8.3-2.3-.1-4.6-.2-6.9-.2-2.3 0-4.6.1-6.9.2 2.2-2.9 4.5-5.7 6.9-8.3zm22.2 21l-3.6-6c3.8.5 7.4 1.1 10.8 1.9-1.1 3.3-2.3 6.8-3.8 10.3-1.1-2.1-2.2-4.2-3.4-6.2zm-54.5-16.2c-1.7-10.5-.3-17.9 3.8-20.3 1-.6 2.2-.9 3.5-.9 6 0 13.5 4.9 21 12.3-3.5 3.8-7 8.2-10.4 13-5.8.5-11.3 1.4-16.5 2.5-.6-2.3-1-4.5-1.4-6.6zm-24.7 29c0-4.7 5.7-9.7 15.7-13.4 2-.8 4.2-1.5 6.4-2.1 1.6 5 3.6 10.3 6 15.6-2.4 5.3-4.5 10.5-6 15.5-13.8-4-22.1-10-22.1-15.6zm28.5 49.3c-4.1-2.4-5.5-9.8-3.8-20.3.3-2.1.8-4.3 1.4-6.6 5.2 1.2 10.7 2 16.5 2.5 3.4 4.8 6.9 9.1 10.4 13-7.4 7.3-14.9 12.3-21 12.3-1.3 0-2.5-.3-3.5-.9zm60.8-20.3c1.7 10.5.3 17.9-3.8 20.3-1 .6-2.2.9-3.5.9-6 0-13.5-4.9-21-12.3 3.5-3.8 7-8.2 10.4-13 5.8-.5 11.3-1.4 16.5-2.5.6 2.3 1 4.5 1.4 6.6zm9-15.6c-2 .8-4.2 1.5-6.4 2.1-1.6-5-3.6-10.3-6-15.6 2.4-5.3 4.5-10.5 6-15.5 13.8 4 22.1 10 22.1 15.6 0 4.7-5.8 9.7-15.7 13.4z"
|
d="M107.3 45.2c-2.2-.8-4.5-1.6-6.9-2.3.6-2.4 1.1-4.8 1.5-7.1 2.1-13.2-.2-22.5-6.6-26.1-1.9-1.1-4-1.6-6.4-1.6-7 0-15.9 5.2-24.9 13.9-9-8.7-17.9-13.9-24.9-13.9-2.4 0-4.5.5-6.4 1.6-6.4 3.7-8.7 13-6.6 26.1.4 2.3.9 4.7 1.5 7.1-2.4.7-4.7 1.4-6.9 2.3-12.5 4.8-19.3 11.4-19.3 18.8s6.9 14 19.3 18.8c2.2.8 4.5 1.6 6.9 2.3-.6 2.4-1.1 4.8-1.5 7.1-2.1 13.2.2 22.5 6.6 26.1 1.9 1.1 4 1.6 6.4 1.6 7.1 0 16-5.2 24.9-13.9 9 8.7 17.9 13.9 24.9 13.9 2.4 0 4.5-.5 6.4-1.6 6.4-3.7 8.7-13 6.6-26.1-.4-2.3-.9-4.7-1.5-7.1 2.4-.7 4.7-1.4 6.9-2.3 12.5-4.8 19.3-11.4 19.3-18.8s-6.8-14-19.3-18.8zm-14.8-30.5c4.1 2.4 5.5 9.8 3.8 20.3-.3 2.1-.8 4.3-1.4 6.6-5.2-1.2-10.7-2-16.5-2.5-3.4-4.8-6.9-9.1-10.4-13 7.4-7.3 14.9-12.3 21-12.3 1.3 0 2.5.3 3.5.9zm-11.2 59.3c-1.8 3.2-3.9 6.4-6.1 9.6-3.7.3-7.4.4-11.2.4-3.9 0-7.6-.1-11.2-.4-2.2-3.2-4.2-6.4-6-9.6-1.9-3.3-3.7-6.7-5.3-10 1.6-3.3 3.4-6.7 5.3-10 1.8-3.2 3.9-6.4 6.1-9.6 3.7-.3 7.4-.4 11.2-.4 3.9 0 7.6.1 11.2.4 2.2 3.2 4.2 6.4 6 9.6 1.9 3.3 3.7 6.7 5.3 10-1.7 3.3-3.4 6.6-5.3 10zm8.3-3.3c1.5 3.5 2.7 6.9 3.8 10.3-3.4.8-7 1.4-10.8 1.9 1.2-1.9 2.5-3.9 3.6-6 1.2-2.1 2.3-4.2 3.4-6.2zm-25.6 27.1c-2.4-2.6-4.7-5.4-6.9-8.3 2.3.1 4.6.2 6.9.2 2.3 0 4.6-.1 6.9-.2-2.2 2.9-4.5 5.7-6.9 8.3zm-18.6-15c-3.8-.5-7.4-1.1-10.8-1.9 1.1-3.3 2.3-6.8 3.8-10.3 1.1 2 2.2 4.1 3.4 6.1 1.2 2.2 2.4 4.1 3.6 6.1zm-7-25.5c-1.5-3.5-2.7-6.9-3.8-10.3 3.4-.8 7-1.4 10.8-1.9-1.2 1.9-2.5 3.9-3.6 6-1.2 2.1-2.3 4.2-3.4 6.2zm25.6-27.1c2.4 2.6 4.7 5.4 6.9 8.3-2.3-.1-4.6-.2-6.9-.2-2.3 0-4.6.1-6.9.2 2.2-2.9 4.5-5.7 6.9-8.3zm22.2 21l-3.6-6c3.8.5 7.4 1.1 10.8 1.9-1.1 3.3-2.3 6.8-3.8 10.3-1.1-2.1-2.2-4.2-3.4-6.2zm-54.5-16.2c-1.7-10.5-.3-17.9 3.8-20.3 1-.6 2.2-.9 3.5-.9 6 0 13.5 4.9 21 12.3-3.5 3.8-7 8.2-10.4 13-5.8.5-11.3 1.4-16.5 2.5-.6-2.3-1-4.5-1.4-6.6zm-24.7 29c0-4.7 5.7-9.7 15.7-13.4 2-.8 4.2-1.5 6.4-2.1 1.6 5 3.6 10.3 6 15.6-2.4 5.3-4.5 10.5-6 15.5-13.8-4-22.1-10-22.1-15.6zm28.5 49.3c-4.1-2.4-5.5-9.8-3.8-20.3.3-2.1.8-4.3 1.4-6.6 5.2 1.2 10.7 2 16.5 2.5 3.4 4.8 6.9 9.1 10.4 13-7.4 7.3-14.9 12.3-21 12.3-1.3 0-2.5-.3-3.5-.9zm60.8-20.3c1.7 10.5.3 17.9-3.8 20.3-1 .6-2.2.9-3.5.9-6 0-13.5-4.9-21-12.3 3.5-3.8 7-8.2 10.4-13 5.8-.5 11.3-1.4 16.5-2.5.6 2.3 1 4.5 1.4 6.6zm9-15.6c-2 .8-4.2 1.5-6.4 2.1-1.6-5-3.6-10.3-6-15.6 2.4-5.3 4.5-10.5 6-15.5 13.8 4 22.1 10 22.1 15.6 0 4.7-5.8 9.7-15.7 13.4z"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
@@ -1,5 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="absolute top-0 left-0 -m-4 h-10 w-10 text-white"
|
class={isAbsolute
|
||||||
|
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white'
|
||||||
|
: 'mx-auto w-8 h-8 text-white'}
|
||||||
viewBox="0 0 32 32"
|
viewBox="0 0 32 32"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="absolute top-0 left-0 -m-4 h-10 w-10"
|
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="Layer_1"
|
id="Layer_1"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,4 +1,13 @@
|
|||||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500" viewBox="0 0 128 128">
|
<script lang="ts">
|
||||||
|
export let isAbsolute = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute
|
||||||
|
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500'
|
||||||
|
: 'mx-auto w-8 h-8 text-green-500'}
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="m-2.3125e-8 8.9337 49.854 0.1586 14.167 24.47 14.432-24.47 49.547-0.1577-63.834 110.14zm126.98 0.6374-24.36 0.0207-38.476 66.052-38.453-66.052-24.749-0.0194 63.211 107.89zm-25.149-0.008-22.745 0.16758l-15.053 24.647-14.817-24.647-22.794-0.1679 37.731 64.476zM25.997 9.3929l23.002 0.0087M25.997 9.3929l23.002 0.0087"
|
d="m-2.3125e-8 8.9337 49.854 0.1586 14.167 24.47 14.432-24.47 49.547-0.1577-63.834 110.14zm126.98 0.6374-24.36 0.0207-38.476 66.052-38.453-66.052-24.749-0.0194 63.211 107.89zm-25.149-0.008-22.745 0.16758l-15.053 24.647-14.817-24.647-22.794-0.1679 37.731 64.476zM25.997 9.3929l23.002 0.0087M25.997 9.3929l23.002 0.0087"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 676 B After Width: | Height: | Size: 794 B |
19
apps/ui/src/lib/components/svg/applications/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
export { default as Rust } from './Rust.svelte';
|
||||||
|
export { default as Nodejs } from './Nodejs.svelte';
|
||||||
|
export { default as React } from './React.svelte';
|
||||||
|
export { default as Svelte } from './Svelte.svelte';
|
||||||
|
export { default as Vuejs } from './Vuejs.svelte';
|
||||||
|
export { default as Php } from './PHP.svelte';
|
||||||
|
export { default as Python } from './Python.svelte';
|
||||||
|
export { default as Static } from './Static.svelte';
|
||||||
|
export { default as Nestjs } from './Nestjs.svelte';
|
||||||
|
export { default as Nuxtjs } from './Nuxtjs.svelte';
|
||||||
|
export { default as Nextjs } from './Nextjs.svelte';
|
||||||
|
export { default as Gatsby } from './Gatsby.svelte';
|
||||||
|
export { default as Docker } from './Docker.svelte';
|
||||||
|
export { default as Astro } from './Astro.svelte';
|
||||||
|
export { default as Eleventy } from './Eleventy.svelte';
|
||||||
|
export { default as Deno } from './Deno.svelte';
|
||||||
|
export { default as Laravel } from './Laravel.svelte';
|
||||||
|
export { default as Heroku } from './Heroku.svelte';
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Icons from '$lib/components/svg/databases';
|
||||||
|
export let type: any;
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if type === 'mysql'}
|
||||||
|
<Icons.MySQL {isAbsolute} />
|
||||||
|
{:else if type === 'postgresql'}
|
||||||
|
<Icons.PostgreSQL {isAbsolute} />
|
||||||
|
{:else if type === 'mongodb'}
|
||||||
|
<Icons.MongoDB {isAbsolute} />
|
||||||
|
{:else if type === 'mariadb'}
|
||||||
|
<Icons.MariaDB {isAbsolute} />
|
||||||
|
{:else if type === 'redis'}
|
||||||
|
<Icons.Redis {isAbsolute} />
|
||||||
|
{:else if type === 'couchdb'}
|
||||||
|
<Icons.CouchDB {isAbsolute} />
|
||||||
|
{/if}
|
||||||
10
apps/ui/src/lib/components/svg/databases/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
export { default as Clickhouse } from './Clickhouse.svelte';
|
||||||
|
export { default as CouchDB } from './CouchDB.svelte';
|
||||||
|
export { default as MariaDB } from './MariaDB.svelte';
|
||||||
|
export { default as MongoDB } from './MongoDB.svelte';
|
||||||
|
export { default as MySQL } from './MySQL.svelte';
|
||||||
|
export { default as PostgreSQL } from './PostgreSQL.svelte';
|
||||||
|
export { default as Redis } from './Redis.svelte';
|
||||||
|
|
||||||
|
|
||||||
21
apps/ui/src/lib/components/svg/services/Appwrite.svelte
Normal file
@@ -5,7 +5,7 @@
|
|||||||
<svg
|
<svg
|
||||||
viewBox="0 0 700 240"
|
viewBox="0 0 700 240"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class={isAbsolute ? 'w-36 absolute top-0 left-0 -m-3 -mt-5' : 'w-28 mx-auto'}
|
class={isAbsolute ? 'w-36 absolute top-0 left-0 -m-3 -mt-5' : 'w-28 h-28 mx-auto'}
|
||||||
><path fill="#FDBC3D" d="m90.694 107.498-.981.39-20.608 8.23 6.332 6.547z" /><path
|
><path fill="#FDBC3D" d="m90.694 107.498-.981.39-20.608 8.23 6.332 6.547z" /><path
|
||||||
fill="#8EC63F"
|
fill="#8EC63F"
|
||||||
d="M61.139 77.914 46.632 93 56.9 103.547c8.649-7.169 17.832-10.502 18.653-10.789L61.139 77.914z"
|
d="M61.139 77.914 46.632 93 56.9 103.547c8.649-7.169 17.832-10.502 18.653-10.789L61.139 77.914z"
|
||||||
|
|||||||
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 h-8 mx-auto'}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
style="isolation:isolate"
|
||||||
|
viewBox="0 0 400 400"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW">
|
||||||
|
<rect width="400" height="400" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW)">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d=" M 276.155 367.684 L 337.655 367.684 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 258.617 C 267.987 291.29 238.678 308.586 202.162 308.586 C 156.998 308.586 127.689 282.641 127.689 226.906 L 127.689 173.094 C 127.689 117.359 156.998 91.414 202.162 91.414 C 241.08 91.414 261.74 112.554 271.83 138.5 L 331.409 104.386 C 306.424 52.976 261.74 26.55 202.162 26.55 C 111.353 26.55 50.333 88.531 50.333 201.441 C 50.333 313.872 110.873 373.45 187.748 373.45 C 238.197 373.45 268.947 347.985 273.752 314.352 L 276.155 314.352 L 276.155 367.684 Z "
|
||||||
|
fill="rgb(132,24,128)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 139.701 175.78 L 139.701 173.094 C 139.701 117.359 169.01 91.414 214.174 91.414 C 253.092 91.414 273.752 112.554 283.842 138.5 L 343.421 104.386 C 318.436 52.976 273.752 26.55 214.174 26.55 C 128.962 26.55 69.981 81.125 63.033 181.145 L 139.701 175.78 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 349.667 305.194 L 349.667 247.137 L 279.998 252.019 L 279.998 258.617 C 279.998 291.29 250.69 308.586 214.174 308.586 C 179.697 308.586 154.459 293.467 144.446 261.518 L 70.341 266.711 C 76.285 288.796 85.348 307.563 96.86 322.909 L 349.667 305.194 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 247.03 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 251.912 L 337.655 247.03 Z M 132.401 261.413 C 129.319 251.534 127.689 240.048 127.689 226.906 L 127.689 175.099 L 51.069 180.468 C 50.581 187.25 50.333 194.242 50.333 201.441 C 50.333 225.632 53.136 247.376 58.301 266.606 L 132.401 261.413 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 305.862 L 337.655 367.684 L 276.155 367.684 L 276.155 314.352 L 273.752 314.352 C 268.947 347.985 238.197 373.45 187.748 373.45 C 146.712 373.45 110.33 356.473 85.327 323.543 L 337.655 305.862 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(255,63,42)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
alt="plausible logo"
|
alt="plausible logo"
|
||||||
class={isAbsolute ? 'w-9 absolute top-0 left-0 -m-4' : 'w-6 mx-auto'}
|
class={isAbsolute ? 'w-9 h-12 absolute top-0 left-0 -m-4' : 'w-6 h-8 mx-auto'}
|
||||||
src="/plausible.png"
|
src="/plausible.png"
|
||||||
/>
|
/>
|
||||||
|
|||||||
56
apps/ui/src/lib/components/svg/services/Searxng.svelte
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
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"
|
||||||
|
class={isAbsolute ? 'w-12 h-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 h-8 mx-auto'}
|
||||||
|
>
|
||||||
|
<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 />
|
||||||
|
</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>
|
||||||
43
apps/ui/src/lib/components/svg/services/ServiceIcons.svelte
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let type: string;
|
||||||
|
export let isAbsolute = true;
|
||||||
|
import * as Icons from '$lib/components/svg/services';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if type === 'plausibleanalytics'}
|
||||||
|
<Icons.PlausibleAnalytics {isAbsolute} />
|
||||||
|
{:else if type === 'nocodb'}
|
||||||
|
<Icons.NocoDb {isAbsolute} />
|
||||||
|
{:else if type === 'minio'}
|
||||||
|
<Icons.MinIo {isAbsolute} />
|
||||||
|
{:else if type === 'vscodeserver'}
|
||||||
|
<Icons.VsCodeServer {isAbsolute} />
|
||||||
|
{:else if type === 'wordpress'}
|
||||||
|
<Icons.Wordpress {isAbsolute} />
|
||||||
|
{:else if type === 'vaultwarden'}
|
||||||
|
<Icons.VaultWarden {isAbsolute} />
|
||||||
|
{:else if type === 'languagetool'}
|
||||||
|
<Icons.LanguageTool {isAbsolute} />
|
||||||
|
{:else if type === 'n8n'}
|
||||||
|
<Icons.N8n {isAbsolute} />
|
||||||
|
{:else if type === 'uptimekuma'}
|
||||||
|
<Icons.UptimeKuma {isAbsolute} />
|
||||||
|
{:else if type === 'ghost'}
|
||||||
|
<Icons.Ghost {isAbsolute} />
|
||||||
|
{:else if type === 'meilisearch'}
|
||||||
|
<Icons.MeiliSearch {isAbsolute} />
|
||||||
|
{:else if type === 'umami'}
|
||||||
|
<Icons.Umami {isAbsolute} />
|
||||||
|
{:else if type === 'hasura'}
|
||||||
|
<Icons.Hasura {isAbsolute} />
|
||||||
|
{:else if type === 'fider'}
|
||||||
|
<Icons.Fider {isAbsolute} />
|
||||||
|
{:else if type === 'appwrite'}
|
||||||
|
<Icons.Appwrite {isAbsolute} />
|
||||||
|
{:else if type === 'moodle'}
|
||||||
|
<Icons.Moodle {isAbsolute} />
|
||||||
|
{:else if type === 'glitchTip'}
|
||||||
|
<Icons.GlitchTip {isAbsolute} />
|
||||||
|
{:else if type === 'searxng'}
|
||||||
|
<Icons.Searxng {isAbsolute} />
|
||||||
|
{/if}
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
export { default as PlausibleAnalytics } from './PlausibleAnalytics.svelte';
|
export { default as PlausibleAnalytics } from './PlausibleAnalytics.svelte';
|
||||||
export { default as NocoDb } from './NocoDB.svelte';
|
export { default as NocoDb } from './NocoDB.svelte';
|
||||||
export { default as MinIo } from './MinIO.svelte';
|
export { default as MinIo } from './MinIO.svelte';
|
||||||
export { default as VsCodeServer } from './VSCodeServer.svelte';
|
export { default as VsCodeServer } from './VSCodeServer.svelte';
|
||||||
export { default as Wordpress } from './Wordpress.svelte';
|
export { default as Wordpress } from './Wordpress.svelte';
|
||||||
export { default as VaultWarden } from './VaultWarden.svelte';
|
export { default as VaultWarden } from './VaultWarden.svelte';
|
||||||
export { default as LanguageTool } from './LanguageTool.svelte';
|
export { default as LanguageTool } from './LanguageTool.svelte';
|
||||||
export { default as N8n } from './N8n.svelte';
|
export { default as N8n } from './N8n.svelte';
|
||||||
export { default as UptimeKuma } from './UptimeKuma.svelte';
|
export { default as UptimeKuma } from './UptimeKuma.svelte';
|
||||||
export { default as Ghost } from './Ghost.svelte';
|
export { default as Ghost } from './Ghost.svelte';
|
||||||
export { default as MeiliSearch } from './MeiliSearch.svelte';
|
export { default as MeiliSearch } from './MeiliSearch.svelte';
|
||||||
export { default as Umami } from './Umami.svelte';
|
export { default as Umami } from './Umami.svelte';
|
||||||
export { default as Hasura } from './Hasura.svelte';
|
export { default as Hasura } from './Hasura.svelte';
|
||||||
export { default as Fider } from './Fider.svelte';
|
export { default as Fider } from './Fider.svelte';
|
||||||
export { default as Moodle } from './Moodle.svelte';
|
export { default as Appwrite } from './Appwrite.svelte';
|
||||||
|
export { default as Moodle } from './Moodle.svelte';
|
||||||
|
export { default as GlitchTip } from './GlitchTip.svelte';
|
||||||
|
export { default as Searxng } from './Searxng.svelte';
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"wait_new_version_startup": "Waiting for the new version to start...",
|
"wait_new_version_startup": "Waiting for the new version to start...",
|
||||||
"new_version": "New version reachable. Reloading...",
|
"new_version": "New version reachable. Reloading...",
|
||||||
"switch_to_a_different_team": "Switch to a different team...",
|
"switch_to_a_different_team": "Switch to a different team...",
|
||||||
"update_available": "Update available"
|
"update_available": "Update Available"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"you_can_find_your_way_back": "You can find your way back",
|
"you_can_find_your_way_back": "You can find your way back",
|
||||||
@@ -144,8 +144,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"need_during_buildtime": "Need during buildtime?",
|
"need_during_buildtime": "Need during buildtime?",
|
||||||
"setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-applications font-bold'>staging</span> environments.",
|
"setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
|
||||||
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-applications font-bold'>staging</span> environments.",
|
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
|
||||||
"redeploy": "Redeploy",
|
"redeploy": "Redeploy",
|
||||||
"no_previews_available": "No previews available"
|
"no_previews_available": "No previews available"
|
||||||
},
|
},
|
||||||
@@ -159,13 +159,13 @@
|
|||||||
"storage_saved": "Storage saved.",
|
"storage_saved": "Storage saved.",
|
||||||
"storage_updated": "Storage updated.",
|
"storage_updated": "Storage updated.",
|
||||||
"storage_deleted": "Storage deleted.",
|
"storage_deleted": "Storage deleted.",
|
||||||
"persistent_storage_explainer": "You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache."
|
"persistent_storage_explainer": "You can specify any folder that you want to be persistent across deployments.<br><span class='text-green-500 font-bold'>/example</span> means it will preserve <span class='text-green-500 font-bold'>/app/example</span> in the container as <span class='text-green-500 font-bold'>/app</span> is <span class='text-green-500 font-bold'>the root directory</span> for your application.<br><br>This is useful for storing data such as a <span class='text-green-500 font-bold'>database (SQLite)</span> or a <span class='text-green-500 font-bold'>cache</span>."
|
||||||
},
|
},
|
||||||
"deployment_queued": "Deployment queued.",
|
"deployment_queued": "Deployment queued.",
|
||||||
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
|
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
|
||||||
"stop_application": "Stop application",
|
"stop_application": "Stop Application",
|
||||||
"permission_denied_stop_application": "You do not have permission to stop the application.",
|
"permission_denied_stop_application": "You do not have permission to stop the application.",
|
||||||
"rebuild_application": "Rebuild application",
|
"rebuild_application": "Rebuild Application",
|
||||||
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
|
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
|
||||||
"build_and_start_application": "Deploy",
|
"build_and_start_application": "Deploy",
|
||||||
"permission_denied_build_and_start_application": "You do not have permission to deploy application.",
|
"permission_denied_build_and_start_application": "You do not have permission to deploy application.",
|
||||||
@@ -194,14 +194,14 @@
|
|||||||
"application": "Application",
|
"application": "Application",
|
||||||
"url_fqdn": "URL (FQDN)",
|
"url_fqdn": "URL (FQDN)",
|
||||||
"domain_fqdn": "Domain (FQDN)",
|
"domain_fqdn": "Domain (FQDN)",
|
||||||
"https_explainer": "If you specify <span class='text-applications font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-applications font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
|
"https_explainer": "If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
|
||||||
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
||||||
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-applications'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
||||||
"install_command": "Install Command",
|
"install_command": "Install Command",
|
||||||
"build_command": "Build Command",
|
"build_command": "Build Command",
|
||||||
"start_command": "Start Command",
|
"start_command": "Start Command",
|
||||||
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-applications font-bold'>monorepos</span>.",
|
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>.",
|
||||||
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-applications font-bold'>dist</span>,<span class='text-applications font-bold'>_site</span> or <span class='text-applications font-bold'>public</span>.",
|
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>.",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"enable_automatic_deployment": "Enable Automatic Deployment",
|
"enable_automatic_deployment": "Enable Automatic Deployment",
|
||||||
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
|
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
"features": "Caractéristiques",
|
"features": "Caractéristiques",
|
||||||
"git_repository": "Dépôt Git",
|
"git_repository": "Dépôt Git",
|
||||||
"git_source": "Source Git",
|
"git_source": "Source Git",
|
||||||
"https_explainer": "Si vous spécifiez <span class='text-applications font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-applications font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
|
"https_explainer": "Si vous spécifiez <span class='text-green-500 font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-green-500 font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
|
||||||
"install_command": "Commande d'installation",
|
"install_command": "Commande d'installation",
|
||||||
"logs": "Journaux des applications",
|
"logs": "Journaux des applications",
|
||||||
"no_applications_found": "Aucune application trouvée",
|
"no_applications_found": "Aucune application trouvée",
|
||||||
@@ -78,11 +78,11 @@
|
|||||||
"need_during_buildtime": "Besoin pendant la build ?",
|
"need_during_buildtime": "Besoin pendant la build ?",
|
||||||
"no_previews_available": "Aucun aperçu disponible",
|
"no_previews_available": "Aucun aperçu disponible",
|
||||||
"redeploy": "Redéployer",
|
"redeploy": "Redéployer",
|
||||||
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-applications font-bold'>de mise en scène</span>.",
|
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>.",
|
||||||
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-applications font-bold'>de mise en scène</span>."
|
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>."
|
||||||
},
|
},
|
||||||
"previews": "Aperçus",
|
"previews": "Aperçus",
|
||||||
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-applications font-bold'>dist</span>,<span class='text-applications font-bold'>_site</span> ou <span \nclass='text-applications font-bold'>public</span>.",
|
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> ou <span \nclass='text-green-500 font-bold'>public</span>.",
|
||||||
"rebuild_application": "Re-build l'application",
|
"rebuild_application": "Re-build l'application",
|
||||||
"secret": "secrets",
|
"secret": "secrets",
|
||||||
"secrets": {
|
"secrets": {
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
"use_isbuildsecret": "Utiliser isBuildSecret"
|
"use_isbuildsecret": "Utiliser isBuildSecret"
|
||||||
},
|
},
|
||||||
"settings_saved": "Paramètres sauvegardés.",
|
"settings_saved": "Paramètres sauvegardés.",
|
||||||
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-applications'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
|
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-green-500'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
|
||||||
"ssl_www_and_non_www": "Générer SSL pour www et non-www ?",
|
"ssl_www_and_non_www": "Générer SSL pour www et non-www ?",
|
||||||
"start_command": "Démarrer la commande",
|
"start_command": "Démarrer la commande",
|
||||||
"stop_application": "Arrêter l'application",
|
"stop_application": "Arrêter l'application",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { dev } from '$app/env';
|
||||||
|
import cuid from 'cuid';
|
||||||
import { writable, readable, type Writable } from 'svelte/store';
|
import { writable, readable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
interface AppSession {
|
interface AppSession {
|
||||||
@@ -21,7 +23,7 @@ interface AddToast {
|
|||||||
type?: "info" | "success" | "error",
|
type?: "info" | "success" | "error",
|
||||||
message: string,
|
message: string,
|
||||||
timeout?: number | undefined
|
timeout?: number | undefined
|
||||||
}
|
}
|
||||||
export const loginEmail: Writable<string | undefined> = writable()
|
export const loginEmail: Writable<string | undefined> = writable()
|
||||||
export const appSession: Writable<AppSession> = writable({
|
export const appSession: Writable<AppSession> = writable({
|
||||||
ipv4: null,
|
ipv4: null,
|
||||||
@@ -69,42 +71,61 @@ export const features = readable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const location: Writable<null | string> = writable(null)
|
export const location: Writable<null | string> = writable(null)
|
||||||
export const setLocation = (resource: any) => {
|
export const setLocation = (resource: any, settings?: any) => {
|
||||||
|
if (resource.settings.isBot && resource.exposePort) {
|
||||||
|
disabledButton.set(false);
|
||||||
|
return location.set(`http://${dev ? 'localhost' : settings.ipv4}:${resource.exposePort}`)
|
||||||
|
}
|
||||||
if (GITPOD_WORKSPACE_URL && resource.exposePort) {
|
if (GITPOD_WORKSPACE_URL && resource.exposePort) {
|
||||||
const { href } = new URL(GITPOD_WORKSPACE_URL);
|
const { href } = new URL(GITPOD_WORKSPACE_URL);
|
||||||
const newURL = href
|
const newURL = href
|
||||||
.replace('https://', `https://${resource.exposePort}-`)
|
.replace('https://', `https://${resource.exposePort}-`)
|
||||||
.replace(/\/$/, '');
|
.replace(/\/$/, '');
|
||||||
return location.set(newURL)
|
return location.set(newURL)
|
||||||
} else if (CODESANDBOX_HOST){
|
} else if (CODESANDBOX_HOST) {
|
||||||
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/,resource.exposePort)}`
|
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, resource.exposePort)}`
|
||||||
return location.set(newURL)
|
return location.set(newURL)
|
||||||
|
}
|
||||||
|
if (resource.fqdn) {
|
||||||
|
return location.set(resource.fqdn)
|
||||||
|
} else {
|
||||||
|
location.set(null);
|
||||||
|
disabledButton.set(false);
|
||||||
}
|
}
|
||||||
return location.set(resource.fqdn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toasts: any = writable([])
|
export const toasts: any = writable([])
|
||||||
|
|
||||||
export const dismissToast = (id: number) => {
|
export const dismissToast = (id: string) => {
|
||||||
toasts.update((all: any) => all.filter((t: any) => t.id !== id))
|
toasts.update((all: any) => all.filter((t: any) => t.id !== id))
|
||||||
|
}
|
||||||
|
export const pauseToast = (id: string) => {
|
||||||
|
toasts.update((all: any) => {
|
||||||
|
const index = all.findIndex((t: any) => t.id === id);
|
||||||
|
if (index > -1) clearTimeout(all[index].timeoutInterval);
|
||||||
|
return all;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const resumeToast = (id: string) => {
|
||||||
|
toasts.update((all: any) => {
|
||||||
|
const index = all.findIndex((t: any) => t.id === id);
|
||||||
|
if (index > -1) {
|
||||||
|
all[index].timeoutInterval = setTimeout(() => {
|
||||||
|
dismissToast(id)
|
||||||
|
}, all[index].timeout)
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addToast = (toast: AddToast) => {
|
export const addToast = (toast: AddToast) => {
|
||||||
// Create a unique ID so we can easily find/remove it
|
const id = cuid();
|
||||||
// if it is dismissible/has a timeout.
|
const defaults = {
|
||||||
const id = Math.floor(Math.random() * 10000)
|
id,
|
||||||
|
type: 'info',
|
||||||
// Setup some sensible defaults for a toast.
|
timeout: 2000,
|
||||||
const defaults = {
|
}
|
||||||
id,
|
let t: any = { ...defaults, ...toast }
|
||||||
type: 'info',
|
if (t.timeout) t.timeoutInterval = setTimeout(() => dismissToast(id), t.timeout)
|
||||||
timeout: 2000,
|
toasts.update((all: any) => [t, ...all])
|
||||||
}
|
|
||||||
|
|
||||||
// Push the toast to the top of the list of toasts
|
|
||||||
const t = { ...defaults, ...toast }
|
|
||||||
toasts.update((all: any) => [t, ...all])
|
|
||||||
|
|
||||||
// If toast is dismissible, dismiss it after "timeout" amount of time.
|
|
||||||
if (t.timeout) setTimeout(() => dismissToast(id), t.timeout)
|
|
||||||
}
|
}
|
||||||
@@ -170,6 +170,16 @@ export function findBuildPack(pack: string, packageManager = 'npm') {
|
|||||||
port: 80
|
port: 80
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (pack === 'heroku') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 5000
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: 'node',
|
name: 'node',
|
||||||
fancyName: 'Node.js',
|
fancyName: 'Node.js',
|
||||||
@@ -187,119 +197,137 @@ export const buildPacks = [
|
|||||||
name: 'node',
|
name: 'node',
|
||||||
fancyName: 'Node.js',
|
fancyName: 'Node.js',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'static',
|
name: 'static',
|
||||||
fancyName: 'Static',
|
fancyName: 'Static',
|
||||||
hoverColor: 'hover:bg-orange-700',
|
hoverColor: 'hover:bg-orange-700',
|
||||||
color: 'bg-orange-700'
|
color: 'bg-orange-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'php',
|
name: 'php',
|
||||||
fancyName: 'PHP',
|
fancyName: 'PHP',
|
||||||
hoverColor: 'hover:bg-indigo-700',
|
hoverColor: 'hover:bg-indigo-700',
|
||||||
color: 'bg-indigo-700'
|
color: 'bg-indigo-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'laravel',
|
name: 'laravel',
|
||||||
fancyName: 'Laravel',
|
fancyName: 'Laravel',
|
||||||
hoverColor: 'hover:bg-indigo-700',
|
hoverColor: 'hover:bg-indigo-700',
|
||||||
color: 'bg-indigo-700'
|
color: 'bg-indigo-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'docker',
|
name: 'docker',
|
||||||
fancyName: 'Docker',
|
fancyName: 'Docker',
|
||||||
hoverColor: 'hover:bg-sky-700',
|
hoverColor: 'hover:bg-sky-700',
|
||||||
color: 'bg-sky-700'
|
color: 'bg-sky-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'svelte',
|
name: 'svelte',
|
||||||
fancyName: 'Svelte',
|
fancyName: 'Svelte',
|
||||||
hoverColor: 'hover:bg-orange-700',
|
hoverColor: 'hover:bg-orange-700',
|
||||||
color: 'bg-orange-700'
|
color: 'bg-orange-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vuejs',
|
name: 'vuejs',
|
||||||
fancyName: 'VueJS',
|
fancyName: 'VueJS',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nuxtjs',
|
name: 'nuxtjs',
|
||||||
fancyName: 'NuxtJS',
|
fancyName: 'NuxtJS',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'gatsby',
|
name: 'gatsby',
|
||||||
fancyName: 'Gatsby',
|
fancyName: 'Gatsby',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'astro',
|
name: 'astro',
|
||||||
fancyName: 'Astro',
|
fancyName: 'Astro',
|
||||||
hoverColor: 'hover:bg-pink-700',
|
hoverColor: 'hover:bg-pink-700',
|
||||||
color: 'bg-pink-700'
|
color: 'bg-pink-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'eleventy',
|
name: 'eleventy',
|
||||||
fancyName: 'Eleventy',
|
fancyName: 'Eleventy',
|
||||||
hoverColor: 'hover:bg-red-700',
|
hoverColor: 'hover:bg-red-700',
|
||||||
color: 'bg-red-700'
|
color: 'bg-red-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'react',
|
name: 'react',
|
||||||
fancyName: 'React',
|
fancyName: 'React',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'preact',
|
name: 'preact',
|
||||||
fancyName: 'Preact',
|
fancyName: 'Preact',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nextjs',
|
name: 'nextjs',
|
||||||
fancyName: 'NextJS',
|
fancyName: 'NextJS',
|
||||||
hoverColor: 'hover:bg-blue-700',
|
hoverColor: 'hover:bg-blue-700',
|
||||||
color: 'bg-blue-700'
|
color: 'bg-blue-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nestjs',
|
name: 'nestjs',
|
||||||
fancyName: 'NestJS',
|
fancyName: 'NestJS',
|
||||||
hoverColor: 'hover:bg-red-700',
|
hoverColor: 'hover:bg-red-700',
|
||||||
color: 'bg-red-700'
|
color: 'bg-red-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'rust',
|
name: 'rust',
|
||||||
fancyName: 'Rust',
|
fancyName: 'Rust',
|
||||||
hoverColor: 'hover:bg-pink-700',
|
hoverColor: 'hover:bg-pink-700',
|
||||||
color: 'bg-pink-700'
|
color: 'bg-pink-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'python',
|
name: 'python',
|
||||||
fancyName: 'Python',
|
fancyName: 'Python',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700',
|
||||||
|
isCoolifyBuildPack: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'deno',
|
name: 'deno',
|
||||||
fancyName: 'Deno',
|
fancyName: 'Deno',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700',
|
||||||
}
|
isCoolifyBuildPack: true,
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// name: 'heroku',
|
name: 'heroku',
|
||||||
// fancyName: 'Heroku Buildpack',
|
fancyName: 'Heroku',
|
||||||
// hoverColor: 'hover:bg-indigo-700',
|
hoverColor: 'hover:bg-purple-700',
|
||||||
// color: 'bg-indigo-700'
|
color: 'bg-purple-700',
|
||||||
// }
|
isHerokuBuildPack: true,
|
||||||
|
}
|
||||||
];
|
];
|
||||||
export const scanningTemplates = {
|
export const scanningTemplates = {
|
||||||
'@sveltejs/kit': {
|
'@sveltejs/kit': {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
async function createSecret(isNew: any) {
|
async function createSecret(isNew: any) {
|
||||||
try {
|
try {
|
||||||
if (!name || !value) return
|
if (!name || !value) return;
|
||||||
await saveSecret({
|
await saveSecret({
|
||||||
isNew,
|
isNew,
|
||||||
name,
|
name,
|
||||||
@@ -53,12 +53,16 @@
|
|||||||
name = '';
|
name = '';
|
||||||
value = '';
|
value = '';
|
||||||
isBuildSecret = false;
|
isBuildSecret = false;
|
||||||
|
addToast({
|
||||||
|
message: 'Secret added.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
dispatch('refresh');
|
|
||||||
addToast({
|
addToast({
|
||||||
message: 'Secret removed.',
|
message: 'Secret updated.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
|
dispatch('refresh');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -79,7 +83,7 @@
|
|||||||
applicationId: id
|
applicationId: id
|
||||||
});
|
});
|
||||||
addToast({
|
addToast({
|
||||||
message: 'Secret removed.',
|
message: 'Secret updated.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
export const load: Load = async ({ fetch, url, params }) => {
|
export const load: Load = async ({ fetch, url, params }) => {
|
||||||
try {
|
try {
|
||||||
const response = await get(`/applications/${params.id}`);
|
const response = await get(`/applications/${params.id}`);
|
||||||
let { application, appId, settings, isQueueActive } = response;
|
let { application, appId, settings } = response;
|
||||||
if (!application || Object.entries(application).length === 0) {
|
if (!application || Object.entries(application).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
application
|
application,
|
||||||
|
settings
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
application,
|
application,
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
export let application: any;
|
||||||
|
export let settings: any;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import { del, get, post } from '$lib/api';
|
import { del, get, post } from '$lib/api';
|
||||||
@@ -65,10 +66,9 @@
|
|||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
let isQueueActive= false;
|
|
||||||
$disabledButton =
|
$disabledButton =
|
||||||
!$appSession.isAdmin ||
|
!$appSession.isAdmin ||
|
||||||
!application.fqdn ||
|
(!application.fqdn && !application.settings.isBot) ||
|
||||||
!application.gitSource ||
|
!application.gitSource ||
|
||||||
!application.repository ||
|
!application.repository ||
|
||||||
!application.destinationDocker ||
|
!application.destinationDocker ||
|
||||||
@@ -76,13 +76,13 @@
|
|||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
async function handleDeploySubmit() {
|
async function handleDeploySubmit(forceRebuild = false) {
|
||||||
try {
|
try {
|
||||||
const { buildId } = await post(`/applications/${id}/deploy`, { ...application });
|
const { buildId } = await post(`/applications/${id}/deploy`, { ...application, forceRebuild });
|
||||||
addToast({
|
addToast({
|
||||||
message: $t('application.deployment_queued'),
|
message: $t('application.deployment_queued'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -114,30 +114,33 @@
|
|||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if ($status.application.loading) return;
|
if ($status.application.loading) return;
|
||||||
$status.application.loading = true;
|
$status.application.loading = true;
|
||||||
const data = await get(`/applications/${id}/status`);
|
const data = await get(`/applications/${id}/status`);
|
||||||
isQueueActive = data.isQueueActive;
|
|
||||||
$status.application.isRunning = data.isRunning;
|
$status.application.isRunning = data.isRunning;
|
||||||
$status.application.isExited = data.isExited;
|
$status.application.isExited = data.isExited;
|
||||||
$status.application.loading = false;
|
$status.application.loading = false;
|
||||||
$status.application.initialLoading = false;
|
$status.application.initialLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$status.application.initialLoading = true;
|
$status.application.initialLoading = true;
|
||||||
$location = null;
|
$location = null;
|
||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
});
|
});
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
setLocation(application);
|
setLocation(application, settings);
|
||||||
|
|
||||||
$status.application.isRunning = false;
|
$status.application.isRunning = false;
|
||||||
$status.application.isExited = false;
|
$status.application.isExited = false;
|
||||||
$status.application.loading = false;
|
$status.application.loading = false;
|
||||||
if (application.gitSourceId && application.destinationDockerId && application.fqdn) {
|
if (
|
||||||
|
application.gitSourceId &&
|
||||||
|
application.destinationDockerId &&
|
||||||
|
(application.fqdn || application.settings.isBot)
|
||||||
|
) {
|
||||||
await getStatus();
|
await getStatus();
|
||||||
statusInterval = setInterval(async () => {
|
statusInterval = setInterval(async () => {
|
||||||
await getStatus();
|
await getStatus();
|
||||||
@@ -173,9 +176,10 @@
|
|||||||
<polyline points="15 4 20 4 20 9" />
|
<polyline points="15 4 20 4 20 9" />
|
||||||
</svg></a
|
</svg></a
|
||||||
>
|
>
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="border border-coolgray-500 h-8" />
|
|
||||||
{#if $status.application.isExited}
|
{#if $status.application.isExited}
|
||||||
<a
|
<a
|
||||||
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||||
@@ -250,16 +254,13 @@
|
|||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={$disabledButton || !isQueueActive}
|
disabled={$disabledButton}
|
||||||
class:hover:text-green-500={isQueueActive}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
|
||||||
data-tip={$appSession.isAdmin
|
data-tip={$appSession.isAdmin
|
||||||
? isQueueActive
|
? 'Force Rebuild Application'
|
||||||
? 'Rebuild application'
|
|
||||||
: 'Autoupdate inprogress. Cannot rebuild application.'
|
|
||||||
: 'You do not have permission to rebuild application.'}
|
: 'You do not have permission to rebuild application.'}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -281,7 +282,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
<form on:submit|preventDefault={() => handleDeploySubmit(false)}>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={$disabledButton}
|
disabled={$disabledButton}
|
||||||
@@ -353,7 +354,7 @@
|
|||||||
<button
|
<button
|
||||||
disabled={$disabledButton}
|
disabled={$disabledButton}
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
data-tip="Secret"
|
data-tip="Secrets"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -403,37 +404,39 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button></a
|
</button></a
|
||||||
>
|
>
|
||||||
<a
|
{#if !application.settings.isBot}
|
||||||
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
<a
|
||||||
sveltekit:prefetch
|
href={!$disabledButton ? `/applications/${id}/previews` : null}
|
||||||
class="hover:text-orange-500 rounded"
|
sveltekit:prefetch
|
||||||
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
class="hover:text-orange-500 rounded"
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
>
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
|
||||||
<button
|
|
||||||
disabled={$disabledButton}
|
|
||||||
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
|
||||||
data-tip="Previews"
|
|
||||||
>
|
>
|
||||||
<svg
|
<button
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
disabled={$disabledButton}
|
||||||
class="w-6 h-6"
|
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
|
||||||
viewBox="0 0 24 24"
|
data-tip="Previews"
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<svg
|
||||||
<circle cx="7" cy="18" r="2" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<circle cx="7" cy="6" r="2" />
|
class="w-6 h-6"
|
||||||
<circle cx="17" cy="12" r="2" />
|
viewBox="0 0 24 24"
|
||||||
<line x1="7" y1="8" x2="7" y2="16" />
|
stroke-width="1.5"
|
||||||
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
stroke="currentColor"
|
||||||
</svg></button
|
fill="none"
|
||||||
></a
|
stroke-linecap="round"
|
||||||
>
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<circle cx="7" cy="18" r="2" />
|
||||||
|
<circle cx="7" cy="6" r="2" />
|
||||||
|
<circle cx="17" cy="12" r="2" />
|
||||||
|
<line x1="7" y1="8" x2="7" y2="16" />
|
||||||
|
<path d="M7 8a4 4 0 0 0 4 4h4" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
<div class="border border-coolgray-500 h-8" />
|
<div class="border border-coolgray-500 h-8" />
|
||||||
<a
|
<a
|
||||||
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
delete tempBuildPack.color;
|
delete tempBuildPack.color;
|
||||||
delete tempBuildPack.hoverColor;
|
delete tempBuildPack.hoverColor;
|
||||||
|
|
||||||
if (foundConfig.buildPack !== name) {
|
if (foundConfig?.buildPack !== name) {
|
||||||
await post(`/applications/${id}`, { ...tempBuildPack, buildPack: name });
|
await post(`/applications/${id}`, { ...tempBuildPack, buildPack: name });
|
||||||
}
|
}
|
||||||
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });
|
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { get, post } from '$lib/api';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import Select from 'svelte-select';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
let publicRepositoryLink: string;
|
||||||
|
let projectId: number;
|
||||||
|
let repositoryName: string;
|
||||||
|
let branchName: string;
|
||||||
|
let ownerName: string;
|
||||||
|
let type: string;
|
||||||
|
let branchSelectOptions: any = [];
|
||||||
|
let loading = {
|
||||||
|
branches: false
|
||||||
|
};
|
||||||
|
async function loadBranches() {
|
||||||
|
try {
|
||||||
|
loading.branches = true;
|
||||||
|
|
||||||
|
const protocol = publicRepositoryLink.split(':')[0];
|
||||||
|
const gitUrl = publicRepositoryLink.replace('http://', '').replace('https://', '');
|
||||||
|
|
||||||
|
let [host, ...path] = gitUrl.split('/');
|
||||||
|
const [owner, repository, ...branch] = path;
|
||||||
|
|
||||||
|
ownerName = owner;
|
||||||
|
repositoryName = repository;
|
||||||
|
|
||||||
|
if (host === 'github.com') {
|
||||||
|
host = 'api.github.com';
|
||||||
|
type = 'github';
|
||||||
|
if (branch[0] === 'tree' && branch[1]) {
|
||||||
|
branchName = branch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (host === 'gitlab.com') {
|
||||||
|
host = 'gitlab.com/api/v4';
|
||||||
|
type = 'gitlab';
|
||||||
|
if (branch[1] === 'tree' && branch[2]) {
|
||||||
|
branchName = branch[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const apiUrl = `${protocol}://${host}`;
|
||||||
|
if (type === 'github') {
|
||||||
|
const repositoryDetails = await get(`${apiUrl}/repos/${ownerName}/${repositoryName}`);
|
||||||
|
projectId = repositoryDetails.id.toString();
|
||||||
|
}
|
||||||
|
if (type === 'gitlab') {
|
||||||
|
const repositoryDetails = await get(`${apiUrl}/projects/${ownerName}%2F${repositoryName}`);
|
||||||
|
projectId = repositoryDetails.id.toString();
|
||||||
|
}
|
||||||
|
if (type === 'github' && branchName) {
|
||||||
|
try {
|
||||||
|
await get(`${apiUrl}/repos/${ownerName}/${repositoryName}/branches/${branchName}`);
|
||||||
|
await saveRepository();
|
||||||
|
loading.branches = false;
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === 'gitlab' && branchName) {
|
||||||
|
try {
|
||||||
|
await get(
|
||||||
|
`${apiUrl}/projects/${ownerName}%2F${repositoryName}/repository/branches/${branchName}`
|
||||||
|
);
|
||||||
|
await saveRepository();
|
||||||
|
loading.branches = false;
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let branches: any[] = [];
|
||||||
|
let page = 1;
|
||||||
|
let branchCount = 0;
|
||||||
|
const loadedBranches = await loadBranchesByPage(
|
||||||
|
apiUrl,
|
||||||
|
ownerName,
|
||||||
|
repositoryName,
|
||||||
|
page,
|
||||||
|
type
|
||||||
|
);
|
||||||
|
branches = branches.concat(loadedBranches);
|
||||||
|
branchCount = branches.length;
|
||||||
|
if (branchCount === 100) {
|
||||||
|
while (branchCount === 100) {
|
||||||
|
page = page + 1;
|
||||||
|
const nextBranches = await loadBranchesByPage(
|
||||||
|
apiUrl,
|
||||||
|
ownerName,
|
||||||
|
repositoryName,
|
||||||
|
page,
|
||||||
|
type
|
||||||
|
);
|
||||||
|
branches = branches.concat(nextBranches);
|
||||||
|
branchCount = nextBranches.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading.branches = false;
|
||||||
|
branchSelectOptions = branches.map((branch: any) => ({
|
||||||
|
value: branch.name,
|
||||||
|
label: branch.name
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.branches = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function loadBranchesByPage(
|
||||||
|
apiUrl: string,
|
||||||
|
owner: string,
|
||||||
|
repository: string,
|
||||||
|
page = 1,
|
||||||
|
type: string
|
||||||
|
) {
|
||||||
|
if (type === 'github') {
|
||||||
|
return await get(`${apiUrl}/repos/${owner}/${repository}/branches?per_page=100&page=${page}`);
|
||||||
|
}
|
||||||
|
if (type === 'gitlab') {
|
||||||
|
return await get(
|
||||||
|
`${apiUrl}/projects/${ownerName}%2F${repositoryName}/repository/branches?page=${page}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function saveRepository(event?: any) {
|
||||||
|
try {
|
||||||
|
if (event?.detail?.value) {
|
||||||
|
branchName = event.detail.value;
|
||||||
|
}
|
||||||
|
await post(`/applications/${id}/configuration/source`, {
|
||||||
|
gitSourceId: null,
|
||||||
|
forPublic: true,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
await post(`/applications/${id}/configuration/repository`, {
|
||||||
|
repository: `${ownerName}/${repositoryName}`,
|
||||||
|
branch: branchName,
|
||||||
|
projectId,
|
||||||
|
autodeploy: false,
|
||||||
|
webhookToken: null,
|
||||||
|
isPublicRepository: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return await goto(`/applications/${id}/configuration/destination`);
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-5xl">
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
<div class="flex">
|
||||||
|
<form class="flex" on:submit|preventDefault={loadBranches}>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<input
|
||||||
|
placeholder="eg: https://github.com/coollabsio/nodejs-example/tree/main"
|
||||||
|
class="text-xs"
|
||||||
|
bind:value={publicRepositoryLink}
|
||||||
|
/>
|
||||||
|
{#if branchSelectOptions.length > 0}
|
||||||
|
<div class="custom-select-wrapper">
|
||||||
|
<Select
|
||||||
|
placeholder={loading.branches
|
||||||
|
? $t('application.configuration.loading_branches')
|
||||||
|
: !publicRepositoryLink
|
||||||
|
? $t('application.configuration.select_a_repository_first')
|
||||||
|
: $t('application.configuration.select_a_branch')}
|
||||||
|
isWaiting={loading.branches}
|
||||||
|
showIndicator={!!publicRepositoryLink && !loading.branches}
|
||||||
|
id="branches"
|
||||||
|
on:select={saveRepository}
|
||||||
|
items={branchSelectOptions}
|
||||||
|
isDisabled={loading.branches || !!!publicRepositoryLink}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn mx-4 bg-orange-600" class:loading={loading.branches} type="submit"
|
||||||
|
>Load Repository</button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Explainer
|
||||||
|
text="Examples:<br><br>https://github.com/coollabsio/nodejs-example<br>https://github.com/coollabsio/nodejs-example/tree/main<br>https://gitlab.com/aleveha/fastify-example<br>https://gitlab.com/aleveha/fastify-example/-/tree/master<br><br>Only works with Github.com and Gitlab.com."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
import { browser } from '$app/env';
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/templates';
|
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/templates';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
@@ -48,6 +47,7 @@
|
|||||||
export let branch: any;
|
export let branch: any;
|
||||||
export let type: any;
|
export let type: any;
|
||||||
export let application: any;
|
export let application: any;
|
||||||
|
export let isPublicRepository: boolean;
|
||||||
|
|
||||||
function checkPackageJSONContents({ key, json }: { key: any; json: any }) {
|
function checkPackageJSONContents({ key, json }: { key: any; json: any }) {
|
||||||
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
if (error.message === 'Bad credentials') {
|
if (error.message === 'Bad credentials') {
|
||||||
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
||||||
$appSession.tokens.github = token;
|
$appSession.tokens.github = token;
|
||||||
return await scanRepository()
|
return await scanRepository();
|
||||||
}
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -246,7 +246,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await scanRepository();
|
if (!isPublicRepository) {
|
||||||
|
await scanRepository();
|
||||||
|
} else {
|
||||||
|
scanning = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -263,11 +267,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
<div class="max-w-5xl mx-auto ">
|
||||||
{#each buildPacks as buildPack}
|
<div class="title pb-2">Coolify</div>
|
||||||
<div class="p-2">
|
<div class="flex flex-wrap justify-center">
|
||||||
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true) as buildPack}
|
||||||
</div>
|
<div class="p-2">
|
||||||
{/each}
|
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-w-5xl mx-auto ">
|
||||||
|
<div class="title pb-2">Other</div>
|
||||||
|
<div class="flex flex-wrap justify-center">
|
||||||
|
{#each buildPacks.filter((bp) => bp.isHerokuBuildPack === true) as buildPack}
|
||||||
|
<div class="p-2">
|
||||||
|
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -55,6 +56,11 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onMount(async () => {
|
||||||
|
if (destinations.length === 1) {
|
||||||
|
await handleSubmit(destinations[0].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -65,7 +71,9 @@
|
|||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{#if !destinations || ownDestinations.length === 0}
|
{#if !destinations || ownDestinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_destination')}</div>
|
<div class="pb-2 text-center font-bold">
|
||||||
|
{$t('application.configuration.no_configurable_destination')}
|
||||||
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -48,3 +48,4 @@
|
|||||||
<GitlabRepositories {application} {appId} {settings} />
|
<GitlabRepositories {application} {appId} {settings} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
|
import PublicRepository from './_PublicRepository.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -71,120 +73,126 @@
|
|||||||
{$t('application.configuration.select_a_git_source')}
|
{$t('application.configuration.select_a_git_source')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="max-w-5xl mx-auto ">
|
||||||
{#if !filteredSources || ownSources.length === 0}
|
<div class="title pb-8">Git App</div>
|
||||||
<div class="flex-col">
|
<div class="flex flex-wrap justify-center">
|
||||||
<div class="pb-2 text-center font-bold">
|
{#if !filteredSources || ownSources.length === 0}
|
||||||
{$t('application.configuration.no_configurable_git')}
|
<div class="flex-col">
|
||||||
</div>
|
<div class="pb-2 text-center font-bold">
|
||||||
<div class="flex justify-center">
|
{$t('application.configuration.no_configurable_git')}
|
||||||
<a
|
</div>
|
||||||
href="/sources/new?from={$page.url.pathname}"
|
<div class="flex justify-center">
|
||||||
class="add-icon bg-orange-600 hover:bg-orange-500"
|
<a
|
||||||
>
|
href="/sources/new?from={$page.url.pathname}"
|
||||||
<svg
|
class="add-icon bg-orange-600 hover:bg-orange-500"
|
||||||
class="w-6"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
><path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
|
||||||
/></svg
|
|
||||||
>
|
>
|
||||||
</a>
|
<svg
|
||||||
|
class="w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
><path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
{:else}
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
|
{#each ownSources as source}
|
||||||
{#each ownSources as source}
|
<div class="p-2 relative">
|
||||||
<div class="p-2 relative">
|
<div class="absolute -m-4">
|
||||||
<div class="absolute -m-4">
|
{#if source?.type === 'gitlab'}
|
||||||
{#if source?.type === 'gitlab'}
|
<svg viewBox="0 0 128 128" class="w-8">
|
||||||
<svg viewBox="0 0 128 128" class="w-8">
|
<path
|
||||||
<path
|
fill="#FC6D26"
|
||||||
fill="#FC6D26"
|
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
fill="#FC6D26"
|
||||||
fill="#FC6D26"
|
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
|
||||||
/><path
|
|
||||||
fill="#FCA326"
|
|
||||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
|
||||||
/><path
|
|
||||||
fill="#E24329"
|
|
||||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
|
||||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
|
||||||
fill="#FCA326"
|
|
||||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
|
||||||
/><path
|
|
||||||
fill="#E24329"
|
|
||||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{:else if source?.type === 'github'}
|
|
||||||
<svg viewBox="0 0 128 128" class="w-8">
|
|
||||||
<g fill="#ffffff"
|
|
||||||
><path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
|
||||||
/><path
|
/><path
|
||||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
fill="#FCA326"
|
||||||
/></g
|
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||||
>
|
/><path
|
||||||
</svg>
|
fill="#E24329"
|
||||||
{/if}
|
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||||
|
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||||
|
fill="#FCA326"
|
||||||
|
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||||
|
/><path
|
||||||
|
fill="#E24329"
|
||||||
|
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if source?.type === 'github'}
|
||||||
|
<svg viewBox="0 0 128 128" class="w-8">
|
||||||
|
<g fill="#ffffff"
|
||||||
|
><path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||||
|
/><path
|
||||||
|
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||||
|
/></g
|
||||||
|
>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
|
<button
|
||||||
|
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
type="submit"
|
||||||
|
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
||||||
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
>
|
||||||
|
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
||||||
|
{#if source.gitlabApp && !source.gitlabAppId}
|
||||||
|
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
{/each}
|
||||||
<button
|
</div>
|
||||||
disabled={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
type="submit"
|
|
||||||
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
>
|
|
||||||
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
|
||||||
{#if source.gitlabApp && !source.gitlabAppId}
|
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
|
||||||
Configuration missing
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if otherSources.length > 0 && $appSession.teamId === '0'}
|
{#if otherSources.length > 0 && $appSession.teamId === '0'}
|
||||||
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherSources as source}
|
||||||
|
<div class="p-2">
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
|
<button
|
||||||
|
disabled={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
type="submit"
|
||||||
|
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
||||||
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
>
|
||||||
|
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
||||||
|
{#if source.gitlabApp && !source.gitlabAppId}
|
||||||
|
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
||||||
|
{$t('application.configuration.configuration_missing')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
</div>
|
||||||
{#each otherSources as source}
|
<div class="title py-4">Public Repository</div>
|
||||||
<div class="p-2">
|
|
||||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
<PublicRepository />
|
||||||
<button
|
|
||||||
disabled={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
type="submit"
|
|
||||||
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
|
||||||
>
|
|
||||||
<div class="font-bold text-xl text-center truncate">{source.name}</div>
|
|
||||||
{#if source.gitlabApp && !source.gitlabAppId}
|
|
||||||
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
|
|
||||||
{$t('application.configuration.configuration_missing')}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
if (stuff?.application?.id) {
|
if (stuff?.application?.id) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
application: stuff.application
|
application: stuff.application,
|
||||||
|
settings: stuff.settings
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
export let application: any;
|
||||||
|
export let settings: any;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
@@ -60,6 +62,7 @@
|
|||||||
let previews = application.settings.previews;
|
let previews = application.settings.previews;
|
||||||
let dualCerts = application.settings.dualCerts;
|
let dualCerts = application.settings.dualCerts;
|
||||||
let autodeploy = application.settings.autodeploy;
|
let autodeploy = application.settings.autodeploy;
|
||||||
|
let isBot = application.settings.isBot;
|
||||||
|
|
||||||
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||||
let isNonWWWDomainOK = false;
|
let isNonWWWDomainOK = false;
|
||||||
@@ -99,7 +102,7 @@
|
|||||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||||
await handleSubmit();
|
await handleSubmit();
|
||||||
}
|
}
|
||||||
domainEl.focus();
|
// !isBot && domainEl.focus();
|
||||||
await getUsage();
|
await getUsage();
|
||||||
usageInterval = setInterval(async () => {
|
usageInterval = setInterval(async () => {
|
||||||
await getUsage();
|
await getUsage();
|
||||||
@@ -129,19 +132,26 @@
|
|||||||
if (name === 'autodeploy') {
|
if (name === 'autodeploy') {
|
||||||
autodeploy = !autodeploy;
|
autodeploy = !autodeploy;
|
||||||
}
|
}
|
||||||
|
if (name === 'isBot') {
|
||||||
|
if ($status.application.isRunning) return;
|
||||||
|
isBot = !isBot;
|
||||||
|
application.settings.isBot = isBot;
|
||||||
|
setLocation(application, settings);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/settings`, {
|
await post(`/applications/${id}/settings`, {
|
||||||
previews,
|
previews,
|
||||||
debug,
|
debug,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
|
isBot,
|
||||||
autodeploy,
|
autodeploy,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
projectId: application.projectId
|
projectId: application.projectId
|
||||||
});
|
});
|
||||||
return addToast({
|
return addToast({
|
||||||
message: $t('application.settings_saved'),
|
message: $t('application.settings_saved'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (name === 'debug') {
|
if (name === 'debug') {
|
||||||
debug = !debug;
|
debug = !debug;
|
||||||
@@ -155,29 +165,33 @@
|
|||||||
if (name === 'autodeploy') {
|
if (name === 'autodeploy') {
|
||||||
autodeploy = !autodeploy;
|
autodeploy = !autodeploy;
|
||||||
}
|
}
|
||||||
|
if (name === 'isBot') {
|
||||||
|
isBot = !isBot;
|
||||||
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (loading || !application.fqdn) return;
|
if (loading || (!application.fqdn && !isBot)) return;
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||||
if (application.deploymentType)
|
if (application.deploymentType)
|
||||||
application.deploymentType = application.deploymentType.toLowerCase();
|
application.deploymentType = application.deploymentType.toLowerCase();
|
||||||
await post(`/applications/${id}/check`, {
|
!isBot &&
|
||||||
fqdn: application.fqdn,
|
(await post(`/applications/${id}/check`, {
|
||||||
forceSave,
|
fqdn: application.fqdn,
|
||||||
dualCerts,
|
forceSave,
|
||||||
exposePort: application.exposePort
|
dualCerts,
|
||||||
});
|
exposePort: application.exposePort
|
||||||
|
}));
|
||||||
await post(`/applications/${id}`, { ...application });
|
await post(`/applications/${id}`, { ...application });
|
||||||
setLocation(application);
|
setLocation(application, settings);
|
||||||
$disabledButton = false;
|
$disabledButton = false;
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
addToast({
|
addToast({
|
||||||
message: 'Configuration saved.',
|
message: 'Configuration saved.',
|
||||||
type: 'success',
|
type: 'success'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -222,9 +236,9 @@
|
|||||||
try {
|
try {
|
||||||
await get(`/applications/${id}/check?domain=${domain}`);
|
await get(`/applications/${id}/check?domain=${domain}`);
|
||||||
addToast({
|
addToast({
|
||||||
message: 'DNS configuration is valid.',
|
message: 'DNS configuration is valid.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -289,29 +303,21 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||||
<div class="text-2xl font-bold">Application Usage</div>
|
<div class="text-2xl font-bold">Application Usage</div>
|
||||||
<div class="mx-auto">
|
<div class="text-center">
|
||||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
<div class="stat w-64">
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
<div class="stat-title">Used Memory / Memory Limit</div>
|
||||||
<dt class=" text-sm font-medium text-white">Used Memory / Memory Limit</dt>
|
<div class="stat-value text-xl">{usage?.MemUsage}</div>
|
||||||
<dd class="mt-1 text-xl font-semibold text-white">
|
</div>
|
||||||
{usage?.MemUsage}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
<div class="stat w-64">
|
||||||
<dt class="truncate text-sm font-medium text-white">Used CPU</dt>
|
<div class="stat-title">Used CPU</div>
|
||||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
<div class="stat-value text-xl">{usage?.CPUPerc}</div>
|
||||||
{usage?.CPUPerc}
|
</div>
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
<div class="stat w-64">
|
||||||
<dt class="truncate text-sm font-medium text-white">Network IO</dt>
|
<div class="stat-title">Network IO</div>
|
||||||
<dd class="mt-1 text-xl font-semibold text-white ">
|
<div class="stat-value text-xl">{usage?.NetIO}</div>
|
||||||
{usage?.NetIO}
|
</div>
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
@@ -324,11 +330,10 @@
|
|||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
type="submit"
|
type="submit"
|
||||||
class:bg-applications={!loading}
|
class:bg-applications={!loading}
|
||||||
class:loading={loading}
|
class:loading
|
||||||
class:bg-orange-600={forceSave}
|
class:bg-orange-600={forceSave}
|
||||||
class:hover:bg-orange-400={forceSave}
|
class:hover:bg-orange-400={forceSave}
|
||||||
disabled={loading}
|
disabled={loading}>{$t('forms.save')}</button
|
||||||
>{$t('forms.save')}</button
|
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -341,53 +346,62 @@
|
|||||||
<label for="gitSource" class="text-base font-bold text-stone-100"
|
<label for="gitSource" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.git_source')}</label
|
>{$t('application.git_source')}</label
|
||||||
>
|
>
|
||||||
<a
|
{#if isDisabled || application.settings.isPublicRepository}
|
||||||
href={!isDisabled
|
<input
|
||||||
? `/applications/${id}/configuration/source?from=/applications/${id}`
|
disabled={isDisabled || application.settings.isPublicRepository}
|
||||||
: ''}
|
|
||||||
class="no-underline"
|
|
||||||
><input
|
|
||||||
value={application.gitSource.name}
|
value={application.gitSource.name}
|
||||||
id="gitSource"
|
/>
|
||||||
disabled
|
{:else}
|
||||||
class="cursor-pointer hover:bg-coolgray-500"
|
<a
|
||||||
/></a
|
href={`/applications/${id}/configuration/source?from=/applications/${id}`}
|
||||||
>
|
class="no-underline"
|
||||||
|
><input
|
||||||
|
value={application.gitSource.name}
|
||||||
|
id="gitSource"
|
||||||
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
|
/></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="repository" class="text-base font-bold text-stone-100"
|
<label for="repository" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.git_repository')}</label
|
>{$t('application.git_repository')}</label
|
||||||
>
|
>
|
||||||
<a
|
{#if isDisabled || application.settings.isPublicRepository}
|
||||||
href={!isDisabled
|
<input
|
||||||
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
|
disabled={isDisabled || application.settings.isPublicRepository}
|
||||||
: ''}
|
|
||||||
class="no-underline"
|
|
||||||
><input
|
|
||||||
value="{application.repository}/{application.branch}"
|
value="{application.repository}/{application.branch}"
|
||||||
id="repository"
|
/>
|
||||||
disabled
|
{:else}
|
||||||
class="cursor-pointer hover:bg-coolgray-500"
|
<a
|
||||||
/></a
|
href={`/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`}
|
||||||
>
|
class="no-underline"
|
||||||
|
><input
|
||||||
|
value="{application.repository}/{application.branch}"
|
||||||
|
id="repository"
|
||||||
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
|
/></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="buildPack" class="text-base font-bold text-stone-100"
|
<label for="buildPack" class="text-base font-bold text-stone-100"
|
||||||
>{$t('application.build_pack')}</label
|
>{$t('application.build_pack')}</label
|
||||||
>
|
>
|
||||||
<a
|
{#if isDisabled}
|
||||||
href={!isDisabled
|
<input class="capitalize" disabled={isDisabled} value={application.buildPack} />
|
||||||
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
|
{:else}
|
||||||
: ''}
|
<a
|
||||||
class="no-underline "
|
href={`/applications/${id}/configuration/buildpack?from=/applications/${id}`}
|
||||||
>
|
class="no-underline "
|
||||||
<input
|
>
|
||||||
value={application.buildPack}
|
<input
|
||||||
id="buildPack"
|
value={application.buildPack}
|
||||||
disabled
|
id="buildPack"
|
||||||
class="cursor-pointer hover:bg-coolgray-500"
|
class="cursor-pointer hover:bg-coolgray-500 capitalize"
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
<label for="destination" class="text-base font-bold text-stone-100"
|
<label for="destination" class="text-base font-bold text-stone-100"
|
||||||
@@ -474,77 +488,89 @@
|
|||||||
<div class="title">{$t('application.application')}</div>
|
<div class="title">{$t('application.application')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<Setting
|
||||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
isCenter={false}
|
||||||
>{$t('application.url_fqdn')}</label
|
bind:setting={isBot}
|
||||||
>
|
on:click={() => changeSettings('isBot')}
|
||||||
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
title="Is your application a bot?"
|
||||||
<Explainer
|
description="You can deploy applications without domains. <br>You can also make them to listen on <span class='text-green-500 font-bold'>IP:EXPOSEDPORT</span> as well.<br></Setting><br>Useful to host <span class='text-green-500 font-bold'>Twitch bots, regular jobs, or anything that does not require an incoming connection.</span>"
|
||||||
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
|
disabled={$status.application.isRunning}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if !isBot}
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<div class="flex-col">
|
||||||
|
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
>{$t('application.url_fqdn')}</label
|
||||||
|
>
|
||||||
|
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
||||||
|
<Explainer
|
||||||
|
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<Explainer text={$t('application.https_explainer')} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
readonly={isDisabled}
|
||||||
|
disabled={isDisabled}
|
||||||
|
bind:this={domainEl}
|
||||||
|
name="fqdn"
|
||||||
|
id="fqdn"
|
||||||
|
required
|
||||||
|
bind:value={application.fqdn}
|
||||||
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
|
placeholder="eg: https://coollabs.io"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{#if forceSave}
|
||||||
<Explainer text={$t('application.https_explainer')} />
|
<div class="flex-col space-y-2 pt-4 text-center">
|
||||||
</div>
|
{#if isNonWWWDomainOK}
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
readonly={isDisabled}
|
|
||||||
disabled={isDisabled}
|
|
||||||
bind:this={domainEl}
|
|
||||||
name="fqdn"
|
|
||||||
id="fqdn"
|
|
||||||
required
|
|
||||||
bind:value={application.fqdn}
|
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
|
||||||
placeholder="eg: https://coollabs.io"
|
|
||||||
/>
|
|
||||||
{#if forceSave}
|
|
||||||
<div class="flex-col space-y-2 pt-4 text-center">
|
|
||||||
{#if isNonWWWDomainOK}
|
|
||||||
<button
|
|
||||||
class="bg-green-600 hover:bg-green-500"
|
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
|
||||||
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
class="bg-red-600 hover:bg-red-500"
|
|
||||||
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
|
||||||
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{#if dualCerts}
|
|
||||||
{#if isWWWDomainOK}
|
|
||||||
<button
|
<button
|
||||||
class="bg-green-600 hover:bg-green-500"
|
class="btn btn-sm bg-green-600 hover:bg-green-500"
|
||||||
on:click|preventDefault={() =>
|
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
||||||
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
|
||||||
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
|
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="bg-red-600 hover:bg-red-500"
|
class="btn btn-sm bg-red-600 hover:bg-red-500"
|
||||||
on:click|preventDefault={() =>
|
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
|
||||||
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
|
||||||
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
|
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{#if dualCerts}
|
||||||
</div>
|
{#if isWWWDomainOK}
|
||||||
{/if}
|
<button
|
||||||
|
class="btn btn-sm bg-green-600 hover:bg-green-500"
|
||||||
|
on:click|preventDefault={() =>
|
||||||
|
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
||||||
|
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="btn btn-sm bg-red-600 hover:bg-red-500"
|
||||||
|
on:click|preventDefault={() =>
|
||||||
|
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
|
||||||
|
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 items-center pb-8">
|
||||||
<div class="grid grid-cols-2 items-center pb-8">
|
<Setting
|
||||||
<Setting
|
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
disabled={$status.application.isRunning}
|
||||||
disabled={$status.application.isRunning}
|
isCenter={false}
|
||||||
isCenter={false}
|
bind:setting={dualCerts}
|
||||||
bind:setting={dualCerts}
|
title={$t('application.ssl_www_and_non_www')}
|
||||||
title={$t('application.ssl_www_and_non_www')}
|
description={$t('application.ssl_explainer')}
|
||||||
description={$t('application.ssl_explainer')}
|
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
||||||
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{#if application.buildPack === 'python'}
|
{#if application.buildPack === 'python'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
|
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
|
||||||
@@ -605,6 +631,9 @@
|
|||||||
bind:value={application.port}
|
bind:value={application.port}
|
||||||
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
||||||
/>
|
/>
|
||||||
|
<Explainer
|
||||||
|
text={'The port your application listens on.'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
@@ -707,7 +736,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if application.buildPack !== 'laravel'}
|
{#if application.buildPack !== 'laravel' && application.buildPack !== 'heroku'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||||
@@ -751,24 +780,28 @@
|
|||||||
<div class="title">{$t('application.features')}</div>
|
<div class="title">{$t('application.features')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10 pb-10">
|
<div class="px-10 pb-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
{#if !application.settings.isPublicRepository}
|
||||||
<Setting
|
<div class="grid grid-cols-2 items-center">
|
||||||
isCenter={false}
|
<Setting
|
||||||
bind:setting={autodeploy}
|
isCenter={false}
|
||||||
on:click={() => changeSettings('autodeploy')}
|
bind:setting={autodeploy}
|
||||||
title={$t('application.enable_automatic_deployment')}
|
on:click={() => changeSettings('autodeploy')}
|
||||||
description={$t('application.enable_auto_deploy_webhooks')}
|
title={$t('application.enable_automatic_deployment')}
|
||||||
/>
|
description={$t('application.enable_auto_deploy_webhooks')}
|
||||||
</div>
|
/>
|
||||||
<div class="grid grid-cols-2 items-center">
|
</div>
|
||||||
<Setting
|
{/if}
|
||||||
isCenter={false}
|
{#if !application.settings.isBot}
|
||||||
bind:setting={previews}
|
<div class="grid grid-cols-2 items-center">
|
||||||
on:click={() => changeSettings('previews')}
|
<Setting
|
||||||
title={$t('application.enable_mr_pr_previews')}
|
isCenter={false}
|
||||||
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
bind:setting={previews}
|
||||||
/>
|
on:click={() => changeSettings('previews')}
|
||||||
</div>
|
title={$t('application.enable_mr_pr_previews')}
|
||||||
|
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
|
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
|
||||||
data-tip="Follow logs"
|
data-tip="Follow logs"
|
||||||
class:text-green-500={followingBuild}
|
class:text-green-500={followingBuild}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -146,6 +146,7 @@
|
|||||||
class="tooltip tooltip-primary tooltip-top flex cursor-pointer items-center justify-center border-l-2 py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl"
|
class="tooltip tooltip-primary tooltip-top flex cursor-pointer items-center justify-center border-l-2 py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl"
|
||||||
class:bg-coolgray-400={buildId === build.id}
|
class:bg-coolgray-400={buildId === build.id}
|
||||||
class:border-red-500={build.status === 'failed'}
|
class:border-red-500={build.status === 'failed'}
|
||||||
|
class:border-orange-500={build.status === 'canceled'}
|
||||||
class:border-green-500={build.status === 'success'}
|
class:border-green-500={build.status === 'success'}
|
||||||
class:border-yellow-500={build.status === 'running'}
|
class:border-yellow-500={build.status === 'running'}
|
||||||
>
|
>
|
||||||
@@ -157,7 +158,6 @@
|
|||||||
{build.type}
|
{build.type}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1" />
|
|
||||||
|
|
||||||
<div class="w-48 text-center text-xs">
|
<div class="w-48 text-center text-xs">
|
||||||
{#if build.status === 'running'}
|
{#if build.status === 'running'}
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
{#if !noMoreBuilds}
|
{#if !noMoreBuilds}
|
||||||
{#if buildCount > 5}
|
{#if buildCount > 5}
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}
|
<button disabled={noMoreBuilds} class=" btn btn-sm w-full" on:click={loadMoreBuilds}
|
||||||
>{$t('application.build.load_more')}</button
|
>{$t('application.build.load_more')}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom"
|
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom"
|
||||||
data-tip="Follow logs"
|
data-tip="Follow logs"
|
||||||
class:text-green-500={followingLogs}
|
class:text-green-500={followingLogs}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -194,12 +194,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
<button class="btn btn-sm bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
||||||
>{$t('application.preview.redeploy')}</button
|
>{$t('application.preview.redeploy')}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
class:bg-red-600={!loading.removing}
|
class:bg-red-600={!loading.removing}
|
||||||
class:hover:bg-red-500={!loading.removing}
|
class:hover:bg-red-500={!loading.removing}
|
||||||
disabled={loading.removing}
|
disabled={loading.removing}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
const batchSecretsPairs = eachValuePair
|
const batchSecretsPairs = eachValuePair
|
||||||
.filter((secret) => !secret.startsWith('#') && secret)
|
.filter((secret) => !secret.startsWith('#') && secret)
|
||||||
.map((secret) => {
|
.map((secret) => {
|
||||||
const [name, value] = secret.split('=');
|
const [name, ...rest] = secret.split('=');
|
||||||
|
const value = rest.join('=');
|
||||||
const cleanValue = value?.replaceAll('"', '') || '';
|
const cleanValue = value?.replaceAll('"', '') || '';
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -87,9 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||||
<div class="flex justify-center py-4 text-center">
|
|
||||||
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
|
|
||||||
</div>
|
|
||||||
<table class="mx-auto border-separate text-left">
|
<table class="mx-auto border-separate text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="h-12">
|
<tr class="h-12">
|
||||||
@@ -109,4 +107,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="flex justify-center py-4 text-center">
|
||||||
|
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,24 +26,8 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { getDomain } from '$lib/common';
|
import { getDomain } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
|
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
|
||||||
|
|
||||||
import Rust from '$lib/components/svg/applications/Rust.svelte';
|
|
||||||
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
|
|
||||||
import React from '$lib/components/svg/applications/React.svelte';
|
|
||||||
import Svelte from '$lib/components/svg/applications/Svelte.svelte';
|
|
||||||
import Vuejs from '$lib/components/svg/applications/Vuejs.svelte';
|
|
||||||
import PHP from '$lib/components/svg/applications/PHP.svelte';
|
|
||||||
import Python from '$lib/components/svg/applications/Python.svelte';
|
|
||||||
import Static from '$lib/components/svg/applications/Static.svelte';
|
|
||||||
import Nestjs from '$lib/components/svg/applications/Nestjs.svelte';
|
|
||||||
import Nuxtjs from '$lib/components/svg/applications/Nuxtjs.svelte';
|
|
||||||
import Nextjs from '$lib/components/svg/applications/Nextjs.svelte';
|
|
||||||
import Gatsby from '$lib/components/svg/applications/Gatsby.svelte';
|
|
||||||
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
|
||||||
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
|
||||||
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
|
||||||
import Deno from '$lib/components/svg/applications/Deno.svelte';
|
|
||||||
import Laravel from '$lib/components/svg/applications/Laravel.svelte';
|
|
||||||
ownApplications = applications.filter((application) => {
|
ownApplications = applications.filter((application) => {
|
||||||
if (application.teams[0].id === $appSession.teamId) {
|
if (application.teams[0].id === $appSession.teamId) {
|
||||||
return application;
|
return application;
|
||||||
@@ -63,10 +47,7 @@
|
|||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl">{$t('index.applications')}</div>
|
<div class="mr-4 text-2xl">{$t('index.applications')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button on:click={newApplication} class="btn btn-square btn-sm bg-applications">
|
||||||
on:click={newApplication}
|
|
||||||
class="btn btn-square btn-sm bg-applications"
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -83,7 +64,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !applications || ownApplications.length === 0}
|
{#if !applications || ownApplications.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
||||||
@@ -96,41 +77,7 @@
|
|||||||
<a href="/applications/{application.id}" class="p-2 no-underline">
|
<a href="/applications/{application.id}" class="p-2 no-underline">
|
||||||
<div class="box-selection group relative hover:bg-green-600">
|
<div class="box-selection group relative hover:bg-green-600">
|
||||||
{#if application.buildPack}
|
{#if application.buildPack}
|
||||||
{#if application.buildPack.toLowerCase() === 'rust'}
|
<ApplicationsIcons {application} />
|
||||||
<Rust />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'node'}
|
|
||||||
<Nodejs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'react'}
|
|
||||||
<React />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'svelte'}
|
|
||||||
<Svelte />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'vuejs'}
|
|
||||||
<Vuejs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'php'}
|
|
||||||
<PHP />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'python'}
|
|
||||||
<Python />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'static'}
|
|
||||||
<Static />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'nestjs'}
|
|
||||||
<Nestjs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
|
|
||||||
<Nuxtjs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'nextjs'}
|
|
||||||
<Nextjs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'gatsby'}
|
|
||||||
<Gatsby />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'docker'}
|
|
||||||
<Docker />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'astro'}
|
|
||||||
<Astro />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'eleventy'}
|
|
||||||
<Eleventy />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'deno'}
|
|
||||||
<Deno />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'laravel'}
|
|
||||||
<Laravel />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||||
@@ -140,6 +87,9 @@
|
|||||||
{#if application.fqdn}
|
{#if application.fqdn}
|
||||||
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if application.settings.isBot}
|
||||||
|
<div class="truncate text-center">BOT</div>
|
||||||
|
{/if}
|
||||||
{#if application.destinationDocker?.name}
|
{#if application.destinationDocker?.name}
|
||||||
<div class="truncate text-center">{application.destinationDocker.name}</div>
|
<div class="truncate text-center">{application.destinationDocker.name}</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -151,7 +101,7 @@
|
|||||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
Destination Missing
|
Destination Missing
|
||||||
</div>
|
</div>
|
||||||
{:else if !application.fqdn}
|
{:else if !application.fqdn && !application.settings.isBot}
|
||||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
URL Missing
|
URL Missing
|
||||||
</div>
|
</div>
|
||||||
@@ -167,41 +117,7 @@
|
|||||||
<a href="/applications/{application.id}" class="p-2 no-underline">
|
<a href="/applications/{application.id}" class="p-2 no-underline">
|
||||||
<div class="box-selection group relative hover:bg-green-600">
|
<div class="box-selection group relative hover:bg-green-600">
|
||||||
{#if application.buildPack}
|
{#if application.buildPack}
|
||||||
{#if application.buildPack.toLowerCase() === 'rust'}
|
<ApplicationsIcons {application} />
|
||||||
<Rust />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'node'}
|
|
||||||
<Nodejs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'react'}
|
|
||||||
<React />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'svelte'}
|
|
||||||
<Svelte />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'vuejs'}
|
|
||||||
<Vuejs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'php'}
|
|
||||||
<PHP />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'python'}
|
|
||||||
<Python />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'static'}
|
|
||||||
<Static />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'nestjs'}
|
|
||||||
<Nestjs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
|
|
||||||
<Nuxtjs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'nextjs'}
|
|
||||||
<Nextjs />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'gatsby'}
|
|
||||||
<Gatsby />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'docker'}
|
|
||||||
<Docker />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'astro'}
|
|
||||||
<Astro />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'eleventy'}
|
|
||||||
<Eleventy />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'deno'}
|
|
||||||
<Deno />
|
|
||||||
{:else if application.buildPack.toLowerCase() === 'laravel'}
|
|
||||||
<Laravel />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||||
|
|||||||
@@ -58,7 +58,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function changeSettings(name: any) {
|
async function changeSettings(name: any) {
|
||||||
if (publicLoading || !$status.database.isRunning) return;
|
if (name !== 'appendOnly') {
|
||||||
|
if (publicLoading || !$status.database.isRunning) return;
|
||||||
|
}
|
||||||
publicLoading = true;
|
publicLoading = true;
|
||||||
let data = {
|
let data = {
|
||||||
isPublic,
|
isPublic,
|
||||||
@@ -92,9 +94,9 @@
|
|||||||
await post(`/databases/${id}`, { ...database, isRunning: $status.database.isRunning });
|
await post(`/databases/${id}`, { ...database, isRunning: $status.database.isRunning });
|
||||||
generateDbDetails();
|
generateDbDetails();
|
||||||
addToast({
|
addToast({
|
||||||
message: 'Configuration saved.',
|
message: 'Configuration saved.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -111,7 +113,7 @@
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
class:loading={loading}
|
class:loading
|
||||||
class:bg-databases={!loading}
|
class:bg-databases={!loading}
|
||||||
disabled={loading}>{$t('forms.save')}</button
|
disabled={loading}>{$t('forms.save')}</button
|
||||||
>
|
>
|
||||||
@@ -245,6 +247,7 @@
|
|||||||
{#if database.type === 'redis'}
|
{#if database.type === 'redis'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
loading={publicLoading}
|
||||||
bind:setting={appendOnly}
|
bind:setting={appendOnly}
|
||||||
on:click={() => changeSettings('appendOnly')}
|
on:click={() => changeSettings('appendOnly')}
|
||||||
title={$t('database.change_append_only_mode')}
|
title={$t('database.change_append_only_mode')}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@@ -45,6 +46,11 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onMount(async () => {
|
||||||
|
if (destinations.length === 1) {
|
||||||
|
await handleSubmit(destinations[0].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -55,7 +61,9 @@
|
|||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
{#if !destinations || destinations.length === 0}
|
{#if !destinations || destinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_destination')}</div>
|
<div class="pb-2 text-center font-bold">
|
||||||
|
{$t('application.configuration.no_configurable_destination')}
|
||||||
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -31,18 +31,11 @@
|
|||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
|
|
||||||
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
|
|
||||||
import CouchDB from '$lib/components/svg/databases/CouchDB.svelte';
|
|
||||||
import MongoDB from '$lib/components/svg/databases/MongoDB.svelte';
|
|
||||||
import MariaDB from '$lib/components/svg/databases/MariaDB.svelte';
|
|
||||||
import MySQL from '$lib/components/svg/databases/MySQL.svelte';
|
|
||||||
import PostgreSQL from '$lib/components/svg/databases/PostgreSQL.svelte';
|
|
||||||
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
|
||||||
async function handleSubmit(type: any) {
|
async function handleSubmit(type: any) {
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${id}/configuration/type`, { type });
|
await post(`/databases/${id}/configuration/type`, { type });
|
||||||
@@ -62,21 +55,8 @@
|
|||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<form on:submit|preventDefault={() => handleSubmit(type.name)}>
|
<form on:submit|preventDefault={() => handleSubmit(type.name)}>
|
||||||
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-purple-700">
|
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-purple-700">
|
||||||
{#if type.name === 'clickhouse'}
|
<DatabaseIcons type={type.name} isAbsolute={true} />
|
||||||
<Clickhouse isAbsolute />
|
{type.fancyName}
|
||||||
{:else if type.name === 'couchdb'}
|
|
||||||
<CouchDB isAbsolute />
|
|
||||||
{:else if type.name === 'mongodb'}
|
|
||||||
<MongoDB isAbsolute />
|
|
||||||
{:else if type.name === 'mariadb'}
|
|
||||||
<MariaDB isAbsolute />
|
|
||||||
{:else if type.name === 'mysql'}
|
|
||||||
<MySQL isAbsolute />
|
|
||||||
{:else if type.name === 'postgresql'}
|
|
||||||
<PostgreSQL isAbsolute />
|
|
||||||
{:else if type.name === 'redis'}
|
|
||||||
<Redis isAbsolute />
|
|
||||||
{/if}{type.fancyName}
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||