Compare commits

..

72 Commits

Author SHA1 Message Date
Andras Bacsai
1881e646d4 Merge pull request #132 from coollabsio/next
v2.0.11
2022-02-15 10:51:25 +01:00
Andras Bacsai
aa98808a1a fix: Typo 2022-02-15 10:46:44 +01:00
Andras Bacsai
f9a2232703 fix: Small fixes 2022-02-15 10:42:26 +01:00
Andras Bacsai
19d6be8663 fix: Load more button 2022-02-15 10:35:22 +01:00
Andras Bacsai
0eb7c890ad fix: GitHub sync PR's 2022-02-15 10:25:23 +01:00
Andras Bacsai
7bfa68aa58 chore: version++ 2022-02-15 09:40:04 +01:00
Andras Bacsai
857a38050e fix: Window error in SSR 2022-02-15 09:39:45 +01:00
Andras Bacsai
c5b7f92caf feat: Follow logs 2022-02-15 09:38:16 +01:00
Andras Bacsai
df31ffd7fb Merge pull request #127 from SaraVieira/logs-improvements
UX improvements for the log page
2022-02-15 09:02:43 +01:00
Andras Bacsai
0df0322d36 Merge pull request #129 from coollabsio/next
v2.0.10
2022-02-15 08:52:45 +01:00
Andras Bacsai
260552322d chore: Version++ 2022-02-15 08:50:17 +01:00
Andras Bacsai
88ef6496a2 fix: Coolify proxy start 2022-02-15 08:50:02 +01:00
Andras Bacsai
bdf123bf7b fix: Stopping service without proxy 2022-02-15 08:38:16 +01:00
Andras Bacsai
8fc3760eef fix: Error handling 2022-02-14 16:52:00 +01:00
Sara Vieira
5656f6f709 add env example back 2022-02-14 16:01:08 +01:00
Andras Bacsai
53e7e8b77e version bump 2022-02-14 15:59:00 +01:00
Andras Bacsai
b990915b7a fix: Typo 2022-02-14 15:58:44 +01:00
Sara Vieira
15b7822ffd put db in the right place 2022-02-14 15:58:42 +01:00
Sara Vieira
cfa28419cb add yarn lock to gitignore 2022-02-14 15:56:28 +01:00
Sara Vieira
30ef0d2a3a some log improvements 2022-02-14 15:55:19 +01:00
Andras Bacsai
755f99200a Create README.md 2022-02-14 11:32:19 +01:00
Andras Bacsai
7af79ed3a2 Update README.md 2022-02-14 11:29:56 +01:00
Andras Bacsai
2971e14269 Merge pull request #123 from coollabsio/next
v2.0.8
2022-02-14 10:09:07 +01:00
Andras Bacsai
01954aaf30 Merge pull request #122 from habibyuri/patch-1
Allow Docker Apache write permissions
2022-02-14 10:03:31 +01:00
Andras Bacsai
da018a8f2a fix: Branch used does not throw error 2022-02-14 09:57:17 +01:00
Andras Bacsai
77400bbbb0 fix: Truncate git clone errors 2022-02-14 09:48:46 +01:00
Andras Bacsai
3c3333d3df fix: Validate secrets 2022-02-14 09:47:09 +01:00
Andras Bacsai
4963bd4144 - Bump version
- Fix type
2022-02-14 09:28:56 +01:00
Andras Bacsai
b4a418dded - Rename error handler.
- Truncate errors.
- Better error tags, release version etc.
2022-02-14 09:28:37 +01:00
Yuri Habib
a724b0daee Allow Docker Apache write permissions
I had a permission problem using PHP scripts, 
Running ` chown -R www-data /var/www/html ` after SSHing into the docker container helped fixing the problem.
2022-02-13 14:58:13 -08:00
Andras Bacsai
88aa620cb4 Merge pull request #121 from coollabsio/next
v2.0.7
2022-02-13 23:45:49 +01:00
Andras Bacsai
70d3448110 fix: New secret should have default values 2022-02-13 23:31:52 +01:00
Andras Bacsai
09a1a406a6 Bump version 2022-02-13 23:19:08 +01:00
Andras Bacsai
40939d0b7f fix: Build secrets should be visible in runtime 2022-02-13 23:17:50 +01:00
Andras Bacsai
aec1d184c8 feat: www <-> non-www redirection 2022-02-13 22:56:37 +01:00
Andras Bacsai
69d3cb5dd8 feat: www <-> non-www redirection for apps 2022-02-13 15:04:00 +01:00
Andras Bacsai
3deff162bb code cleanup 2022-02-13 13:46:51 +01:00
Andras Bacsai
4ed54568d3 fix: package.json 2022-02-12 15:34:55 +01:00
Andras Bacsai
004724da55 Merge pull request #119 from coollabsio/next
Unique secrets by applications
2022-02-12 15:31:53 +01:00
Andras Bacsai
97e9b5ffe3 Merge branch 'main' into next 2022-02-12 15:31:45 +01:00
Andras Bacsai
45f920f802 fix: unique secret by application
css: redesign secret page
2022-02-12 15:23:58 +01:00
Andras Bacsai
2b31532d19 Update README.md 2022-02-11 23:04:50 +01:00
Andras Bacsai
e7a6ecf95b fix: Typo 2022-02-11 21:50:47 +01:00
Andras Bacsai
545c98cee0 Merge pull request #117 from coollabsio/next
Update README.md
2022-02-11 21:50:19 +01:00
Andras Bacsai
d29ccbfe37 Update README.md 2022-02-11 21:49:30 +01:00
Andras Bacsai
d0807862e6 Merge pull request #116 from coollabsio/next
v2.0.5
2022-02-11 21:37:01 +01:00
Andras Bacsai
b92616dc14 chore: Version 2022-02-11 21:29:35 +01:00
Andras Bacsai
a1a436300d fix: Check sentry 2022-02-11 21:27:28 +01:00
Andras Bacsai
16a5aeb1ba chore: Version 2022-02-11 21:23:16 +01:00
Andras Bacsai
872095ff7a fix: Local static assets 2022-02-11 21:22:41 +01:00
Andras Bacsai
d88f2ea4c3 fix: More error handling in proxy configuration + cleanups 2022-02-11 21:14:47 +01:00
Andras Bacsai
02e0385ab8 Preserve build image as well 2022-02-11 21:07:31 +01:00
Andras Bacsai
c9751d4cd9 More images to tag 2022-02-11 21:06:48 +01:00
Andras Bacsai
162b637992 fix: Cleanup images 2022-02-11 21:04:48 +01:00
Andras Bacsai
a10ddd4063 fix: Delete all build files 2022-02-11 20:28:56 +01:00
Andras Bacsai
f46ccc63a7 Update staging release (final, I promise) 2022-02-11 15:46:17 +01:00
Andras Bacsai
fc04a45744 Update staging script (again) 2022-02-11 15:35:01 +01:00
Andras Bacsai
90c2b59a51 Update staging script 2022-02-11 15:34:45 +01:00
Andras Bacsai
d6bee99c1b update releast script 2022-02-11 15:31:53 +01:00
Andras Bacsai
0871d47568 feat: VaultWarden service 2022-02-11 15:31:25 +01:00
Andras Bacsai
5c646c1898 fix: Haproxy check should not throw error 2022-02-11 15:11:10 +01:00
Andras Bacsai
8974de165f fix: PreventDefault on a button, thats all 2022-02-11 13:43:45 +01:00
Andras Bacsai
e622294b87 feat: New update process (#115) 2022-02-11 11:46:47 +01:00
Andras Bacsai
cf9d32b556 Merge pull request #114 from coollabsio/next
fix: Docker Engine bugs
2022-02-11 09:22:28 +01:00
Andras Bacsai
e2d6b5bf64 fix: version 2022-02-11 09:13:51 +01:00
Andras Bacsai
dec58fd6d1 feat: Use tags in update 2022-02-11 09:00:45 +01:00
Andras Bacsai
dbb2241213 fix: Docker Engine bug related to live-restore and IPs 2022-02-11 08:42:47 +01:00
Andras Bacsai
3bd8ac5820 Merge pull request #112 from coollabsio/next
v2.0.3
2022-02-10 22:12:45 +01:00
Andras Bacsai
f514aa676d fix: Status is not available yet 2022-02-10 22:10:02 +01:00
Andras Bacsai
73fc9755dd fix: Only delete id.rsa in case of it exists 2022-02-10 22:06:22 +01:00
Andras Bacsai
5089c843b6 chore: Version bump 2022-02-10 21:56:44 +01:00
Andras Bacsai
cd527f2bce fix: Capture non-error as error 2022-02-10 21:56:19 +01:00
148 changed files with 1253 additions and 800 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/build /build
/.svelte-kit /.svelte-kit
/package /package
/yarn.lock
.env .env
.env.prod .env.prod

View File

@@ -8,6 +8,8 @@ RUN yarn build
FROM node:16.14.0-alpine FROM node:16.14.0-alpine
WORKDIR /app WORKDIR /app
LABEL coolify.managed true
RUN apk add --no-cache git openssh-client curl jq cmake sqlite RUN apk add --no-cache git openssh-client curl jq cmake sqlite
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6 RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6

View File

@@ -2,6 +2,66 @@
An open-source & self-hostable Heroku / Netlify alternative. An open-source & self-hostable Heroku / Netlify alternative.
## Installation
Installation is automated with the following command:
```bash
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
```
## Migration from v1
A fresh installation is necessary. v2 is not compatible with v1.
## Features
### Git Sources
You can use the official ones or your self hosted version!
- Github
- GitLab
- Bitbucket (WIP)
### Destinations
- Local Docker Engine
- Remote Docker Engine (WIP)
- Kubernetes (WIP)
### Applications
- Static sites
- NodeJS
- VueJS
- NuxtJS
- NextJS
- React/Preact
- NextJS
- Gatsby
- Svelte
- PHP
- Rust
- Dockerfile (you can provide it)
### Databases
- MongoDB
- MySQL
- PostgreSQL
- CouchDB
- Redis
### One-click services
- [WordPress](https://wordpress.org)
- [Plausible Analytics](https://plausible.io)
- [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io)
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
## Support ## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
@@ -11,7 +71,7 @@ An open-source & self-hostable Heroku / Netlify alternative.
## Roadmap ## Roadmap
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1) [See the Roadmap here](https://github.com/orgs/coollabsio/projects/3/views/8)
## License ## License

View File

@@ -2,7 +2,7 @@ version: '3.8'
services: services:
coolify: coolify:
image: coollabsio/coolify:latest image: coollabsio/coolify:${TAG:-latest}
restart: always restart: always
container_name: coolify container_name: coolify
ports: ports:

View File

@@ -1,12 +1,12 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.2", "version": "2.0.11",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", "dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
"dev:stop": "docker-compose -f docker-compose-dev.yaml down", "dev:stop": "docker compose -f docker-compose-dev.yaml down",
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10", "dev:logs": "docker compose -f docker-compose-dev.yaml logs -f --tail 10",
"studio": "npx prisma studio", "studio": "npx prisma studio",
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js", "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
"build": "svelte-kit build", "build": "svelte-kit build",
@@ -16,8 +16,9 @@
"db:generate": "prisma generate", "db:generate": "prisma generate",
"db:push": "prisma db push && prisma generate", "db:push": "prisma db push && prisma generate",
"db:seed": "prisma db seed", "db:seed": "prisma db seed",
"prerelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .", "release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
"release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest", "release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine", "release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine", "release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine", "release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[name,applicationId]` on the table `Secret` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX "Secret_name_key";
-- CreateIndex
CREATE UNIQUE INDEX "Secret_name_applicationId_key" ON "Secret"("name", "applicationId");

View File

@@ -105,13 +105,15 @@ model ApplicationSettings {
model Secret { model Secret {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String
value String value String
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]) application Application @relation(fields: [applicationId], references: [id])
applicationId String applicationId String
@@unique([name, applicationId])
} }
model BuildLog { model BuildLog {

View File

@@ -4,9 +4,6 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/poppins/poppins.css" />
<title>Coolify</title> <title>Coolify</title>
%svelte.head% %svelte.head%
</head> </head>

View File

@@ -11,6 +11,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`); Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`);
Dockerfile.push(`EXPOSE 80`); Dockerfile.push(`EXPOSE 80`);
Dockerfile.push('CMD ["apache2-foreground"]'); Dockerfile.push('CMD ["apache2-foreground"]');
Dockerfile.push('RUN chown -R www-data /var/www/html');
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -8,16 +8,26 @@ import * as db from '$lib/database';
import { buildLogQueue } from './queues'; import { buildLogQueue } from './queues';
import { version as currentVersion } from '../../package.json'; import { version as currentVersion } from '../../package.json';
import { dockerInstance } from './docker';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Cookie from 'cookie'; import Cookie from 'cookie';
import os from 'os';
try { try {
if (!dev) { if (!dev) {
Sentry.init({ Sentry.init({
dsn: process.env['COOLIFY_SENTRY_DSN'], dsn: process.env['COOLIFY_SENTRY_DSN'],
tracesSampleRate: 0, tracesSampleRate: 0,
environment: 'production' environment: 'production',
debug: true,
release: currentVersion,
initialScope: {
tags: {
appId: process.env['COOLIFY_APP_ID'],
'os.arch': os.arch(),
'os.platform': os.platform(),
'os.release': os.release()
}
}
}); });
} }
} catch (err) { } catch (err) {
@@ -67,7 +77,6 @@ export const getTeam = (event) => {
}; };
export const getUserDetails = async (event, isAdminRequired = true) => { export const getUserDetails = async (event, isAdminRequired = true) => {
// try {
const teamId = getTeam(event); const teamId = getTeam(event);
const userId = event.locals.session.data.uid || null; const userId = event.locals.session.data.uid || null;
const { permission = 'read' } = await db.prisma.permission.findFirst({ const { permission = 'read' } = await db.prisma.permission.findFirst({
@@ -91,18 +100,6 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
} }
return payload; return payload;
// } catch (err) {
// console.log(err);
// return {
// teamId: null,
// userId: null,
// permission: 'read',
// status: 401,
// body: {
// message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
// }
// };
// }
}; };
export function getEngine(engine) { export function getEngine(engine) {

View File

@@ -20,9 +20,6 @@
function showActions(value) { function showActions(value) {
actionsShow = value; actionsShow = value;
// if (value === false) {
// showPassword = false;
// }
} }
function copyToClipboard() { function copyToClipboard() {
if (isHttps && navigator.clipboard) { if (isHttps && navigator.clipboard) {

View File

@@ -6,7 +6,6 @@
export let description; export let description;
export let isPadding = true; export let isPadding = true;
export let disabled = false; export let disabled = false;
// export let disabledReason = '';
</script> </script>
<li class="flex items-center py-4"> <li class="flex items-center py-4">
@@ -14,11 +13,6 @@
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p> <p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
<Explainer text={description} /> <Explainer text={description} />
</div> </div>
<!-- {#if disabled}
<div class="flex" class:px-4={isPadding} class:pr-32={!isPadding}>
<p class="text-xs font-bold text-stone-400">{disabledReason}</p>
</div>
{:else} -->
<div <div
type="button" type="button"
on:click on:click

View File

@@ -5,5 +5,5 @@
<img <img
alt="minio logo" alt="minio logo"
class={isAbsolute ? 'w-7 absolute top-0 left-0 -m-3 -mt-5' : 'w-4 mx-auto'} class={isAbsolute ? 'w-7 absolute top-0 left-0 -m-3 -mt-5' : 'w-4 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/minio/MINIO_Bird.png" src="/minio.png"
/> />

View File

@@ -5,5 +5,5 @@
<img <img
alt="nocodb logo" alt="nocodb logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'} class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/nocodb/nocodb.png" src="/nocodb.png"
/> />

View File

@@ -5,5 +5,5 @@
<img <img
alt="plausible logo" alt="plausible logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'} class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/plausible/logo_sm.png" src="/plausible.png"
/> />

View File

@@ -0,0 +1,35 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Icon"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
>
<style type="text/css">
.st0 {
fill: #175ddc;
}
.st1 {
fill: #ffffff;
}
</style>
<path
id="Background"
class="st0"
d="M1024,864c0,88.4-71.6,160-160,160H160C71.6,1024,0,952.4,0,864V160C0,71.6,71.6,0,160,0h704 c88.4,0,160,71.6,160,160V864z"
/>
<path
id="Identity"
class="st1"
d="M829.8,128.6c-6.5-6.5-14.2-9.7-23-9.7H217.2c-8.9,0-16.5,3.2-23,9.7c-6.5,6.5-9.7,14.2-9.7,23 v393.1c0,29.3,5.7,58.4,17.1,87.3c11.4,28.8,25.6,54.4,42.5,76.8c16.9,22.3,37,44.1,60.4,65.3c23.4,21.2,45,38.7,64.7,52.7 c19.8,14,40.4,27.2,61.9,39.7c21.5,12.5,36.8,20.9,45.8,25.3c9,4.4,16.3,7.9,21.7,10.2c4.1,2,8.5,3.1,13.3,3.1c4.8,0,9.2-1,13.3-3.1 c5.5-2.4,12.7-5.8,21.8-10.2c9-4.4,24.3-12.9,45.8-25.3c21.5-12.5,42.1-25.7,61.9-39.7c19.8-14,41.4-31.6,64.8-52.7 c23.4-21.2,43.5-42.9,60.4-65.3c16.9-22.4,31-47.9,42.5-76.8c11.4-28.8,17.1-57.9,17.1-87.3V151.7 C839.6,142.8,836.3,135.1,829.8,128.6z M753.8,548.4c0,142.3-241.8,264.9-241.8,264.9V203.1h241.8 C753.8,203.1,753.8,406.1,753.8,548.4z"
/>
</svg>

View File

@@ -1,6 +1,6 @@
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy'; import { removeProxyConfiguration, removeWwwRedirection } from '$lib/haproxy';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common'; import { getDomain, removeDestinationDocker } from '$lib/common';
import { prisma } from './common'; import { prisma } from './common';
@@ -59,10 +59,14 @@ export async function removeApplication({ id, teamId }) {
const id = containerObj.ID; const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1]; const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine }); await removeDestinationDocker({ id, engine: destinationDocker.engine });
if (preview) { try {
await removeProxyConfiguration({ domain: `${preview}.${domain}` }); if (preview) {
} else { await removeProxyConfiguration({ domain: `${preview}.${domain}` });
await removeProxyConfiguration({ domain }); } else {
await removeProxyConfiguration({ domain });
}
} catch (error) {
console.log(error);
} }
} }
} }
@@ -113,6 +117,13 @@ export async function getApplicationWebhook({ projectId, branch }) {
throw { status: 404, body: { message: e.message } }; throw { status: 404, body: { message: e.message } };
} }
} }
export async function getApplicationById({ id }) {
const body = await prisma.application.findFirst({
where: { id }
});
return { ...body };
}
export async function getApplication({ id, teamId }) { export async function getApplication({ id, teamId }) {
let body = await prisma.application.findFirst({ let body = await prisma.application.findFirst({
where: { id, teams: { some: { id: teamId } } }, where: { id, teams: { some: { id: teamId } } },

View File

@@ -1,5 +1,5 @@
import { getDomain } from '$lib/common'; import { getDomain } from '$lib/common';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function isBranchAlreadyUsed({ repository, branch, id }) { export async function isBranchAlreadyUsed({ repository, branch, id }) {
const application = await prisma.application.findUnique({ const application = await prisma.application.findUnique({

View File

@@ -36,20 +36,36 @@ if (dev) {
} }
export const prisma = new PrismaClient(prismaOptions); export const prisma = new PrismaClient(prismaOptions);
export function PrismaErrorHandler(e) { export function ErrorHandler(e) {
sentry.captureException(e); if (e! instanceof Error) {
e = new Error(e.toString());
}
let truncatedError = e;
if (e.message?.includes('docker run')) {
let truncatedArray = [];
truncatedArray = truncatedError.message.split('-').filter((line) => {
if (!line.startsWith('e ')) {
return line;
}
});
truncatedError.message = truncatedArray.join('-');
}
if (e.message?.includes('git clone')) {
truncatedError.message = 'git clone failed';
}
sentry.captureException(truncatedError);
const payload = { const payload = {
status: e.status || 500, status: truncatedError.status || 500,
body: { body: {
message: 'Ooops, something is not okay, are you okay?', message: 'Ooops, something is not okay, are you okay?',
error: e.error || e.message error: truncatedError.error || truncatedError.message
} }
}; };
if (e.name === 'NotFoundError') { if (truncatedError?.name === 'NotFoundError') {
payload.status = 404; payload.status = 404;
} }
if (e instanceof P.PrismaClientKnownRequestError) { if (truncatedError instanceof P.PrismaClientKnownRequestError) {
if (e.code === 'P2002') { if (truncatedError?.code === 'P2002') {
payload.body.message = 'Already exists. Choose another name.'; payload.body.message = 'Already exists. Choose another name.';
} }
} }
@@ -113,6 +129,12 @@ export const supportedServiceTypesAndVersions = [
fancyName: 'Wordpress', fancyName: 'Wordpress',
baseImage: 'wordpress', baseImage: 'wordpress',
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'] versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3']
},
{
name: 'vaultwarden',
fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server',
versions: ['latest']
} }
]; ];

View File

@@ -2,7 +2,7 @@ import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import cuid from 'cuid'; import cuid from 'cuid';
import { generatePassword } from '.'; import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common'; import { prisma, ErrorHandler } from './common';
import getPort from 'get-port'; import getPort from 'get-port';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common'; import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
@@ -114,15 +114,6 @@ export async function updateDatabase({
}); });
} }
// export async function setDatabaseSettings({ id, isPublic }) {
// try {
// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } })
// return { status: 201 }
// } catch (e) {
// throw PrismaErrorHandler(e)
// }
// }
export async function stopDatabase(database) { export async function stopDatabase(database) {
let everStarted = false; let everStarted = false;
const { const {

View File

@@ -1,8 +1,8 @@
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy } from '$lib/haproxy';
import { getDatabaseImage } from '.'; import { getDatabaseImage } from '.';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function listDestinations(teamId) { export async function listDestinations(teamId) {
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } }); return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
@@ -37,11 +37,9 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
const host = getEngine(engine); 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}`); asyncExecShell(
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`); `DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.managed="true" -t "${baseImage}:${version}" -`
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`); );
asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`);
asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`);
} }
} }
} }
@@ -111,22 +109,4 @@ export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
where: { engine }, where: { engine },
data: { isCoolifyProxyUsed } data: { isCoolifyProxyUsed }
}); });
// if (isCoolifyProxyUsed) {
// await installCoolifyProxy(engine)
// await configureNetworkCoolifyProxy(engine)
// } else {
// // TODO: must check if other destination is using the proxy??? or not?
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
// if (!domain) {
// await uninstallCoolifyProxy(engine)
// } else {
// return {
// stastus: 500,
// body: {
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
// }
// }
// }
// }
} }

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function listSources(teamId) { export async function listSources(teamId) {
return await prisma.gitSource.findMany({ return await prisma.gitSource.findMany({

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function addInstallation({ gitSourceId, installation_id }) { export async function addInstallation({ gitSourceId, installation_id }) {
const source = await prisma.gitSource.findUnique({ const source = await prisma.gitSource.findUnique({

View File

@@ -1,5 +1,5 @@
import { encrypt } from '$lib/crypto'; import { encrypt } from '$lib/crypto';
import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common'; import { generateSshKeyPair, prisma } from './common';
export async function updateDeployKey({ id, deployKeyId }) { export async function updateDeployKey({ id, deployKeyId }) {
const application = await prisma.application.findUnique({ const application = await prisma.application.findUnique({

View File

@@ -1,4 +1,4 @@
import { prisma, PrismaErrorHandler } from './common'; import { prisma, ErrorHandler } from './common';
export async function listLogs({ buildId, last = 0 }) { export async function listLogs({ buildId, last = 0 }) {
try { try {
@@ -7,7 +7,7 @@ export async function listLogs({ buildId, last = 0 }) {
orderBy: { time: 'asc' } orderBy: { time: 'asc' }
}); });
return [...body]; return [...body];
} catch (e) { } catch (error) {
throw PrismaErrorHandler(e); return ErrorHandler(error);
} }
} }

View File

@@ -1,5 +1,5 @@
import { encrypt } from '$lib/crypto'; import { encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function listSecrets({ applicationId }) { export async function listSecrets({ applicationId }) {
return await prisma.secret.findMany({ return await prisma.secret.findMany({

View File

@@ -1,8 +1,7 @@
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import cuid from 'cuid'; import cuid from 'cuid';
import { generatePassword } from '.'; import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function listServices(teamId) { export async function listServices(teamId) {
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } }); return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
@@ -99,6 +98,13 @@ export async function configureServiceType({ id, type }) {
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } } wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
} }
}); });
} else if (type === 'vaultwarden') {
await prisma.service.update({
where: { id },
data: {
type
}
});
} }
} }
export async function setService({ id, version }) { export async function setService({ id, version }) {
@@ -115,6 +121,9 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
export async function updateNocoDbOrMinioService({ id, fqdn, name }) { export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } }); return await prisma.service.update({ where: { id }, data: { fqdn, name } });
} }
export async function updateVaultWardenService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVsCodeServer({ id, fqdn, name }) { export async function updateVsCodeServer({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } }); return await prisma.service.update({ where: { id }, data: { fqdn, name } });
} }

View File

@@ -1,4 +1,4 @@
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
export async function listTeams() { export async function listTeams() {
return await prisma.team.findMany(); return await prisma.team.findMany();

View File

@@ -1,8 +1,8 @@
import cuid from 'cuid'; import cuid from 'cuid';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
import { prisma, PrismaErrorHandler } from './common'; import { prisma } from './common';
import { asyncExecShell, removeContainer, uniqueName } from '$lib/common'; import { asyncExecShell, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { startCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy } from '$lib/haproxy';
@@ -28,12 +28,12 @@ export async function login({ email, password }) {
console.log('Network created'); console.log('Network created');
}) })
.catch(() => { .catch(() => {
console.log('Network already exists'); console.log('Network already exists.');
}); });
startCoolifyProxy('/var/run/docker.sock') startCoolifyProxy('/var/run/docker.sock')
.then(() => { .then(() => {
console.log('Coolify Proxy Started'); console.log('Coolify Proxy started.');
}) })
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
@@ -97,17 +97,6 @@ export async function login({ email, password }) {
}); });
} }
} }
// const token = jsonwebtoken.sign({}, secretKey, {
// expiresIn: 15778800,
// algorithm: 'HS256',
// audience: 'coolify',
// issuer: 'coolify',
// jwtid: uid,
// subject: `User:${uid}`,
// notBefore: -1000
// });
return { return {
status: 200, status: 200,
headers: { headers: {

View File

@@ -1,5 +1,5 @@
import { dev } from '$app/env'; import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import got from 'got'; import got from 'got';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
@@ -51,11 +51,11 @@ export async function completeTransaction(transactionId) {
export async function removeProxyConfiguration({ domain }) { export async function removeProxyConfiguration({ domain }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
const transactionId = await getNextTransactionId();
const backendFound = await haproxy const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`) .get(`v2/services/haproxy/configuration/backends/${domain}`)
.json(); .json();
if (backendFound) { if (backendFound) {
const transactionId = await getNextTransactionId();
await haproxy await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, { .delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: { searchParams: {
@@ -65,6 +65,7 @@ export async function removeProxyConfiguration({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} }
await removeWwwRedirection(domain);
} }
export async function forceSSLOffApplication({ domain }) { export async function forceSSLOffApplication({ domain }) {
if (!dev) { if (!dev) {
@@ -120,7 +121,9 @@ export async function forceSSLOnApplication({ domain }) {
.json(); .json();
let nextRule = 0; let nextRule = 0;
if (rules.data.length > 0) { if (rules.data.length > 0) {
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`)); const rule = rules.data.find((rule) =>
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) return; if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1; nextRule = rules.data[rules.data.length - 1].index + 1;
} }
@@ -134,7 +137,7 @@ export async function forceSSLOnApplication({ domain }) {
json: { json: {
index: nextRule, index: nextRule,
cond: 'if', cond: 'if',
cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`, cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
type: 'redirect', type: 'redirect',
redir_type: 'scheme', redir_type: 'scheme',
redir_value: 'https', redir_value: 'https',
@@ -155,11 +158,7 @@ export async function forceSSLOnApplication({ domain }) {
export async function deleteProxy({ id }) { export async function deleteProxy({ id }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy);
} catch (error) {
return;
}
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
@@ -184,94 +183,7 @@ export async function deleteProxy({ id }) {
await completeTransaction(transactionId); await completeTransaction(transactionId);
} }
} }
// export async function configureProxyForDatabase({ id, port, isPublic, privatePort }) {
// const haproxy = await haproxyInstance()
// try {
// await checkHAProxy()
// } catch (error) {
// return
// }
// let alreadyConfigured = false
// try {
// const backend: any = await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json()
// const server: any = await haproxy.get(`v2/services/haproxy/configuration/servers/${id}`, {
// searchParams: {
// backend: id
// },
// }).json()
// if (backend.data.name === id) {
// if (server.data.port === privatePort) {
// if (server.data.check === 'enabled') {
// if (server.data.address === id) {
// alreadyConfigured = true
// }
// }
// }
// }
// } catch (error) {
// console.log('error getting backend or server', error.response.body)
// }
// if (alreadyConfigured) return
// const transactionId = await getNextTransactionId()
// try {
// await haproxy.post('v2/services/haproxy/configuration/backends', {
// searchParams: {
// transaction_id: transactionId
// },
// json: {
// "init-addr": "last,libc,none",
// "mode": "tcp",
// "name": id
// }
// })
// await haproxy.post('v2/services/haproxy/configuration/servers', {
// searchParams: {
// transaction_id: transactionId,
// backend: id
// },
// json: {
// "address": id,
// "check": "enabled",
// "name": id,
// "port": privatePort
// }
// })
// await haproxy.post('v2/services/haproxy/configuration/frontends', {
// searchParams: {
// transaction_id: transactionId,
// backend: id
// },
// json: {
// "default_backend": id,
// "mode": "tcp",
// "name": id
// }
// })
// await haproxy.post('v2/services/haproxy/configuration/binds', {
// searchParams: {
// transaction_id: transactionId,
// frontend: id
// },
// json: {
// "address": "*",
// "name": id,
// "port": port
// }
// })
// } catch (error) {
// console.log(error.response.body)
// throw error.response.body
// } finally {
// try {
// await completeTransaction(transactionId)
// } catch (error) {
// console.log(error.response.body)
// }
// }
// await configureDatabaseVisibility({ id, isPublic })
// }
export async function reloadHaproxy(engine) { export async function reloadHaproxy(engine) {
const host = getEngine(engine); const host = getEngine(engine);
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`); return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
@@ -356,7 +268,8 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
} }
} }
export async function configureCoolifyProxyOff({ domain }) { export async function configureCoolifyProxyOff(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
@@ -374,19 +287,24 @@ export async function configureCoolifyProxyOff({ domain }) {
if (!dev) { if (!dev) {
await forceSSLOffApplication({ domain }); await forceSSLOffApplication({ domain });
} }
await setWwwRedirection(fqdn);
} catch (error) { } catch (error) {
throw error?.response?.body || error; throw error?.response?.body || error;
} }
} }
export async function checkHAProxy(haproxy) { export async function checkHAProxy(haproxy?: any) {
if (!haproxy) haproxy = await haproxyInstance(); if (!haproxy) haproxy = await haproxyInstance();
try { try {
await haproxy.get('v2/info'); await haproxy.get('v2/info');
} catch (error) { } catch (error) {
throw 'HAProxy is not running, but it should be!'; throw {
message:
'Coolify Proxy is not running, but it should be!<br><br>Start it in the "Destinations" menu.'
};
} }
} }
export async function configureCoolifyProxyOn({ domain }) { export async function configureCoolifyProxyOn(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
let serverConfigured = false; let serverConfigured = false;
@@ -481,8 +399,12 @@ export async function startTcpProxy(destinationDocker, id, publicPort, privatePo
try { try {
if (foundDB && !found) { if (foundDB && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
return await asyncExecShell( return await asyncExecShell(
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}` `DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
); );
} }
} catch (error) { } catch (error) {
@@ -499,8 +421,12 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
try { try {
if (foundDB && !found) { if (foundDB && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
return await asyncExecShell( return await asyncExecShell(
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}` `DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
); );
} }
} catch (error) { } catch (error) {
@@ -512,8 +438,12 @@ export async function startCoolifyProxy(engine) {
const found = await checkContainer(engine, 'coolify-haproxy'); const found = await checkContainer(engine, 'coolify-haproxy');
const { proxyPassword, proxyUser } = await db.listSettings(); const { proxyPassword, proxyUser } = await db.listSettings();
if (!found) { if (!found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}` `DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
); );
} }
await configureNetworkCoolifyProxy(engine); await configureNetworkCoolifyProxy(engine);
@@ -574,14 +504,18 @@ export async function configureNetworkCoolifyProxy(engine) {
export async function configureSimpleServiceProxyOn({ id, domain, port }) { export async function configureSimpleServiceProxyOn({ id, domain, port }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { await checkHAProxy(haproxy);
await checkHAProxy(haproxy);
} catch (error) {
return;
}
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
return; const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
} catch (error) {} } catch (error) {}
try { try {
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
@@ -607,6 +541,12 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
port: port port: port
} }
}); });
console.log({
address: id,
check: 'enabled',
name: id,
port: port
});
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -616,11 +556,9 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
export async function configureSimpleServiceProxyOff({ domain }) { export async function configureSimpleServiceProxyOff({ domain }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
await haproxy await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, { .delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: { searchParams: {
@@ -630,5 +568,89 @@ export async function configureSimpleServiceProxyOff({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) {} } catch (error) {}
await removeWwwRedirection(domain);
return; return;
} }
export async function removeWwwRedirection(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy();
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
await completeTransaction(transactionId);
}
}
}
export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
const transactionId = await getNextTransactionId();
try {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
let nextRule = 0;
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
json: {
index: nextRule,
cond: 'if',
cond_test: contTest,
type: 'redirect',
redir_type: 'location',
redir_value: redirectValue,
redir_code: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
await completeTransaction(transactionId);
}
}

View File

@@ -2,7 +2,7 @@ import { asyncExecShell, saveBuildLog } from '$lib/common';
import got from 'got'; import got from 'got';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export default async function ({ export default async function ({
applicationId, applicationId,
@@ -45,6 +45,6 @@ export default async function ({
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', ''); return commit.replace('\n', '');
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
} }

View File

@@ -1,5 +1,5 @@
import { asyncExecShell, saveBuildLog } from '$lib/common'; import { asyncExecShell, saveBuildLog } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export default async function ({ export default async function ({
applicationId, applicationId,

View File

@@ -2,15 +2,17 @@ import { dev } from '$app/env';
import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy'; import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common'; import { asyncExecShell, getEngine } from './common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import cuid from 'cuid';
export async function letsEncrypt({ domain, isCoolify = false, id = null }) { export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
try { try {
const randomCuid = cuid();
if (dev) { if (dev) {
return await forceSSLOnApplication({ domain }); return await forceSSLOnApplication({ domain });
} else { } else {
if (isCoolify) { if (isCoolify) {
await asyncExecShell( await asyncExecShell(
`docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email` `docker run --rm --name certbot-${randomCuid} -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
); );
const { stderr } = await asyncExecShell( const { stderr } = await asyncExecShell(
@@ -33,10 +35,10 @@ export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
if (data.destinationDockerId && data.destinationDocker) { if (data.destinationDockerId && data.destinationDocker) {
const host = getEngine(data.destinationDocker.engine); const host = getEngine(data.destinationDocker.engine);
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email` `DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
); );
const { stderr } = await asyncExecShell( const { stderr } = await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name bash -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem` `DOCKER_HOST=${host} docker run --rm --name bash-${randomCuid} -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
); );
if (stderr) throw new Error(stderr); if (stderr) throw new Error(stderr);
await forceSSLOnApplication({ domain }); await forceSSLOnApplication({ domain });

View File

@@ -4,9 +4,10 @@ import * as buildpacks from '../buildPacks';
import * as importers from '../importers'; import * as importers from '../importers';
import { dockerInstance } from '../docker'; import { dockerInstance } from '../docker';
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common'; import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
import { configureProxyForApplication, reloadHaproxy } from '../haproxy'; import { configureProxyForApplication, reloadHaproxy, setWwwRedirection } from '../haproxy';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { decrypt } from '$lib/crypto'; import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common';
import { import {
copyBaseConfigurationFiles, copyBaseConfigurationFiles,
makeLabelForStandaloneApplication, makeLabelForStandaloneApplication,
@@ -63,10 +64,6 @@ export default async function (job) {
if (destinationDockerId) { if (destinationDockerId) {
destinationType = 'docker'; destinationType = 'docker';
} }
// Not implemented yet
// if (destinationKubernetesId) {
// destinationType = 'kubernetes'
// }
if (destinationType === 'docker') { if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
@@ -208,9 +205,7 @@ export default async function (job) {
const envs = []; const envs = [];
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { secrets.forEach((secret) => {
if (!secret.isBuildSecret) { envs.push(`${secret.name}=${secret.value}`);
envs.push(`${secret.name}=${secret.value}`);
}
}); });
} }
await fs.writeFile(`${workdir}/.env`, envs.join('\n')); await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
@@ -246,19 +241,23 @@ export default async function (job) {
} catch (error) { } catch (error) {
throw new Error(error); throw new Error(error);
} }
try {
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId }); saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port }); await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId }); if (isHttps) await letsEncrypt({ domain, id: applicationId });
await reloadHaproxy(destinationDocker.engine); await setWwwRedirection(fqdn);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId }); await reloadHaproxy(destinationDocker.engine);
} else { saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
saveBuildLog({ } else {
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.', saveBuildLog({
buildId, line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
applicationId buildId,
}); applicationId
});
}
} catch (error) {
sentry.captureException(error);
} }
} }
} }

View File

@@ -1,22 +1,43 @@
import { dev } from '$app/env'; import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { prisma } from '$lib/database'; import { prisma } from '$lib/database';
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
export default async function () { export default async function () {
if (!dev) { if (!dev) {
const destinationDockers = await prisma.destinationDocker.findMany(); const destinationDockers = await prisma.destinationDocker.findMany();
for (const destinationDocker of destinationDockers) { for (const destinationDocker of destinationDockers) {
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
// Tagging images with labels
try {
const images = [
`coollabsio/${defaultProxyImageTcp}`,
`coollabsio/${defaultProxyImageHttp}`,
'certbot/certbot:latest',
'node:16.14.0-alpine',
'alpine:latest',
'nginx:stable-alpine',
'node:lts',
'php:apache',
'rust:latest'
];
for (const image of images) {
await asyncExecShell(
`DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.managed="true" -t "${image}" -`
);
}
} catch (error) {}
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`); await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
} catch (error) { } catch (error) {
//
console.log(error); console.log(error);
} }
// Cleanup images that are not managed by coolify
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`); await asyncExecShell(
`DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.managed=true' -a -f`
);
} catch (error) { } catch (error) {
//
console.log(error); console.log(error);
} }
} }

View File

@@ -107,7 +107,7 @@ cron().catch((error) => {
console.log(error); console.log(error);
}); });
const buildQueueName = dev ? cuid() : 'build_queue'; const buildQueueName = 'build_queue';
const buildQueue = new Queue(buildQueueName, connectionOptions); const buildQueue = new Queue(buildQueueName, connectionOptions);
const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), { const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), {
concurrency: 2, concurrency: 2,
@@ -120,9 +120,8 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} finally { } finally {
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`; const workdir = `/tmp/build-sources/${job.data.repository}/`;
await asyncExecShell(`rm -fr ${workdir}`); await asyncExecShell(`rm -fr ${workdir}`);
await asyncExecShell(`rm /tmp/build-sources/${job.data.repository}/id.rsa`);
} }
return; return;
}); });
@@ -134,9 +133,8 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`; const workdir = `/tmp/build-sources/${job.data.repository}`;
await asyncExecShell(`rm -fr ${workdir}`); await asyncExecShell(`rm -fr ${workdir}`);
await asyncExecShell(`rm /tmp/build-sources/${job.data.repository}/id.rsa`);
} }
saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id }); saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id });
saveBuildLog({ saveBuildLog({
@@ -146,28 +144,6 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
}); });
}); });
// const letsEncryptQueueName = dev ? cuid() : 'letsencrypt_queue'
// const letsEncryptQueue = new Queue(letsEncryptQueueName, connectionOptions)
// const letsEncryptWorker = new Worker(letsEncryptQueueName, async (job) => await letsencrypt(job), {
// concurrency: 1,
// ...connectionOptions
// })
// letsEncryptWorker.on('completed', async () => {
// // TODO: Save letsencrypt logs as build logs!
// console.log('[DEBUG] Lets Encrypt job completed')
// })
// letsEncryptWorker.on('failed', async (job: Job, failedReason: string) => {
// try {
// await prisma.applicationSettings.updateMany({ where: { applicationId: job.data.id }, data: { forceSSL: false } })
// } catch (error) {
// console.log(error)
// }
// console.log('[DEBUG] Lets Encrypt job failed')
// console.log(failedReason)
// })
const buildLogQueueName = dev ? cuid() : 'log_queue'; const buildLogQueueName = dev ? cuid() : 'log_queue';
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions); const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), { const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {

View File

@@ -1,5 +1,5 @@
import { getDomain } from '$lib/common'; import { getDomain } from '$lib/common';
import { prisma } from '$lib/database'; import { getApplicationById, prisma } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { import {
checkContainer, checkContainer,
@@ -7,6 +7,7 @@ import {
configureProxyForApplication, configureProxyForApplication,
forceSSLOnApplication, forceSSLOnApplication,
reloadHaproxy, reloadHaproxy,
setWwwRedirection,
startCoolifyProxy startCoolifyProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
import * as db from '$lib/database'; import * as db from '$lib/database';
@@ -29,17 +30,21 @@ export default async function () {
if (configuration.Labels['coolify.type'] === 'standalone-application') { if (configuration.Labels['coolify.type'] === 'standalone-application') {
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration; const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
if (fqdn) { if (fqdn) {
const domain = getDomain(fqdn); const found = await getApplicationById({ id: applicationId });
await configureProxyForApplication({ if (found) {
domain, const domain = getDomain(fqdn);
imageId: pullmergeRequestId await configureProxyForApplication({
? `${applicationId}-${pullmergeRequestId}` domain,
: applicationId, imageId: pullmergeRequestId
applicationId, ? `${applicationId}-${pullmergeRequestId}`
port : applicationId,
}); applicationId,
const isHttps = fqdn.startsWith('https://'); port
if (isHttps) await forceSSLOnApplication({ domain }); });
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain });
await setWwwRedirection(fqdn);
}
} }
} }
} }
@@ -52,6 +57,7 @@ export default async function () {
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
if (!found) await startCoolifyProxy('/var/run/docker.sock'); if (!found) await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn({ domain }); await configureCoolifyProxyOn({ domain });
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain }); if (isHttps) await forceSSLOnApplication({ domain });
} }

