Merge branch 'next' into edge-db
This commit is contained in:
commit
801b9c1483
@ -15,6 +15,7 @@
|
|||||||
"settings": {},
|
"settings": {},
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
"extensions": [
|
"extensions": [
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"svelte.svelte-vscode",
|
"svelte.svelte-vscode",
|
||||||
"ardenivanov.svelte-intellisense",
|
"ardenivanov.svelte-intellisense",
|
||||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -3,6 +3,6 @@ contact_links:
|
|||||||
- name: 🤔 Questions and Help
|
- name: 🤔 Questions and Help
|
||||||
url: https://discord.com/invite/6rDM4fkymF
|
url: https://discord.com/invite/6rDM4fkymF
|
||||||
about: Reach out to us on discord or our github discussions page.
|
about: Reach out to us on discord or our github discussions page.
|
||||||
- name: 🙋♂️ service request
|
- name: 🙋♂️ Service request
|
||||||
url: https://feedback.coolify.io/
|
url: https://feedback.coolify.io/
|
||||||
about: want to request a new service? for e.g wordpress, hasura, appwrite etc...
|
about: want to request a new service? for e.g wordpress, hasura, appwrite etc...
|
||||||
|
@ -9,3 +9,5 @@ tasks:
|
|||||||
ports:
|
ports:
|
||||||
- port: 3001
|
- port: 3001
|
||||||
visibility: public
|
visibility: public
|
||||||
|
- port: 3000
|
||||||
|
visibility: public
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:18-alpine as build
|
FROM node:18-alpine3.16 as build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache curl
|
RUN apk add --no-cache curl
|
||||||
@ -9,7 +9,7 @@ RUN pnpm install
|
|||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
# Production build
|
# Production build
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine3.16
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
@ -27,8 +27,10 @@ RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl
|
|||||||
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/
|
||||||
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
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.3.4 -o ~/.docker/cli-plugins/docker-compose
|
# 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
|
||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||||
|
|
||||||
COPY --from=build /app/apps/api/build/ .
|
COPY --from=build /app/apps/api/build/ .
|
||||||
|
103
README.md
103
README.md
@ -1,6 +1,32 @@
|
|||||||
# Coolify
|
# Coolify
|
||||||
|
|
||||||
An open-source & self-hostable Heroku / Netlify alternative.
|
An open-source & self-hostable Heroku / Netlify alternative
|
||||||
|
(ARM support is in beta).
|
||||||
|
|
||||||
|
## Financial Contributors
|
||||||
|
|
||||||
|
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||||
|
|
||||||
|
### Individuals
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
|
### Organizations
|
||||||
|
|
||||||
|
Support this project with your organization. Your logo will show up here with a link to your website.
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/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/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>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Live Demo
|
## Live Demo
|
||||||
|
|
||||||
@ -12,6 +38,8 @@ https://demo.coolify.io/
|
|||||||
|
|
||||||
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!
|
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
|
## How to install
|
||||||
|
|
||||||
Installation is automated with the following command:
|
Installation is automated with the following command:
|
||||||
@ -28,60 +56,65 @@ wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./in
|
|||||||
|
|
||||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
||||||
|
|
||||||
## Features
|
---
|
||||||
|
|
||||||
ARM support is in beta!
|
## Features
|
||||||
|
|
||||||
### Git Sources
|
### Git Sources
|
||||||
|
|
||||||
You can use the following Git Sources to be auto-deployed to your Coolifyt instance! (Self-hosted versions are also supported.)
|
You can use the following Git Sources to be auto-deployed to your Coolify instance! (Self-hosted versions are also supported.)
|
||||||
|
|
||||||
- Github
|
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
|
||||||
- GitLab
|
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
|
||||||
- Bitbucket (WIP)
|
|
||||||
|
|
||||||
### Destinations
|
### Destinations
|
||||||
|
|
||||||
You can deploy your applications to the following destinations:
|
You can deploy your applications to the following destinations:
|
||||||
|
|
||||||
- Local Docker Engine
|
- Local Docker Engine
|
||||||
- Remote Docker Engine (WIP)
|
- Remote Docker Engine
|
||||||
- Kubernetes (WIP)
|
|
||||||
|
|
||||||
### Applications
|
### Applications
|
||||||
|
|
||||||
These are the predefined build packs, but with the Docker build pack, you can host anything that is hostable with a single Dockerfile.
|
Predefined build packs to cover the basic needs to deploy applications.
|
||||||
|
|
||||||
- Static sites
|
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.
|
||||||
- NodeJS
|
|
||||||
- VueJS
|
<a href="https://html5.org/">
|
||||||
- NuxtJS
|
<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>
|
||||||
- NextJS
|
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
|
||||||
- React/Preact
|
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
|
||||||
- Gatsby
|
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
|
||||||
- Svelte
|
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
|
||||||
- PHP
|
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
|
||||||
- Laravel
|
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
|
||||||
- Rust
|
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
|
||||||
- Docker
|
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
|
||||||
- Python
|
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
|
||||||
- Deno
|
<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
|
### Databases
|
||||||
|
|
||||||
One-click database is ready to be used internally or shared over the internet:
|
One-click database is ready to be used internally or shared over the internet:
|
||||||
|
|
||||||
- MongoDB
|
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
|
||||||
- MariaDB
|
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
|
||||||
- MySQL
|
<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>
|
||||||
- PostgreSQL
|
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
|
||||||
- CouchDB
|
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
|
||||||
- Redis
|
<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>
|
||||||
|
|
||||||
### One-click services
|
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!
|
||||||
|
|
||||||
You can host cool open-source services as well:
|
|
||||||
|
|
||||||
|
### 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)
|
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
||||||
- [Ghost](https://ghost.org)
|
- [Ghost](https://ghost.org)
|
||||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
||||||
@ -97,6 +130,9 @@ You can host cool open-source services as well:
|
|||||||
- [Fider](https://fider.io)
|
- [Fider](https://fider.io)
|
||||||
- [Hasura](https://hasura.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
|
## Migration from v1
|
||||||
|
|
||||||
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
||||||
@ -108,9 +144,6 @@ A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
|||||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
||||||
- Discord: [Invitation](https://discord.gg/xhBCC7eGKw)
|
- Discord: [Invitation](https://discord.gg/xhBCC7eGKw)
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
See [our contribution guide](./CONTRIBUTING.md).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
6
TODO.md
Normal file
6
TODO.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
- RDE Application DNS check not working
|
||||||
|
- Check DNS configurations for app/service/coolify with RDE and local engines
|
||||||
|
|
||||||
|
|
||||||
|
# Low
|
||||||
|
- Create previews model in Coolify DB
|
@ -16,11 +16,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/autoload": "5.1.0",
|
"@fastify/autoload": "5.1.0",
|
||||||
"@fastify/cookie": "7.1.0",
|
"@fastify/cookie": "7.3.1",
|
||||||
"@fastify/cors": "8.0.0",
|
"@fastify/cors": "8.0.0",
|
||||||
"@fastify/env": "4.0.0",
|
"@fastify/env": "4.0.0",
|
||||||
"@fastify/jwt": "6.3.1",
|
"@fastify/jwt": "6.3.1",
|
||||||
"@fastify/static": "6.4.0",
|
"@fastify/static": "6.4.1",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@prisma/client": "3.15.2",
|
"@prisma/client": "3.15.2",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
@ -29,30 +29,33 @@
|
|||||||
"cabin": "9.1.2",
|
"cabin": "9.1.2",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.3",
|
"dayjs": "1.11.4",
|
||||||
"dockerode": "3.3.2",
|
"dockerode": "3.3.2",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"fastify": "4.2.1",
|
"fastify": "4.3.0",
|
||||||
"fastify-plugin": "4.0.0",
|
"fastify-plugin": "4.0.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.1.2",
|
"get-port": "6.1.2",
|
||||||
"got": "12.1.0",
|
"got": "12.2.0",
|
||||||
"is-ip": "4.0.0",
|
"is-ip": "5.0.0",
|
||||||
|
"is-port-reachable": "4.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"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.2.0",
|
"p-queue": "7.2.0",
|
||||||
|
"public-ip": "6.0.1",
|
||||||
|
"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.0.4",
|
"@types/node": "18.6.1",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.30.6",
|
"@typescript-eslint/eslint-plugin": "5.31.0",
|
||||||
"@typescript-eslint/parser": "5.30.6",
|
"@typescript-eslint/parser": "5.31.0",
|
||||||
"esbuild": "0.14.49",
|
"esbuild": "0.14.50",
|
||||||
"eslint": "8.19.0",
|
"eslint": "8.20.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,20 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Moodle" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"defaultUsername" TEXT NOT NULL,
|
||||||
|
"defaultPassword" TEXT NOT NULL,
|
||||||
|
"defaultEmail" TEXT NOT NULL,
|
||||||
|
"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 "Moodle_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Moodle_serviceId_key" ON "Moodle"("serviceId");
|
@ -0,0 +1,21 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_DestinationDocker" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"network" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"engine" TEXT,
|
||||||
|
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"remoteIpAddress" TEXT,
|
||||||
|
"remoteUser" TEXT,
|
||||||
|
"remotePort" INTEGER,
|
||||||
|
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "updatedAt" FROM "DestinationDocker";
|
||||||
|
DROP TABLE "DestinationDocker";
|
||||||
|
ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
|
||||||
|
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
@ -0,0 +1,33 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SshKey" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"privateKey" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_DestinationDocker" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"network" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"engine" TEXT,
|
||||||
|
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"remoteIpAddress" TEXT,
|
||||||
|
"remoteUser" TEXT,
|
||||||
|
"remotePort" INTEGER,
|
||||||
|
"remoteVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"sshKeyId" TEXT,
|
||||||
|
CONSTRAINT "DestinationDocker_sshKeyId_fkey" FOREIGN KEY ("sshKeyId") REFERENCES "SshKey" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt" FROM "DestinationDocker";
|
||||||
|
DROP TABLE "DestinationDocker";
|
||||||
|
ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
|
||||||
|
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "ipv4" TEXT;
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "ipv6" TEXT;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "arch" TEXT;
|
@ -0,0 +1,16 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_SshKey" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"privateKey" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"teamId" TEXT,
|
||||||
|
CONSTRAINT "SshKey_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_SshKey" ("createdAt", "id", "name", "privateKey", "updatedAt") SELECT "createdAt", "id", "name", "privateKey", "updatedAt" FROM "SshKey";
|
||||||
|
DROP TABLE "SshKey";
|
||||||
|
ALTER TABLE "new_SshKey" RENAME TO "SshKey";
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
@ -23,6 +23,9 @@ model Setting {
|
|||||||
isTraefikUsed Boolean @default(true)
|
isTraefikUsed Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
ipv4 String?
|
||||||
|
ipv6 String?
|
||||||
|
arch String?
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
@ -30,39 +33,40 @@ model User {
|
|||||||
email String @unique
|
email String @unique
|
||||||
type String
|
type String
|
||||||
password String?
|
password String?
|
||||||
teams Team[]
|
|
||||||
permission Permission[]
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
permission Permission[]
|
||||||
|
teams Team[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Permission {
|
model Permission {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
user User @relation(fields: [userId], references: [id])
|
|
||||||
userId String
|
userId String
|
||||||
team Team @relation(fields: [teamId], references: [id])
|
|
||||||
teamId String
|
teamId String
|
||||||
permission String
|
permission String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
team Team @relation(fields: [teamId], references: [id])
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Team {
|
model Team {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
users User[]
|
|
||||||
name String?
|
name String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
databaseId String?
|
||||||
|
serviceId String?
|
||||||
|
permissions Permission[]
|
||||||
|
sshKey SshKey[]
|
||||||
applications Application[]
|
applications Application[]
|
||||||
|
database Database[]
|
||||||
|
destinationDocker DestinationDocker[]
|
||||||
gitSources GitSource[]
|
gitSources GitSource[]
|
||||||
gitHubApps GithubApp[]
|
gitHubApps GithubApp[]
|
||||||
gitLabApps GitlabApp[]
|
gitLabApps GitlabApp[]
|
||||||
destinationDocker DestinationDocker[]
|
service Service[]
|
||||||
permissions Permission[]
|
users User[]
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
database Database[] @relation(references: [id])
|
|
||||||
databaseId String?
|
|
||||||
service Service[] @relation(references: [id])
|
|
||||||
serviceId String?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model TeamInvitation {
|
model TeamInvitation {
|
||||||
@ -101,21 +105,20 @@ model Application {
|
|||||||
denoOptions String?
|
denoOptions String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
settings ApplicationSettings?
|
|
||||||
teams Team[]
|
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
|
||||||
gitSourceId String?
|
gitSourceId String?
|
||||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
|
||||||
secrets Secret[]
|
|
||||||
persistentStorage ApplicationPersistentStorage[]
|
|
||||||
baseImage String?
|
baseImage String?
|
||||||
baseBuildImage String?
|
baseBuildImage String?
|
||||||
|
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||||
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
persistentStorage ApplicationPersistentStorage[]
|
||||||
|
settings ApplicationSettings?
|
||||||
|
secrets Secret[]
|
||||||
|
teams Team[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model ApplicationSettings {
|
model ApplicationSettings {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
|
||||||
applicationId String @unique
|
applicationId String @unique
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
debug Boolean @default(false)
|
debug Boolean @default(false)
|
||||||
@ -123,26 +126,27 @@ model ApplicationSettings {
|
|||||||
autodeploy Boolean @default(true)
|
autodeploy Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
|
||||||
applicationId String
|
applicationId String
|
||||||
path String
|
path String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
|
|
||||||
@@unique([applicationId, path])
|
@@unique([applicationId, path])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ServicePersistentStorage {
|
model ServicePersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
serviceId String
|
serviceId String
|
||||||
path String
|
path String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
|
||||||
@@unique([serviceId, path])
|
@@unique([serviceId, path])
|
||||||
}
|
}
|
||||||
@ -155,8 +159,8 @@ model Secret {
|
|||||||
isBuildSecret Boolean @default(false)
|
isBuildSecret Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
|
||||||
applicationId String
|
applicationId String
|
||||||
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
|
|
||||||
@@unique([name, applicationId, isPRMRSecret])
|
@@unique([name, applicationId, isPRMRSecret])
|
||||||
}
|
}
|
||||||
@ -167,8 +171,8 @@ model ServiceSecret {
|
|||||||
value String
|
value String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
serviceId String
|
serviceId String
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
|
||||||
@@unique([name, serviceId])
|
@@unique([name, serviceId])
|
||||||
}
|
}
|
||||||
@ -200,21 +204,37 @@ model DestinationDocker {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
network String @unique
|
network String @unique
|
||||||
name String
|
name String
|
||||||
engine String
|
engine String?
|
||||||
remoteEngine Boolean @default(false)
|
remoteEngine Boolean @default(false)
|
||||||
|
remoteIpAddress String?
|
||||||
|
remoteUser String?
|
||||||
|
remotePort Int?
|
||||||
|
remoteVerified Boolean @default(false)
|
||||||
isCoolifyProxyUsed Boolean? @default(false)
|
isCoolifyProxyUsed Boolean? @default(false)
|
||||||
teams Team[]
|
|
||||||
application Application[]
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
sshKeyId String?
|
||||||
|
sshKey SshKey? @relation(fields: [sshKeyId], references: [id])
|
||||||
|
application Application[]
|
||||||
database Database[]
|
database Database[]
|
||||||
service Service[]
|
service Service[]
|
||||||
|
teams Team[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model SshKey {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
privateKey String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
teamId String?
|
||||||
|
team Team? @relation(fields: [teamId], references: [id])
|
||||||
|
destinationDocker DestinationDocker[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model GitSource {
|
model GitSource {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
teams Team[]
|
|
||||||
type String?
|
type String?
|
||||||
apiUrl String?
|
apiUrl String?
|
||||||
htmlUrl String?
|
htmlUrl String?
|
||||||
@ -223,16 +243,16 @@ model GitSource {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
githubAppId String? @unique
|
githubAppId String? @unique
|
||||||
githubApp GithubApp? @relation(fields: [githubAppId], references: [id])
|
|
||||||
application Application[]
|
|
||||||
gitlabAppId String? @unique
|
gitlabAppId String? @unique
|
||||||
gitlabApp GitlabApp? @relation(fields: [gitlabAppId], references: [id])
|
gitlabApp GitlabApp? @relation(fields: [gitlabAppId], references: [id])
|
||||||
|
githubApp GithubApp? @relation(fields: [githubAppId], references: [id])
|
||||||
|
application Application[]
|
||||||
|
teams Team[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model GithubApp {
|
model GithubApp {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String? @unique
|
name String? @unique
|
||||||
teams Team[]
|
|
||||||
appId Int?
|
appId Int?
|
||||||
installationId Int?
|
installationId Int?
|
||||||
clientId String?
|
clientId String?
|
||||||
@ -242,13 +262,13 @@ model GithubApp {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
gitSource GitSource?
|
gitSource GitSource?
|
||||||
|
teams Team[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model GitlabApp {
|
model GitlabApp {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
oauthId Int @unique
|
oauthId Int @unique
|
||||||
groupName String? @unique
|
groupName String? @unique
|
||||||
teams Team[]
|
|
||||||
deployKeyId Int?
|
deployKeyId Int?
|
||||||
privateSshKey String?
|
privateSshKey String?
|
||||||
publicSshKey String?
|
publicSshKey String?
|
||||||
@ -258,6 +278,7 @@ model GitlabApp {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
gitSource GitSource?
|
gitSource GitSource?
|
||||||
|
teams Team[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Database {
|
model Database {
|
||||||
@ -271,22 +292,22 @@ model Database {
|
|||||||
dbUserPassword String?
|
dbUserPassword String?
|
||||||
rootUser String?
|
rootUser String?
|
||||||
rootUserPassword String?
|
rootUserPassword String?
|
||||||
settings DatabaseSettings?
|
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
teams Team[]
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
settings DatabaseSettings?
|
||||||
|
teams Team[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model DatabaseSettings {
|
model DatabaseSettings {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
database Database @relation(fields: [databaseId], references: [id])
|
|
||||||
databaseId String @unique
|
databaseId String @unique
|
||||||
isPublic Boolean @default(false)
|
isPublic Boolean @default(false)
|
||||||
appendOnly Boolean @default(true)
|
appendOnly Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
database Database @relation(fields: [databaseId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
@ -297,23 +318,23 @@ model Service {
|
|||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
teams Team[]
|
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
fider Fider?
|
||||||
|
ghost Ghost?
|
||||||
|
hasura Hasura?
|
||||||
|
meiliSearch MeiliSearch?
|
||||||
minio Minio?
|
minio Minio?
|
||||||
|
moodle Moodle?
|
||||||
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
|
persistentStorage ServicePersistentStorage[]
|
||||||
|
serviceSecret ServiceSecret[]
|
||||||
|
umami Umami?
|
||||||
vscodeserver Vscodeserver?
|
vscodeserver Vscodeserver?
|
||||||
wordpress Wordpress?
|
wordpress Wordpress?
|
||||||
ghost Ghost?
|
teams Team[]
|
||||||
serviceSecret ServiceSecret[]
|
|
||||||
meiliSearch MeiliSearch?
|
|
||||||
persistentStorage ServicePersistentStorage[]
|
|
||||||
umami Umami?
|
|
||||||
hasura Hasura?
|
|
||||||
fider Fider?
|
|
||||||
moodle Moodle?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@ -328,9 +349,9 @@ model PlausibleAnalytics {
|
|||||||
secretKeyBase String?
|
secretKeyBase String?
|
||||||
scriptName String @default("plausible.js")
|
scriptName String @default("plausible.js")
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Minio {
|
model Minio {
|
||||||
@ -340,18 +361,18 @@ model Minio {
|
|||||||
publicPort Int?
|
publicPort Int?
|
||||||
apiFqdn String?
|
apiFqdn String?
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Vscodeserver {
|
model Vscodeserver {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
password String
|
password String
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Wordpress {
|
model Wordpress {
|
||||||
@ -374,9 +395,9 @@ model Wordpress {
|
|||||||
ftpHostKey String?
|
ftpHostKey String?
|
||||||
ftpHostKeyPrivate String?
|
ftpHostKeyPrivate String?
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Ghost {
|
model Ghost {
|
||||||
@ -390,18 +411,18 @@ model Ghost {
|
|||||||
mariadbDatabase String?
|
mariadbDatabase String?
|
||||||
mariadbPublicPort Int?
|
mariadbPublicPort Int?
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model MeiliSearch {
|
model MeiliSearch {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
masterKey String
|
masterKey String
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Umami {
|
model Umami {
|
||||||
@ -413,9 +434,9 @@ model Umami {
|
|||||||
postgresqlPublicPort Int?
|
postgresqlPublicPort Int?
|
||||||
umamiAdminPassword String
|
umamiAdminPassword String
|
||||||
hashSalt String
|
hashSalt String
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Hasura {
|
model Hasura {
|
||||||
@ -426,9 +447,9 @@ model Hasura {
|
|||||||
postgresqlDatabase String
|
postgresqlDatabase String
|
||||||
postgresqlPublicPort Int?
|
postgresqlPublicPort Int?
|
||||||
graphQLAdminPassword String
|
graphQLAdminPassword String
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Fider {
|
model Fider {
|
||||||
@ -448,9 +469,9 @@ model Fider {
|
|||||||
emailSmtpUser String?
|
emailSmtpUser String?
|
||||||
emailSmtpPassword String?
|
emailSmtpPassword String?
|
||||||
emailSmtpEnableStartTls Boolean @default(false)
|
emailSmtpEnableStartTls Boolean @default(false)
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Moodle {
|
model Moodle {
|
||||||
@ -465,7 +486,7 @@ model Moodle {
|
|||||||
mariadbRootUserPassword String
|
mariadbRootUserPassword String
|
||||||
mariadbDatabase String
|
mariadbDatabase String
|
||||||
mariadbPublicPort Int?
|
mariadbPublicPort Int?
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ async function main() {
|
|||||||
data: {
|
data: {
|
||||||
isRegistrationEnabled: true,
|
isRegistrationEnabled: true,
|
||||||
proxyPassword: encrypt(generatePassword()),
|
proxyPassword: encrypt(generatePassword()),
|
||||||
proxyUser: cuid()
|
proxyUser: cuid(),
|
||||||
|
arch: process.arch
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,7 +5,7 @@ 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, prisma } from './lib/common';
|
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
import { scheduler } from './lib/scheduler';
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
@ -101,10 +101,10 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
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('deployApplication');
|
||||||
await scheduler.start('cleanupStorage');
|
await scheduler.start('cleanupStorage');
|
||||||
await scheduler.start('checkProxies')
|
await scheduler.start('checkProxies');
|
||||||
|
|
||||||
// Check if no build is running
|
// Check if no build is running
|
||||||
|
|
||||||
@ -130,12 +130,37 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
|
|||||||
if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication');
|
if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await getArch();
|
||||||
|
await getIPAddress();
|
||||||
});
|
});
|
||||||
|
async function getIPAddress() {
|
||||||
|
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
||||||
|
try {
|
||||||
|
const settings = await listSettings();
|
||||||
|
if (!settings.ipv4) {
|
||||||
|
const ipv4 = await publicIpv4({ timeout: 2000 })
|
||||||
|
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settings.ipv6) {
|
||||||
|
const ipv6 = await publicIpv6({ timeout: 2000 })
|
||||||
|
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) { }
|
||||||
|
}
|
||||||
async function initServer() {
|
async function initServer() {
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`docker network create --attachable coolify`);
|
await asyncExecShell(`docker network create --attachable coolify`);
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
|
async function getArch() {
|
||||||
|
try {
|
||||||
|
const settings = await prisma.setting.findFirst({})
|
||||||
|
if (settings && !settings.arch) {
|
||||||
|
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
import { parentPort } from 'node:worker_threads';
|
import { parentPort } from 'node:worker_threads';
|
||||||
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, asyncExecShell } from '../lib/common';
|
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd } from '../lib/common';
|
||||||
import { checkContainer, getEngine } from '../lib/docker';
|
import { checkContainer } from '../lib/docker';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
// Coolify Proxy
|
// Coolify Proxy local
|
||||||
const engine = '/var/run/docker.sock';
|
const engine = '/var/run/docker.sock';
|
||||||
const localDocker = await prisma.destinationDocker.findFirst({
|
const localDocker = await prisma.destinationDocker.findFirst({
|
||||||
where: { engine, network: 'coolify' }
|
where: { engine, network: 'coolify' }
|
||||||
});
|
});
|
||||||
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||||
// Remove HAProxy
|
// Remove HAProxy
|
||||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
const found = await checkContainer({ dockerId: localDocker.id, container: 'coolify-haproxy' });
|
||||||
const host = getEngine(engine);
|
|
||||||
if (found) {
|
if (found) {
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST="${host}" docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`
|
dockerId: localDocker.id,
|
||||||
);
|
command: `docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await startTraefikProxy(engine);
|
await startTraefikProxy(localDocker.id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,12 +32,14 @@ import { checkContainer, getEngine } from '../lib/docker';
|
|||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
const { privatePort } = generateDatabaseConfiguration(database);
|
const { privatePort } = generateDatabaseConfiguration(database);
|
||||||
// Remove HAProxy
|
// Remove HAProxy
|
||||||
const found = await checkContainer(engine, `haproxy-for-${publicPort}`);
|
const found = await checkContainer({
|
||||||
const host = getEngine(engine);
|
dockerId: localDocker.id, container: `haproxy-for-${publicPort}`
|
||||||
|
});
|
||||||
if (found) {
|
if (found) {
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST="${host}" docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}`
|
dockerId: localDocker.id,
|
||||||
);
|
command: `docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
|
|
||||||
@ -52,12 +54,12 @@ import { checkContainer, getEngine } from '../lib/docker';
|
|||||||
const { destinationDockerId, destinationDocker, id } = service;
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
// Remove HAProxy
|
// Remove HAProxy
|
||||||
const found = await checkContainer(engine, `haproxy-for-${ftpPublicPort}`);
|
const found = await checkContainer({ dockerId: localDocker.id, container: `haproxy-for-${ftpPublicPort}` });
|
||||||
const host = getEngine(engine);
|
|
||||||
if (found) {
|
if (found) {
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST="${host}" docker stop -t 0 haproxy-for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort} `
|
dockerId: localDocker.id,
|
||||||
);
|
command: `docker stop -t 0 haproxy -for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort}`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||||
}
|
}
|
||||||
@ -73,12 +75,12 @@ import { checkContainer, getEngine } from '../lib/docker';
|
|||||||
const { destinationDockerId, destinationDocker, id } = service;
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
// Remove HAProxy
|
// Remove HAProxy
|
||||||
const found = await checkContainer(engine, `${id}-${publicPort}`);
|
const found = await checkContainer({ dockerId: localDocker.id, container: `${id}-${publicPort}` });
|
||||||
const host = getEngine(engine);
|
|
||||||
if (found) {
|
if (found) {
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST="${host}" docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort}`
|
dockerId: localDocker.id,
|
||||||
);
|
command: `docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort} `
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { parentPort } from 'node:worker_threads';
|
import { parentPort } from 'node:worker_threads';
|
||||||
import { asyncExecShell, cleanupDockerStorage, isDev, prisma, version } from '../lib/common';
|
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, version } from '../lib/common';
|
||||||
import { getEngine } from '../lib/docker';
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
const engines = [...new Set(destinationDockers.map(({ engine }) => engine))];
|
let enginesDone = new Set()
|
||||||
for (const engine of engines) {
|
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;
|
let lowDiskSpace = false;
|
||||||
const host = getEngine(engine);
|
|
||||||
try {
|
try {
|
||||||
let stdout = null
|
let stdout = null
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const output = await asyncExecShell(
|
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
||||||
`DOCKER_HOST=${host} docker exec coolify sh -c 'df -kPT /'`
|
|
||||||
);
|
|
||||||
stdout = output.stdout;
|
stdout = output.stdout;
|
||||||
} else {
|
} else {
|
||||||
const output = await asyncExecShell(
|
const output = await asyncExecShell(
|
||||||
@ -53,7 +53,7 @@ import { getEngine } from '../lib/docker';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
await cleanupDockerStorage(host, lowDiskSpace, false)
|
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||||
}
|
}
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
} else process.exit(0);
|
} else process.exit(0);
|
||||||
|
@ -4,8 +4,7 @@ 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 { asyncExecShell, createDirectories, decrypt, getDomain, prisma } from '../lib/common';
|
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
|
||||||
import { dockerInstance, getEngine } from '../lib/docker';
|
|
||||||
import * as importers from '../lib/importers';
|
import * as importers from '../lib/importers';
|
||||||
import * as buildpacks from '../lib/buildPacks';
|
import * as buildpacks from '../lib/buildPacks';
|
||||||
|
|
||||||
@ -104,9 +103,6 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
destinationType = 'docker';
|
destinationType = 'docker';
|
||||||
}
|
}
|
||||||
if (destinationType === 'docker') {
|
if (destinationType === 'docker') {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
|
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||||
const configuration = await setDefaultConfiguration(message);
|
const configuration = await setDefaultConfiguration(message);
|
||||||
@ -185,18 +181,23 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} else {
|
} else {
|
||||||
deployNeeded = true;
|
deployNeeded = true;
|
||||||
}
|
}
|
||||||
const image = await docker.engine.getImage(`${applicationId}:${tag}`);
|
|
||||||
let imageFound = false;
|
let imageFound = false;
|
||||||
try {
|
try {
|
||||||
await image.inspect();
|
await executeDockerCmd({
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker image inspect ${applicationId}:${tag}`
|
||||||
|
})
|
||||||
imageFound = true;
|
imageFound = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
if (!imageFound || deployNeeded) {
|
// if (!imageFound || deployNeeded) {
|
||||||
|
if (true) {
|
||||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||||
if (buildpacks[buildPack])
|
if (buildpacks[buildPack])
|
||||||
await buildpacks[buildPack]({
|
await buildpacks[buildPack]({
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
domain,
|
domain,
|
||||||
@ -212,7 +213,6 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
commit,
|
commit,
|
||||||
tag,
|
tag,
|
||||||
workdir,
|
workdir,
|
||||||
docker,
|
|
||||||
port: exposePort ? `${exposePort}:${port}` : port,
|
port: exposePort ? `${exposePort}:${port}` : port,
|
||||||
installCommand,
|
installCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
@ -238,8 +238,8 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${imageId}`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker rm ${imageId}`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
container_name: imageId,
|
container_name: imageId,
|
||||||
volumes,
|
volumes,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
networks: [docker.network],
|
networks: [destinationDocker.network],
|
||||||
labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
@ -318,16 +318,14 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
[docker.network]: {
|
[destinationDocker.network]: {
|
||||||
external: true
|
external: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
volumes: Object.assign({}, ...composeVolumes)
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
};
|
};
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
await asyncExecShell(
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
|
||||||
);
|
|
||||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { asyncExecShell, base64Encode, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
|
import { base64Encode, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
|
||||||
import { scheduler } from "../scheduler";
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { day } from "../dayjs";
|
import { day } from "../dayjs";
|
||||||
|
|
||||||
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
||||||
const nodeBased = [
|
const nodeBased = [
|
||||||
'react',
|
'react',
|
||||||
@ -511,8 +511,8 @@ export async function buildImage({
|
|||||||
applicationId,
|
applicationId,
|
||||||
tag,
|
tag,
|
||||||
workdir,
|
workdir,
|
||||||
docker,
|
|
||||||
buildId,
|
buildId,
|
||||||
|
dockerId,
|
||||||
isCache = false,
|
isCache = false,
|
||||||
debug = false,
|
debug = false,
|
||||||
dockerFileLocation = '/Dockerfile'
|
dockerFileLocation = '/Dockerfile'
|
||||||
@ -522,6 +522,9 @@ 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.`,
|
||||||
@ -529,16 +532,61 @@ export async function buildImage({
|
|||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||||
const stream = await docker.engine.buildImage(
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
{ src: ['.'], context: workdir },
|
const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
||||||
{
|
if (debug) {
|
||||||
dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
const array = stderr.split('\n')
|
||||||
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
for (const line of array) {
|
||||||
|
if (line !== '\n') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
await streamEvents({ stream, docker, buildId, applicationId, debug });
|
|
||||||
await saveBuildLog({ line: `Building image successful!`, 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) {
|
||||||
|
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
||||||
|
} else {
|
||||||
|
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function streamEvents({ stream, docker, buildId, applicationId, debug }) {
|
export async function streamEvents({ stream, docker, buildId, applicationId, debug }) {
|
||||||
@ -617,18 +665,16 @@ export function makeLabelForStandaloneApplication({
|
|||||||
|
|
||||||
export async function buildCacheImageWithNode(data, imageForBuild) {
|
export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||||
const {
|
const {
|
||||||
applicationId,
|
|
||||||
tag,
|
|
||||||
workdir,
|
workdir,
|
||||||
docker,
|
|
||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
installCommand,
|
installCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
debug,
|
|
||||||
secrets,
|
secrets,
|
||||||
pullmergeRequestId
|
pullmergeRequestId
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
|
|
||||||
const isPnpm = checkPnpm(installCommand, buildCommand);
|
const isPnpm = checkPnpm(installCommand, buildCommand);
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||||
@ -659,11 +705,12 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||||
Dockerfile.push(`RUN ${buildCommand}`);
|
Dockerfile.push(`RUN ${buildCommand}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
await buildImage({ ...data, isCache: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
||||||
const { applicationId, tag, workdir, docker, buildId, debug, secrets, pullmergeRequestId } = data;
|
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
||||||
|
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
@ -687,22 +734,16 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
|
|||||||
Dockerfile.push(`COPY resources /app/resources`);
|
Dockerfile.push(`COPY resources /app/resources`);
|
||||||
Dockerfile.push(`RUN yarn install && yarn production`);
|
Dockerfile.push(`RUN yarn install && yarn production`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
await buildImage({ ...data, isCache: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildCacheImageWithCargo(data, imageForBuild) {
|
export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||||
const {
|
const {
|
||||||
applicationId,
|
applicationId,
|
||||||
tag,
|
|
||||||
workdir,
|
workdir,
|
||||||
docker,
|
|
||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
debug,
|
|
||||||
secrets
|
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
@ -717,5 +758,5 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
|||||||
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
||||||
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
||||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||||
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
|
await buildImage({ ...data, isCache: true });
|
||||||
}
|
}
|
@ -1,18 +1,18 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
export default async function ({
|
export default async function (data) {
|
||||||
applicationId,
|
let {
|
||||||
debug,
|
applicationId,
|
||||||
tag,
|
debug,
|
||||||
workdir,
|
tag,
|
||||||
docker,
|
workdir,
|
||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets,
|
secrets,
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
dockerFileLocation
|
dockerFileLocation
|
||||||
}) {
|
} = data
|
||||||
try {
|
try {
|
||||||
const file = `${workdir}${dockerFileLocation}`;
|
const file = `${workdir}${dockerFileLocation}`;
|
||||||
let dockerFileOut = `${workdir}`;
|
let dockerFileOut = `${workdir}`;
|
||||||
@ -45,7 +45,7 @@ export default async function ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
|
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||||
await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation });
|
await buildImage(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,43 @@
|
|||||||
import { asyncExecShell } from './common';
|
import { executeDockerCmd } from './common';
|
||||||
import Dockerode from 'dockerode';
|
|
||||||
export function getEngine(engine: string): string {
|
|
||||||
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
|
|
||||||
}
|
|
||||||
export function dockerInstance({ destinationDocker }): { engine: Dockerode; network: string } {
|
|
||||||
return {
|
|
||||||
engine: new Dockerode({
|
|
||||||
socketPath: destinationDocker.engine
|
|
||||||
}),
|
|
||||||
network: destinationDocker.network
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkContainer(engine: string, container: string, remove = false): Promise<boolean> {
|
export function formatLabelsOnDocker(data) {
|
||||||
const host = getEngine(engine);
|
return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => {
|
||||||
|
const labels = container.Labels.split(',')
|
||||||
|
let jsonLabels = {}
|
||||||
|
labels.forEach(l => {
|
||||||
|
const name = l.split('=')[0]
|
||||||
|
const value = l.split('=')[1]
|
||||||
|
jsonLabels = { ...jsonLabels, ...{ [name]: value } }
|
||||||
|
})
|
||||||
|
container.Labels = jsonLabels;
|
||||||
|
return container
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<boolean> {
|
||||||
let containerFound = false;
|
let containerFound = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { stdout } = await asyncExecShell(
|
const { stdout } = await executeDockerCmd({
|
||||||
`DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}`
|
dockerId,
|
||||||
);
|
command:
|
||||||
|
`docker inspect --format '{{json .State}}' ${container}`
|
||||||
|
});
|
||||||
|
|
||||||
const parsedStdout = JSON.parse(stdout);
|
const parsedStdout = JSON.parse(stdout);
|
||||||
const status = parsedStdout.Status;
|
const status = parsedStdout.Status;
|
||||||
const isRunning = status === 'running';
|
const isRunning = status === 'running';
|
||||||
if (status === 'created') {
|
if (status === 'created') {
|
||||||
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
await executeDockerCmd({
|
||||||
|
dockerId,
|
||||||
|
command:
|
||||||
|
`docker rm ${container}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (remove && status === 'exited') {
|
if (remove && status === 'exited') {
|
||||||
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
await executeDockerCmd({
|
||||||
|
dockerId,
|
||||||
|
command:
|
||||||
|
`docker rm ${container}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
containerFound = true;
|
containerFound = true;
|
||||||
@ -38,13 +48,10 @@ export async function checkContainer(engine: string, container: string, remove =
|
|||||||
return containerFound;
|
return containerFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
|
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
|
||||||
let isExited = false;
|
let isExited = false;
|
||||||
const host = getEngine(engine);
|
|
||||||
try {
|
try {
|
||||||
const { stdout } = await asyncExecShell(
|
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
|
||||||
`DOCKER_HOST="${host}" docker inspect -f '{{.State.Status}}' ${containerName}`
|
|
||||||
);
|
|
||||||
if (stdout.trim() === 'exited') {
|
if (stdout.trim() === 'exited') {
|
||||||
isExited = true;
|
isExited = true;
|
||||||
}
|
}
|
||||||
@ -57,19 +64,17 @@ export async function isContainerExited(engine: string, containerName: string):
|
|||||||
|
|
||||||
export async function removeContainer({
|
export async function removeContainer({
|
||||||
id,
|
id,
|
||||||
engine
|
dockerId
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
engine: string;
|
dockerId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const host = getEngine(engine);
|
|
||||||
try {
|
try {
|
||||||
const { stdout } = await asyncExecShell(
|
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||||
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
|
|
||||||
);
|
|
||||||
if (JSON.parse(stdout).Running) {
|
if (JSON.parse(stdout).Running) {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${id}`);
|
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker rm ${id}`);
|
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -5,12 +5,12 @@ 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 { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||||
import { checkContainer, dockerInstance, getEngine, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||||
import { scheduler } from '../../../../lib/scheduler';
|
import { scheduler } from '../../../../lib/scheduler';
|
||||||
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication } from './types';
|
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
export async function listApplications(request: FastifyRequest) {
|
export async function listApplications(request: FastifyRequest) {
|
||||||
@ -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 }
|
include: { teams: true, destinationDocker: true }
|
||||||
});
|
});
|
||||||
const settings = await prisma.setting.findFirst()
|
const settings = await prisma.setting.findFirst()
|
||||||
return {
|
return {
|
||||||
@ -57,7 +57,28 @@ export async function getImages(request: FastifyRequest<GetImages>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return { baseImage, baseBuildImage, baseBuildImages, baseImages, publishDirectory, port }
|
return { baseBuildImage, baseBuildImages, publishDirectory, port }
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { teamId } = request.user
|
||||||
|
let isRunning = false;
|
||||||
|
let isExited = false;
|
||||||
|
|
||||||
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
if (application?.destinationDockerId) {
|
||||||
|
isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
|
||||||
|
isExited = await isContainerExited(application.destinationDocker.id, id);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isQueueActive: scheduler.workers.has('deployApplication'),
|
||||||
|
isRunning,
|
||||||
|
isExited,
|
||||||
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
@ -68,17 +89,9 @@ export async function getApplication(request: FastifyRequest<OnlyId>) {
|
|||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
let isRunning = false;
|
|
||||||
let isExited = false;
|
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId && application.destinationDocker?.engine) {
|
|
||||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
|
||||||
isExited = await isContainerExited(application.destinationDocker.engine, id);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
isQueueActive: scheduler.workers.has('deployApplication'),
|
|
||||||
isRunning,
|
|
||||||
isExited,
|
|
||||||
application,
|
application,
|
||||||
appId
|
appId
|
||||||
};
|
};
|
||||||
@ -279,16 +292,35 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function stopPreviewApplication(request: FastifyRequest<StopPreviewApplication>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { pullmergeRequestId } = request.body
|
||||||
|
const { teamId } = request.user
|
||||||
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
if (application?.destinationDockerId) {
|
||||||
|
const container = `${id}-${pullmergeRequestId}`
|
||||||
|
const { id: dockerId } = application.destinationDocker;
|
||||||
|
const found = await checkContainer({ dockerId, container });
|
||||||
|
if (found) {
|
||||||
|
await removeContainer({ id: container, dockerId: application.destinationDocker.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reply.code(201).send();
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId && application.destinationDocker?.engine) {
|
if (application?.destinationDockerId) {
|
||||||
const { engine } = application.destinationDocker;
|
const { id: dockerId } = application.destinationDocker;
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
@ -304,17 +336,17 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (application?.destinationDockerId && application.destinationDocker?.engine && application.destinationDocker?.network) {
|
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
||||||
const host = getEngine(application.destinationDocker.engine);
|
const { stdout: containers } = await executeDockerCmd({
|
||||||
const { stdout: containers } = await asyncExecShell(
|
dockerId: application.destinationDocker.id,
|
||||||
`DOCKER_HOST=${host} docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||||
);
|
})
|
||||||
if (containers) {
|
if (containers) {
|
||||||
const containersArray = containers.trim().split('\n');
|
const containersArray = containers.trim().split('\n');
|
||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
const containerObj = JSON.parse(container);
|
const containerObj = JSON.parse(container);
|
||||||
const id = containerObj.ID;
|
const id = containerObj.ID;
|
||||||
await removeContainer({ id, engine: application.destinationDocker.engine });
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,33 +365,48 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { domain } = request.query
|
||||||
|
const { fqdn, settings: { dualCerts } } = await prisma.application.findUnique({ where: { id }, include: { settings: true } })
|
||||||
|
return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
|
||||||
let { exposePort, fqdn, forceSave, dualCerts } = request.body
|
let { exposePort, fqdn, forceSave, dualCerts } = request.body
|
||||||
fqdn = fqdn.toLowerCase();
|
|
||||||
|
|
||||||
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
|
if (exposePort) exposePort = Number(exposePort);
|
||||||
|
|
||||||
|
const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
||||||
const found = await isDomainConfigured({ id, fqdn });
|
|
||||||
|
const found = await isDomainConfigured({ id, fqdn, dockerId });
|
||||||
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) {
|
||||||
exposePort = Number(exposePort);
|
|
||||||
|
|
||||||
if (exposePort < 1024 || exposePort > 65535) {
|
if (exposePort < 1024 || exposePort > 65535) {
|
||||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||||
}
|
}
|
||||||
const { default: getPort } = await import('get-port');
|
|
||||||
const publicPort = await getPort({ port: exposePort });
|
if (configuredPort !== exposePort) {
|
||||||
if (publicPort !== exposePort) {
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
if (availablePort.toString() !== exposePort.toString()) {
|
||||||
|
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
return await checkDomainsIsValidInDNS({ hostname: request.hostname.split(':')[0], fqdn, dualCerts });
|
let hostname = request.hostname.split(':')[0];
|
||||||
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
|
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -375,7 +422,7 @@ export async function getUsage(request) {
|
|||||||
|
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application.destinationDockerId) {
|
if (application.destinationDockerId) {
|
||||||
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]);
|
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.id, id)]);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
usage
|
usage
|
||||||
@ -701,21 +748,20 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
|
|||||||
secret.value = decrypt(secret.value);
|
secret.value = decrypt(secret.value);
|
||||||
return secret;
|
return secret;
|
||||||
});
|
});
|
||||||
|
|
||||||
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
|
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
|
||||||
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
|
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
|
||||||
const destinationDocker = await prisma.destinationDocker.findFirst({
|
const application = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } });
|
||||||
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
|
const { stdout } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker container ls --filter 'name=${id}-' --format "{{json .}}"` })
|
||||||
});
|
if (stdout === '') {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
return {
|
||||||
const listContainers = await docker.engine.listContainers({
|
containers: [],
|
||||||
filters: { network: [destinationDocker.network], name: [id] }
|
applicationSecrets: [],
|
||||||
});
|
PRMRSecrets: []
|
||||||
const containers = listContainers.filter((container) => {
|
}
|
||||||
return (
|
}
|
||||||
container.Labels['coolify.configuration'] &&
|
const containers = formatLabelsOnDocker(stdout).filter(container => container.Labels['coolify.configuration'] && container.Labels['coolify.type'] === 'standalone-application')
|
||||||
container.Labels['coolify.type'] === 'standalone-application'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const jsonContainers = containers
|
const jsonContainers = containers
|
||||||
.map((container) =>
|
.map((container) =>
|
||||||
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
|
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
|
||||||
@ -733,50 +779,46 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
|
console.log({ status, message })
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
|
export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
let { since = 0 } = request.query
|
let { since = 0 } = request.query
|
||||||
if (since !== 0) {
|
if (since !== 0) {
|
||||||
since = day(since).unix();
|
since = day(since).unix();
|
||||||
}
|
}
|
||||||
const { destinationDockerId, destinationDocker } = await prisma.application.findUnique({
|
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
|
||||||
try {
|
try {
|
||||||
const container = await docker.engine.getContainer(id);
|
// const found = await checkContainer({ dockerId, container: id })
|
||||||
if (container) {
|
// if (found) {
|
||||||
const { default: ansi } = await import('strip-ansi')
|
const { default: ansi } = await import('strip-ansi')
|
||||||
const logs = (
|
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
||||||
await container.logs({
|
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
stdout: true,
|
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
stderr: true,
|
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||||
timestamps: true,
|
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
|
||||||
since,
|
return { logs: sortedLogs }
|
||||||
tail: 5000
|
// }
|
||||||
})
|
} catch (error) {
|
||||||
)
|
const { statusCode } = error;
|
||||||
.toString()
|
if (statusCode === 404) {
|
||||||
.split('\n')
|
|
||||||
.map((l) => ansi(l.slice(8)))
|
|
||||||
.filter((a) => a);
|
|
||||||
return {
|
return {
|
||||||
logs
|
logs: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
logs: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
message: 'No logs found.'
|
||||||
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers';
|
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
|
||||||
|
|
||||||
import type { CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage } from './types';
|
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.addHook('onRequest', async (request) => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
@ -17,9 +17,14 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply));
|
fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply));
|
||||||
fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply));
|
fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply));
|
||||||
|
|
||||||
|
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
||||||
|
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
||||||
|
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
||||||
|
|
||||||
fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply));
|
fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply));
|
||||||
|
|
||||||
|
fastify.get<CheckDomain>('/:id/check', async (request) => await checkDomain(request));
|
||||||
fastify.post<CheckDNS>('/:id/check', async (request) => await checkDNS(request));
|
fastify.post<CheckDNS>('/:id/check', async (request) => await checkDNS(request));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/:id/secrets', async (request) => await getSecrets(request));
|
fastify.get<OnlyId>('/:id/secrets', async (request) => await getSecrets(request));
|
||||||
|
@ -30,6 +30,9 @@ export interface SaveApplicationSettings extends OnlyId {
|
|||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
}
|
}
|
||||||
|
export interface CheckDomain extends OnlyId {
|
||||||
|
Querystring: { domain: string; };
|
||||||
|
}
|
||||||
export interface CheckDNS extends OnlyId {
|
export interface CheckDNS extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string; };
|
||||||
Body: {
|
Body: {
|
||||||
@ -115,3 +118,9 @@ export interface DeployApplication extends OnlyId {
|
|||||||
branch: string
|
branch: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StopPreviewApplication extends OnlyId {
|
||||||
|
Body: {
|
||||||
|
pullmergeRequestId: string | null,
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { errorHandler, version } from '../../../../lib/common';
|
import { errorHandler, listSettings, version } from '../../../../lib/common';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get('/', async () => {
|
fastify.get('/', async () => {
|
||||||
|
const settings = await listSettings()
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
|
ipv4: settings.ipv4,
|
||||||
|
ipv6: settings.ipv6,
|
||||||
version,
|
version,
|
||||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||||
|
@ -3,24 +3,20 @@ import type { FastifyRequest } from 'fastify';
|
|||||||
import { FastifyReply } from 'fastify';
|
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 { asyncExecShell, ComposeFile, createDirectories, decrypt, encrypt, errorHandler, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTcpProxy, 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 { dockerInstance, getEngine } from '../../../../lib/docker';
|
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';
|
||||||
import { SaveDatabaseType } from './types';
|
import { SaveDatabaseType } from './types';
|
||||||
|
|
||||||
export async function listDatabases(request: FastifyRequest) {
|
export async function listDatabases(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
let databases = []
|
const databases = await prisma.database.findMany({
|
||||||
if (teamId === '0') {
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
databases = await prisma.database.findMany({ include: { teams: true } });
|
include: { teams: true, destinationDocker: true }
|
||||||
} else {
|
});
|
||||||
databases = await prisma.database.findMany({
|
|
||||||
where: { teams: { some: { id: teamId } } },
|
|
||||||
include: { teams: true }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
databases
|
databases
|
||||||
}
|
}
|
||||||
@ -56,6 +52,36 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
let isRunning = false;
|
||||||
|
|
||||||
|
const database = await prisma.database.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
const { destinationDockerId, destinationDocker } = database;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
try {
|
||||||
|
const { stdout } = await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||||
|
|
||||||
|
if (JSON.parse(stdout).Running) {
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isRunning
|
||||||
|
}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getDatabase(request: FastifyRequest<OnlyId>) {
|
export async function getDatabase(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
@ -69,29 +95,11 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
}
|
}
|
||||||
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 } = database;
|
|
||||||
let isRunning = false;
|
|
||||||
if (destinationDockerId) {
|
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { stdout } = await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (JSON.parse(stdout).Running) {
|
|
||||||
isRunning = true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const configuration = generateDatabaseConfiguration(database);
|
const configuration = generateDatabaseConfiguration(database);
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
return {
|
return {
|
||||||
privatePort: configuration?.privatePort,
|
privatePort: configuration?.privatePort,
|
||||||
database,
|
database,
|
||||||
isRunning,
|
|
||||||
versions: await getDatabaseVersions(database.type),
|
versions: await getDatabaseVersions(database.type),
|
||||||
settings
|
settings
|
||||||
};
|
};
|
||||||
@ -164,16 +172,15 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
destinationDocker: { engine },
|
destinationDocker: { engine, id: dockerId },
|
||||||
version,
|
version,
|
||||||
type
|
type
|
||||||
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
|
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
|
||||||
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const host = getEngine(engine);
|
|
||||||
if (type && version) {
|
if (type && version) {
|
||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type);
|
||||||
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
|
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reply.code(201).send({})
|
return reply.code(201).send({})
|
||||||
@ -194,7 +201,7 @@ export async function getDatabaseUsage(request: FastifyRequest<OnlyId>) {
|
|||||||
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);
|
||||||
if (database.destinationDockerId) {
|
if (database.destinationDockerId) {
|
||||||
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.engine, id)]);
|
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.id, id)]);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
usage
|
usage
|
||||||
@ -225,7 +232,6 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
generateDatabaseConfiguration(database);
|
generateDatabaseConfiguration(database);
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const volumeName = volume.split(':')[0];
|
const volumeName = volume.split(':')[0];
|
||||||
const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
|
const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
|
||||||
|
|
||||||
@ -267,13 +273,13 @@ 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 {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${volumeName}`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker volume create ${volumeName}` })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
|
||||||
if (isPublic) await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
return {};
|
return {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw {
|
throw {
|
||||||
@ -311,39 +317,27 @@ export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
}
|
}
|
||||||
export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) {
|
export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { since = 0 } = request.query
|
let { since = 0 } = request.query
|
||||||
if (since !== 0) {
|
if (since !== 0) {
|
||||||
since = day(since).unix();
|
since = day(since).unix();
|
||||||
}
|
}
|
||||||
const { destinationDockerId, destinationDocker } = await prisma.database.findUnique({
|
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
|
||||||
try {
|
try {
|
||||||
const container = await docker.engine.getContainer(id);
|
// const found = await checkContainer({ dockerId, container: id })
|
||||||
if (container) {
|
// if (found) {
|
||||||
const { default: ansi } = await import('strip-ansi')
|
const { default: ansi } = await import('strip-ansi')
|
||||||
const logs = (
|
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
||||||
await container.logs({
|
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
stdout: true,
|
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
stderr: true,
|
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||||
timestamps: true,
|
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
|
||||||
since,
|
return { logs: sortedLogs }
|
||||||
tail: 5000
|
// }
|
||||||
})
|
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => ansi(l.slice(8)))
|
|
||||||
.filter((a) => a);
|
|
||||||
return {
|
|
||||||
logs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { statusCode } = error;
|
const { statusCode } = error;
|
||||||
if (statusCode === 404) {
|
if (statusCode === 404) {
|
||||||
@ -432,8 +426,10 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { isPublic, appendOnly = true } = request.body;
|
const { isPublic, appendOnly = true } = request.body;
|
||||||
const publicPort = await getFreePort();
|
|
||||||
const settings = await listSettings();
|
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
const publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@ -453,11 +449,7 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
await prisma.database.update({ where: { id }, data: { publicPort } });
|
await prisma.database.update({ where: { id }, data: { publicPort } });
|
||||||
if (settings.isTraefikUsed) {
|
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
|
||||||
} else {
|
|
||||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await prisma.database.update({ where: { id }, data: { publicPort: null } });
|
await prisma.database.update({ where: { id }, data: { publicPort: null } });
|
||||||
await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);
|
await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||||
|
|
||||||
import type { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
import type { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||||
import type { SaveDatabaseType } from './types';
|
import type { SaveDatabaseType } from './types';
|
||||||
@ -15,6 +15,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
|
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
|
||||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
|
fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
|
||||||
|
|
||||||
|
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
|
||||||
|
|
||||||
fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request));
|
fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request));
|
||||||
|
|
||||||
fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request));
|
fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request));
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import { asyncExecShell, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
import sshConfig from 'ssh-config'
|
||||||
import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker';
|
import fs from 'fs/promises'
|
||||||
|
import os from 'os';
|
||||||
|
|
||||||
|
import { asyncExecShell, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||||
|
import { checkContainer } from '../../../../lib/docker';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||||
|
|
||||||
export async function listDestinations(request: FastifyRequest) {
|
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
|
const { onlyVerified = false } = request.query
|
||||||
let destinations = []
|
let destinations = []
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||||
@ -18,6 +23,9 @@ export async function listDestinations(request: FastifyRequest) {
|
|||||||
include: { teams: true }
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (onlyVerified) {
|
||||||
|
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
destinations
|
destinations
|
||||||
}
|
}
|
||||||
@ -44,7 +52,8 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
|
|||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const destination = await prisma.destinationDocker.findFirst({
|
const destination = await prisma.destinationDocker.findFirst({
|
||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { sshKey: true }
|
||||||
});
|
});
|
||||||
if (!destination && id !== 'new') {
|
if (!destination && id !== 'new') {
|
||||||
throw { status: 404, message: `Destination not found.` };
|
throw { status: 404, message: `Destination not found.` };
|
||||||
@ -52,23 +61,8 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
|
|||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
let payload = {
|
let payload = {
|
||||||
destination,
|
destination,
|
||||||
settings,
|
settings
|
||||||
state: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (destination?.remoteEngine) {
|
|
||||||
// const { stdout } = await asyncExecShell(
|
|
||||||
// `ssh -p ${destination.port} ${destination.user}@${destination.ipAddress} "docker ps -a"`
|
|
||||||
// );
|
|
||||||
// console.log(stdout)
|
|
||||||
// const engine = await generateRemoteEngine(destination);
|
|
||||||
// // await saveSshKey(destination);
|
|
||||||
// payload.state = await checkContainer(engine, 'coolify-haproxy');
|
|
||||||
} else {
|
|
||||||
const containerName = 'coolify-proxy';
|
|
||||||
payload.state =
|
|
||||||
destination?.engine && (await checkContainer(destination.engine, containerName));
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...payload
|
...payload
|
||||||
};
|
};
|
||||||
@ -79,68 +73,68 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
|
|||||||
}
|
}
|
||||||
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
|
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
|
||||||
let { name, network, engine, isCoolifyProxyUsed } = request.body
|
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
if (id === 'new') {
|
const { id } = request.params
|
||||||
const host = getEngine(engine);
|
|
||||||
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
|
||||||
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
|
||||||
if (found.length === 0) {
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
|
||||||
}
|
|
||||||
await prisma.destinationDocker.create({
|
|
||||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
|
||||||
});
|
|
||||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
|
||||||
const destination = destinations.find((destination) => destination.network === network);
|
|
||||||
|
|
||||||
if (destinations.length > 0) {
|
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||||
const proxyConfigured = destinations.find(
|
if (id === 'new') {
|
||||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
console.log(engine)
|
||||||
);
|
if (engine) {
|
||||||
if (proxyConfigured) {
|
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
||||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
if (stdout === '') {
|
||||||
|
await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network create --attachable ${network}`);
|
||||||
}
|
}
|
||||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
await prisma.destinationDocker.create({
|
||||||
}
|
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||||
if (isCoolifyProxyUsed) {
|
});
|
||||||
const settings = await prisma.setting.findFirst();
|
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||||
if (settings?.isTraefikUsed) {
|
const destination = destinations.find((destination) => destination.network === network);
|
||||||
await startTraefikProxy(engine);
|
if (destinations.length > 0) {
|
||||||
} else {
|
const proxyConfigured = destinations.find(
|
||||||
await startCoolifyProxy(engine);
|
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||||
|
);
|
||||||
|
if (proxyConfigured) {
|
||||||
|
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||||
|
}
|
||||||
|
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||||
}
|
}
|
||||||
|
if (isCoolifyProxyUsed) {
|
||||||
|
await startTraefikProxy(destination.id);
|
||||||
|
}
|
||||||
|
return reply.code(201).send({ id: destination.id });
|
||||||
|
} else {
|
||||||
|
const destination = await prisma.destinationDocker.create({
|
||||||
|
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort }
|
||||||
|
});
|
||||||
|
return reply.code(201).send({ id: destination.id })
|
||||||
}
|
}
|
||||||
return reply.code(201).send({ id: destination.id });
|
|
||||||
} else {
|
} else {
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
|
console.log({ status, message })
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const destination = await prisma.destinationDocker.delete({ where: { id } });
|
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||||
if (destination.isCoolifyProxyUsed) {
|
if (isCoolifyProxyUsed) {
|
||||||
const host = getEngine(destination.engine);
|
if (engine || remoteVerified) {
|
||||||
const { network } = destination;
|
const { stdout: found } = await executeDockerCmd({
|
||||||
const settings = await prisma.setting.findFirst();
|
dockerId: id,
|
||||||
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy';
|
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||||
const { stdout: found } = await asyncExecShell(
|
})
|
||||||
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'`
|
if (found) {
|
||||||
);
|
await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
|
||||||
if (found) {
|
await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` })
|
||||||
await asyncExecShell(
|
}
|
||||||
`DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}`
|
|
||||||
);
|
|
||||||
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await prisma.destinationDocker.delete({ where: { id } });
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -163,34 +157,105 @@ export async function saveDestinationSettings(request: FastifyRequest<SaveDestin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startProxy(request: FastifyRequest<Proxy>) {
|
export async function startProxy(request: FastifyRequest<Proxy>) {
|
||||||
const { engine } = request.body;
|
const { id } = request.params
|
||||||
try {
|
try {
|
||||||
await startTraefikProxy(engine);
|
await startTraefikProxy(id);
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
await stopTraefikProxy(engine);
|
console.log({ status, message })
|
||||||
|
await stopTraefikProxy(id);
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function stopProxy(request: FastifyRequest<Proxy>) {
|
export async function stopProxy(request: FastifyRequest<Proxy>) {
|
||||||
const { engine } = request.body;
|
const { id } = request.params
|
||||||
try {
|
try {
|
||||||
await stopTraefikProxy(engine);
|
await stopTraefikProxy(id);
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function restartProxy(request: FastifyRequest<Proxy>) {
|
export async function restartProxy(request: FastifyRequest<Proxy>) {
|
||||||
const { engine } = request.body;
|
const { id } = request.params
|
||||||
try {
|
try {
|
||||||
await stopTraefikProxy(engine);
|
await stopTraefikProxy(id);
|
||||||
await startTraefikProxy(engine);
|
await startTraefikProxy(id);
|
||||||
await prisma.destinationDocker.updateMany({
|
await prisma.destinationDocker.update({
|
||||||
where: { engine },
|
where: { id },
|
||||||
data: { isCoolifyProxyUsed: true }
|
data: { isCoolifyProxyUsed: true }
|
||||||
});
|
});
|
||||||
return {}
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
await prisma.destinationDocker.update({
|
||||||
|
where: { id },
|
||||||
|
data: { isCoolifyProxyUsed: false }
|
||||||
|
});
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assignSSHKey(request: FastifyRequest) {
|
||||||
|
try {
|
||||||
|
const { id: sshKeyId } = request.body;
|
||||||
|
const { id } = request.params;
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
|
||||||
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function verifyRemoteDockerEngine(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const homedir = os.homedir();
|
||||||
|
|
||||||
|
const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } })
|
||||||
|
|
||||||
|
await fs.writeFile(`/tmp/id_rsa_verification_${id}`, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 })
|
||||||
|
|
||||||
|
const host = `ssh://${remoteUser}@${remoteIpAddress}`
|
||||||
|
|
||||||
|
const config = sshConfig.parse('')
|
||||||
|
const found = config.find({ Host: remoteIpAddress })
|
||||||
|
if (!found) {
|
||||||
|
config.append({
|
||||||
|
Host: remoteIpAddress,
|
||||||
|
Port: remotePort.toString(),
|
||||||
|
User: remoteUser,
|
||||||
|
IdentityFile: `/tmp/id_rsa_verification_${id}`,
|
||||||
|
StrictHostKeyChecking: 'no'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fs.stat(`${homedir}/.ssh/`)
|
||||||
|
} catch (error) {
|
||||||
|
await fs.mkdir(`${homedir}/.ssh/`)
|
||||||
|
}
|
||||||
|
await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
||||||
|
|
||||||
|
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
||||||
|
|
||||||
|
if (!stdout) {
|
||||||
|
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
||||||
|
return reply.code(201).send()
|
||||||
|
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||||
|
const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy' })
|
||||||
|
return {
|
||||||
|
isRunning
|
||||||
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy } from './handlers';
|
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.addHook('onRequest', async (request) => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
return await request.jwtVerify()
|
return await request.jwtVerify()
|
||||||
})
|
})
|
||||||
fastify.get('/', async (request) => await listDestinations(request));
|
fastify.get<ListDestinations>('/', async (request) => await listDestinations(request));
|
||||||
fastify.post<CheckDestination>('/check', async (request) => await checkDestination(request));
|
fastify.post<CheckDestination>('/check', async (request) => await checkDestination(request));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
||||||
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
||||||
|
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
||||||
|
|
||||||
fastify.post<SaveDestinationSettings>('/:id/settings', async (request, reply) => await saveDestinationSettings(request));
|
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
||||||
fastify.post<Proxy>('/:id/start', async (request, reply) => await startProxy(request));
|
fastify.post<Proxy>('/:id/start', async (request,) => await startProxy(request));
|
||||||
fastify.post<Proxy>('/:id/stop', async (request, reply) => await stopProxy(request));
|
fastify.post<Proxy>('/:id/stop', async (request) => await stopProxy(request));
|
||||||
fastify.post<Proxy>('/:id/restart', async (request, reply) => await restartProxy(request));
|
fastify.post<Proxy>('/:id/restart', async (request) => await restartProxy(request));
|
||||||
|
|
||||||
|
fastify.post('/:id/configuration/sshKey', async (request) => await assignSSHKey(request));
|
||||||
|
|
||||||
|
fastify.post('/:id/verify', async (request, reply) => await verifyRemoteDockerEngine(request, reply));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { OnlyId } from "../../../../types"
|
import { OnlyId } from "../../../../types"
|
||||||
|
|
||||||
|
export interface ListDestinations {
|
||||||
|
Querystring: {
|
||||||
|
onlyVerified: string
|
||||||
|
}
|
||||||
|
}
|
||||||
export interface CheckDestination {
|
export interface CheckDestination {
|
||||||
Body: {
|
Body: {
|
||||||
network: string
|
network: string
|
||||||
@ -20,7 +25,5 @@ export interface SaveDestinationSettings extends OnlyId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface Proxy extends OnlyId {
|
export interface Proxy extends OnlyId {
|
||||||
Body: {
|
|
||||||
engine: string
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -17,7 +17,8 @@ export async function hashPassword(password: string): Promise<string> {
|
|||||||
|
|
||||||
export async function cleanupManually() {
|
export async function cleanupManually() {
|
||||||
try {
|
try {
|
||||||
await cleanupDockerStorage('unix:///var/run/docker.sock', true, true)
|
const destination = await prisma.destinationDocker.findFirst({ where: { engine: '/var/run/docker.sock' } })
|
||||||
|
await cleanupDockerStorage(destination.id, true, true)
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -154,7 +155,6 @@ export async function login(request: FastifyRequest<Login>, reply: FastifyReply)
|
|||||||
}
|
}
|
||||||
if (userFound) {
|
if (userFound) {
|
||||||
if (userFound.type === 'email') {
|
if (userFound.type === 'email') {
|
||||||
// TODO: Review this one
|
|
||||||
if (userFound.password === 'RESETME') {
|
if (userFound.password === 'RESETME') {
|
||||||
const hashedPassword = await hashPassword(password);
|
const hashedPassword = await hashPassword(password);
|
||||||
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||||
|
@ -273,11 +273,15 @@ export async function inviteToTeam(request: FastifyRequest<InviteToTeam>, reply:
|
|||||||
const { email, permission, teamId, teamName } = request.body;
|
const { email, permission, teamId, teamName } = request.body;
|
||||||
const userFound = await prisma.user.findUnique({ where: { email } });
|
const userFound = await prisma.user.findUnique({ where: { email } });
|
||||||
if (!userFound) {
|
if (!userFound) {
|
||||||
throw `No user found with '${email}' email address.`
|
throw {
|
||||||
|
message: `No user found with '${email}' email address.`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const uid = userFound.id;
|
const uid = userFound.id;
|
||||||
if (uid === userId) {
|
if (uid === userId) {
|
||||||
throw `Invitation to yourself? Whaaaaat?`
|
throw {
|
||||||
|
message: `Invitation to yourself? Whaaaaat?`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const alreadyInTeam = await prisma.team.findFirst({
|
const alreadyInTeam = await prisma.team.findFirst({
|
||||||
where: { id: teamId, users: { some: { id: uid } } }
|
where: { id: teamId, users: { some: { id: uid } } }
|
||||||
|
@ -2,13 +2,13 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions } from '../../../../lib/common';
|
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort, checkDomainsIsValidInDNS } from '../../../../lib/common';
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
||||||
|
|
||||||
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
|
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
|
||||||
// try {
|
// try {
|
||||||
@ -145,15 +145,10 @@ import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteSer
|
|||||||
export async function listServices(request: FastifyRequest) {
|
export async function listServices(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
let services = []
|
const services = await prisma.service.findMany({
|
||||||
if (teamId === '0') {
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
services = await prisma.service.findMany({ include: { teams: true } });
|
include: { teams: true, destinationDocker: true }
|
||||||
} else {
|
});
|
||||||
services = await prisma.service.findMany({
|
|
||||||
where: { teams: { some: { id: teamId } } },
|
|
||||||
include: { teams: true }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
services
|
services
|
||||||
}
|
}
|
||||||
@ -172,43 +167,41 @@ export async function newService(request: FastifyRequest, reply: FastifyReply) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id } = request.params;
|
||||||
|
|
||||||
|
let isRunning = false;
|
||||||
|
let isExited = false
|
||||||
|
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const { destinationDockerId, settings } = service;
|
||||||
|
|
||||||
|
if (destinationDockerId) {
|
||||||
|
isRunning = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
|
||||||
|
isExited = await isContainerExited(service.destinationDocker.id, id);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isRunning,
|
||||||
|
isExited,
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getService(request: FastifyRequest<OnlyId>) {
|
export async function getService(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const settings = await listSettings()
|
||||||
if (!service) {
|
if (!service) {
|
||||||
throw { status: 404, message: 'Service not found.' }
|
throw { status: 404, message: 'Service not found.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { destinationDockerId, destinationDocker, type, version, settings } = service;
|
|
||||||
let isRunning = false;
|
|
||||||
if (destinationDockerId) {
|
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const docker = dockerInstance({ destinationDocker });
|
|
||||||
const baseImage = getServiceImage(type);
|
|
||||||
const images = getServiceImages(type);
|
|
||||||
docker.engine.pull(`${baseImage}:${version}`);
|
|
||||||
if (images?.length > 0) {
|
|
||||||
for (const image of images) {
|
|
||||||
docker.engine.pull(`${image}:latest`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const { stdout } = await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (JSON.parse(stdout).Running) {
|
|
||||||
isRunning = true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
isRunning,
|
|
||||||
service,
|
service,
|
||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
@ -282,7 +275,7 @@ export async function getServiceUsage(request: FastifyRequest<OnlyId>) {
|
|||||||
|
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
if (service.destinationDockerId) {
|
if (service.destinationDockerId) {
|
||||||
[usage] = await Promise.all([getContainerUsage(service.destinationDocker.engine, id)]);
|
[usage] = await Promise.all([getContainerUsage(service.destinationDocker.id, id)]);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
usage
|
usage
|
||||||
@ -299,33 +292,22 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
|||||||
if (since !== 0) {
|
if (since !== 0) {
|
||||||
since = day(since).unix();
|
since = day(since).unix();
|
||||||
}
|
}
|
||||||
const { destinationDockerId, destinationDocker } = await prisma.service.findUnique({
|
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.service.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
|
||||||
try {
|
try {
|
||||||
const container = await docker.engine.getContainer(id);
|
// const found = await checkContainer({ dockerId, container: id })
|
||||||
if (container) {
|
// if (found) {
|
||||||
const { default: ansi } = await import('strip-ansi')
|
const { default: ansi } = await import('strip-ansi')
|
||||||
const logs = (
|
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
||||||
await container.logs({
|
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
stdout: true,
|
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
stderr: true,
|
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||||
timestamps: true,
|
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
|
||||||
since,
|
return { logs: sortedLogs }
|
||||||
tail: 5000
|
// }
|
||||||
})
|
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
.split('\n')
|
|
||||||
.map((l) => ansi(l.slice(8)))
|
|
||||||
.filter((a) => a);
|
|
||||||
return {
|
|
||||||
logs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { statusCode } = error;
|
const { statusCode } = error;
|
||||||
if (statusCode === 404) {
|
if (statusCode === 404) {
|
||||||
@ -364,40 +346,57 @@ export async function saveServiceSettings(request: FastifyRequest<SaveServiceSet
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function checkServiceDomain(request: FastifyRequest<CheckServiceDomain>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { domain } = request.query
|
||||||
|
const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } })
|
||||||
|
return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function checkService(request: FastifyRequest<CheckService>) {
|
export async function checkService(request: FastifyRequest<CheckService>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { fqdn, exposePort, otherFqdns } = request.body;
|
let { fqdn, exposePort, forceSave, otherFqdns, dualCerts } = request.body;
|
||||||
|
|
||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
|
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
|
||||||
if (exposePort) exposePort = Number(exposePort);
|
if (exposePort) exposePort = Number(exposePort);
|
||||||
|
|
||||||
let found = await isDomainConfigured({ id, fqdn });
|
const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
||||||
|
|
||||||
|
let found = await isDomainConfigured({ id, fqdn, dockerId });
|
||||||
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 (otherFqdns && otherFqdns.length > 0) {
|
if (otherFqdns && otherFqdns.length > 0) {
|
||||||
for (const ofqdn of otherFqdns) {
|
for (const ofqdn of otherFqdns) {
|
||||||
found = await isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true });
|
found = await isDomainConfigured({ id, fqdn: ofqdn, dockerId });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` }
|
throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
const { default: getPort } = await import('get-port');
|
|
||||||
exposePort = Number(exposePort);
|
|
||||||
|
|
||||||
if (exposePort < 1024 || exposePort > 65535) {
|
if (exposePort < 1024 || exposePort > 65535) {
|
||||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicPort = await getPort({ port: exposePort });
|
if (configuredPort !== exposePort) {
|
||||||
if (publicPort !== exposePort) {
|
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
if (availablePort.toString() !== exposePort.toString()) {
|
||||||
|
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
|
let hostname = request.hostname.split(':')[0];
|
||||||
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
|
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||||
|
}
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -742,7 +741,6 @@ async function startPlausibleAnalyticsService(request: FastifyRequest<ServiceSta
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('plausibleanalytics');
|
const port = getServiceMainPort('plausibleanalytics');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -859,10 +857,8 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
};
|
};
|
||||||
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));
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
|
|
||||||
);
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -877,17 +873,17 @@ async function stopPlausibleAnalyticsService(request: FastifyRequest<ServiceStar
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
const engine = destinationDocker.engine;
|
||||||
|
|
||||||
let found = await checkContainer(engine, id);
|
let found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
found = await checkContainer(engine, `${id}-postgresql`);
|
found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-postgresql`, engine });
|
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
found = await checkContainer(engine, `${id}-clickhouse`);
|
found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-clickhouse` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-clickhouse`, engine });
|
await removeContainer({ id: `${id}-clickhouse`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,7 +901,6 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||||
service;
|
service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('nocodb');
|
const port = getServiceMainPort('nocodb');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -956,8 +951,8 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
};
|
};
|
||||||
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));
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -970,10 +965,9 @@ async function stopNocodbService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
const found = await checkContainer(engine, id);
|
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
@ -999,10 +993,10 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
} = service;
|
} = service;
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('minio');
|
const port = getServiceMainPort('minio');
|
||||||
|
|
||||||
const publicPort = await getFreePort();
|
const { service: { destinationDocker: { id: dockerId } } } = await prisma.minio.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } })
|
||||||
|
const publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
|
||||||
const consolePort = 9001;
|
const consolePort = 9001;
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1058,8 +1052,8 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
};
|
};
|
||||||
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));
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -1071,13 +1065,12 @@ async function stopMinioService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } })
|
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } })
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
const found = await checkContainer(engine, id);
|
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
@ -1103,7 +1096,6 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
} = service;
|
} = service;
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('vscodeserver');
|
const port = getServiceMainPort('vscodeserver');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1175,16 +1167,16 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
const changePermissionOn = persistentStorage.map((p) => p.path);
|
const changePermissionOn = persistentStorage.map((p) => p.path);
|
||||||
if (changePermissionOn.length > 0) {
|
if (changePermissionOn.length > 0) {
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST=${host} docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join(
|
dockerId: destinationDocker.id, command: `docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join(
|
||||||
' '
|
' '
|
||||||
)}`
|
)}`
|
||||||
);
|
})
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -1196,12 +1188,11 @@ async function stopVscodeService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
const found = await checkContainer(engine, id);
|
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
@ -1236,7 +1227,6 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
} = service;
|
} = service;
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const image = getServiceImage(type);
|
const image = getServiceImage(type);
|
||||||
const port = getServiceMainPort('wordpress');
|
const port = getServiceMainPort('wordpress');
|
||||||
|
|
||||||
@ -1328,8 +1318,10 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
}
|
}
|
||||||
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));
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1346,28 +1338,27 @@ async function stopWordpressService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
wordpress: { ftpEnabled }
|
wordpress: { ftpEnabled }
|
||||||
} = service;
|
} = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, `${id}-mysql`);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mysql` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-mysql`, engine });
|
await removeContainer({ id: `${id}-mysql`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (ftpEnabled) {
|
if (ftpEnabled) {
|
||||||
const found = await checkContainer(engine, `${id}-ftp`);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-ftp`, engine });
|
await removeContainer({ id: `${id}-ftp`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
await prisma.wordpress.update({
|
await prisma.wordpress.update({
|
||||||
where: { serviceId: id },
|
where: { serviceId: id },
|
||||||
@ -1393,7 +1384,6 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
|
|||||||
service;
|
service;
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('vaultwarden');
|
const port = getServiceMainPort('vaultwarden');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1444,8 +1434,10 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
|
|||||||
};
|
};
|
||||||
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));
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1456,14 +1448,12 @@ async function stopVaultwardenService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -1483,7 +1473,6 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
|
|||||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||||
service;
|
service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('languagetool');
|
const port = getServiceMainPort('languagetool');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1536,8 +1525,9 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1548,14 +1538,12 @@ async function stopLanguageToolService(request: FastifyRequest<ServiceStartStop>
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -1575,7 +1563,6 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||||
service;
|
service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('n8n');
|
const port = getServiceMainPort('n8n');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1628,8 +1615,10 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
};
|
};
|
||||||
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));
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1640,14 +1629,12 @@ async function stopN8nService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -1667,7 +1654,6 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||||
service;
|
service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('uptimekuma');
|
const port = getServiceMainPort('uptimekuma');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1719,8 +1705,9 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1731,14 +1718,12 @@ async function stopUptimekumaService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -1774,7 +1759,6 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
} = service;
|
} = service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
const image = getServiceImage(type);
|
const image = getServiceImage(type);
|
||||||
@ -1871,8 +1855,9 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1883,18 +1868,16 @@ async function stopGhostService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let found = await checkContainer(engine, id);
|
let found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
found = await checkContainer(engine, `${id}-mariadb`);
|
found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mariadb` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-mariadb`, engine });
|
await removeContainer({ id: `${id}-mariadb`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -1917,7 +1900,6 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
|
|||||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
|
||||||
service;
|
service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('meilisearch');
|
const port = getServiceMainPort('meilisearch');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -1972,8 +1954,10 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -1984,14 +1968,12 @@ async function stopMeilisearchService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -2024,7 +2006,6 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
} = service;
|
} = service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('umami');
|
const port = getServiceMainPort('umami');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -2191,8 +2172,10 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -2203,22 +2186,20 @@ async function stopUmamiService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, `${id}-postgresql`);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-postgresql`, engine });
|
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -2245,7 +2226,6 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
||||||
} = service;
|
} = service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('hasura');
|
const port = getServiceMainPort('hasura');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -2327,8 +2307,9 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -2339,22 +2320,20 @@ async function stopHasuraService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, `${id}-postgresql`);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-postgresql`, engine });
|
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -2396,7 +2375,6 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
} = service;
|
} = service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('fider');
|
const port = getServiceMainPort('fider');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
@ -2489,8 +2467,8 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -2502,22 +2480,20 @@ async function stopFiderService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, `${id}-postgresql`);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-postgresql`, engine });
|
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -2554,12 +2530,10 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
} = service;
|
} = service;
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
|
||||||
const port = getServiceMainPort('moodle');
|
const port = getServiceMainPort('moodle');
|
||||||
|
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
const image = getServiceImage(type);
|
const image = getServiceImage(type);
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const config = {
|
const config = {
|
||||||
moodle: {
|
moodle: {
|
||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
@ -2652,8 +2626,10 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
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));
|
||||||
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -2665,22 +2641,20 @@ async function stopMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const { destinationDockerId, destinationDocker } = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, id);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const found = await checkContainer(engine, `${id}-mariadb`);
|
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mariadb` });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id: `${id}-mariadb`, engine });
|
await removeContainer({ id: `${id}-mariadb`, dockerId: destinationDocker.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -2703,14 +2677,10 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
|
|||||||
plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
||||||
} = await getServiceFromDB({ id, teamId });
|
} = await getServiceFromDB({ id, teamId });
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
await executeDockerCmd({
|
||||||
const container = await docker.engine.getContainer(id);
|
dockerId: destinationDocker.id,
|
||||||
const command = await container.exec({
|
command: `docker exec ${id} 'psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"'`
|
||||||
Cmd: [
|
})
|
||||||
`psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"`
|
|
||||||
]
|
|
||||||
});
|
|
||||||
await command.start();
|
|
||||||
return await reply.code(201).send()
|
return await reply.code(201).send()
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Could not activate users.' }
|
throw { status: 500, message: 'Could not activate users.' }
|
||||||
@ -2722,7 +2692,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { ftpEnabled } = request.body;
|
const { ftpEnabled } = request.body;
|
||||||
|
|
||||||
const publicPort = await getFreePort();
|
const { service: { destinationDocker: { id: dockerId } } } = await prisma.wordpress.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } })
|
||||||
|
|
||||||
|
const publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
|
||||||
let ftpUser = cuid();
|
let ftpUser = cuid();
|
||||||
let ftpPassword = generatePassword();
|
let ftpPassword = generatePassword();
|
||||||
|
|
||||||
@ -2742,7 +2715,6 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
ftpHostKeyPrivate
|
ftpHostKeyPrivate
|
||||||
} = data;
|
} = data;
|
||||||
const { network, engine } = destinationDocker;
|
const { network, engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
|
||||||
if (ftpEnabled) {
|
if (ftpEnabled) {
|
||||||
if (user) ftpUser = user;
|
if (user) ftpUser = user;
|
||||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||||
@ -2789,7 +2761,7 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isRunning = await checkContainer(engine, `${id}-ftp`);
|
const isRunning = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||||
@ -2841,9 +2813,11 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
);
|
);
|
||||||
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
|
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
|
||||||
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
dockerId: destinationDocker.id,
|
||||||
);
|
command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
return reply.code(201).send({
|
return reply.code(201).send({
|
||||||
publicPort,
|
publicPort,
|
||||||
@ -2856,9 +2830,11 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
data: { ftpPublicPort: null }
|
data: { ftpPublicPort: null }
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(
|
await executeDockerCmd({
|
||||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
dockerId: destinationDocker.id,
|
||||||
);
|
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||||
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@ import {
|
|||||||
activatePlausibleUsers,
|
activatePlausibleUsers,
|
||||||
activateWordpressFtp,
|
activateWordpressFtp,
|
||||||
checkService,
|
checkService,
|
||||||
|
checkServiceDomain,
|
||||||
deleteService,
|
deleteService,
|
||||||
deleteServiceSecret,
|
deleteServiceSecret,
|
||||||
deleteServiceStorage,
|
deleteServiceStorage,
|
||||||
getService,
|
getService,
|
||||||
getServiceLogs,
|
getServiceLogs,
|
||||||
getServiceSecrets,
|
getServiceSecrets,
|
||||||
|
getServiceStatus,
|
||||||
getServiceStorages,
|
getServiceStorages,
|
||||||
getServiceType,
|
getServiceType,
|
||||||
getServiceUsage,
|
getServiceUsage,
|
||||||
@ -28,7 +30,7 @@ import {
|
|||||||
} from './handlers';
|
} from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.addHook('onRequest', async (request) => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
@ -41,6 +43,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply));
|
fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply));
|
||||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request));
|
fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request));
|
||||||
|
|
||||||
|
fastify.get<OnlyId>('/:id/status', async (request) => await getServiceStatus(request));
|
||||||
|
|
||||||
|
fastify.get<CheckServiceDomain>('/:id/check', async (request) => await checkServiceDomain(request));
|
||||||
fastify.post<CheckService>('/:id/check', async (request) => await checkService(request));
|
fastify.post<CheckService>('/:id/check', async (request) => await checkService(request));
|
||||||
|
|
||||||
fastify.post<SaveServiceSettings>('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply));
|
fastify.post<SaveServiceSettings>('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply));
|
||||||
|
@ -25,9 +25,16 @@ export interface SaveServiceSettings extends OnlyId {
|
|||||||
dualCerts: boolean
|
dualCerts: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export interface CheckServiceDomain extends OnlyId {
|
||||||
|
Querystring: {
|
||||||
|
domain: string
|
||||||
|
}
|
||||||
|
}
|
||||||
export interface CheckService extends OnlyId {
|
export interface CheckService extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
fqdn: string,
|
fqdn: string,
|
||||||
|
forceSave: boolean,
|
||||||
|
dualCerts: boolean,
|
||||||
exposePort: number,
|
exposePort: number,
|
||||||
otherFqdns: Array<string>
|
otherFqdns: Array<string>
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
import { promises as dns } from 'dns';
|
import { promises as dns } from 'dns';
|
||||||
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { checkDomainsIsValidInDNS, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
|
||||||
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types';
|
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
|
||||||
|
|
||||||
|
|
||||||
export async function listAllSettings(request: FastifyRequest) {
|
export async function listAllSettings(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
|
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
||||||
|
const unencryptedKeys = []
|
||||||
|
if (sshKeys.length > 0) {
|
||||||
|
for (const key of sshKeys) {
|
||||||
|
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
settings
|
settings,
|
||||||
|
sshKeys: unencryptedKeys
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@ -68,7 +77,8 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
|||||||
throw "Domain already configured";
|
throw "Domain already configured";
|
||||||
}
|
}
|
||||||
if (isDNSCheckEnabled && !forceSave) {
|
if (isDNSCheckEnabled && !forceSave) {
|
||||||
return await checkDomainsIsValidInDNS({ hostname: request.hostname.split(':')[0], fqdn, dualCerts });
|
const hostname = request.hostname.split(':')[0]
|
||||||
|
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -83,4 +93,31 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
|||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { privateKey, name } = request.body;
|
||||||
|
const found = await prisma.sshKey.findMany({ where: { name } })
|
||||||
|
if (found.length > 0) {
|
||||||
|
throw {
|
||||||
|
message: "Name already used. Choose another one please."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const encryptedSSHKey = encrypt(privateKey)
|
||||||
|
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } })
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function deleteSSHKey(request: FastifyRequest<DeleteSSHKey>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const { id } = request.body;
|
||||||
|
await prisma.sshKey.delete({ where: { id } })
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { checkDNS, checkDomain, deleteDomain, listAllSettings, saveSettings } from './handlers';
|
import { checkDNS, checkDomain, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
|
||||||
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types';
|
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
|
||||||
|
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
@ -13,6 +13,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
|
fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
|
||||||
fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
|
fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
|
||||||
|
|
||||||
|
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
|
||||||
|
fastify.delete<DeleteSSHKey>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
@ -28,4 +28,15 @@ export interface CheckDNS {
|
|||||||
Params: {
|
Params: {
|
||||||
domain: string,
|
domain: string,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
export interface SaveSSHKey {
|
||||||
|
Body: {
|
||||||
|
privateKey: string,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export interface DeleteSSHKey {
|
||||||
|
Body: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
}
|
}
|
@ -75,18 +75,18 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
if (!allowedGithubEvents.includes(githubEvent)) {
|
if (!allowedGithubEvents.includes(githubEvent)) {
|
||||||
throw { status: 500, message: 'Event not allowed.' }
|
throw { status: 500, message: 'Event not allowed.' }
|
||||||
}
|
}
|
||||||
let repository, projectId, branch;
|
let projectId, branch;
|
||||||
const body = request.body
|
const body = request.body
|
||||||
if (githubEvent === 'push') {
|
if (githubEvent === 'push') {
|
||||||
repository = body.repository;
|
projectId = body.repository.id;
|
||||||
projectId = repository.id;
|
branch = body.ref.includes('/') ? body.ref.split('/')[2] : body.ref;
|
||||||
branch = body.ref.split('/')[2];
|
|
||||||
} else if (githubEvent === 'pull_request') {
|
} else if (githubEvent === 'pull_request') {
|
||||||
repository = body.pull_request.head.repo;
|
projectId = body.pull_request.base.repo.id;
|
||||||
projectId = repository.id;
|
branch = body.pull_request.base.ref.includes('/') ? body.pull_request.base.ref.split('/')[2] : body.pull_request.base.ref;
|
||||||
branch = body.pull_request.head.ref.split('/')[2];
|
}
|
||||||
|
if (!projectId || !branch) {
|
||||||
|
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
|
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||||
if (applicationFound) {
|
if (applicationFound) {
|
||||||
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret || null;
|
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret || null;
|
||||||
@ -154,7 +154,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
} else if (githubEvent === 'pull_request') {
|
} else if (githubEvent === 'pull_request') {
|
||||||
const pullmergeRequestId = body.number;
|
const pullmergeRequestId = body.number;
|
||||||
const pullmergeRequestAction = body.action;
|
const pullmergeRequestAction = body.action;
|
||||||
const sourceBranch = body.pull_request.head.ref;
|
const sourceBranch = body.pull_request.head.ref.includes('/') ? body.pull_request.head.ref.split('/')[2] : body.pull_request.head.ref;
|
||||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||||
throw { status: 500, message: 'Action not allowed.' }
|
throw { status: 500, message: 'Action not allowed.' }
|
||||||
}
|
}
|
||||||
@ -162,8 +162,10 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
if (applicationFound.settings.previews) {
|
if (applicationFound.settings.previews) {
|
||||||
if (applicationFound.destinationDockerId) {
|
if (applicationFound.destinationDockerId) {
|
||||||
const isRunning = await checkContainer(
|
const isRunning = await checkContainer(
|
||||||
applicationFound.destinationDocker.engine,
|
{
|
||||||
applicationFound.id
|
dockerId: applicationFound.destinationDocker.id,
|
||||||
|
container: applicationFound.id
|
||||||
|
}
|
||||||
);
|
);
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
throw { status: 500, message: 'Application not running.' }
|
throw { status: 500, message: 'Application not running.' }
|
||||||
@ -204,8 +206,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
} else if (pullmergeRequestAction === 'closed') {
|
} else if (pullmergeRequestAction === 'closed') {
|
||||||
if (applicationFound.destinationDockerId) {
|
if (applicationFound.destinationDockerId) {
|
||||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
||||||
const engine = applicationFound.destinationDocker.engine;
|
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
|
||||||
await removeContainer({ id, engine });
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
message: 'Removed preview. Thank you!'
|
message: 'Removed preview. Thank you!'
|
||||||
|
@ -8,12 +8,22 @@ export interface GitHubEvents {
|
|||||||
Body: {
|
Body: {
|
||||||
number: string,
|
number: string,
|
||||||
action: string,
|
action: string,
|
||||||
repository: string,
|
repository: {
|
||||||
|
id: string,
|
||||||
|
},
|
||||||
ref: string,
|
ref: string,
|
||||||
pull_request: {
|
pull_request: {
|
||||||
|
base: {
|
||||||
|
ref: string,
|
||||||
|
repo: {
|
||||||
|
id: string,
|
||||||
|
}
|
||||||
|
},
|
||||||
head: {
|
head: {
|
||||||
ref: string,
|
ref: string,
|
||||||
repo: string
|
repo: {
|
||||||
|
id: string,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
|||||||
}
|
}
|
||||||
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
||||||
} catch ({ status, message, ...other }) {
|
} catch ({ status, message, ...other }) {
|
||||||
console.log(other)
|
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,8 +116,10 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
if (applicationFound.settings.previews) {
|
if (applicationFound.settings.previews) {
|
||||||
if (applicationFound.destinationDockerId) {
|
if (applicationFound.destinationDockerId) {
|
||||||
const isRunning = await checkContainer(
|
const isRunning = await checkContainer(
|
||||||
applicationFound.destinationDocker.engine,
|
{
|
||||||
applicationFound.id
|
dockerId: applicationFound.destinationDocker.id,
|
||||||
|
container: applicationFound.id
|
||||||
|
}
|
||||||
);
|
);
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
throw { status: 500, message: 'Application not running.' }
|
throw { status: 500, message: 'Application not running.' }
|
||||||
@ -164,7 +165,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
if (applicationFound.destinationDockerId) {
|
if (applicationFound.destinationDockerId) {
|
||||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
||||||
const engine = applicationFound.destinationDocker.engine;
|
const engine = applicationFound.destinationDocker.engine;
|
||||||
await removeContainer({ id, engine });
|
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
message: 'Removed preview. Thank you!'
|
message: 'Removed preview. Thank you!'
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma, supportedServiceTypesAndVersions } from "../../../lib/common";
|
import { errorHandler, getDomain, isDev, prisma, supportedServiceTypesAndVersions, include, executeDockerCmd } from "../../../lib/common";
|
||||||
import { getEngine } from "../../../lib/docker";
|
|
||||||
import { TraefikOtherConfiguration } from "./types";
|
import { TraefikOtherConfiguration } from "./types";
|
||||||
|
|
||||||
function configureMiddleware(
|
function configureMiddleware(
|
||||||
@ -167,6 +166,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const applications = await prisma.application.findMany({
|
const applications = await prisma.application.findMany({
|
||||||
|
where: { destinationDocker: { remoteEngine: false } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
const data = {
|
const data = {
|
||||||
@ -184,7 +184,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
settings: { previews, dualCerts }
|
settings: { previews, dualCerts }
|
||||||
} = application;
|
} = application;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const { engine, network } = destinationDocker;
|
const { network, id: dockerId } = destinationDocker;
|
||||||
const isRunning = true;
|
const isRunning = true;
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
@ -205,10 +205,7 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (previews) {
|
if (previews) {
|
||||||
const host = getEngine(engine);
|
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
||||||
const { stdout } = await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
|
||||||
);
|
|
||||||
const containers = stdout
|
const containers = stdout
|
||||||
.trim()
|
.trim()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
@ -235,7 +232,11 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const services = await listServicesWithIncludes();
|
const services: any = await prisma.service.findMany({
|
||||||
|
where: { destinationDocker: { remoteEngine: false } },
|
||||||
|
include,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
const {
|
const {
|
||||||
@ -248,7 +249,6 @@ export async function traefikConfiguration(request, reply) {
|
|||||||
plausibleAnalytics
|
plausibleAnalytics
|
||||||
} = service;
|
} = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const { engine } = destinationDocker;
|
|
||||||
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
const port = found.ports.main;
|
const port = found.ports.main;
|
||||||
@ -487,4 +487,219 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
|
|||||||
console.log(status, message);
|
console.log(status, message);
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remoteTraefikConfiguration(request: FastifyRequest) {
|
||||||
|
const { id } = request.params
|
||||||
|
try {
|
||||||
|
const traefik = {
|
||||||
|
http: {
|
||||||
|
routers: {},
|
||||||
|
services: {},
|
||||||
|
middlewares: {
|
||||||
|
'redirect-to-https': {
|
||||||
|
redirectscheme: {
|
||||||
|
scheme: 'https'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-http': {
|
||||||
|
redirectscheme: {
|
||||||
|
scheme: 'http'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-non-www': {
|
||||||
|
redirectregex: {
|
||||||
|
regex: '^https?://www\\.(.+)',
|
||||||
|
replacement: 'http://${1}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-www': {
|
||||||
|
redirectregex: {
|
||||||
|
regex: '^https?://(?:www\\.)?(.+)',
|
||||||
|
replacement: 'http://www.${1}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const applications = await prisma.application.findMany({
|
||||||
|
where: { destinationDocker: { id } },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
applications: [],
|
||||||
|
services: [],
|
||||||
|
coolify: []
|
||||||
|
};
|
||||||
|
for (const application of applications) {
|
||||||
|
const {
|
||||||
|
fqdn,
|
||||||
|
id,
|
||||||
|
port,
|
||||||
|
destinationDocker,
|
||||||
|
destinationDockerId,
|
||||||
|
settings: { previews, dualCerts }
|
||||||
|
} = application;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { id: dockerId, network } = destinationDocker;
|
||||||
|
const isRunning = true;
|
||||||
|
if (fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
if (isRunning) {
|
||||||
|
data.applications.push({
|
||||||
|
id,
|
||||||
|
container: id,
|
||||||
|
port: port || 3000,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (previews) {
|
||||||
|
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
||||||
|
const containers = stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((a) => a)
|
||||||
|
.map((c) => c.replace(/"/g, ''));
|
||||||
|
if (containers.length > 0) {
|
||||||
|
for (const container of containers) {
|
||||||
|
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||||
|
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||||
|
data.applications.push({
|
||||||
|
id: container,
|
||||||
|
container,
|
||||||
|
port: port || 3000,
|
||||||
|
domain: previewDomain,
|
||||||
|
isRunning,
|
||||||
|
nakedDomain,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const services: any = await prisma.service.findMany({
|
||||||
|
where: { destinationDocker: { id } },
|
||||||
|
include,
|
||||||
|
orderBy: { createdAt: 'desc' }
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const service of services) {
|
||||||
|
const {
|
||||||
|
fqdn,
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
destinationDocker,
|
||||||
|
destinationDockerId,
|
||||||
|
dualCerts,
|
||||||
|
plausibleAnalytics
|
||||||
|
} = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
|
if (found) {
|
||||||
|
const port = found.ports.main;
|
||||||
|
const publicPort = service[type]?.publicPort;
|
||||||
|
const isRunning = true;
|
||||||
|
if (fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
if (isRunning) {
|
||||||
|
// Plausible Analytics custom script
|
||||||
|
let scriptName = false;
|
||||||
|
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
|
||||||
|
scriptName = plausibleAnalytics.scriptName;
|
||||||
|
}
|
||||||
|
|
||||||
|
let container = id;
|
||||||
|
let otherDomain = null;
|
||||||
|
let otherNakedDomain = null;
|
||||||
|
let otherIsHttps = null;
|
||||||
|
let otherIsWWW = null;
|
||||||
|
|
||||||
|
if (type === 'minio' && service.minio.apiFqdn) {
|
||||||
|
otherDomain = getDomain(service.minio.apiFqdn);
|
||||||
|
otherNakedDomain = otherDomain.replace(/^www\./, '');
|
||||||
|
otherIsHttps = service.minio.apiFqdn.startsWith('https://');
|
||||||
|
otherIsWWW = service.minio.apiFqdn.includes('www.');
|
||||||
|
}
|
||||||
|
data.services.push({
|
||||||
|
id,
|
||||||
|
container,
|
||||||
|
type,
|
||||||
|
otherDomain,
|
||||||
|
otherNakedDomain,
|
||||||
|
otherIsHttps,
|
||||||
|
otherIsWWW,
|
||||||
|
port,
|
||||||
|
publicPort,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
scriptName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (const application of data.applications) {
|
||||||
|
configureMiddleware(application, traefik);
|
||||||
|
}
|
||||||
|
for (const service of data.services) {
|
||||||
|
const { id, scriptName } = service;
|
||||||
|
|
||||||
|
configureMiddleware(service, traefik);
|
||||||
|
if (service.type === 'minio') {
|
||||||
|
service.id = id + '-minio';
|
||||||
|
service.container = id;
|
||||||
|
service.domain = service.otherDomain;
|
||||||
|
service.nakedDomain = service.otherNakedDomain;
|
||||||
|
service.isHttps = service.otherIsHttps;
|
||||||
|
service.isWWW = service.otherIsWWW;
|
||||||
|
service.port = 9000;
|
||||||
|
configureMiddleware(service, traefik);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptName) {
|
||||||
|
traefik.http.middlewares[`${id}-redir`] = {
|
||||||
|
replacepathregex: {
|
||||||
|
regex: `/js/${scriptName}`,
|
||||||
|
replacement: '/js/plausible.js'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const coolify of data.coolify) {
|
||||||
|
configureMiddleware(coolify, traefik);
|
||||||
|
}
|
||||||
|
if (Object.keys(traefik.http.routers).length === 0) {
|
||||||
|
traefik.http.routers = null;
|
||||||
|
}
|
||||||
|
if (Object.keys(traefik.http.services).length === 0) {
|
||||||
|
traefik.http.services = null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...traefik
|
||||||
|
}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,10 +1,12 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
||||||
import { TraefikOtherConfiguration } from './types';
|
import { TraefikOtherConfiguration } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
||||||
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
||||||
|
|
||||||
|
fastify.get('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
@ -36,3 +36,4 @@ export interface SaveDatabaseSettings extends OnlyId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
"format": "prettier --write --plugin-search-dir=. ."
|
"format": "prettier --write --plugin-search-dir=. ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.23.3",
|
"@playwright/test": "1.23.4",
|
||||||
"@sveltejs/kit": "1.0.0-next.375",
|
"@sveltejs/kit": "1.0.0-next.377",
|
||||||
"@types/js-cookie": "3.0.2",
|
"@types/js-cookie": "3.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.30.6",
|
"@typescript-eslint/eslint-plugin": "5.30.6",
|
||||||
"@typescript-eslint/parser": "5.30.6",
|
"@typescript-eslint/parser": "5.30.6",
|
||||||
"autoprefixer": "10.4.7",
|
"autoprefixer": "10.4.7",
|
||||||
"eslint": "8.19.0",
|
"eslint": "8.20.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.14",
|
"postcss": "8.4.14",
|
||||||
@ -34,11 +34,11 @@
|
|||||||
"tailwindcss-scrollbar": "0.1.0",
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
"tslib": "2.4.0",
|
"tslib": "2.4.0",
|
||||||
"typescript": "4.7.4",
|
"typescript": "4.7.4",
|
||||||
"vite": "^3.0.0"
|
"vite": "3.0.1"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.36",
|
"@sveltejs/adapter-static": "1.0.0-next.37",
|
||||||
"@zerodevx/svelte-toast": "0.7.2",
|
"@zerodevx/svelte-toast": "0.7.2",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
|
@ -250,7 +250,7 @@
|
|||||||
"no_destination_found": "No destination found",
|
"no_destination_found": "No destination found",
|
||||||
"new_error_network_already_exists": "Network {{network}} already configured for another team!",
|
"new_error_network_already_exists": "Network {{network}} already configured for another team!",
|
||||||
"new": {
|
"new": {
|
||||||
"saving_and_configuring_proxy": "Saving and configuring proxy...",
|
"saving_and_configuring_proxy": "Saving...",
|
||||||
"install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy.",
|
"install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy.",
|
||||||
"add_new_destination": "Add New Destination",
|
"add_new_destination": "Add New Destination",
|
||||||
"predefined_destinations": "Predefined destinations"
|
"predefined_destinations": "Predefined destinations"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { writable, readable, type Writable, type Readable } from 'svelte/store';
|
import { writable, readable, type Writable, type Readable } from 'svelte/store';
|
||||||
|
|
||||||
interface AppSession {
|
interface AppSession {
|
||||||
|
ipv4: string | null,
|
||||||
|
ipv6: string | null,
|
||||||
version: string | null,
|
version: string | null,
|
||||||
userId: string | null,
|
userId: string | null,
|
||||||
teamId: string | null,
|
teamId: string | null,
|
||||||
@ -17,6 +19,8 @@ interface AppSession {
|
|||||||
}
|
}
|
||||||
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,
|
||||||
|
ipv6: null,
|
||||||
version: null,
|
version: null,
|
||||||
userId: null,
|
userId: null,
|
||||||
teamId: null,
|
teamId: null,
|
||||||
@ -31,7 +35,6 @@ export const appSession: Writable<AppSession> = writable({
|
|||||||
gitlab: null
|
gitlab: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export const isTraefikUsed: Writable<boolean> = writable(false);
|
|
||||||
export const disabledButton: Writable<boolean> = writable(false);
|
export const disabledButton: Writable<boolean> = writable(false);
|
||||||
export const status: Writable<any> = writable({
|
export const status: Writable<any> = writable({
|
||||||
application: {
|
application: {
|
||||||
@ -41,14 +44,16 @@ export const status: Writable<any> = writable({
|
|||||||
initialLoading: true
|
initialLoading: true
|
||||||
},
|
},
|
||||||
service: {
|
service: {
|
||||||
initialLoading: true,
|
isRunning: false,
|
||||||
|
isExited: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
isRunning: false
|
initialLoading: true
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
initialLoading: true,
|
isRunning: false,
|
||||||
|
isExited: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
isRunning: false
|
initialLoading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -60,7 +65,6 @@ 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) => {
|
||||||
console.log(GITPOD_WORKSPACE_URL)
|
|
||||||
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
|
||||||
|
@ -65,11 +65,12 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let baseSettings: any;
|
export let baseSettings: any;
|
||||||
|
$appSession.ipv4 = baseSettings.ipv4;
|
||||||
|
$appSession.ipv6 = baseSettings.ipv6;
|
||||||
$appSession.version = baseSettings.version;
|
$appSession.version = baseSettings.version;
|
||||||
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
||||||
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
||||||
|
|
||||||
export let settings: any;
|
|
||||||
export let userId: string;
|
export let userId: string;
|
||||||
export let teamId: string;
|
export let teamId: string;
|
||||||
export let permission: string;
|
export let permission: string;
|
||||||
@ -84,15 +85,13 @@
|
|||||||
import UpdateAvailable from '$lib/components/UpdateAvailable.svelte';
|
import UpdateAvailable from '$lib/components/UpdateAvailable.svelte';
|
||||||
import PageLoader from '$lib/components/PageLoader.svelte';
|
import PageLoader from '$lib/components/PageLoader.svelte';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession, isTraefikUsed } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
|
|
||||||
if (userId) $appSession.userId = userId;
|
if (userId) $appSession.userId = userId;
|
||||||
if (teamId) $appSession.teamId = teamId;
|
if (teamId) $appSession.teamId = teamId;
|
||||||
if (permission) $appSession.permission = permission;
|
if (permission) $appSession.permission = permission;
|
||||||
if (isAdmin) $appSession.isAdmin = isAdmin;
|
if (isAdmin) $appSession.isAdmin = isAdmin;
|
||||||
|
|
||||||
$isTraefikUsed = settings?.isTraefikUsed || false;
|
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
try {
|
try {
|
||||||
Cookies.remove('token');
|
Cookies.remove('token');
|
||||||
@ -322,33 +321,31 @@
|
|||||||
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{#if $appSession.teamId === '0'}
|
<a
|
||||||
<a
|
sveltekit:prefetch
|
||||||
sveltekit:prefetch
|
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
|
||||||
href="/settings"
|
class="icons tooltip-yellow-500 tooltip-right bg-coolgray-200 hover:text-yellow-500"
|
||||||
class="icons tooltip-yellow-500 tooltip-right bg-coolgray-200 hover:text-yellow-500"
|
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
|
||||||
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
data-tooltip="Settings"
|
||||||
data-tooltip="Settings"
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-8 w-8"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<svg
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
class="h-8 w-8"
|
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
stroke-width="1.5"
|
<circle cx="12" cy="12" r="3" />
|
||||||
stroke="currentColor"
|
</svg>
|
||||||
fill="none"
|
</a>
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path
|
|
||||||
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"
|
|
||||||
/>
|
|
||||||
<circle cx="12" cy="12" r="3" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="icons tooltip-red-500 tooltip-right bg-coolgray-200 hover:text-red-500"
|
class="icons tooltip-red-500 tooltip-right bg-coolgray-200 hover:text-red-500"
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
isQueueActive,
|
|
||||||
application
|
application
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
@ -53,7 +52,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
export let application: any;
|
||||||
export let isQueueActive: 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';
|
||||||
@ -68,7 +66,7 @@
|
|||||||
|
|
||||||
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 ||
|
||||||
@ -114,12 +112,12 @@
|
|||||||
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}`);
|
const data = await get(`/applications/${id}/status`);
|
||||||
isQueueActive = data.isQueueActive;
|
isQueueActive = data.isQueueActive;
|
||||||
$status.application.isRunning = data.isRunning;
|
$status.application.isRunning = data.isRunning;
|
||||||
$status.application.isExited = data.isExited;
|
$status.application.isExited = data.isExited;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
redirect: `/applications/${params.id}`
|
redirect: `/applications/${params.id}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const response = await get(`/destinations`);
|
const response = await get(`/destinations?onlyVerified=true`);
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...response
|
...response
|
||||||
@ -65,7 +65,7 @@
|
|||||||
<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">{$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
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
import Setting from './_Setting.svelte';
|
import Setting from './_Setting.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
$: isDisabled = !$appSession.isAdmin || $status.application.isRunning;
|
$: isDisabled = !$appSession.isAdmin || $status.application.isRunning || $status.application.initialLoading;
|
||||||
|
|
||||||
let domainEl: HTMLInputElement;
|
let domainEl: HTMLInputElement;
|
||||||
|
|
||||||
@ -308,7 +308,7 @@
|
|||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<!-- svelte-ignore missing-declaration -->
|
<!-- svelte-ignore missing-declaration -->
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5">
|
||||||
<div class="title">{$t('general')}</div>
|
<div class="title">{$t('general')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
@ -536,7 +536,7 @@
|
|||||||
<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={isDisabled}
|
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')}
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
{#if currentStatus === 'queued'}
|
{#if currentStatus === 'queued'}
|
||||||
<div class="text-center font-bold text-xl">{$t('application.build.queued_waiting_exec')}</div>
|
<div class="text-center font-bold text-xl">{$t('application.build.queued_waiting_exec')}</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-end sticky top-0 p-2">
|
<div class="flex justify-end sticky top-0 p-1 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={followBuild}
|
||||||
class="bg-transparent hover:text-green-500 hover:bg-coolgray-500"
|
class="bg-transparent hover:text-green-500 hover:bg-coolgray-500"
|
||||||
|
@ -1,32 +1,13 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
import type { Load } from '@sveltejs/kit';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
|
||||||
try {
|
|
||||||
const response = await get(`/applications/${params.id}/logs`);
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
application: stuff.application,
|
|
||||||
...response
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
error: new Error(`Could not load ${url}`)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
|
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
let application: any = {};
|
||||||
|
let logsLoading = false;
|
||||||
let loadLogsInterval: any = null;
|
let loadLogsInterval: any = null;
|
||||||
let logs: any = [];
|
let logs: any = [];
|
||||||
let lastLog: any = null;
|
let lastLog: any = null;
|
||||||
@ -37,6 +18,8 @@
|
|||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
const response = await get(`/applications/${id}`);
|
||||||
|
application = response.application;
|
||||||
loadAllLogs();
|
loadAllLogs();
|
||||||
loadLogsInterval = setInterval(() => {
|
loadLogsInterval = setInterval(() => {
|
||||||
loadLogs();
|
loadLogs();
|
||||||
@ -48,6 +31,7 @@
|
|||||||
});
|
});
|
||||||
async function loadAllLogs() {
|
async function loadAllLogs() {
|
||||||
try {
|
try {
|
||||||
|
logsLoading = true;
|
||||||
const data: any = await get(`/applications/${id}/logs`);
|
const data: any = await get(`/applications/${id}/logs`);
|
||||||
if (data?.logs) {
|
if (data?.logs) {
|
||||||
lastLog = data.logs[data.logs.length - 1];
|
lastLog = data.logs[data.logs.length - 1];
|
||||||
@ -56,9 +40,12 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
logsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function loadLogs() {
|
async function loadLogs() {
|
||||||
|
if (logsLoading) return;
|
||||||
try {
|
try {
|
||||||
const newLogs: any = await get(
|
const newLogs: any = await get(
|
||||||
`/applications/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
|
`/applications/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
|
||||||
@ -157,7 +144,7 @@
|
|||||||
{#if loadLogsInterval}
|
{#if loadLogsInterval}
|
||||||
<LoadingLogs />
|
<LoadingLogs />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-end sticky top-0 p-2 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"
|
class="bg-transparent"
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
export const load: Load = async ({ fetch, params, stuff, url }) => {
|
export const load: Load = async ({ stuff, url }) => {
|
||||||
try {
|
try {
|
||||||
const response = await get(`/applications/${params.id}/previews`);
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
application: stuff.application,
|
application: stuff.application
|
||||||
...response
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -19,10 +17,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let containers: any;
|
|
||||||
export let application: any;
|
export let application: any;
|
||||||
export let PRMRSecrets: any;
|
|
||||||
export let applicationSecrets: any;
|
|
||||||
import Secret from './_Secret.svelte';
|
import Secret from './_Secret.svelte';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
@ -31,12 +26,33 @@
|
|||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import Loading from '$lib/components/Loading.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
let containers: any;
|
||||||
|
let PRMRSecrets: any;
|
||||||
|
let applicationSecrets: any;
|
||||||
|
let loading = {
|
||||||
|
init: true,
|
||||||
|
removing: false
|
||||||
|
};
|
||||||
async function refreshSecrets() {
|
async function refreshSecrets() {
|
||||||
const data = await get(`/applications/${id}/secrets`);
|
const data = await get(`/applications/${id}/secrets`);
|
||||||
PRMRSecrets = [...data.secrets];
|
PRMRSecrets = [...data.secrets];
|
||||||
}
|
}
|
||||||
|
async function removeApplication(container: any) {
|
||||||
|
try {
|
||||||
|
loading.removing = true;
|
||||||
|
await post(`/applications/${id}/stop/preview`, {
|
||||||
|
pullmergeRequestId: container.pullmergeRequestId
|
||||||
|
});
|
||||||
|
return window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
async function redeploy(container: any) {
|
async function redeploy(container: any) {
|
||||||
try {
|
try {
|
||||||
const { buildId } = await post(`/applications/${id}/deploy`, {
|
const { buildId } = await post(`/applications/${id}/deploy`, {
|
||||||
@ -55,6 +71,19 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
loading.init = true;
|
||||||
|
const response = await get(`/applications/${id}/previews`);
|
||||||
|
containers = response.containers;
|
||||||
|
PRMRSecrets = response.PRMRSecrets;
|
||||||
|
applicationSecrets = response.applicationSecrets;
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.init = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||||
@ -62,7 +91,7 @@
|
|||||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||||
Preview Deployments
|
Preview Deployments
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs">{application.name} </span>
|
<span class="text-xs">{application?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
|
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
|
||||||
<a
|
<a
|
||||||
@ -108,68 +137,81 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-6xl px-6 pt-4">
|
{#if loading.init}
|
||||||
<div class="flex justify-center py-4 text-center">
|
<Loading />
|
||||||
<Explainer
|
{:else}
|
||||||
customClass="w-full"
|
<div class="mx-auto max-w-6xl px-6 pt-4">
|
||||||
text={applicationSecrets.length === 0
|
<div class="flex justify-center py-4 text-center">
|
||||||
? "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."
|
<Explainer
|
||||||
: "These values overwrite application secrets in PR/MR deployments. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
|
customClass="w-full"
|
||||||
/>
|
text={applicationSecrets.length === 0
|
||||||
</div>
|
? "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."
|
||||||
{#if applicationSecrets.length !== 0}
|
: "These values overwrite application secrets in PR/MR deployments. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
|
||||||
<table class="mx-auto border-separate text-left">
|
/>
|
||||||
<thead>
|
</div>
|
||||||
<tr class="h-12">
|
{#if applicationSecrets.length !== 0}
|
||||||
<th scope="col">{$t('forms.name')}</th>
|
<table class="mx-auto border-separate text-left">
|
||||||
<th scope="col">{$t('forms.value')}</th>
|
<thead>
|
||||||
<th scope="col" class="w-64 text-center"
|
<tr class="h-12">
|
||||||
>{$t('application.preview.need_during_buildtime')}</th
|
<th scope="col">{$t('forms.name')}</th>
|
||||||
>
|
<th scope="col">{$t('forms.value')}</th>
|
||||||
<th scope="col" class="w-96 text-center">{$t('forms.action')}</th>
|
<th scope="col" class="w-64 text-center"
|
||||||
</tr>
|
>{$t('application.preview.need_during_buildtime')}</th
|
||||||
</thead>
|
>
|
||||||
<tbody>
|
<th scope="col" class="w-96 text-center">{$t('forms.action')}</th>
|
||||||
{#each applicationSecrets as secret}
|
</tr>
|
||||||
{#key secret.id}
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<Secret
|
{#each applicationSecrets as secret}
|
||||||
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
|
{#key secret.id}
|
||||||
isPRMRSecret
|
<tr>
|
||||||
name={secret.name}
|
<Secret
|
||||||
value={secret.value}
|
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
|
||||||
isBuildSecret={secret.isBuildSecret}
|
isPRMRSecret
|
||||||
on:refresh={refreshSecrets}
|
name={secret.name}
|
||||||
/>
|
value={secret.value}
|
||||||
</tr>
|
isBuildSecret={secret.isBuildSecret}
|
||||||
{/key}
|
on:refresh={refreshSecrets}
|
||||||
{/each}
|
/>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
{/key}
|
||||||
{/if}
|
{/each}
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
<div class="mx-auto max-w-4xl py-10">
|
|
||||||
<div class="flex flex-wrap justify-center space-x-2">
|
|
||||||
{#if containers.length > 0}
|
|
||||||
{#each containers as container}
|
|
||||||
<a href={container.fqdn} class="p-2 no-underline" target="_blank">
|
|
||||||
<div class="box-selection text-center hover:border-transparent hover:bg-coolgray-200">
|
|
||||||
<div class="truncate text-center text-xl font-bold">{getDomain(container.fqdn)}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
|
||||||
>{$t('application.preview.redeploy')}</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<div class="flex-col">
|
|
||||||
<div class="text-center font-bold text-xl">
|
|
||||||
{$t('application.preview.no_previews_available')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="mx-auto max-w-4xl py-10">
|
||||||
|
<div class="flex flex-wrap justify-center space-x-2">
|
||||||
|
{#if containers.length > 0}
|
||||||
|
{#each containers as container}
|
||||||
|
<a href={container.fqdn} class="p-2 no-underline" target="_blank">
|
||||||
|
<div class="box-selection text-center hover:border-transparent hover:bg-green-600">
|
||||||
|
<div class="truncate text-center text-xl font-bold">{getDomain(container.fqdn)}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
|
||||||
|
>{$t('application.preview.redeploy')}</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<button
|
||||||
|
class:bg-red-600={!loading.removing}
|
||||||
|
class:hover:bg-red-500={!loading.removing}
|
||||||
|
disabled={loading.removing}
|
||||||
|
on:click={() => removeApplication(container)}
|
||||||
|
>{loading.removing ? 'Removing...' : 'Remove Application'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="text-center font-bold text-xl">
|
||||||
|
{$t('application.preview.no_previews_available')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<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 ownApplications as application}
|
{#each ownApplications as application}
|
||||||
<a href="/applications/{application.id}" class="w-96 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'}
|
{#if application.buildPack.toLowerCase() === 'rust'}
|
||||||
@ -140,15 +140,18 @@
|
|||||||
{#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.destinationDocker?.name}
|
||||||
|
<div class="truncate text-center">{application.destinationDocker.name}</div>
|
||||||
|
{/if}
|
||||||
{#if !application.gitSourceId || !application.repository || !application.branch}
|
{#if !application.gitSourceId || !application.repository || !application.branch}
|
||||||
<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">
|
||||||
Git Source Missing
|
Git Source Missing
|
||||||
</div>
|
</div>
|
||||||
{:else if !application.destinationDockerId}
|
{:else if !application.destinationDockerId}
|
||||||
<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}
|
||||||
<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>
|
||||||
@ -158,10 +161,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if otherApplications.length > 0 && $appSession.teamId === '0'}
|
{#if otherApplications.length > 0 && $appSession.teamId === '0'}
|
||||||
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Applications</div>
|
<div class="px-6 pb-5 pt-10 text-2xl font-bold text-center">Other Applications</div>
|
||||||
<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 otherApplications as application}
|
{#each otherApplications as application}
|
||||||
<a href="/applications/{application.id}" class="w-96 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'}
|
{#if application.buildPack.toLowerCase() === 'rust'}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let database: any;
|
export let database: any;
|
||||||
export let privatePort: any;
|
export let privatePort: any;
|
||||||
export let settings: any;
|
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
@ -17,8 +16,9 @@
|
|||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession, status } from '$lib/store';
|
import { appSession, status } from '$lib/store';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
@ -49,12 +49,15 @@
|
|||||||
databaseDbUser = '';
|
databaseDbUser = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateUrl(): string {
|
function generateUrl(): string {
|
||||||
return `${database.type}://${
|
return `${database.type}://${
|
||||||
databaseDbUser ? databaseDbUser + ':' : ''
|
databaseDbUser ? databaseDbUser + ':' : ''
|
||||||
}${databaseDbUserPassword}@${
|
}${databaseDbUserPassword}@${
|
||||||
isPublic ? (settings.fqdn ? getDomain(settings.fqdn) : window.location.hostname) : database.id
|
isPublic
|
||||||
|
? database.destinationDocker.remoteEngine
|
||||||
|
? database.destinationDocker.remoteIpAddress
|
||||||
|
: $appSession.ipv4
|
||||||
|
: database.id
|
||||||
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`;
|
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +106,7 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5">
|
||||||
<div class="title">{$t('general')}</div>
|
<div class="title">{$t('general')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
@ -153,7 +156,8 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
value={database.version}
|
value={database.version}
|
||||||
disabled={$status.database.isRunning}
|
readonly
|
||||||
|
disabled={$status.database.isRunning || $status.database.initialLoading}
|
||||||
class:cursor-pointer={!$status.database.isRunning}
|
class:cursor-pointer={!$status.database.isRunning}
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
@ -203,9 +207,16 @@
|
|||||||
<EdgeDB {database} />
|
<EdgeDB {database} />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
||||||
<label for="url" class="text-base font-bold text-stone-100"
|
<div>
|
||||||
>{$t('database.connection_string')}</label
|
<label for="url" class="text-base font-bold text-stone-100"
|
||||||
>
|
>{$t('database.connection_string')}</label
|
||||||
|
>
|
||||||
|
{#if !isPublic && database.destinationDocker.remoteEngine}
|
||||||
|
<Explainer
|
||||||
|
text="You can only access the database with this URL if your application is deployed to the same Destination."
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
textarea={true}
|
textarea={true}
|
||||||
placeholder={$t('forms.generated_automatically_after_start')}
|
placeholder={$t('forms.generated_automatically_after_start')}
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
import { errorNotification, handlerNotFoundLoad } from '$lib/common';
|
||||||
import { appSession, status } from '$lib/store';
|
import { appSession, status, disabledButton } from '$lib/store';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
import Loading from '$lib/components/Loading.svelte';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
@ -66,6 +66,9 @@
|
|||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let statusInterval: any = false;
|
let statusInterval: any = false;
|
||||||
|
|
||||||
|
$disabledButton = !$appSession.isAdmin;
|
||||||
|
|
||||||
async function deleteDatabase() {
|
async function deleteDatabase() {
|
||||||
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
|
const sure = confirm(`Are you sure you would like to delete '${database.name}'?`);
|
||||||
if (sure) {
|
if (sure) {
|
||||||
@ -104,7 +107,7 @@
|
|||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if ($status.database.loading) return;
|
if ($status.database.loading) return;
|
||||||
$status.database.loading = true;
|
$status.database.loading = true;
|
||||||
const data = await get(`/databases/${id}`);
|
const data = await get(`/databases/${id}/status`);
|
||||||
$status.database.isRunning = data.isRunning;
|
$status.database.isRunning = data.isRunning;
|
||||||
$status.database.initialLoading = false;
|
$status.database.initialLoading = false;
|
||||||
$status.database.loading = false;
|
$status.database.loading = false;
|
||||||
@ -138,6 +141,32 @@
|
|||||||
<Loading fullscreen cover />
|
<Loading fullscreen cover />
|
||||||
{:else}
|
{:else}
|
||||||
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
||||||
|
{#if $status.database.isExited}
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/databases/${id}/logs` : null}
|
||||||
|
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
|
||||||
|
data-tooltip="Service exited with an error!"
|
||||||
|
sveltekit:prefetch
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentcolor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||||
|
/>
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
{#if $status.database.initialLoading}
|
{#if $status.database.initialLoading}
|
||||||
<button
|
<button
|
||||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
redirect: `/database/${params.id}`
|
redirect: `/database/${params.id}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const response = await get(`/destinations`);
|
const response = await get(`/destinations?onlyVerified=true`);
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...response
|
...response
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<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">{$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
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
import Databases from './_Databases/_Databases.svelte';
|
import Databases from './_Databases/_Databases.svelte';
|
||||||
import { status } from '$lib/store';
|
import { status } from '$lib/store';
|
||||||
export let database: any;
|
export let database: any;
|
||||||
export let settings: any;
|
|
||||||
export let privatePort: any;
|
export let privatePort: any;
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@ -86,4 +85,4 @@
|
|||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Databases bind:database {privatePort} {settings} />
|
<Databases bind:database {privatePort}/>
|
||||||
|
@ -1,31 +1,13 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
import type { Load } from '@sveltejs/kit';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
|
||||||
try {
|
|
||||||
const response = await get(`/databases/${params.id}/logs`);
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
database: stuff.database,
|
|
||||||
...response
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
error: new Error(`Could not load ${url}`)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let database: any;
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import LoadingLogs from './_Loading.svelte';
|
import LoadingLogs from './_Loading.svelte';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loadLogsInterval: any = null;
|
let loadLogsInterval: any = null;
|
||||||
let logs: any = [];
|
let logs: any = [];
|
||||||
@ -34,9 +16,12 @@ import { errorNotification } from '$lib/common';
|
|||||||
let followingLogs: any;
|
let followingLogs: any;
|
||||||
let logsEl: any;
|
let logsEl: any;
|
||||||
let position = 0;
|
let position = 0;
|
||||||
|
let loadingLogs = false;
|
||||||
|
let database: any = {};
|
||||||
|
|
||||||
const { id } = $page.params;
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
const { logs: firstLogs } = await get(`/databases/${id}/logs`);
|
||||||
|
logs = firstLogs;
|
||||||
loadAllLogs();
|
loadAllLogs();
|
||||||
loadLogsInterval = setInterval(() => {
|
loadLogsInterval = setInterval(() => {
|
||||||
loadLogs();
|
loadLogs();
|
||||||
@ -48,6 +33,7 @@ import { errorNotification } from '$lib/common';
|
|||||||
});
|
});
|
||||||
async function loadAllLogs() {
|
async function loadAllLogs() {
|
||||||
try {
|
try {
|
||||||
|
loadingLogs = true;
|
||||||
const data: any = await get(`/databases/${id}/logs`);
|
const data: any = await get(`/databases/${id}/logs`);
|
||||||
if (data?.logs) {
|
if (data?.logs) {
|
||||||
lastLog = data.logs[data.logs.length - 1];
|
lastLog = data.logs[data.logs.length - 1];
|
||||||
@ -56,13 +42,15 @@ import { errorNotification } from '$lib/common';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loadingLogs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function loadLogs() {
|
async function loadLogs() {
|
||||||
|
if (loadingLogs) return;
|
||||||
try {
|
try {
|
||||||
const newLogs: any = await get(
|
loadingLogs = true;
|
||||||
`/databases/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
|
const newLogs: any = await get(`/databases/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`);
|
||||||
);
|
|
||||||
|
|
||||||
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
|
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
|
||||||
logs = logs.concat(newLogs.logs);
|
logs = logs.concat(newLogs.logs);
|
||||||
@ -70,6 +58,8 @@ import { errorNotification } from '$lib/common';
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loadingLogs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function detect() {
|
function detect() {
|
||||||
@ -137,7 +127,7 @@ import { errorNotification } from '$lib/common';
|
|||||||
{#if loadLogsInterval}
|
{#if loadLogsInterval}
|
||||||
<LoadingLogs />
|
<LoadingLogs />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-end sticky top-0 p-2 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"
|
class="bg-transparent"
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<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 ownDatabases as database}
|
{#each ownDatabases as database}
|
||||||
<a href="/databases/{database.id}" class="w-96 p-2 no-underline">
|
<a href="/databases/{database.id}" class="p-2 no-underline">
|
||||||
<div class="box-selection group relative hover:bg-purple-600">
|
<div class="box-selection group relative hover:bg-purple-600">
|
||||||
{#if database.type === 'clickhouse'}
|
{#if database.type === 'clickhouse'}
|
||||||
<Clickhouse isAbsolute />
|
<Clickhouse isAbsolute />
|
||||||
@ -103,6 +103,9 @@
|
|||||||
{#if $appSession.teamId === '0' && otherDatabases.length > 0}
|
{#if $appSession.teamId === '0' && otherDatabases.length > 0}
|
||||||
<div class="truncate text-center">{database.teams[0].name}</div>
|
<div class="truncate text-center">{database.teams[0].name}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if database.destinationDocker?.name}
|
||||||
|
<div class="truncate text-center">{database.destinationDocker.name}</div>
|
||||||
|
{/if}
|
||||||
{#if !database.type}
|
{#if !database.type}
|
||||||
<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">
|
||||||
{$t('application.configuration.configuration_missing')}
|
{$t('application.configuration.configuration_missing')}
|
||||||
@ -113,10 +116,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if otherDatabases.length > 0 && $appSession.teamId === '0'}
|
{#if otherDatabases.length > 0 && $appSession.teamId === '0'}
|
||||||
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Databases</div>
|
<div class="px-6 pb-5 pt-10 text-2xl font-bold text-center">Other Databases</div>
|
||||||
<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 otherDatabases as database}
|
{#each otherDatabases as database}
|
||||||
<a href="/databases/{database.id}" class="w-96 p-2 no-underline">
|
<a href="/databases/{database.id}" class="p-2 no-underline">
|
||||||
<div class="box-selection group relative hover:bg-purple-600">
|
<div class="box-selection group relative hover:bg-purple-600">
|
||||||
{#if database.type === 'clickhouse'}
|
{#if database.type === 'clickhouse'}
|
||||||
<Clickhouse isAbsolute />
|
<Clickhouse isAbsolute />
|
||||||
|
@ -1,56 +1,65 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let destination: any;
|
export let destination: any;
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
export let state: any;
|
|
||||||
|
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
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 Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
|
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
|
||||||
let loading = false;
|
let loading = {
|
||||||
let loadingProxy = false;
|
restart: false,
|
||||||
let restarting = false;
|
proxy: false,
|
||||||
|
save: false
|
||||||
|
};
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading.save = true;
|
||||||
try {
|
try {
|
||||||
return await post(`/destinations/${id}`, { ...destination });
|
await post(`/destinations/${id}`, { ...destination });
|
||||||
|
toast.push('Configuration saved.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading.save = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (state === false && destination.isCoolifyProxyUsed === true) {
|
loading.proxy = true;
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
const { isRunning } = await get(`/destinations/${id}/status`);
|
||||||
|
let proxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
|
if (isRunning === false && destination.isCoolifyProxyUsed === true) {
|
||||||
try {
|
try {
|
||||||
await post(`/destinations/${id}/settings`, {
|
await post(`/destinations/${id}/settings`, {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
isCoolifyProxyUsed: proxyUsed,
|
||||||
engine: destination.engine
|
engine: destination.engine
|
||||||
});
|
});
|
||||||
await stopProxy();
|
await stopProxy();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
} else if (state === true && destination.isCoolifyProxyUsed === false) {
|
} else if (isRunning === true && destination.isCoolifyProxyUsed === false) {
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
|
||||||
try {
|
try {
|
||||||
await post(`/destinations/${id}/settings`, {
|
await post(`/destinations/${id}/settings`, {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
isCoolifyProxyUsed: proxyUsed,
|
||||||
engine: destination.engine
|
engine: destination.engine
|
||||||
});
|
});
|
||||||
await startProxy();
|
await startProxy();
|
||||||
|
destination.isCoolifyProxyUsed = proxyUsed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.proxy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loading.proxy = false;
|
||||||
});
|
});
|
||||||
async function changeProxySetting() {
|
async function changeProxySetting() {
|
||||||
if (!cannotDisable) {
|
if (!cannotDisable) {
|
||||||
@ -67,7 +76,7 @@
|
|||||||
}
|
}
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
try {
|
try {
|
||||||
loadingProxy = true;
|
loading.proxy = true;
|
||||||
await post(`/destinations/${id}/settings`, {
|
await post(`/destinations/${id}/settings`, {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
||||||
engine: destination.engine
|
engine: destination.engine
|
||||||
@ -80,7 +89,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loadingProxy = false;
|
loading.proxy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +113,7 @@
|
|||||||
const sure = confirm($t('destination.confirm_restart_proxy'));
|
const sure = confirm($t('destination.confirm_restart_proxy'));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
try {
|
try {
|
||||||
restarting = true;
|
loading.restart = true;
|
||||||
toast.push($t('destination.coolify_proxy_restarting'));
|
toast.push($t('destination.coolify_proxy_restarting'));
|
||||||
await post(`/destinations/${id}/restart`, {
|
await post(`/destinations/${id}/restart`, {
|
||||||
engine: destination.engine,
|
engine: destination.engine,
|
||||||
@ -115,7 +124,7 @@
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
} finally {
|
} finally {
|
||||||
restarting = false;
|
loading.restart = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,23 +137,20 @@
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="bg-sky-600 hover:bg-sky-500"
|
class="bg-sky-600 hover:bg-sky-500"
|
||||||
class:bg-sky-600={!loading}
|
class:bg-sky-600={!loading.save}
|
||||||
class:hover:bg-sky-500={!loading}
|
class:hover:bg-sky-500={!loading.save}
|
||||||
disabled={loading}
|
disabled={loading.save}
|
||||||
>{loading ? $t('forms.saving') : $t('forms.save')}
|
>{loading.save ? $t('forms.saving') : $t('forms.save')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
|
class={loading.restart ? '' : 'bg-red-600 hover:bg-red-500'}
|
||||||
disabled={restarting}
|
disabled={loading.restart}
|
||||||
on:click|preventDefault={forceRestartProxy}
|
on:click|preventDefault={forceRestartProxy}
|
||||||
>{restarting
|
>{loading.restart
|
||||||
? $t('destination.restarting_please_wait')
|
? $t('destination.restarting_please_wait')
|
||||||
: $t('destination.force_restart_proxy')}</button
|
: $t('destination.force_restart_proxy')}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
|
|
||||||
>Scan for applications</button
|
|
||||||
> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10 ">
|
<div class="grid grid-cols-2 items-center px-10 ">
|
||||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||||
@ -168,10 +174,6 @@
|
|||||||
value={destination.engine}
|
value={destination.engine}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="flex items-center">
|
|
||||||
<label for="remoteEngine">Remote Engine?</label>
|
|
||||||
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
|
|
||||||
</div> -->
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@ -186,7 +188,7 @@
|
|||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
loading={loadingProxy}
|
loading={loading.proxy}
|
||||||
disabled={cannotDisable}
|
disabled={cannotDisable}
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
bind:setting={destination.isCoolifyProxyUsed}
|
||||||
on:click={changeProxySetting}
|
on:click={changeProxySetting}
|
||||||
|
@ -21,10 +21,9 @@
|
|||||||
payload = {
|
payload = {
|
||||||
name: $t('sources.remote_docker'),
|
name: $t('sources.remote_docker'),
|
||||||
remoteEngine: true,
|
remoteEngine: true,
|
||||||
ipAddress: null,
|
remoteIpAddress: null,
|
||||||
user: 'root',
|
remoteUser: 'root',
|
||||||
port: 22,
|
remotePort: 22,
|
||||||
sshPrivateKey: null,
|
|
||||||
network: cuid(),
|
network: cuid(),
|
||||||
isCoolifyProxyUsed: true
|
isCoolifyProxyUsed: true
|
||||||
};
|
};
|
||||||
@ -44,7 +43,7 @@
|
|||||||
<button class="w-32" on:click={() => setPredefined('localDocker')}
|
<button class="w-32" on:click={() => setPredefined('localDocker')}
|
||||||
>{$t('sources.local_docker')}</button
|
>{$t('sources.local_docker')}</button
|
||||||
>
|
>
|
||||||
<!-- <button class="w-32" on:click={() => setPredefined('remoteDocker')}>Remote Docker</button> -->
|
<button class="w-32" on:click={() => setPredefined('remoteDocker')}>Remote Docker</button>
|
||||||
<!-- <button class="w-32" on:click={() => setPredefined('kubernetes')}>Kubernetes</button> -->
|
<!-- <button class="w-32" on:click={() => setPredefined('kubernetes')}>Kubernetes</button> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,23 +5,35 @@
|
|||||||
|
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
|
if (loading) return;
|
||||||
try {
|
try {
|
||||||
const { id } = await post('/new/destination/docker', {
|
loading = true;
|
||||||
|
await post(`/destinations/check`, { network: payload.network });
|
||||||
|
const { id } = await post(`/destinations/new`, {
|
||||||
...payload
|
...payload
|
||||||
});
|
});
|
||||||
return await goto(`/destinations/${id}`);
|
return await goto(`/destinations/${id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="text-center flex justify-center">
|
||||||
|
<Explainer
|
||||||
|
customClass="max-w-[32rem]"
|
||||||
|
text="Remote Docker Engines are using <span class='text-white font-bold'>SSH connection</span> to initiate connection. You need to setup an <span class='text-white font-bold'>SSH key</span> in advance on the server and install Docker. <br>See <a class='text-white' href='https://docs.coollabs.io'>docs</a> for more details."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<div class="flex justify-center px-6 pb-8">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex items-center space-x-2 pb-5">
|
<div class="flex items-center space-x-2 pb-5">
|
||||||
@ -44,40 +56,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="ipAddress" class="text-base font-bold text-stone-100"
|
<label for="remoteIpAddress" class="text-base font-bold text-stone-100"
|
||||||
>{$t('forms.ip_address')}</label
|
>{$t('forms.ip_address')}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
name="ipAddress"
|
name="remoteIpAddress"
|
||||||
placeholder="{$t('forms.eg')}: 192.168..."
|
placeholder="{$t('forms.eg')}: 192.168..."
|
||||||
bind:value={payload.ipAddress}
|
bind:value={payload.remoteIpAddress}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="user" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
|
<label for="remoteUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
|
||||||
<input required name="user" placeholder="{$t('forms.eg')}: root" bind:value={payload.user} />
|
<input
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
|
|
||||||
<input required name="port" placeholder="{$t('forms.eg')}: 22" bind:value={payload.port} />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="sshPrivateKey" class="text-base font-bold text-stone-100"
|
|
||||||
>{$t('forms.ssh_private_key')}</label
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
rows="10"
|
|
||||||
class="resize-none"
|
|
||||||
required
|
required
|
||||||
name="sshPrivateKey"
|
name="remoteUser"
|
||||||
placeholder="{$t('forms.eg')}: -----BEGIN...."
|
placeholder="{$t('forms.eg')}: root"
|
||||||
bind:value={payload.sshPrivateKey}
|
bind:value={payload.remoteUser}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="remotePort" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
name="remotePort"
|
||||||
|
placeholder="{$t('forms.eg')}: 22"
|
||||||
|
bind:value={payload.remotePort}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
||||||
<input
|
<input
|
||||||
|
@ -1,80 +1,90 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let destination: any;
|
export let destination: any;
|
||||||
export let settings: any
|
export let settings: any;
|
||||||
export let state: any
|
|
||||||
|
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
import { post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, generateRemoteEngine } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { appSession } from '$lib/store';
|
import { appSession } from '$lib/store';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
|
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
|
||||||
// let scannedApps = [];
|
let loading = {
|
||||||
let loading = false;
|
restart: false,
|
||||||
let restarting = false;
|
proxy: true,
|
||||||
|
save: false,
|
||||||
|
verify: false
|
||||||
|
};
|
||||||
|
|
||||||
|
$: isDisabled = !$appSession.isAdmin;
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading.save = true;
|
||||||
try {
|
try {
|
||||||
return await post(`/destinations/${id}.json`, { ...destination });
|
await post(`/destinations/${id}`, { ...destination });
|
||||||
} catch (error ) {
|
toast.push('Configuration saved.');
|
||||||
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading.save = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// async function scanApps() {
|
|
||||||
// scannedApps = [];
|
|
||||||
// const data = await fetch(`/destinations/${id}/scan.json`);
|
|
||||||
// const { containers } = await data.json();
|
|
||||||
// scannedApps = containers;
|
|
||||||
// }
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (state === false && destination.isCoolifyProxyUsed === true) {
|
loading.proxy = true;
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
if (destination.remoteEngine && destination.remoteVerified) {
|
||||||
try {
|
const { isRunning } = await get(`/destinations/${id}/status`);
|
||||||
await post(`/destinations/${id}/settings.json`, {
|
if (isRunning === false && destination.isCoolifyProxyUsed === true) {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
engine: destination.engine
|
try {
|
||||||
});
|
await post(`/destinations/${id}/settings`, {
|
||||||
await stopProxy();
|
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
||||||
} catch (error ) {
|
engine: destination.engine
|
||||||
return errorNotification(error);
|
});
|
||||||
}
|
await stopProxy();
|
||||||
} else if (state === true && destination.isCoolifyProxyUsed === false) {
|
} catch (error) {
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
return errorNotification(error);
|
||||||
try {
|
}
|
||||||
await post(`/destinations/${id}/settings.json`, {
|
} else if (isRunning === true && destination.isCoolifyProxyUsed === false) {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
engine: destination.engine
|
try {
|
||||||
});
|
await post(`/destinations/${id}/settings`, {
|
||||||
await startProxy();
|
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
||||||
} catch ( error ) {
|
engine: destination.engine
|
||||||
return errorNotification(error);
|
});
|
||||||
|
await startProxy();
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loading.proxy = false;
|
||||||
});
|
});
|
||||||
async function changeProxySetting() {
|
async function changeProxySetting() {
|
||||||
|
loading.proxy = true;
|
||||||
if (!cannotDisable) {
|
if (!cannotDisable) {
|
||||||
const isProxyActivated = destination.isCoolifyProxyUsed;
|
const isProxyActivated = destination.isCoolifyProxyUsed;
|
||||||
if (isProxyActivated) {
|
if (isProxyActivated) {
|
||||||
const sure = confirm(
|
const sure = confirm(
|
||||||
`Are you sure you want to ${
|
`Are you sure you want to ${
|
||||||
destination.isCoolifyProxyUsed ? 'disable' : 'enable'
|
destination.isCoolifyProxyUsed ? 'disable' : 'enable'
|
||||||
} Coolify proxy? It will remove the proxy for all configured networks and all deployments on '${
|
} Coolify proxy? It will remove the proxy for all configured networks and all deployments! Nothing will be reachable if you do it!`
|
||||||
destination.engine
|
|
||||||
}'! Nothing will be reachable if you do it!`
|
|
||||||
);
|
);
|
||||||
if (!sure) return;
|
if (!sure) {
|
||||||
|
loading.proxy = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
|
let proxyUsed = !destination.isCoolifyProxyUsed;
|
||||||
try {
|
try {
|
||||||
await post(`/destinations/${id}/settings.json`, {
|
await post(`/destinations/${id}/settings`, {
|
||||||
isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
|
isCoolifyProxyUsed: proxyUsed,
|
||||||
engine: destination.engine
|
engine: destination.engine
|
||||||
});
|
});
|
||||||
if (isProxyActivated) {
|
if (isProxyActivated) {
|
||||||
@ -82,15 +92,17 @@ import { appSession } from '$lib/store';
|
|||||||
} else {
|
} else {
|
||||||
await startProxy();
|
await startProxy();
|
||||||
}
|
}
|
||||||
|
destination.isCoolifyProxyUsed = proxyUsed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.proxy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function stopProxy() {
|
async function stopProxy() {
|
||||||
try {
|
try {
|
||||||
const engine = generateRemoteEngine(destination);
|
await post(`/destinations/${id}/stop`, { engine: destination.engine });
|
||||||
await post(`/destinations/${id}/stop.json`, { engine });
|
|
||||||
return toast.push($t('destination.coolify_proxy_stopped'));
|
return toast.push($t('destination.coolify_proxy_stopped'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@ -98,8 +110,7 @@ import { appSession } from '$lib/store';
|
|||||||
}
|
}
|
||||||
async function startProxy() {
|
async function startProxy() {
|
||||||
try {
|
try {
|
||||||
const engine = generateRemoteEngine(destination);
|
await post(`/destinations/${id}/start`, { engine: destination.engine });
|
||||||
await post(`/destinations/${id}/start.json`, { engine });
|
|
||||||
return toast.push($t('destination.coolify_proxy_started'));
|
return toast.push($t('destination.coolify_proxy_started'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@ -109,9 +120,9 @@ import { appSession } from '$lib/store';
|
|||||||
const sure = confirm($t('destination.confirm_restart_proxy'));
|
const sure = confirm($t('destination.confirm_restart_proxy'));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
try {
|
try {
|
||||||
restarting = true;
|
loading.restart = true;
|
||||||
toast.push($t('destination.coolify_proxy_restarting'));
|
toast.push($t('destination.coolify_proxy_restarting'));
|
||||||
await post(`/destinations/${id}/restart.json`, {
|
await post(`/destinations/${id}/restart`, {
|
||||||
engine: destination.engine,
|
engine: destination.engine,
|
||||||
fqdn: settings.fqdn
|
fqdn: settings.fqdn
|
||||||
});
|
});
|
||||||
@ -119,9 +130,24 @@ import { appSession } from '$lib/store';
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
} finally {
|
||||||
|
loading.restart = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function verifyRemoteDocker() {
|
||||||
|
try {
|
||||||
|
loading.verify = true;
|
||||||
|
await post(`/destinations/${id}/verify`, {});
|
||||||
|
destination.remoteVerified = true;
|
||||||
|
toast.push('Remote Docker Engine verified!');
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.verify = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
@ -131,23 +157,28 @@ import { appSession } from '$lib/store';
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="bg-sky-600 hover:bg-sky-500"
|
class="bg-sky-600 hover:bg-sky-500"
|
||||||
class:bg-sky-600={!loading}
|
class:bg-sky-600={!loading.save}
|
||||||
class:hover:bg-sky-500={!loading}
|
class:hover:bg-sky-500={!loading.save}
|
||||||
disabled={loading}
|
disabled={loading.save}
|
||||||
>{loading ? $t('forms.saving') : $t('forms.save')}
|
>{loading.save ? $t('forms.saving') : $t('forms.save')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
{#if !destination.remoteVerified}
|
||||||
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
|
<button
|
||||||
disabled={restarting}
|
disabled={loading.verify}
|
||||||
on:click|preventDefault={forceRestartProxy}
|
on:click|preventDefault|stopPropagation={verifyRemoteDocker}
|
||||||
>{restarting
|
>{loading.verify ? 'Verifying...' : 'Verify Remote Docker Engine'}</button
|
||||||
? $t('destination.restarting_please_wait')
|
>
|
||||||
: $t('destination.force_restart_proxy')}</button
|
{:else}
|
||||||
>
|
<button
|
||||||
|
class={loading.restart ? '' : 'bg-red-600 hover:bg-red-500'}
|
||||||
|
disabled={loading.restart}
|
||||||
|
on:click|preventDefault={forceRestartProxy}
|
||||||
|
>{loading.restart
|
||||||
|
? $t('destination.restarting_please_wait')
|
||||||
|
: $t('destination.force_restart_proxy')}</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
|
|
||||||
>Scan for applications</button
|
|
||||||
> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10 ">
|
<div class="grid grid-cols-2 items-center px-10 ">
|
||||||
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
|
||||||
@ -159,22 +190,6 @@ import { appSession } from '$lib/store';
|
|||||||
bind:value={destination.name}
|
bind:value={destination.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
|
||||||
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
id="engine"
|
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
name="engine"
|
|
||||||
placeholder="{$t('forms.eg')}: /var/run/docker.sock"
|
|
||||||
value={destination.engine}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="flex items-center">
|
|
||||||
<label for="remoteEngine">Remote Engine?</label>
|
|
||||||
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
|
|
||||||
</div> -->
|
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@ -186,9 +201,53 @@ import { appSession } from '$lib/store';
|
|||||||
value={destination.network}
|
value={destination.network}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="remoteIpAddress" class="text-base font-bold text-stone-100">IP Address</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="remoteIpAddress"
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="remoteIpAddress"
|
||||||
|
value={destination.remoteIpAddress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="remoteUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="remoteUser"
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="remoteUser"
|
||||||
|
value={destination.remoteUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="remotePort" class="text-base font-bold text-stone-100">Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="remotePort"
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="remotePort"
|
||||||
|
value={destination.remotePort}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="sshKey" class="text-base font-bold text-stone-100">SSH Key</label>
|
||||||
|
<a
|
||||||
|
href={!isDisabled ? `/destinations/${id}/configuration/sshkey?from=/destinations/${id}` : ''}
|
||||||
|
class="no-underline"
|
||||||
|
><input
|
||||||
|
value={destination.sshKey.name}
|
||||||
|
readonly
|
||||||
|
id="sshKey"
|
||||||
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
|
/></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
disabled={cannotDisable}
|
disabled={cannotDisable}
|
||||||
|
loading={loading.proxy}
|
||||||
bind:setting={destination.isCoolifyProxyUsed}
|
bind:setting={destination.isCoolifyProxyUsed}
|
||||||
on:click={changeProxySetting}
|
on:click={changeProxySetting}
|
||||||
title={$t('destination.use_coolify_proxy')}
|
title={$t('destination.use_coolify_proxy')}
|
||||||
@ -200,27 +259,3 @@ import { appSession } from '$lib/store';
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!-- <div class="flex justify-center">
|
|
||||||
{#if payload.isCoolifyProxyUsed}
|
|
||||||
{#if state}
|
|
||||||
<button on:click={stopProxy}>Stop proxy</button>
|
|
||||||
{:else}
|
|
||||||
<button on:click={startProxy}>Start proxy</button>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- {#if scannedApps.length > 0}
|
|
||||||
<div class="flex justify-center px-6 pb-10">
|
|
||||||
<div class="flex space-x-2 h-8 items-center">
|
|
||||||
<div class="font-bold text-xl text-white">Found applications</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="max-w-4xl mx-auto px-6">
|
|
||||||
<div class="flex space-x-2 justify-center">
|
|
||||||
{#each scannedApps as app}
|
|
||||||
<FoundApp {app} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
|
@ -1,27 +1,46 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
export const load: Load = async ({ fetch, url, params }) => {
|
function checkConfiguration(destination: any): string | null {
|
||||||
|
let configurationPhase = null;
|
||||||
|
if (!destination?.remoteEngine) return configurationPhase;
|
||||||
|
if (!destination?.sshKey) {
|
||||||
|
configurationPhase = 'sshkey';
|
||||||
|
}
|
||||||
|
return configurationPhase;
|
||||||
|
}
|
||||||
|
export const load: Load = async ({ url, params }) => {
|
||||||
try {
|
try {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
const response = await get(`/destinations/${id}`);
|
const response = await get(`/destinations/${id}`);
|
||||||
const { destination, settings, state } = response;
|
const { destination, settings } = response;
|
||||||
if (id !== 'new' && (!destination || Object.entries(destination).length === 0)) {
|
if (id !== 'new' && (!destination || Object.entries(destination).length === 0)) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/destinations'
|
redirect: '/destinations'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const configurationPhase = checkConfiguration(destination);
|
||||||
|
if (
|
||||||
|
configurationPhase &&
|
||||||
|
url.pathname !== `/destinations/${params.id}/configuration/${configurationPhase}`
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
status: 302,
|
||||||
|
redirect: `/destinations/${params.id}/configuration/${configurationPhase}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
destination
|
destination
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
destination,
|
destination,
|
||||||
settings,
|
settings
|
||||||
state
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
return handlerNotFoundLoad(error, url);
|
return handlerNotFoundLoad(error, url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
||||||
|
try {
|
||||||
|
const response = await get(`/settings`);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...response
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
error: new Error(`Could not load ${url}`)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { get, post } from '$lib/api';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
const from = $page.url.searchParams.get('from');
|
||||||
|
|
||||||
|
export let sshKeys: any;
|
||||||
|
|
||||||
|
async function handleSubmit(sshKeyId: string) {
|
||||||
|
try {
|
||||||
|
await post(`/destinations/${id}/configuration/sshKey`, { id: sshKeyId });
|
||||||
|
return await goto(from || `/destinations/${id}`);
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
|
<div class="mr-4 text-2xl tracking-tight">Select a SSH Keys</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col justify-center">
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
|
||||||
|
{#if sshKeys.length > 0}
|
||||||
|
{#each sshKeys as sshKey}
|
||||||
|
<div class="p-2 relative">
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(sshKey.id)}>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
|
||||||
|
>
|
||||||
|
<div class="font-bold text-xl text-center truncate">{sshKey.name}</div>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="pb-2 text-center font-bold">No SSH key found</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<a
|
||||||
|
href="/settings/ssh-keys"
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="add-icon bg-sky-600 hover:bg-sky-500"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -39,7 +39,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 tracking-tight">{$t('index.destinations')}</div>
|
<div class="mr-4 text-2xl tracking-tight">{$t('index.destinations')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<a href="/destinations/new" class="add-icon bg-sky-600 hover:bg-sky-500">
|
<a href="/destinations/new" class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||||
<svg
|
<svg
|
||||||
class="w-6"
|
class="w-6"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -66,24 +66,112 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<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 ownDestinations as destination}
|
{#each ownDestinations as destination}
|
||||||
<a href="/destinations/{destination.id}" class="w-96 p-2 no-underline">
|
<a href="/destinations/{destination.id}" class="p-2 no-underline relative">
|
||||||
<div class="box-selection hover:bg-sky-600">
|
<div class="box-selection hover:bg-sky-600 ">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||||
|
/>
|
||||||
|
<path d="M5 10h3v3h-3z" />
|
||||||
|
<path d="M8 10h3v3h-3z" />
|
||||||
|
<path d="M11 10h3v3h-3z" />
|
||||||
|
<path d="M8 7h3v3h-3z" />
|
||||||
|
<path d="M11 7h3v3h-3z" />
|
||||||
|
<path d="M11 4h3v3h-3z" />
|
||||||
|
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||||
|
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||||
|
</svg>
|
||||||
|
{#if destination.remoteEngine}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="absolute top-0 left-9 -m-4 h-6 w-6 text-sky-500 rotate-45"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||||
|
<path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
|
||||||
|
<path d="M6.343 12.343a8 8 0 0 1 11.314 0" />
|
||||||
|
<path d="M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
||||||
{#if $appSession.teamId === '0' && otherDestinations.length > 0}
|
{#if $appSession.teamId === '0' && otherDestinations.length > 0}
|
||||||
<div class="truncate text-center">{destination.teams[0].name}</div>
|
<div class="truncate text-center">{destination.teams[0].name}</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="truncate text-center">{destination.network}</div>
|
<div class="truncate text-center">{destination.network}</div>
|
||||||
|
{#if $appSession.teamId === '0' && destination.remoteVerified === false && destination.remoteEngine}
|
||||||
|
<div class="truncate text-center text-sm text-red-500">Not verified yet</div>
|
||||||
|
{/if}
|
||||||
|
{#if destination.remoteEngine && !destination.sshKeyId}
|
||||||
|
<div class="truncate text-center text-sm text-red-500">SSH Key missing!</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if otherDestinations.length > 0 && $appSession.teamId === '0'}
|
{#if otherDestinations.length > 0 && $appSession.teamId === '0'}
|
||||||
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Destinations</div>
|
<div class="px-6 pb-5 pt-10 text-2xl font-bold text-center">Other Destinations</div>
|
||||||
<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 otherDestinations as destination}
|
{#each otherDestinations as destination}
|
||||||
<a href="/destinations/{destination.id}" class="w-96 p-2 no-underline">
|
<a href="/destinations/{destination.id}" class="p-2 no-underline relative">
|
||||||
<div class="box-selection hover:bg-sky-600">
|
<div class="box-selection hover:bg-sky-600">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
|
||||||
|
/>
|
||||||
|
<path d="M5 10h3v3h-3z" />
|
||||||
|
<path d="M8 10h3v3h-3z" />
|
||||||
|
<path d="M11 10h3v3h-3z" />
|
||||||
|
<path d="M8 7h3v3h-3z" />
|
||||||
|
<path d="M11 7h3v3h-3z" />
|
||||||
|
<path d="M11 4h3v3h-3z" />
|
||||||
|
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
|
||||||
|
<line x1="10" y1="16" x2="10" y2="16.01" />
|
||||||
|
</svg>
|
||||||
|
{#if destination.remoteEngine}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="absolute top-0 left-9 -m-4 h-6 w-6 text-sky-500 rotate-45"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||||
|
<path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
|
||||||
|
<path d="M6.343 12.343a8 8 0 0 1 11.314 0" />
|
||||||
|
<path d="M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
<div class="truncate text-center text-xl font-bold">{destination.name}</div>
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<div class="truncate text-center">{destination.teams[0].name}</div>
|
<div class="truncate text-center">{destination.teams[0].name}</div>
|
||||||
|
@ -189,54 +189,52 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<div class="title font-bold">Teams</div>
|
<div class="title font-bold">Teams</div>
|
||||||
<div class="flex items-center justify-center pt-10">
|
<div class="flex-col items-center justify-center pt-10">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-row flex-wrap justify-center px-2 pb-10 md:flex-row">
|
||||||
<div class="flex flex-row flex-wrap justify-center px-2 pb-10 md:flex-row">
|
{#each ownTeams as team}
|
||||||
{#each ownTeams as team}
|
<a href="/iam/team/{team.id}" class="p-2 no-underline">
|
||||||
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
|
<div class="box-selection relative">
|
||||||
<div class="box-selection relative">
|
<div>
|
||||||
<div>
|
<div class="truncate text-center text-xl font-bold">
|
||||||
<div class="truncate text-center text-xl font-bold">
|
{team.name}
|
||||||
{team.name}
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 text-center text-xs">
|
|
||||||
{team.permissions?.length} member(s)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center pt-3">
|
<div class="mt-1 text-center text-xs">
|
||||||
<button
|
{team.permissions?.length} member(s)
|
||||||
on:click|preventDefault={() => switchTeam(team.id)}
|
|
||||||
class:bg-fuchsia-600={$appSession.teamId !== team.id}
|
|
||||||
class:hover:bg-fuchsia-500={$appSession.teamId !== team.id}
|
|
||||||
class:bg-transparent={$appSession.teamId === team.id}
|
|
||||||
disabled={$appSession.teamId === team.id}
|
|
||||||
>{$appSession.teamId === team.id ? 'Current Team' : 'Switch Team'}</button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center justify-center pt-3">
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={() => switchTeam(team.id)}
|
||||||
|
class:bg-fuchsia-600={$appSession.teamId !== team.id}
|
||||||
|
class:hover:bg-fuchsia-500={$appSession.teamId !== team.id}
|
||||||
|
class:bg-transparent={$appSession.teamId === team.id}
|
||||||
|
disabled={$appSession.teamId === team.id}
|
||||||
|
>{$appSession.teamId === team.id ? 'Current Team' : 'Switch Team'}</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if $appSession.teamId === '0' && allTeams.length > 0}
|
||||||
|
<div class="pb-5 pt-10 text-xl font-bold">Other Teams</div>
|
||||||
|
<div class="flex flex-row flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each allTeams as team}
|
||||||
|
<a href="/iam/team/{team.id}" class="p-2 no-underline">
|
||||||
|
<div
|
||||||
|
class="box-selection relative"
|
||||||
|
class:hover:bg-fuchsia-600={team.id !== '0'}
|
||||||
|
class:hover:bg-red-500={team.id === '0'}
|
||||||
|
>
|
||||||
|
<div class="truncate text-center text-xl font-bold">
|
||||||
|
{team.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 text-center">{team.permissions?.length} member(s)</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if $appSession.teamId === '0' && allTeams.length > 0}
|
{/if}
|
||||||
<div class="pb-5 pt-10 text-xl font-bold">Other Teams</div>
|
|
||||||
<div class="flex flex-row flex-wrap justify-center px-2 md:flex-row">
|
|
||||||
{#each allTeams as team}
|
|
||||||
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
|
|
||||||
<div
|
|
||||||
class="box-selection relative"
|
|
||||||
class:hover:bg-fuchsia-600={team.id !== '0'}
|
|
||||||
class:hover:bg-red-500={team.id === '0'}
|
|
||||||
>
|
|
||||||
<div class="truncate text-center text-xl font-bold">
|
|
||||||
{team.name}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-1 text-center">{team.permissions?.length} member(s)</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -121,10 +121,10 @@
|
|||||||
<div class="flex space-x-2 h-8 items-center justify-center pt-8">
|
<div class="flex space-x-2 h-8 items-center justify-center pt-8">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="hover:bg-coollabs-100 text-white"
|
class="text-white"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
class:hover:bg-coollabs-100={!loading}
|
||||||
class:bg-transparent={loading}
|
class:bg-transparent={loading}
|
||||||
class:text-stone-600={loading}
|
|
||||||
class:bg-coollabs={!loading}
|
class:bg-coollabs={!loading}
|
||||||
>{loading ? $t('register.registering') : $t('register.register')}</button
|
>{loading ? $t('register.registering') : $t('register.register')}</button
|
||||||
>
|
>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
|
import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
@ -30,25 +30,63 @@
|
|||||||
import Moodle from './_Moodle.svelte';
|
import Moodle from './_Moodle.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
$: isDisabled =
|
||||||
|
!$appSession.isAdmin || $status.service.isRunning || $status.service.initialLoading;
|
||||||
|
|
||||||
|
let forceSave = false;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let loadingVerification = false;
|
let loadingVerification = false;
|
||||||
let dualCerts = service.dualCerts;
|
let dualCerts = service.dualCerts;
|
||||||
|
|
||||||
|
let nonWWWDomain = service.fqdn && getDomain(service.fqdn).replace(/^www\./, '');
|
||||||
|
let isNonWWWDomainOK = false;
|
||||||
|
let isWWWDomainOK = false;
|
||||||
|
|
||||||
|
async function isDNSValid(domain: any, isWWW: any) {
|
||||||
|
try {
|
||||||
|
await get(`/services/${id}/check?domain=${domain}`);
|
||||||
|
toast.push('DNS configuration is valid.');
|
||||||
|
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/services/${id}/check`, {
|
await post(`/services/${id}/check`, {
|
||||||
fqdn: service.fqdn,
|
fqdn: service.fqdn,
|
||||||
|
forceSave,
|
||||||
|
dualCerts,
|
||||||
otherFqdns: service.minio?.apiFqdn ? [service.minio?.apiFqdn] : [],
|
otherFqdns: service.minio?.apiFqdn ? [service.minio?.apiFqdn] : [],
|
||||||
exposePort: service.exposePort
|
exposePort: service.exposePort
|
||||||
});
|
});
|
||||||
await post(`/services/${id}`, { ...service });
|
await post(`/services/${id}`, { ...service });
|
||||||
setLocation(service)
|
setLocation(service);
|
||||||
$disabledButton = false;
|
$disabledButton = false;
|
||||||
|
forceSave = false;
|
||||||
toast.push('Configuration saved.');
|
toast.push('Configuration saved.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
//@ts-ignore
|
||||||
|
if (error?.message.startsWith($t('application.dns_not_set_partial_error'))) {
|
||||||
|
forceSave = true;
|
||||||
|
if (dualCerts) {
|
||||||
|
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
||||||
|
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
||||||
|
} else {
|
||||||
|
const isWWW = getDomain(service.fqdn).includes('www.');
|
||||||
|
if (isWWW) {
|
||||||
|
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
||||||
|
} else {
|
||||||
|
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
@ -102,14 +140,21 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6 pb-12">
|
<div class="mx-auto max-w-4xl px-6 pb-12">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5">
|
||||||
<div class="title">{$t('general')}</div>
|
<div class="title">{$t('general')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class:bg-pink-600={!loading}
|
class:bg-pink-600={!loading}
|
||||||
|
class:bg-orange-600={forceSave}
|
||||||
class:hover:bg-pink-500={!loading}
|
class:hover:bg-pink-500={!loading}
|
||||||
disabled={loading}>{loading ? $t('forms.saving') : $t('forms.save')}</button
|
class:hover:bg-orange-400={forceSave}
|
||||||
|
disabled={loading}
|
||||||
|
>{loading
|
||||||
|
? $t('forms.saving')
|
||||||
|
: forceSave
|
||||||
|
? $t('forms.confirm_continue')
|
||||||
|
: $t('forms.save')}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if service.type === 'plausibleanalytics' && $status.service.isRunning}
|
{#if service.type === 'plausibleanalytics' && $status.service.isRunning}
|
||||||
@ -145,7 +190,7 @@
|
|||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="version" class="text-base font-bold text-stone-100">Version / Tag</label>
|
<label for="version" class="text-base font-bold text-stone-100">Version / Tag</label>
|
||||||
<a
|
<a
|
||||||
href={$appSession.isAdmin && !$status.service.isRunning
|
href={$appSession.isAdmin && !$status.service.isRunning && !$status.service.initialLoading
|
||||||
? `/services/${id}/configuration/version?from=/services/${id}`
|
? `/services/${id}/configuration/version?from=/services/${id}`
|
||||||
: ''}
|
: ''}
|
||||||
class="no-underline"
|
class="no-underline"
|
||||||
@ -153,7 +198,8 @@
|
|||||||
<input
|
<input
|
||||||
value={service.version}
|
value={service.version}
|
||||||
id="service"
|
id="service"
|
||||||
disabled={$status.service.isRunning}
|
readonly
|
||||||
|
disabled={$status.service.isRunning || $status.service.initialLoading}
|
||||||
class:cursor-pointer={!$status.service.isRunning}
|
class:cursor-pointer={!$status.service.isRunning}
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
@ -183,8 +229,8 @@
|
|||||||
|
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
placeholder="eg: https://console.min.io"
|
placeholder="eg: https://console.min.io"
|
||||||
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
readonly={isDisabled}
|
||||||
disabled={!$appSession.isAdmin || $status.service.isRunning}
|
disabled={isDisabled}
|
||||||
name="fqdn"
|
name="fqdn"
|
||||||
id="fqdn"
|
id="fqdn"
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
@ -201,7 +247,7 @@
|
|||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
placeholder="eg: https://min.io"
|
placeholder="eg: https://min.io"
|
||||||
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
||||||
disabled={!$appSession.isAdmin || $status.service.isRunning}
|
disabled={isDisabled}
|
||||||
name="apiFqdn"
|
name="apiFqdn"
|
||||||
id="apiFqdn"
|
id="apiFqdn"
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
@ -221,7 +267,9 @@
|
|||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
placeholder="eg: https://analytics.coollabs.io"
|
placeholder="eg: https://analytics.coollabs.io"
|
||||||
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
||||||
disabled={!$appSession.isAdmin || $status.service.isRunning}
|
disabled={!$appSession.isAdmin ||
|
||||||
|
$status.service.isRunning ||
|
||||||
|
$status.service.initialLoading}
|
||||||
name="fqdn"
|
name="fqdn"
|
||||||
id="fqdn"
|
id="fqdn"
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
@ -230,7 +278,38 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#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
|
||||||
|
class="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="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 class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
disabled={$status.service.isRunning}
|
disabled={$status.service.isRunning}
|
||||||
@ -245,7 +324,9 @@
|
|||||||
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
<label for="exposePort" class="text-base font-bold text-stone-100">Exposed Port</label>
|
||||||
<input
|
<input
|
||||||
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
readonly={!$appSession.isAdmin && !$status.service.isRunning}
|
||||||
disabled={!$appSession.isAdmin || $status.service.isRunning}
|
disabled={!$appSession.isAdmin ||
|
||||||
|
$status.service.isRunning ||
|
||||||
|
$status.service.initialLoading}
|
||||||
name="exposePort"
|
name="exposePort"
|
||||||
id="exposePort"
|
id="exposePort"
|
||||||
bind:value={service.exposePort}
|
bind:value={service.exposePort}
|
||||||
|
@ -76,13 +76,13 @@
|
|||||||
<label for="extraConfig">{$t('forms.extra_config')}</label>
|
<label for="extraConfig">{$t('forms.extra_config')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
bind:value={service.wordpress.extraConfig}
|
bind:value={service.wordpress.extraConfig}
|
||||||
disabled={$status.service.isRunning}
|
disabled={$status.service.isRunning || $status.service.initialLoading}
|
||||||
readonly={$status.service.isRunning}
|
readonly={$status.service.isRunning}
|
||||||
class:resize-none={$status.service.isRunning}
|
class:resize-none={$status.service.isRunning}
|
||||||
rows="5"
|
rows="5"
|
||||||
name="extraConfig"
|
name="extraConfig"
|
||||||
id="extraConfig"
|
id="extraConfig"
|
||||||
placeholder={!$status.service.isRunning
|
placeholder={!$status.service.isRunning && !$status.service.initialLoading
|
||||||
? `${$t('forms.eg')}:
|
? `${$t('forms.eg')}:
|
||||||
|
|
||||||
define('WP_ALLOW_MULTISITE', true);
|
define('WP_ALLOW_MULTISITE', true);
|
||||||
@ -112,7 +112,14 @@ define('SUBDOMAIN_INSTALL', false);`
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="ftpPassword">Password</label>
|
<label for="ftpPassword">Password</label>
|
||||||
<CopyPasswordField id="ftpPassword" isPasswordField readonly disabled name="ftpPassword" value={ftpPassword} />
|
<CopyPasswordField
|
||||||
|
id="ftpPassword"
|
||||||
|
isPasswordField
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="ftpPassword"
|
||||||
|
value={ftpPassword}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
@ -120,8 +120,9 @@
|
|||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if ($status.service.loading) return;
|
if ($status.service.loading) return;
|
||||||
$status.service.loading = true;
|
$status.service.loading = true;
|
||||||
const data = await get(`/services/${id}`);
|
const data = await get(`/services/${id}/status`);
|
||||||
$status.service.isRunning = data.isRunning;
|
$status.service.isRunning = data.isRunning;
|
||||||
|
$status.service.isExited = data.isExited;
|
||||||
$status.service.initialLoading = false;
|
$status.service.initialLoading = false;
|
||||||
$status.service.loading = false;
|
$status.service.loading = false;
|
||||||
}
|
}
|
||||||
@ -173,6 +174,32 @@
|
|||||||
>
|
>
|
||||||
<div class="border border-stone-700 h-8" />
|
<div class="border border-stone-700 h-8" />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $status.service.isExited}
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/services/${id}/logs` : null}
|
||||||
|
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
|
||||||
|
data-tooltip="Service exited with an error!"
|
||||||
|
sveltekit:prefetch
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentcolor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||||
|
/>
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
{#if $status.service.initialLoading}
|
{#if $status.service.initialLoading}
|
||||||
<button
|
<button
|
||||||
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
||||||
@ -347,7 +374,7 @@
|
|||||||
>
|
>
|
||||||
<div class="border border-stone-700 h-8" />
|
<div class="border border-stone-700 h-8" />
|
||||||
<a
|
<a
|
||||||
href={$status.service.isRunning ? `/services/${id}/logs` : null}
|
href={!$disabledButton && $status.service.isRunning ? `/services/${id}/logs` : null}
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
class="hover:text-pink-500 rounded"
|
class="hover:text-pink-500 rounded"
|
||||||
class:text-pink-500={$page.url.pathname === `/services/${id}/logs`}
|
class:text-pink-500={$page.url.pathname === `/services/${id}/logs`}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
redirect: `/services/${params.id}`
|
redirect: `/services/${params.id}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const response = await get(`/destinations`);
|
const response = await get(`/destinations?onlyVerified=true`);
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...response
|
...response
|
||||||
@ -54,7 +54,7 @@
|
|||||||
<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">{$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
|
||||||
|
@ -1,32 +1,13 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
import type { Load } from '@sveltejs/kit';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
export const load: Load = async ({ fetch, params, url, stuff }) => {
|
|
||||||
try {
|
|
||||||
const response = await get(`/services/${params.id}/logs`);
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
service: stuff.service,
|
|
||||||
...response
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
error: new Error(`Could not load ${url}`)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let service: any;
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import LoadingLogs from './_Loading.svelte';
|
import LoadingLogs from './_Loading.svelte';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
|
||||||
|
let service: any = {};
|
||||||
|
let logsLoading = false;
|
||||||
let loadLogsInterval: any = null;
|
let loadLogsInterval: any = null;
|
||||||
let logs: any = [];
|
let logs: any = [];
|
||||||
let lastLog: any = null;
|
let lastLog: any = null;
|
||||||
@ -36,18 +17,23 @@ import { errorNotification } from '$lib/common';
|
|||||||
let position = 0;
|
let position = 0;
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
const response = await get(`/services/${id}`);
|
||||||
|
service = response.service;
|
||||||
loadAllLogs();
|
loadAllLogs();
|
||||||
loadLogsInterval = setInterval(() => {
|
loadLogsInterval = setInterval(() => {
|
||||||
loadLogs();
|
loadLogs();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
clearInterval(loadLogsInterval);
|
clearInterval(loadLogsInterval);
|
||||||
clearInterval(followingInterval);
|
clearInterval(followingInterval);
|
||||||
});
|
});
|
||||||
async function loadAllLogs() {
|
async function loadAllLogs() {
|
||||||
try {
|
try {
|
||||||
|
logsLoading = true;
|
||||||
const data: any = await get(`/services/${id}/logs`);
|
const data: any = await get(`/services/${id}/logs`);
|
||||||
if (data?.logs) {
|
if (data?.logs) {
|
||||||
lastLog = data.logs[data.logs.length - 1];
|
lastLog = data.logs[data.logs.length - 1];
|
||||||
@ -56,13 +42,14 @@ import { errorNotification } from '$lib/common';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
logsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function loadLogs() {
|
async function loadLogs() {
|
||||||
|
if (logsLoading) return;
|
||||||
try {
|
try {
|
||||||
const newLogs: any = await get(
|
const newLogs: any = await get(`/services/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`);
|
||||||
`/services/${id}/logs?since=${lastLog?.split(' ')[0] || 0}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
|
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
|
||||||
logs = logs.concat(newLogs.logs);
|
logs = logs.concat(newLogs.logs);
|
||||||
@ -137,7 +124,7 @@ import { errorNotification } from '$lib/common';
|
|||||||
{#if loadLogsInterval}
|
{#if loadLogsInterval}
|
||||||
<LoadingLogs />
|
<LoadingLogs />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-end sticky top-0 p-2 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"
|
class="bg-transparent"
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<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 ownServices as service}
|
{#each ownServices as service}
|
||||||
<a href="/services/{service.id}" class="w-96 p-2 no-underline">
|
<a href="/services/{service.id}" class=" p-2 no-underline">
|
||||||
<div class="box-selection group relative hover:bg-pink-600">
|
<div class="box-selection group relative hover:bg-pink-600">
|
||||||
<Services type={service.type} />
|
<Services type={service.type} />
|
||||||
<div class="truncate text-center text-xl font-bold">
|
<div class="truncate text-center text-xl font-bold">
|
||||||
@ -85,6 +85,9 @@
|
|||||||
{#if service.fqdn}
|
{#if service.fqdn}
|
||||||
<div class="truncate text-center">{getDomain(service.fqdn) || ''}</div>
|
<div class="truncate text-center">{getDomain(service.fqdn) || ''}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if service.destinationDocker?.name}
|
||||||
|
<div class="truncate text-center">{service.destinationDocker.name}</div>
|
||||||
|
{/if}
|
||||||
{#if !service.type || !service.fqdn}
|
{#if !service.type || !service.fqdn}
|
||||||
<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">
|
||||||
{$t('application.configuration.configuration_missing')}
|
{$t('application.configuration.configuration_missing')}
|
||||||
@ -95,10 +98,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if otherServices.length > 0 && $appSession.teamId === '0'}
|
{#if otherServices.length > 0 && $appSession.teamId === '0'}
|
||||||
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Services</div>
|
<div class="px-6 pb-5 pt-10 text-2xl font-bold text-center">Other Services</div>
|
||||||
<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 otherServices as service}
|
{#each otherServices as service}
|
||||||
<a href="/services/{service.id}" class="w-96 p-2 no-underline">
|
<a href="/services/{service.id}" class="p-2 no-underline">
|
||||||
<div class="box-selection group relative hover:bg-pink-600">
|
<div class="box-selection group relative hover:bg-pink-600">
|
||||||
<Services type={service.type} />
|
<Services type={service.type} />
|
||||||
<div class="truncate text-center text-xl font-bold">
|
<div class="truncate text-center text-xl font-bold">
|
||||||
|
21
apps/ui/src/routes/settings/_Menu.svelte
Normal file
21
apps/ui/src/routes/settings/_Menu.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { appSession } from '$lib/store';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col pt-4 space-y-6 w-96 px-20">
|
||||||
|
{#if $appSession.teamId === '0'}
|
||||||
|
<a
|
||||||
|
href="/settings/global"
|
||||||
|
class="sub-menu no-underline"
|
||||||
|
class:sub-menu-active={$page.routeId === 'settings/global'}
|
||||||
|
>
|
||||||
|
Global Settings
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
<a
|
||||||
|
href="/settings/ssh-keys"
|
||||||
|
class="sub-menu no-underline"
|
||||||
|
class:sub-menu-active={$page.routeId === 'settings/ssh-keys'}>SSH Keys</a
|
||||||
|
>
|
||||||
|
</div>
|
23
apps/ui/src/routes/settings/__layout.svelte
Normal file
23
apps/ui/src/routes/settings/__layout.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import { get } from '$lib/api';
|
||||||
|
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async () => {
|
||||||
|
try {
|
||||||
|
const response = await get(`/settings`);
|
||||||
|
return {
|
||||||
|
stuff: {
|
||||||
|
...response
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
error: new Error(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<slot />
|
302
apps/ui/src/routes/settings/global.svelte
Normal file
302
apps/ui/src/routes/settings/global.svelte
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async ({ stuff }) => {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...stuff
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
error: new Error(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let settings: any;
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import { del, get, post } from '$lib/api';
|
||||||
|
import { browser } from '$app/env';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { appSession, features } from '$lib/store';
|
||||||
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
|
import Menu from './_Menu.svelte';
|
||||||
|
|
||||||
|
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
||||||
|
let dualCerts = settings.dualCerts;
|
||||||
|
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
||||||
|
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
|
||||||
|
|
||||||
|
let minPort = settings.minPort;
|
||||||
|
let maxPort = settings.maxPort;
|
||||||
|
|
||||||
|
let forceSave = false;
|
||||||
|
let fqdn = settings.fqdn;
|
||||||
|
let nonWWWDomain = fqdn && getDomain(fqdn).replace(/^www\./, '');
|
||||||
|
let isNonWWWDomainOK = false;
|
||||||
|
let isWWWDomainOK = false;
|
||||||
|
let isFqdnSet = !!settings.fqdn;
|
||||||
|
let loading = {
|
||||||
|
save: false,
|
||||||
|
remove: false,
|
||||||
|
proxyMigration: false
|
||||||
|
};
|
||||||
|
|
||||||
|
async function removeFqdn() {
|
||||||
|
if (fqdn) {
|
||||||
|
loading.remove = true;
|
||||||
|
try {
|
||||||
|
const { redirect } = await del(`/settings`, { fqdn });
|
||||||
|
return redirect ? window.location.replace(redirect) : window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.remove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function changeSettings(name: any) {
|
||||||
|
try {
|
||||||
|
resetView();
|
||||||
|
if (name === 'isRegistrationEnabled') {
|
||||||
|
isRegistrationEnabled = !isRegistrationEnabled;
|
||||||
|
}
|
||||||
|
if (name === 'dualCerts') {
|
||||||
|
dualCerts = !dualCerts;
|
||||||
|
}
|
||||||
|
if (name === 'isAutoUpdateEnabled') {
|
||||||
|
isAutoUpdateEnabled = !isAutoUpdateEnabled;
|
||||||
|
}
|
||||||
|
if (name === 'isDNSCheckEnabled') {
|
||||||
|
isDNSCheckEnabled = !isDNSCheckEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
await post(`/settings`, {
|
||||||
|
isRegistrationEnabled,
|
||||||
|
dualCerts,
|
||||||
|
isAutoUpdateEnabled,
|
||||||
|
isDNSCheckEnabled
|
||||||
|
});
|
||||||
|
return toast.push(t.get('application.settings_saved'));
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function handleSubmit() {
|
||||||
|
try {
|
||||||
|
loading.save = true;
|
||||||
|
nonWWWDomain = fqdn && getDomain(fqdn).replace(/^www\./, '');
|
||||||
|
|
||||||
|
if (fqdn !== settings.fqdn) {
|
||||||
|
await post(`/settings/check`, { fqdn, forceSave, dualCerts, isDNSCheckEnabled });
|
||||||
|
await post(`/settings`, { fqdn });
|
||||||
|
return window.location.reload();
|
||||||
|
}
|
||||||
|
if (minPort !== settings.minPort || maxPort !== settings.maxPort) {
|
||||||
|
await post(`/settings`, { minPort, maxPort });
|
||||||
|
settings.minPort = minPort;
|
||||||
|
settings.maxPort = maxPort;
|
||||||
|
}
|
||||||
|
forceSave = false;
|
||||||
|
toast.push('Configuration saved.');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message?.startsWith($t('application.dns_not_set_partial_error'))) {
|
||||||
|
forceSave = true;
|
||||||
|
if (dualCerts) {
|
||||||
|
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
||||||
|
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
||||||
|
} else {
|
||||||
|
const isWWW = getDomain(settings.fqdn).includes('www.');
|
||||||
|
if (isWWW) {
|
||||||
|
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
||||||
|
} else {
|
||||||
|
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.save = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function isDNSValid(domain: any, isWWW: any) {
|
||||||
|
try {
|
||||||
|
await get(`/settings/check?domain=${domain}`);
|
||||||
|
toast.push('DNS configuration is valid.');
|
||||||
|
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function resetView() {
|
||||||
|
forceSave = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
|
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto w-full">
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<Menu />
|
||||||
|
<div>
|
||||||
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
|
<div class="flex space-x-1 pb-6">
|
||||||
|
<div class="title font-bold">{$t('index.global_settings')}</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class:bg-yellow-500={!loading.save}
|
||||||
|
class:bg-orange-600={forceSave}
|
||||||
|
class:hover:bg-yellow-500={!loading.save}
|
||||||
|
class:hover:bg-orange-400={forceSave}
|
||||||
|
disabled={loading.save}
|
||||||
|
>{loading.save
|
||||||
|
? $t('forms.saving')
|
||||||
|
: forceSave
|
||||||
|
? $t('forms.confirm_continue')
|
||||||
|
: $t('forms.save')}</button
|
||||||
|
>
|
||||||
|
|
||||||
|
{#if isFqdnSet}
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={removeFqdn}
|
||||||
|
disabled={loading.remove}
|
||||||
|
class:bg-red-600={!loading.remove}
|
||||||
|
class:hover:bg-red-500={!loading.remove}
|
||||||
|
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
|
<!-- <Language /> -->
|
||||||
|
<div class="grid grid-cols-2 items-start">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="pt-2 text-base font-bold text-stone-100">
|
||||||
|
{$t('application.url_fqdn')}
|
||||||
|
</div>
|
||||||
|
<Explainer text={$t('setting.ssl_explainer')} />
|
||||||
|
</div>
|
||||||
|
<div class="justify-start text-left">
|
||||||
|
<input
|
||||||
|
bind:value={fqdn}
|
||||||
|
readonly={!$appSession.isAdmin || isFqdnSet}
|
||||||
|
disabled={!$appSession.isAdmin || isFqdnSet}
|
||||||
|
on:input={resetView}
|
||||||
|
name="fqdn"
|
||||||
|
id="fqdn"
|
||||||
|
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
||||||
|
placeholder="{$t('forms.eg')}: https://coolify.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
|
||||||
|
class="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="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 class="grid grid-cols-2 items-start py-6">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div class="pt-2 text-base font-bold text-stone-100">
|
||||||
|
{$t('forms.public_port_range')}
|
||||||
|
</div>
|
||||||
|
<Explainer text={$t('forms.public_port_range_explainer')} />
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
||||||
|
<input
|
||||||
|
class="h-8 w-20 px-2"
|
||||||
|
type="number"
|
||||||
|
bind:value={minPort}
|
||||||
|
min="1024"
|
||||||
|
max={maxPort}
|
||||||
|
/>
|
||||||
|
-
|
||||||
|
<input
|
||||||
|
class="h-8 w-20 px-2"
|
||||||
|
type="number"
|
||||||
|
bind:value={maxPort}
|
||||||
|
min={minPort}
|
||||||
|
max="65543"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
bind:setting={isDNSCheckEnabled}
|
||||||
|
title={$t('setting.is_dns_check_enabled')}
|
||||||
|
description={$t('setting.is_dns_check_enabled_explainer')}
|
||||||
|
on:click={() => changeSettings('isDNSCheckEnabled')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
dataTooltip={$t('setting.must_remove_domain_before_changing')}
|
||||||
|
disabled={isFqdnSet}
|
||||||
|
bind:setting={dualCerts}
|
||||||
|
title={$t('application.ssl_www_and_non_www')}
|
||||||
|
description={$t('setting.generate_www_non_www_ssl')}
|
||||||
|
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
bind:setting={isRegistrationEnabled}
|
||||||
|
title={$t('setting.registration_allowed')}
|
||||||
|
description={$t('setting.registration_allowed_explainer')}
|
||||||
|
on:click={() => changeSettings('isRegistrationEnabled')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if browser && $features.beta}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
bind:setting={isAutoUpdateEnabled}
|
||||||
|
title={$t('setting.auto_update_enabled')}
|
||||||
|
description={$t('setting.auto_update_enabled_explainer')}
|
||||||
|
on:click={() => changeSettings('isAutoUpdateEnabled')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,357 +1,8 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
import type { Load } from '@sveltejs/kit';
|
|
||||||
export const load: Load = async () => {
|
|
||||||
try {
|
|
||||||
const response = await get(`/settings`);
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...response
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
error: new Error(error)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let settings: any;
|
import { goto } from '$app/navigation';
|
||||||
|
import { appSession } from '$lib/store';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
if ($appSession.teamId !== '0') {
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
goto('/settings/ssh-keys');
|
||||||
import { del, get, post } from '$lib/api';
|
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
|
||||||
import { browser } from '$app/env';
|
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
|
||||||
import { t } from '$lib/translations';
|
|
||||||
import { appSession, features, isTraefikUsed } from '$lib/store';
|
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
|
||||||
|
|
||||||
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
|
||||||
let dualCerts = settings.dualCerts;
|
|
||||||
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
|
||||||
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
|
|
||||||
$isTraefikUsed = settings.isTraefikUsed;
|
|
||||||
|
|
||||||
let minPort = settings.minPort;
|
|
||||||
let maxPort = settings.maxPort;
|
|
||||||
|
|
||||||
let forceSave = false;
|
|
||||||
let fqdn = settings.fqdn;
|
|
||||||
let nonWWWDomain = fqdn && getDomain(fqdn).replace(/^www\./, '');
|
|
||||||
let isNonWWWDomainOK = false;
|
|
||||||
let isWWWDomainOK = false;
|
|
||||||
let isFqdnSet = !!settings.fqdn;
|
|
||||||
let loading = {
|
|
||||||
save: false,
|
|
||||||
remove: false,
|
|
||||||
proxyMigration: false
|
|
||||||
};
|
|
||||||
|
|
||||||
async function removeFqdn() {
|
|
||||||
if (fqdn) {
|
|
||||||
loading.remove = true;
|
|
||||||
try {
|
|
||||||
const { redirect } = await del(`/settings`, { fqdn });
|
|
||||||
return redirect ? window.location.replace(redirect) : window.location.reload();
|
|
||||||
} catch (error ) {
|
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.remove = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function changeSettings(name: any) {
|
|
||||||
try {
|
|
||||||
resetView();
|
|
||||||
if (name === 'isRegistrationEnabled') {
|
|
||||||
isRegistrationEnabled = !isRegistrationEnabled;
|
|
||||||
}
|
|
||||||
if (name === 'dualCerts') {
|
|
||||||
dualCerts = !dualCerts;
|
|
||||||
}
|
|
||||||
if (name === 'isAutoUpdateEnabled') {
|
|
||||||
isAutoUpdateEnabled = !isAutoUpdateEnabled;
|
|
||||||
}
|
|
||||||
if (name === 'isDNSCheckEnabled') {
|
|
||||||
isDNSCheckEnabled = !isDNSCheckEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
await post(`/settings`, {
|
|
||||||
isRegistrationEnabled,
|
|
||||||
dualCerts,
|
|
||||||
isAutoUpdateEnabled,
|
|
||||||
isDNSCheckEnabled
|
|
||||||
});
|
|
||||||
return toast.push(t.get('application.settings_saved'));
|
|
||||||
} catch (error) {
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function handleSubmit() {
|
|
||||||
try {
|
|
||||||
loading.save = true;
|
|
||||||
nonWWWDomain = fqdn && getDomain(fqdn).replace(/^www\./, '');
|
|
||||||
|
|
||||||
if (fqdn !== settings.fqdn) {
|
|
||||||
await post(`/settings/check`, { fqdn, forceSave, dualCerts, isDNSCheckEnabled });
|
|
||||||
await post(`/settings`, { fqdn });
|
|
||||||
return window.location.reload();
|
|
||||||
}
|
|
||||||
if (minPort !== settings.minPort || maxPort !== settings.maxPort) {
|
|
||||||
await post(`/settings`, { minPort, maxPort });
|
|
||||||
settings.minPort = minPort;
|
|
||||||
settings.maxPort = maxPort;
|
|
||||||
}
|
|
||||||
forceSave = false;
|
|
||||||
} catch (error ) {
|
|
||||||
if (error.message?.startsWith($t('application.dns_not_set_partial_error'))) {
|
|
||||||
forceSave = true;
|
|
||||||
if (dualCerts) {
|
|
||||||
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
|
||||||
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
|
||||||
} else {
|
|
||||||
const isWWW = getDomain(settings.fqdn).includes('www.');
|
|
||||||
if (isWWW) {
|
|
||||||
isWWWDomainOK = await isDNSValid(getDomain(`www.${nonWWWDomain}`), true);
|
|
||||||
} else {
|
|
||||||
isNonWWWDomainOK = await isDNSValid(getDomain(nonWWWDomain), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(error)
|
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.save = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function isDNSValid(domain: any, isWWW: any) {
|
|
||||||
try {
|
|
||||||
await get(`/settings/check?domain=${domain}`);
|
|
||||||
toast.push('DNS configuration is valid.');
|
|
||||||
isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
errorNotification(error);
|
|
||||||
isWWW ? (isWWWDomainOK = false) : (isNonWWWDomainOK = false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function resetView() {
|
|
||||||
forceSave = false;
|
|
||||||
}
|
|
||||||
async function migrateProxy(to: any) {
|
|
||||||
if (loading.proxyMigration) return;
|
|
||||||
try {
|
|
||||||
loading.proxyMigration = true;
|
|
||||||
await post(`/update`, { type: to });
|
|
||||||
const data = await get(`/settings`);
|
|
||||||
$isTraefikUsed = data.settings.isTraefikUsed;
|
|
||||||
return toast.push('Proxy migration started, it takes a few seconds.');
|
|
||||||
} catch (error) {
|
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.proxyMigration = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
goto('/settings/global');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
|
||||||
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
|
|
||||||
</div>
|
|
||||||
{#if $appSession.teamId === '0'}
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
|
||||||
<div class="flex space-x-1 pb-6">
|
|
||||||
<div class="title font-bold">{$t('index.global_settings')}</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class:bg-green-600={!loading.save}
|
|
||||||
class:bg-orange-600={forceSave}
|
|
||||||
class:hover:bg-green-500={!loading.save}
|
|
||||||
class:hover:bg-orange-400={forceSave}
|
|
||||||
disabled={loading.save}
|
|
||||||
>{loading.save
|
|
||||||
? $t('forms.saving')
|
|
||||||
: forceSave
|
|
||||||
? $t('forms.confirm_continue')
|
|
||||||
: $t('forms.save')}</button
|
|
||||||
>
|
|
||||||
|
|
||||||
{#if isFqdnSet}
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={removeFqdn}
|
|
||||||
disabled={loading.remove}
|
|
||||||
class:bg-red-600={!loading.remove}
|
|
||||||
class:hover:bg-red-500={!loading.remove}
|
|
||||||
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
|
||||||
<!-- <Language /> -->
|
|
||||||
<div class="grid grid-cols-2 items-start">
|
|
||||||
<div class="flex-col">
|
|
||||||
<div class="pt-2 text-base font-bold text-stone-100">
|
|
||||||
{$t('application.url_fqdn')}
|
|
||||||
</div>
|
|
||||||
<Explainer text={$t('setting.ssl_explainer')} />
|
|
||||||
</div>
|
|
||||||
<div class="justify-start text-left">
|
|
||||||
<input
|
|
||||||
bind:value={fqdn}
|
|
||||||
readonly={!$appSession.isAdmin || isFqdnSet}
|
|
||||||
disabled={!$appSession.isAdmin || isFqdnSet}
|
|
||||||
on:input={resetView}
|
|
||||||
name="fqdn"
|
|
||||||
id="fqdn"
|
|
||||||
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
|
|
||||||
placeholder="{$t('forms.eg')}: https://coolify.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
|
|
||||||
class="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="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 class="grid grid-cols-2 items-start py-6">
|
|
||||||
<div class="flex-col">
|
|
||||||
<div class="pt-2 text-base font-bold text-stone-100">
|
|
||||||
{$t('forms.public_port_range')}
|
|
||||||
</div>
|
|
||||||
<Explainer text={$t('forms.public_port_range_explainer')} />
|
|
||||||
</div>
|
|
||||||
<div class="mx-auto flex-row items-center justify-center space-y-2">
|
|
||||||
<input
|
|
||||||
class="h-8 w-20 px-2"
|
|
||||||
type="number"
|
|
||||||
bind:value={minPort}
|
|
||||||
min="1024"
|
|
||||||
max={maxPort}
|
|
||||||
/>
|
|
||||||
-
|
|
||||||
<input
|
|
||||||
class="h-8 w-20 px-2"
|
|
||||||
type="number"
|
|
||||||
bind:value={maxPort}
|
|
||||||
min={minPort}
|
|
||||||
max="65543"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<Setting
|
|
||||||
bind:setting={isDNSCheckEnabled}
|
|
||||||
title={$t('setting.is_dns_check_enabled')}
|
|
||||||
description={$t('setting.is_dns_check_enabled_explainer')}
|
|
||||||
on:click={() => changeSettings('isDNSCheckEnabled')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<Setting
|
|
||||||
dataTooltip={$t('setting.must_remove_domain_before_changing')}
|
|
||||||
disabled={isFqdnSet}
|
|
||||||
bind:setting={dualCerts}
|
|
||||||
title={$t('application.ssl_www_and_non_www')}
|
|
||||||
description={$t('setting.generate_www_non_www_ssl')}
|
|
||||||
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<Setting
|
|
||||||
bind:setting={isRegistrationEnabled}
|
|
||||||
title={$t('setting.registration_allowed')}
|
|
||||||
description={$t('setting.registration_allowed_explainer')}
|
|
||||||
on:click={() => changeSettings('isRegistrationEnabled')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if browser && $features.beta}
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<Setting
|
|
||||||
bind:setting={isAutoUpdateEnabled}
|
|
||||||
title={$t('setting.auto_update_enabled')}
|
|
||||||
description={$t('setting.auto_update_enabled_explainer')}
|
|
||||||
on:click={() => changeSettings('isAutoUpdateEnabled')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{#if !settings.isTraefikUsed}
|
|
||||||
<div class="flex space-x-1 pt-6 font-bold">
|
|
||||||
<div class="title">{$t('setting.coolify_proxy_settings')}</div>
|
|
||||||
</div>
|
|
||||||
<Explainer
|
|
||||||
text={$t('setting.credential_stat_explainer', {
|
|
||||||
link: fqdn
|
|
||||||
? `http://${settings.proxyUser}:${settings.proxyPassword}@` + getDomain(fqdn) + ':8404'
|
|
||||||
: browser &&
|
|
||||||
`http://${settings.proxyUser}:${settings.proxyPassword}@` +
|
|
||||||
window.location.hostname +
|
|
||||||
':8404'
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<div class="space-y-2 px-10 py-5">
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="proxyUser">{$t('forms.user')}</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
id="proxyUser"
|
|
||||||
name="proxyUser"
|
|
||||||
value={settings.proxyUser}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-center">
|
|
||||||
<label for="proxyPassword">{$t('forms.password')}</label>
|
|
||||||
<CopyPasswordField
|
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
id="proxyPassword"
|
|
||||||
name="proxyPassword"
|
|
||||||
isPasswordField
|
|
||||||
value={settings.proxyPassword}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
|
||||||
<!-- <Language /> -->
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
150
apps/ui/src/routes/settings/ssh-keys.svelte
Normal file
150
apps/ui/src/routes/settings/ssh-keys.svelte
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async ({ stuff }) => {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...stuff
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
error: new Error(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let sshKeys: any;
|
||||||
|
import { del, post } from '$lib/api';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { errorNotification } from '$lib/common';
|
||||||
|
import Menu from './_Menu.svelte';
|
||||||
|
|
||||||
|
let loading = {
|
||||||
|
save: false
|
||||||
|
};
|
||||||
|
let isModalActive = false;
|
||||||
|
|
||||||
|
let newSSHKey = {
|
||||||
|
name: null,
|
||||||
|
privateKey: null
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
try {
|
||||||
|
await post(`/settings/sshKey`, { ...newSSHKey });
|
||||||
|
return window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function deleteSSHKey(id: string) {
|
||||||
|
const sure = confirm('Are you sure you would like to delete this SSH key?');
|
||||||
|
if (sure) {
|
||||||
|
try {
|
||||||
|
if (!id) return;
|
||||||
|
await del(`/settings/sshKey`, { id });
|
||||||
|
return window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
errorNotification(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
|
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto w-full">
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<Menu />
|
||||||
|
<div class="grid grid-flow-row gap-2 py-4">
|
||||||
|
<div class="flex space-x-1 pb-6">
|
||||||
|
<div class="title font-bold">SSH Keys</div>
|
||||||
|
<button
|
||||||
|
on:click={() => (isModalActive = true)}
|
||||||
|
class:bg-yellow-500={!loading.save}
|
||||||
|
class:hover:bg-yellow-400={!loading.save}
|
||||||
|
disabled={loading.save}>New SSH Key</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-col gap-2 px-10">
|
||||||
|
{#if sshKeys.length === 0}
|
||||||
|
<div class="text-sm ">No SSH keys found</div>
|
||||||
|
{:else}
|
||||||
|
{#each sshKeys as key}
|
||||||
|
<div class="box-selection group relative">
|
||||||
|
<div class="text-xl font-bold">{key.name}</div>
|
||||||
|
<div class="py-3 text-stone-600">Added on {key.createdAt}</div>
|
||||||
|
<button on:click={() => deleteSSHKey(key.id)} class="bg-red-500">Delete</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if isModalActive}
|
||||||
|
<div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||||
|
<div class="fixed inset-0 bg-coolgray-500 bg-opacity-75 transition-opacity" />
|
||||||
|
<div class="fixed z-10 inset-0 overflow-y-auto text-white">
|
||||||
|
<div class="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
|
||||||
|
<form
|
||||||
|
on:submit|preventDefault={handleSubmit}
|
||||||
|
class="relative bg-coolblack rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full sm:p-6 border border-coolgray-500"
|
||||||
|
>
|
||||||
|
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
|
||||||
|
<button
|
||||||
|
on:click={() => (isModalActive = false)}
|
||||||
|
type="button"
|
||||||
|
class=" rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
<svg
|
||||||
|
class="h-6 w-6"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 class="text-lg leading-6 font-medium pb-4" id="modal-title">New SSH Key</h3>
|
||||||
|
<div class="text-xs text-stone-400">Add an SSH key to your Coolify instance.</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label for="privateKey" class="pb-2">Key</label>
|
||||||
|
<textarea
|
||||||
|
id="privateKey"
|
||||||
|
required
|
||||||
|
bind:value={newSSHKey.privateKey}
|
||||||
|
class="w-full"
|
||||||
|
rows={15}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label for="name" class="pb-2">Name</label>
|
||||||
|
<input id="name" required bind:value={newSSHKey.name} class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 flex space-x-4 justify-end">
|
||||||
|
<button type="submit" class="bg-green-600 hover:bg-green-500">Save</button>
|
||||||
|
<button on:click={() => (isModalActive = false)} type="button" class="">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
@ -66,11 +66,7 @@
|
|||||||
});
|
});
|
||||||
const { organization, htmlUrl } = source;
|
const { organization, htmlUrl } = source;
|
||||||
const { fqdn } = settings;
|
const { fqdn } = settings;
|
||||||
const host = dev
|
const host = dev ? getAPIUrl() : fqdn ? fqdn : `http://${window.location.host}` || '';
|
||||||
? getAPIUrl()
|
|
||||||
: fqdn
|
|
||||||
? fqdn
|
|
||||||
: `http://${window.location.host}` || '';
|
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
|
|
||||||
let url = 'settings/apps/new';
|
let url = 'settings/apps/new';
|
||||||
@ -80,9 +76,7 @@
|
|||||||
name: `coolify-${name}`,
|
name: `coolify-${name}`,
|
||||||
url: host,
|
url: host,
|
||||||
hook_attributes: {
|
hook_attributes: {
|
||||||
url: dev
|
url: dev ? getWebhookUrl('github') : `${host}/webhooks/github/events`
|
||||||
? getWebhookUrl('github')
|
|
||||||
: `${host}/webhooks/github/events`
|
|
||||||
},
|
},
|
||||||
redirect_url: `${host}/webhooks/github`,
|
redirect_url: `${host}/webhooks/github`,
|
||||||
callback_urls: [`${host}/login/github/app`],
|
callback_urls: [`${host}/login/github/app`],
|
||||||
@ -118,10 +112,10 @@
|
|||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
{#if !source.githubAppId}
|
{#if !source.githubAppId}
|
||||||
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
<form on:submit|preventDefault={newGithubApp} class="py-4">
|
||||||
<div class="flex space-x-1 pb-7 font-bold">
|
<div class="flex space-x-1 pb-7">
|
||||||
<div class="title">General</div>
|
<div class="title">General</div>
|
||||||
{#if !source.githubAppId}
|
{#if !source.githubAppId}
|
||||||
<button class="bg-orange-600" type="submit">Save</button>
|
<button class="bg-orange-600 font-normal" type="submit">Save</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
@ -173,7 +167,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{:else if source.githubApp?.installationId}
|
{:else if source.githubApp?.installationId}
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-5 font-bold">
|
<div class="flex space-x-1 pb-5 ">
|
||||||
<div class="title">{$t('general')}</div>
|
<div class="title">{$t('general')}</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
@ -218,20 +212,24 @@
|
|||||||
bind:value={source.apiUrl}
|
bind:value={source.apiUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
{#if selfHosted}
|
||||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
<div class="grid grid-cols-2 items-center">
|
||||||
<input
|
<label for="customPort" class="text-base font-bold text-stone-100"
|
||||||
name="customPort"
|
>Custom SSH Port</label
|
||||||
id="customPort"
|
>
|
||||||
disabled={!selfHosted}
|
<input
|
||||||
readonly={!selfHosted}
|
name="customPort"
|
||||||
required
|
id="customPort"
|
||||||
value={source.customPort}
|
disabled={!selfHosted}
|
||||||
/>
|
readonly={!selfHosted}
|
||||||
<Explainer
|
required
|
||||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
value={source.customPort}
|
||||||
/>
|
/>
|
||||||
</div>
|
<Explainer
|
||||||
|
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid grid-cols-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
appSecret: null
|
appSecret: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
$: selfHosted = source.htmlUrl !== 'https://gitlab.com' ;
|
$: selfHosted = source.htmlUrl !== 'https://gitlab.com';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
oauthIdEl && oauthIdEl.focus();
|
oauthIdEl && oauthIdEl.focus();
|
||||||
@ -141,7 +141,7 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-4xl px-6">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
<form on:submit|preventDefault={handleSubmit} class="py-4">
|
||||||
<div class="flex space-x-1 pb-7 font-bold">
|
<div class="flex space-x-1 pb-7">
|
||||||
<div class="title">General</div>
|
<div class="title">General</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
@ -228,20 +228,22 @@
|
|||||||
bind:value={source.apiUrl}
|
bind:value={source.apiUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
{#if selfHosted}
|
||||||
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
<div class="grid grid-cols-2 items-center">
|
||||||
<input
|
<label for="customPort" class="text-base font-bold text-stone-100">Custom SSH Port</label>
|
||||||
name="customPort"
|
<input
|
||||||
id="customPort"
|
name="customPort"
|
||||||
disabled={!selfHosted}
|
id="customPort"
|
||||||
readonly={!selfHosted}
|
disabled={!selfHosted}
|
||||||
required
|
readonly={!selfHosted}
|
||||||
bind:value={source.customPort}
|
required
|
||||||
/>
|
bind:value={source.customPort}
|
||||||
<Explainer
|
/>
|
||||||
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
<Explainer
|
||||||
/>
|
text="If you use a self-hosted version of Git, you can provide custom port for all the Git related actions."
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-start">
|
<div class="grid grid-cols-2 items-start">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100"
|
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<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}
|
||||||
<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
|
<a href="/sources/{source.id}" class="p-2 no-underline">
|
||||||
<div
|
<div
|
||||||
class="box-selection group relative hover:bg-orange-600"
|
class="box-selection group relative hover:bg-orange-600"
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
@ -128,10 +128,10 @@
|
|||||||
</div>
|
</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-2xl font-bold text-center">Other Sources</div>
|
||||||
<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 otherSources as source}
|
{#each otherSources as source}
|
||||||
<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
|
<a href="/sources/{source.id}" class="p-2 no-underline">
|
||||||
<div
|
<div
|
||||||
class="box-selection group hover:bg-orange-600"
|
class="box-selection group hover:bg-orange-600"
|
||||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||||
|
@ -92,7 +92,7 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button, .button {
|
button, .button {
|
||||||
@apply rounded bg-coolgray-200 p-1 px-2 py-1 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600;
|
@apply rounded bg-coolgray-200 p-2 px-3 text-sm outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -104,7 +104,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@apply mr-4 text-base tracking-tight md:text-2xl;
|
@apply mr-4 text-base tracking-tight md:text-2xl font-bold;
|
||||||
}
|
}
|
||||||
.nav-main {
|
.nav-main {
|
||||||
@apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] overflow-auto border-r border-stone-800 bg-coolgray-200 scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200 xl:overflow-visible;
|
@apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] overflow-auto border-r border-stone-800 bg-coolgray-200 scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200 xl:overflow-visible;
|
||||||
@ -395,3 +395,11 @@ a {
|
|||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-menu {
|
||||||
|
@apply text-xl font-bold hover:bg-coolgray-500 rounded p-2 hover:text-white text-stone-200 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-menu-active {
|
||||||
|
@apply bg-coolgray-500 text-white;
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
docker buildx build --platform linux/amd64,linux/arm64 -t coollabsio/prisma-engine:3.15 -f prisma-engine.Dockerfile --push .
|
|
@ -1,10 +0,0 @@
|
|||||||
FROM rust:1.61-alpine3.16 as prisma
|
|
||||||
WORKDIR /prisma
|
|
||||||
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
|
||||||
RUN apk --no-cache add openssl direnv git musl-dev openssl-dev build-base perl protoc
|
|
||||||
RUN git clone --depth=1 --branch=3.15.x https://github.com/prisma/prisma-engines.git /prisma
|
|
||||||
RUN cargo build --release
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
WORKDIR /prisma-engines
|
|
||||||
COPY --from=prisma /prisma/target/release/query-engine /prisma/target/release/migration-engine /prisma/target/release/introspection-engine /prisma/target/release/prisma-fmt /prisma-engines/
|
|
11
package.json
11
package.json
@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.1.3",
|
"version": "3.2.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"oc": "opencollective-setup",
|
||||||
"db:studio": "pnpm run --filter coolify-api db:studio",
|
"db:studio": "pnpm run --filter coolify-api db:studio",
|
||||||
"db:push": "pnpm run --filter coolify-api db:push",
|
"db:push": "pnpm run --filter coolify-api db:push",
|
||||||
"db:seed": "pnpm run --filter coolify-api db:seed",
|
"db:seed": "pnpm run --filter coolify-api db:seed",
|
||||||
@ -18,13 +20,14 @@
|
|||||||
"build": "NODE_ENV=production run-p -n build:*",
|
"build": "NODE_ENV=production run-p -n build:*",
|
||||||
"build:api": "NODE_ENV=production pnpm run --filter coolify-api build",
|
"build:api": "NODE_ENV=production pnpm run --filter coolify-api build",
|
||||||
"build:ui": "NODE_ENV=production pnpm run --filter coolify-ui build",
|
"build:ui": "NODE_ENV=production pnpm run --filter coolify-ui build",
|
||||||
"dockerlogin":"echo $DOCKER_PASS | docker login --username=$DOCKER_USER --password-stdin",
|
"dockerlogin": "echo $DOCKER_PASS | docker login --username=$DOCKER_USER --password-stdin",
|
||||||
"release:staging:amd": "cross-var docker buildx build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
"release:staging:amd": "cross-var docker buildx build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:local":"rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && env | grep '^COOLIFY_' > ./local-serve/.env && cd ./local-serve && pnpm install . && pnpm start"
|
"release:local": "rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && env | grep '^COOLIFY_' > ./local-serve/.env && cd ./local-serve && pnpm install . && pnpm start"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-var": "1.1.0",
|
"cross-var": "1.1.0",
|
||||||
"npm-run-all": "4.1.5"
|
"npm-run-all": "4.1.5",
|
||||||
|
"opencollective-setup": "1.4.1"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"docker",
|
"docker",
|
||||||
|
929
pnpm-lock.yaml
generated
929
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user