View File

@@ -39,8 +39,7 @@
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { asyncSleep } from '$lib/components/common'; import { asyncSleep } from '$lib/components/common';
import { del, get, post } from '$lib/api'; import { del, get, post } from '$lib/api';
import { dev } from '$app/env'; import { browser } from '$app/env';
import Loading from '$lib/components/Loading.svelte';
let isUpdateAvailable = false; let isUpdateAvailable = false;
let updateStatus = { let updateStatus = {
@@ -48,9 +47,10 @@
checking: false, checking: false,
success: null success: null
}; };
let latestVersion = 'latest';
onMount(async () => { onMount(async () => {
if ($session.uid) { if ($session.uid) {
const overrideVersion = browser && window.localStorage.getItem('latestVersion');
try { try {
await get(`/login.json`); await get(`/login.json`);
} catch ({ error }) { } catch ({ error }) {
@@ -62,10 +62,11 @@
updateStatus.checking = true; updateStatus.checking = true;
try { try {
const data = await get(`/update.json`); const data = await get(`/update.json`);
if (data?.isUpdateAvailable) { if (overrideVersion || data?.isUpdateAvailable) {
await post(`/update.json`, { type: 'pull' }); latestVersion = overrideVersion || data.latestVersion;
isUpdateAvailable = overrideVersion ? true : data?.isUpdateAvailable;
await post(`/update.json`, { type: 'pull', latestVersion });
} }
isUpdateAvailable = data?.isUpdateAvailable;
} catch (error) { } catch (error) {
} finally { } finally {
updateStatus.checking = false; updateStatus.checking = false;
@@ -95,53 +96,31 @@
async function update() { async function update() {
updateStatus.loading = true; updateStatus.loading = true;
if (!dev) { try {
try { await post(`/update.json`, { type: 'update', latestVersion });
await post(`/update.json`, { type: 'update' }); toast.push('Update completed.<br>Waiting for the new version to start...');
toast.push('Update completed. Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
} finally {
updateStatus.success = false;
updateStatus.loading = false;
}
} else {
let reachable = false; let reachable = false;
let tries = 0; let tries = 0;
do { do {
await asyncSleep(1000); await asyncSleep(4000);
try { try {
await get(`/undead.json`); await get(`/undead.json`);
reachable = true; reachable = true;
} catch (error) { } catch (error) {
console.log(error);
reachable = false; reachable = false;
} }
console.log(reachable);
if (reachable) break; if (reachable) break;
tries++; tries++;
} while (!reachable || tries < 120); } while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...'); toast.push('New version reachable. Reloading...');
await asyncSleep(2000); updateStatus.loading = false;
window.location.reload(); updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
} catch ({ error }) {
updateStatus.success = false;
updateStatus.loading = false;
return errorNotification(error);
} }
} }
</script> </script>

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -23,6 +23,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -74,13 +74,18 @@
} }
async function isBranchAlreadyUsed() { async function isBranchAlreadyUsed() {
try { try {
return await get( const data = await get(
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}` `/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
); );
} catch ({ error }) { if (data.used) {
return errorNotification(error); errorNotification('This branch is already used by another application.');
} finally { showSave = false;
return true;
}
showSave = true; showSave = true;
} catch ({ error }) {
showSave = false;
return errorNotification(error);
} }
} }

View File

@@ -132,24 +132,20 @@
} }
async function isBranchAlreadyUsed() { async function isBranchAlreadyUsed() {
const url = `/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`;
try { try {
await get(url); const data = await get(
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
);
if (data.used) {
errorNotification('This branch is already used by another application.');
showSave = false;
return true;
}
showSave = true; showSave = true;
} catch (error) { } catch ({ error }) {
showSave = false; return errorNotification(error);
return errorNotification('Branch already configured');
} }
} }
// async function saveDeployKey(deployKeyId: number) {
// try {
// await post(updateDeployKeyIdUrl, { deployKeyId });
// } catch (error) {
// errorNotification(error);
// throw new Error(error);
// }
// }
async function checkSSHKey(sshkeyUrl) { async function checkSSHKey(sshkeyUrl) {
try { try {
return await post(sshkeyUrl, {}); return await post(sshkeyUrl, {});
@@ -203,7 +199,6 @@
const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`); const deployKeyFound = deployKeys.filter((dk) => dk.title === `${appId}-coolify-deploy-key`);
if (deployKeyFound.length > 0) { if (deployKeyFound.length > 0) {
for (const deployKey of deployKeyFound) { for (const deployKey of deployKeyFound) {
console.log(`${deployKeyUrl}/${deployKey.id}`);
await del( await del(
`${deployKeyUrl}/${deployKey.id}`, `${deployKeyUrl}/${deployKey.id}`,
{}, {},

View File

@@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
@@ -21,7 +21,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -36,6 +36,6 @@ export const post: RequestHandler = async (event) => {
await db.configureBuildPack({ id, buildPack }); await db.configureBuildPack({ id, buildPack });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -12,6 +12,6 @@ export const post: RequestHandler = async (event) => {
await db.updateDeployKey({ id, deployKeyId }); await db.updateDeployKey({ id, deployKeyId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
await db.configureDestinationForApplication({ id, destinationId }); await db.configureDestinationForApplication({ id, destinationId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -14,16 +14,14 @@ export const get: RequestHandler = async (event) => {
try { try {
const found = await db.isBranchAlreadyUsed({ repository, branch, id }); const found = await db.isBranchAlreadyUsed({ repository, branch, id });
if (found) {
throw {
error: `Branch ${branch} is already used by another application`
};
}
return { return {
status: 200 status: 200,
body: {
used: found ? true : false
}
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -42,6 +40,6 @@ export const post: RequestHandler = async (event) => {
await db.configureGitRepository({ id, repository, branch, projectId, webhookToken }); await db.configureGitRepository({ id, repository, branch, projectId, webhookToken });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,6 +13,6 @@ export const post: RequestHandler = async (event) => {
await db.configureGitsource({ id, gitSourceId }); await db.configureGitsource({ id, gitSourceId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -7,7 +7,7 @@ export const get: RequestHandler = async (event) => {
try { try {
return await db.getSshKey({ id }); return await db.getSshKey({ id });
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -15,6 +15,6 @@ export const post: RequestHandler = async (event) => {
try { try {
return await db.generateSshKey({ id }); return await db.generateSshKey({ id });
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const del: RequestHandler = async (event) => { export const del: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const del: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -4,7 +4,7 @@ import cuid from 'cuid';
import crypto from 'crypto'; import crypto from 'crypto';
import { buildQueue } from '$lib/queues'; import { buildQueue } from '$lib/queues';
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event); const { teamId, status, body } = await getUserDetails(event);
@@ -37,6 +37,6 @@ export const post: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,7 +1,7 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import { getGithubToken } from '$lib/components/common'; import { getGithubToken } from '$lib/components/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
@@ -44,7 +44,7 @@ export const get: RequestHandler = async (event) => {
}; };
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -82,6 +82,6 @@ export const post: RequestHandler = async (event) => {
}); });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -266,7 +266,7 @@
required required
/> />
<Explainer <Explainer
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>To modify the domain, you must first stop the application." text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
/> />
</div> </div>
</div> </div>

View File

@@ -15,16 +15,32 @@
let loading = true; let loading = true;
let currentStatus; let currentStatus;
let streamInterval; let streamInterval;
let followingBuild;
let followingInterval;
let logsEl;
const { id } = $page.params; const { id } = $page.params;
const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, '');
function followBuild() {
followingBuild = !followingBuild;
if (followingBuild) {
followingInterval = setInterval(() => {
logsEl.scrollTop = logsEl.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, 100);
} else {
window.clearInterval(followingInterval);
}
}
async function streamLogs(sequence = 0) { async function streamLogs(sequence = 0) {
try { try {
let { logs: responseLogs, status } = await get( let { logs: responseLogs, status } = await get(
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}` `/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${sequence}`
); );
currentStatus = status; currentStatus = status;
logs = logs.concat(responseLogs); logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
loading = false; loading = false;
streamInterval = setInterval(async () => { streamInterval = setInterval(async () => {
if (status !== 'running') { if (status !== 'running') {
@@ -38,8 +54,13 @@
); );
status = data.status; status = data.status;
currentStatus = status; currentStatus = status;
logs = logs.concat(data.logs);
logs = logs.concat(data.logs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
dispatch('updateBuildStatus', { status }); dispatch('updateBuildStatus', { status });
if (followingBuild) {
const logEl = document.getElementById('logs');
logEl.scrollTop = logEl.scrollHeight;
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@@ -50,6 +71,7 @@
} }
onDestroy(() => { onDestroy(() => {
clearInterval(streamInterval); clearInterval(streamInterval);
clearInterval(followingInterval);
}); });
onMount(async () => { onMount(async () => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
@@ -60,12 +82,37 @@
{#if loading} {#if loading}
<Loading /> <Loading />
{:else} {:else}
<div class="relative"> <div class="relative ">
{#if currentStatus === 'running'} {#if currentStatus === 'running'}
<LoadingLogs /> <LoadingLogs />
{/if} {/if}
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
<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" />
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="12" y2="16" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
</div>
<div <div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words" class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12"
bind:this={logsEl}
> >
{#each logs as log} {#each logs as log}
<div>{log.line + '\n'}</div> <div>{log.line + '\n'}</div>

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -14,15 +14,15 @@ export const get: RequestHandler = async (event) => {
where: { buildId, time: { gt: sequence } }, where: { buildId, time: { gt: sequence } },
orderBy: { time: 'asc' } orderBy: { time: 'asc' }
}); });
const { status } = await db.prisma.build.findFirst({ where: { id: buildId } }); const data = await db.prisma.build.findFirst({ where: { id: buildId } });
return { return {
body: { body: {
logs, logs,
status status: data?.status || 'running'
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dayjs } from '$lib/dayjs'; import { dayjs } from '$lib/dayjs';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -35,6 +35,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -33,7 +33,6 @@
export let buildCount; export let buildCount;
let buildId; let buildId;
$: buildId;
let skip = 0; let skip = 0;
let noMoreBuilds = buildCount < 5 || buildCount <= skip; let noMoreBuilds = buildCount < 5 || buildCount <= skip;
@@ -92,45 +91,47 @@
Build logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a> Build logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div> </div>
</div> </div>
<div class="flex flex-row justify-start space-x-2 px-10 pt-6 "> <div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex">
<div class="min-w-[16rem] space-y-2"> <div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">
{#each builds as build (build.id)} <div class="top-4 md:sticky">
<div {#each builds as build (build.id)}
data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format( <div
new Date(build.createdAt) data-tooltip={new Intl.DateTimeFormat('default', dateOptions).format(
) + `\n${build.status}`} new Date(build.createdAt)
on:click={() => loadBuild(build.id)} ) + `\n${build.status}`}
class="tooltip-top flex cursor-pointer items-center justify-center rounded-r border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl" on:click={() => loadBuild(build.id)}
class:bg-coolgray-400={buildId === build.id} class="tooltip-top flex cursor-pointer items-center justify-center rounded-r border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl "
class:border-red-500={build.status === 'failed'} class:bg-coolgray-400={buildId === build.id}
class:border-green-500={build.status === 'success'} class:border-red-500={build.status === 'failed'}
class:border-yellow-500={build.status === 'inprogress'} class:border-green-500={build.status === 'success'}
> class:border-yellow-500={build.status === 'inprogress'}
<div class="flex-col px-2"> >
<div class="text-sm font-bold"> <div class="flex-col px-2">
{application.branch} <div class="text-sm font-bold">
{application.branch}
</div>
<div class="text-xs">
{build.type}
</div>
</div> </div>
<div class="text-xs"> <div class="flex-1" />
{build.type}
</div>
</div>
<div class="flex-1" />
<div class="w-48 text-center text-xs"> <div class="w-48 text-center text-xs">
{#if build.status === 'running'} {#if build.status === 'running'}
<div class="font-bold">Running</div> <div class="font-bold">Running</div>
{:else} {:else}
<div>{build.since}</div> <div>{build.since}</div>
<div>Finished in <span class="font-bold">{build.took}s</span></div> <div>Finished in <span class="font-bold">{build.took}s</span></div>
{/if} {/if}
</div>
</div> </div>
</div> {/each}
{/each} </div>
{#if buildCount > 0 && !noMoreBuilds} <div class="flex space-x-2">
<button class="w-full" on:click={loadMoreBuilds}>Load More</button> <button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}>Load More</button>
{/if} </div>
</div> </div>
<div class="w-96 flex-1"> <div class="flex-1 md:w-96">
{#if buildId} {#if buildId}
{#key buildId} {#key buildId}
<svelte:component this={BuildLog} {buildId} on:updateBuildStatus={updateBuildStatus} /> <svelte:component this={BuildLog} {buildId} on:updateBuildStatus={updateBuildStatus} />

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dayjs } from '$lib/dayjs'; import { dayjs } from '$lib/dayjs';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -48,6 +48,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -27,19 +27,23 @@
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { get } from '$lib/api'; import { get } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
let loadLogsInterval = null; let loadLogsInterval = null;
let logs = []; let logs = [];
let followingBuild;
let followingInterval;
let logsEl;
const { id } = $page.params; const { id } = $page.params;
onMount(async () => { onMount(async () => {
loadLogs(); loadLogs();
loadLogsInterval = setInterval(() => { loadLogsInterval = setInterval(() => {
loadLogs(); loadLogs();
}, 3000); }, 1000);
}); });
onDestroy(() => { onDestroy(() => {
clearInterval(loadLogsInterval); clearInterval(loadLogsInterval);
clearInterval(followingInterval);
}); });
async function loadLogs() { async function loadLogs() {
try { try {
@@ -50,6 +54,18 @@
return errorNotification(error); return errorNotification(error);
} }
} }
function followBuild() {
followingBuild = !followingBuild;
if (followingBuild) {
followingInterval = setInterval(() => {
logsEl.scrollTop = logsEl.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, 100);
} else {
window.clearInterval(followingInterval);
}
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@@ -63,8 +79,33 @@
{:else} {:else}
<div class="relative w-full"> <div class="relative w-full">
<LoadingLogs /> <LoadingLogs />
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
<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" />
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="12" y2="16" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
</div>
<div <div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 p-6 whitespace-pre-wrap break-words w-full" class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 p-6 whitespace-pre-wrap break-words w-full mb-10 -mt-12"
bind:this={logsEl}
> >
{#each logs as log} {#each logs as log}
{log + '\n'} {log + '\n'}

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
@@ -39,6 +39,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -6,22 +6,43 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { createEventDispatcher } from 'svelte';
if (name) value = 'ENCRYPTED'; const dispatch = createEventDispatcher();
let nameEl;
let valueEl;
const { id } = $page.params; const { id } = $page.params;
async function removeSecret() { async function removeSecret() {
try { try {
await del(`/applications/${id}/secrets.json`, { name }); await del(`/applications/${id}/secrets.json`, { name });
return window.location.reload(); dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
} }
async function saveSecret() { async function saveSecret() {
const nameValid = nameEl.checkValidity();
const valueValid = valueEl.checkValidity();
if (!nameValid) {
return nameEl.reportValidity();
}
if (!valueValid) {
return valueEl.reportValidity();
}
try { try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret }); await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
return window.location.reload(); dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@@ -33,101 +54,89 @@
} }
</script> </script>
<div class="mx-auto max-w-3xl pt-4"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<div class="flex space-x-2"> <input
<div class="grid grid-flow-row"> id="secretName"
<label for="secretName">Name</label> bind:this={nameEl}
<input bind:value={name}
id="secretName" required
bind:value={name} placeholder="EXAMPLE_VARIABLE"
placeholder="EXAMPLE_VARIABLE" class="-mx-2 w-64 border-2 border-transparent"
class="w-64 border-2 border-transparent" readonly={!isNewSecret}
readonly={!isNewSecret} class:bg-transparent={!isNewSecret}
class:hover:bg-coolgray-200={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} />
/> </td>
</div> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<div class="grid grid-flow-row"> <input
<label for="secretValue">Value (will be encrypted)</label> id="secretValue"
<input bind:value
id="secretValue" bind:this={valueEl}
bind:value required
placeholder="J$#@UIO%HO#$U%H" placeholder="J$#@UIO%HO#$U%H"
class="w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
class:hover:bg-coolgray-200={!isNewSecret} class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret} readonly={!isNewSecret}
/> />
</div> </td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
<div class="w-32 px-2 text-center"> <div
<div class="text-xs">Is build variable?</div> type="button"
on:click={setSecretValue}
<div class="mt-2"> aria-pressed="false"
<ul class="divide-y divide-stone-800"> class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
<li> class:bg-green-600={isBuildSecret}
<div class:bg-stone-700={!isBuildSecret}
type="button" class:opacity-50={!isNewSecret}
on:click={setSecretValue} class:cursor-not-allowed={!isNewSecret}
aria-pressed="false" class:cursor-pointer={isNewSecret}
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out" >
class:bg-green-600={isBuildSecret} <span class="sr-only">Use isBuildSecret</span>
class:bg-stone-700={!isBuildSecret} <span
class:cursor-not-allowed={!isNewSecret} class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:cursor-pointer={isNewSecret} class:translate-x-5={isBuildSecret}
> class:translate-x-0={!isBuildSecret}
<span class="sr-only">Use isBuildSecret</span> >
<span <span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out" class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:translate-x-5={isBuildSecret} class:opacity-0={isBuildSecret}
class:translate-x-0={!isBuildSecret} class:opacity-100={!isBuildSecret}
> aria-hidden="true"
<span >
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in" <svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
class:opacity-0={isBuildSecret} <path
class:opacity-100={!isBuildSecret} d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
aria-hidden="true" stroke="currentColor"
> stroke-width="2"
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12"> stroke-linecap="round"
<path stroke-linejoin="round"
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" />
stroke="currentColor" </svg>
stroke-width="2" </span>
stroke-linecap="round" <span
stroke-linejoin="round" class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
/> aria-hidden="true"
</svg> class:opacity-100={isBuildSecret}
</span> class:opacity-0={!isBuildSecret}
<span >
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out" <svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
aria-hidden="true" <path
class:opacity-100={isBuildSecret} d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
class:opacity-0={!isBuildSecret} />
> </svg>
<svg </span>
class="h-3 w-3 bg-white text-green-600" </span>
fill="currentColor"
viewBox="0 0 12 12"
>
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</div>
</li>
</ul>
</div>
</div>
{#if isNewSecret}
<div class="mt-6">
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="mt-6">
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</div> </div>
</div> </td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="flex justify-center items-end">
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</td>

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -18,7 +18,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -33,7 +33,7 @@ export const post: RequestHandler = async (event) => {
const found = await db.isSecretExists({ id, name }); const found = await db.isSecretExists({ id, name });
if (found) { if (found) {
throw { throw {
error: `Secret ${name} already exists` error: `Secret ${name} already exists.`
}; };
} else { } else {
await db.createSecret({ id, name, value, isBuildSecret }); await db.createSecret({ id, name, value, isBuildSecret });
@@ -42,7 +42,7 @@ export const post: RequestHandler = async (event) => {
}; };
} }
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
export const del: RequestHandler = async (event) => { export const del: RequestHandler = async (event) => {
@@ -58,6 +58,6 @@ export const del: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -24,6 +24,15 @@
export let application; export let application;
import Secret from './_Secret.svelte'; import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@@ -31,11 +40,47 @@
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a> Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div> </div>
</div> </div>
<div class="mx-auto max-w-4xl px-6"> <div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<div class="flex-col justify-start space-y-1"> <table class="mx-auto">
{#each secrets as secret} <thead class=" rounded-xl border-b border-coolgray-500">
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} /> <tr>
{/each} <th
<Secret isNewSecret /> scope="col"
</div> class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
/>
</tr>
</thead>
<tbody class="">
{#each secrets as secret}
{#key secret.id}
<tr class="hover:bg-coolgray-200">
<Secret
name={secret.name}
value={secret.value ? secret.value : 'ENCRYPTED'}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
<tr>
<Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
await db.setApplicationSettings({ id, debug, previews }); await db.setApplicationSettings({ id, debug, previews });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getDomain, getUserDetails } from '$lib/common'; import { getDomain, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { removeProxyConfiguration } from '$lib/haproxy'; import { removeProxyConfiguration } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -26,6 +26,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -16,6 +16,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
await db.configureDestinationForDatabase({ id, destinationId }); await db.configureDestinationForDatabase({ id, destinationId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -27,6 +27,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -31,6 +31,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, stopDatabase } from '$lib/database'; import { ErrorHandler, stopDatabase } from '$lib/database';
import { deleteProxy } from '$lib/haproxy'; import { deleteProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -17,6 +17,6 @@ export const del: RequestHandler = async (event) => {
await db.removeDatabase({ id }); await db.removeDatabase({ id });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, getVersions, PrismaErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, getVersions, ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -25,9 +25,7 @@ export const get: RequestHandler = async (event) => {
state = 'running'; state = 'running';
} }
} catch (error) { } catch (error) {
// if (!error.stderr.includes('No such object')) { //
// console.log(error)
// }
} }
} }
const configuration = generateDatabaseConfiguration(database); const configuration = generateDatabaseConfiguration(database);
@@ -42,7 +40,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -66,6 +64,6 @@ export const post: RequestHandler = async (event) => {
}); });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -29,6 +29,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -77,6 +77,6 @@ export const post: RequestHandler = async (event) => {
}; };
} }
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, stopDatabase } from '$lib/database'; import { ErrorHandler, stopDatabase } from '$lib/database';
import { stopTcpHttpProxy } from '$lib/haproxy'; import { stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -20,6 +20,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -15,6 +15,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common'; import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -12,7 +12,8 @@ export const get: RequestHandler = async (event) => {
try { try {
const destination = await db.getDestination({ id, teamId }); const destination = await db.getDestination({ id, teamId });
const settings = await db.listSettings(); const settings = await db.listSettings();
const state = await checkContainer(destination.engine, 'coolify-haproxy'); const state =
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy'));
return { return {
status: 200, status: 200,
body: { body: {
@@ -22,7 +23,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -36,7 +37,7 @@ export const post: RequestHandler = async (event) => {
await db.updateDestination({ id, name, engine, network }); await db.updateDestination({ id, name, engine, network });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -50,6 +51,6 @@ export const del: RequestHandler = async (event) => {
await db.removeDestination({ id }); await db.removeDestination({ id });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getTeam, getUserDetails } from '$lib/common'; import { asyncExecShell, getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -59,6 +59,6 @@ export const post: RequestHandler = async (event) => {
status: 404 status: 404
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,6 +13,6 @@ export const post: RequestHandler = async (event) => {
await db.setDestinationSettings({ engine, isCoolifyProxyUsed }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -16,6 +16,6 @@ export const post: RequestHandler = async (event) => {
}; };
} catch (error) { } catch (error) {
await stopCoolifyProxy(engine); await stopCoolifyProxy(engine);
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { stopCoolifyProxy } from '$lib/haproxy'; import { stopCoolifyProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (request) => { export const get: RequestHandler = async (request) => {
@@ -14,6 +14,6 @@ export const get: RequestHandler = async (request) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getTeam, getUserDetails } from '$lib/common'; import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -25,7 +25,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -28,6 +28,6 @@ export const get: RequestHandler = async (event) => {
await db.getUser({ userId }); await db.getUser({ userId });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -24,6 +24,6 @@ export const post: RequestHandler = async (event) => {
}); });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails, uniqueName } from '$lib/common'; import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newApplication({ name, teamId }); const { id } = await db.newApplication({ name, teamId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,6 +13,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newDatabase({ name, teamId }); const { id } = await db.newDatabase({ name, teamId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,5 +1,5 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { isDockerNetworkExists, PrismaErrorHandler } from '$lib/database'; import { isDockerNetworkExists, ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -18,6 +18,6 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getUserDetails } from '$lib/common'; import { asyncExecShell, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
const id = await db.newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }); const id = await db.newDestination({ name, teamId, engine, network, isCoolifyProxyUsed });
return { status: 200, body: { id } }; return { status: 200, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails, uniqueName } from '$lib/common'; import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,6 +13,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newService({ name, teamId }); const { id } = await db.newService({ name, teamId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -12,6 +12,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newSource({ name, teamId, type, htmlUrl, apiUrl, organization }); const { id } = await db.newSource({ name, teamId, type, htmlUrl, apiUrl, organization });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (e) { } catch (e) {
return PrismaErrorHandler(e); return ErrorHandler(e);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails, uniqueName } from '$lib/common'; import { getUserDetails, uniqueName } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -13,6 +13,6 @@ export const post: RequestHandler = async (event) => {
const { id } = await db.newTeam({ name, userId }); const { id } = await db.newTeam({ name, userId });
return { status: 201, body: { id } }; return { status: 201, body: { id } };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -110,25 +110,9 @@
required required
/> />
<Explainer <Explainer
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you." text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
/> />
</div> </div>
<!-- {:else}
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="eg: https://analytics.coollabs.io"
readonly={!$session.isAdmin}
name="fqdn"
id="fqdn"
bind:value={service.fqdn}
required
/>
<Explainer
text="If you specify <span class='text-green-600'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you."
/>
</div>
{/if} -->
</div> </div>
{#if service.type === 'plausibleanalytics'} {#if service.type === 'plausibleanalytics'}
<PlausibleAnalytics bind:service {readOnly} /> <PlausibleAnalytics bind:service {readOnly} />

View File

@@ -110,23 +110,23 @@
loading = false; loading = false;
} }
} }
onMount(async () => { // onMount(async () => {
if ( // if (
service.type && // service.type &&
service.destinationDockerId && // service.destinationDockerId &&
service.version && // service.version &&
service.fqdn && // service.fqdn &&
!isRunning // !isRunning
) { // ) {
try { // try {
await post(`/services/${service.id}/${service.type}/stop.json`, {}); // await post(`/services/${service.id}/${service.type}/stop.json`, {});
} catch ({ error }) { // } catch ({ error }) {
return errorNotification(error); // return errorNotification(error);
} finally { // } finally {
loading = false; // loading = false;
} // }
} // }
}); // });
</script> </script>
<nav class="nav-side"> <nav class="nav-side">

View File

@@ -1,6 +1,6 @@
import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common'; import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -21,6 +21,6 @@ export const post: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,6 +14,6 @@ export const post: RequestHandler = async (event) => {
await db.configureDestinationForService({ id, destinationId }); await db.configureDestinationForService({ id, destinationId });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, supportedServiceTypesAndVersions } from '$lib/database'; import { ErrorHandler, supportedServiceTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -27,6 +27,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -36,6 +36,7 @@
import Wordpress from '$lib/components/svg/services/Wordpress.svelte'; import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { post } from '$lib/api'; import { post } from '$lib/api';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
@@ -71,6 +72,8 @@
<VsCodeServer isAbsolute /> <VsCodeServer isAbsolute />
{:else if type.name === 'wordpress'} {:else if type.name === 'wordpress'}
<Wordpress isAbsolute /> <Wordpress isAbsolute />
{:else if type.name === 'vaultwarden'}
<VaultWarden isAbsolute />
{/if}{type.fancyName} {/if}{type.fancyName}
</button> </button>
</form> </form>

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler, supportedServiceTypesAndVersions } from '$lib/database'; import { ErrorHandler, supportedServiceTypesAndVersions } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
@@ -18,7 +18,7 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
@@ -35,6 +35,6 @@ export const post: RequestHandler = async (event) => {
status: 201 status: 201
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const del: RequestHandler = async (events) => { export const del: RequestHandler = async (events) => {
@@ -13,6 +13,6 @@ export const del: RequestHandler = async (events) => {
await db.removeService({ id }); await db.removeService({ id });
return { status: 200 }; return { status: 200 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -4,7 +4,7 @@ import {
generateDatabaseConfiguration, generateDatabaseConfiguration,
getServiceImage, getServiceImage,
getVersions, getVersions,
PrismaErrorHandler ErrorHandler
} from '$lib/database'; } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@@ -43,27 +43,6 @@ export const get: RequestHandler = async (event) => {
} }
}; };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };
// export const post: RequestHandler<Locals, FormData> = async (request) => {
// const { teamId, status, body } = await getUserDetails(request);
// if (status === 401) return { status, body }
// const { id } = request.params
// const name = request.body.get('name')
// const defaultDatabase = request.body.get('defaultDatabase')
// const dbUser = request.body.get('dbUser')
// const dbUserPassword = request.body.get('dbUserPassword')
// const rootUser = request.body.get('rootUser')
// const rootUserPassword = request.body.get('rootUserPassword')
// const version = request.body.get('version')
// try {
// return await db.updateDatabase({ id, name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version })
// } catch (err) {
// return err
// }
// }

View File

@@ -36,6 +36,7 @@
import Wordpress from '$lib/components/svg/services/Wordpress.svelte'; import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import Services from './_Services/_Services.svelte'; import Services from './_Services/_Services.svelte';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
export let service; export let service;
export let isRunning; export let isRunning;
@@ -94,6 +95,10 @@
<a href="https://wordpress.org" target="_blank"> <a href="https://wordpress.org" target="_blank">
<Wordpress /> <Wordpress />
</a> </a>
{:else if service.type === 'vaultwarden'}
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
<VaultWarden />
</a>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -16,6 +16,6 @@ export const post: RequestHandler = async (event) => {
await db.updateNocoDbOrMinioService({ id, fqdn, name }); await db.updateNocoDbOrMinioService({ id, fqdn, name });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return PrismaErrorHandler(error); return ErrorHandler(error);
} }
}; };

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