Compare commits

..

50 Commits

Author SHA1 Message Date
Andras Bacsai
71096acdff chore: version++ 2022-06-17 09:11:03 +02:00
Andras Bacsai
07da696397 Merge pull request #463 from execreate/main
Upgrade PSQL version to fix an issue with indices corruption
2022-06-17 09:10:40 +02:00
Jorilla Abdullaev
41baf150c2 upgrade PSQL version to fix an issue with CREATE INDEX CONCURRENTLY and REINDEX CONCURRENTLY that could cause silent data corruption of indexes 2022-06-17 11:08:47 +05:00
Andras Bacsai
f0a52b2ef4 Merge pull request #460 from coollabsio/scshiv29-dev/main
v2.9.9
2022-06-10 10:45:55 +02:00
Andras Bacsai
54e83fdff1 fix: remove package-lock 2022-06-10 10:45:32 +02:00
Andras Bacsai
46327ff2fc chore: version++ 2022-06-10 10:44:15 +02:00
Andras Bacsai
8b26acc841 fix: host and reload for uvicorn 2022-06-10 10:42:58 +02:00
Andras Bacsai
b90cb5a731 Merge pull request #420 from scshiv29-dev/main
Uvicorn ASGI for Python #388
2022-06-10 10:16:26 +02:00
Andras Bacsai
cd9b642c5e Merge branch 'feat/python' into main 2022-06-10 10:15:54 +02:00
Andras Bacsai
41a928d41b Merge branch 'main' into scshiv29-dev/main 2022-06-10 10:14:50 +02:00
Andras Bacsai
1388bee62c Merge pull request #459 from pucilpet/pucilpet-patch-1
Removed space from the version number
2022-06-10 08:51:36 +02:00
Petteri Pucilowski
8ebc778d40 Removed space from the version number 2022-06-10 01:30:46 +03:00
Andras Bacsai
3a59091b41 fix: nocodb persistency 2022-06-09 19:44:41 +02:00
Andras Bacsai
e764c4651c Merge pull request #454 from coollabsio/next
v2.9.8
2022-06-09 15:50:30 +02:00
Andras Bacsai
10f04d2177 fix: persistent nocodb 2022-06-09 15:49:21 +02:00
Andras Bacsai
119f994b50 chore: version++ 2022-06-09 15:49:07 +02:00
Andras Bacsai
e39541c318 fix: Traefik middleware 2022-06-09 15:18:21 +02:00
Andras Bacsai
cf88885c94 Merge pull request #452 from coollabsio/next
Fixes for v2.9.7
2022-06-09 14:01:20 +02:00
Andras Bacsai
1192346ce3 fix: remove comments 2022-06-09 14:00:41 +02:00
Andras Bacsai
0e3bd85847 fix: remove console log 2022-06-09 14:00:08 +02:00
Andras Bacsai
edeb6c6965 fix: Plausible script and middlewares 2022-06-09 13:59:09 +02:00
Andras Bacsai
138fd5cb6d Merge pull request #451 from coollabsio/next
v2.9.7
2022-06-09 13:34:02 +02:00
Andras Bacsai
155410bd44 Merge branch 'next' of github.com:coollabsio/coolify into next 2022-06-09 13:31:10 +02:00
Andras Bacsai
20bd829c2e Merge pull request #448 from titouanmathis/feat/gitlab-filter-projects-branches
feat: Add support for search for GitLab applications
2022-06-09 13:31:06 +02:00
Andras Bacsai
7b7e222946 chore: version++ 2022-06-09 13:30:29 +02:00
Andras Bacsai
98d901d06c fix: Plausible custom script 2022-06-08 13:45:42 +02:00
Titouan Mathis
4e862cda6f Add support for accessing all projects and branches for GitLab applications
Fix #236
2022-06-03 12:21:42 +02:00
Andras Bacsai
4e940807ae Merge pull request #446 from coollabsio/next
v2.9.6
2022-06-02 22:05:31 +02:00
Andras Bacsai
b081743f54 fix: pnpm command 2022-06-02 21:59:51 +02:00
Andras Bacsai
34bb9f301f fix: Fider changed an env variable name 2022-06-02 20:37:45 +02:00
Andras Bacsai
ed8a6daeea chore: version++ 2022-06-02 20:37:31 +02:00
Andras Bacsai
9e81ab43ac Merge pull request #445 from coollabsio/next
v2.9.5
2022-06-02 11:24:50 +02:00
Andras Bacsai
32d94cbe97 fix: proxy stop missing argument 2022-06-02 11:13:27 +02:00
Andras Bacsai
46a83aa457 chore: version++ 2022-06-02 11:13:14 +02:00
Andras Bacsai
08d7593ca9 Merge pull request #444 from coollabsio/next
v2.9.4
2022-06-01 10:43:30 +02:00
Andras Bacsai
a50f7a7cc2 fix: Revert gh and gl cloning 2022-06-01 10:42:17 +02:00
Andras Bacsai
2c33447f9f fix: typo 2022-05-31 23:23:39 +02:00
Andras Bacsai
d67a3f51ec fix: Demo version forms 2022-05-31 23:09:55 +02:00
Andras Bacsai
2719974262 chore: Version++ 2022-05-31 22:41:33 +02:00
Andras Bacsai
eb5aebd58d Merge pull request #442 from coollabsio/next
fix: Only reconfigure coolify proxy if its missconfigured
2022-05-31 22:30:09 +02:00
Andras Bacsai
98dbf3d8a5 fix: Only reconfigure coolify proxy if its missconfigured 2022-05-31 22:29:50 +02:00
Andras Bacsai
d9489a2cb4 Merge pull request #441 from coollabsio/next
fix: versions
2022-05-31 22:18:16 +02:00
Andras Bacsai
95832d34f7 fix: versions 2022-05-31 22:17:51 +02:00
Andras Bacsai
d3e9aea63d Merge pull request #440 from coollabsio/next
v2.9.3
2022-05-31 22:14:06 +02:00
Andras Bacsai
d6972e2ed1 fix: Recurisve clone instead of submodule 2022-05-31 21:52:25 +02:00
Andras Bacsai
50844e98be chore: version++ 2022-05-31 21:52:12 +02:00
scshiv29-dev
da86f0076b Merge branch 'main' into main 2022-05-19 09:54:10 +05:30
scshiv29-dev
9d8551a9be minor fix 2022-05-19 04:23:23 +00:00
scshiv29-dev
c376123877 added uvicorn 2022-05-11 22:54:13 +05:30
scshiv29-dev
cd3663038f added uvicorn 2022-05-11 22:45:50 +05:30
22 changed files with 252 additions and 128 deletions

9
.gitpod.yml Normal file
View File

@@ -0,0 +1,9 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
tasks:
- init: npm install && npm run build
command: npm run start

View File

@@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.9.2", "version": "2.9.10",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0", "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0",

View File

@@ -70,16 +70,29 @@ async function main() {
} }
}); });
} }
if (compare('2.9.2', version) >= 0) { if (settings.isTraefikUsed) {
// Force stop Coolify Proxy, as it had a bug in < 2.9.2. TrustProxy + api.insecure // Force stop Coolify Proxy, as it had a bug in < 2.9.2. TrustProxy + api.insecure
try { try {
await asyncExecShell(`docker stop -t 0 coolify-proxy && docker rm coolify-proxy`); const { stdout } = await asyncExecShell(
const { stdout: Config } = await asyncExecShell( `docker inspect coolify-proxy --format '{{json .Config.Cmd}}'`
`docker network inspect bridge --format '{{json .IPAM.Config }}'`
); );
const ip = JSON.parse(Config)[0].Gateway; if (
await asyncExecShell( !stdout
`docker run --restart always \ .replaceAll('[', '')
.replaceAll(']', '')
.replaceAll('"', '')
.replace('\n', '')
.split(',')
.includes('--entrypoints.web.forwardedHeaders.insecure=true')
) {
console.log('Reconfiguring Coolify Proxy (Traefik)...');
await asyncExecShell(`docker stop -t 0 coolify-proxy && docker rm coolify-proxy`);
const { stdout: Config } = await asyncExecShell(
`docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
await asyncExecShell(
`docker run --restart always \
--add-host 'host.docker.internal:host-gateway' \ --add-host 'host.docker.internal:host-gateway' \
--add-host 'host.docker.internal:${ip}' \ --add-host 'host.docker.internal:${ip}' \
-v coolify-traefik-letsencrypt:/etc/traefik/acme \ -v coolify-traefik-letsencrypt:/etc/traefik/acme \
@@ -101,7 +114,8 @@ async function main() {
--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \ --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \ --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
--log.level=error` --log.level=error`
); );
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -10,8 +10,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (isPnpm) { if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
Dockerfile.push('RUN pnpm add -g pnpm');
} }
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`);

View File

@@ -35,8 +35,7 @@ const createDockerfile = async (data, image): Promise<void> => {
}); });
} }
if (isPnpm) { if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
Dockerfile.push('RUN pnpm add -g pnpm');
} }
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${installCommand}`);

View File

@@ -36,8 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
}); });
} }
if (isPnpm) { if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
Dockerfile.push('RUN pnpm add -g pnpm');
} }
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${installCommand}`);

View File

@@ -35,8 +35,7 @@ const createDockerfile = async (data, image): Promise<void> => {
}); });
} }
if (isPnpm) { if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
Dockerfile.push('RUN pnpm add -g pnpm');
} }
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${installCommand}`);

View File

@@ -34,6 +34,8 @@ const createDockerfile = async (data, image): Promise<void> => {
} }
if (pythonWSGI?.toLowerCase() === 'gunicorn') { if (pythonWSGI?.toLowerCase() === 'gunicorn') {
Dockerfile.push(`RUN pip install gunicorn`); Dockerfile.push(`RUN pip install gunicorn`);
} else if (pythonWSGI?.toLowerCase() === 'uvicorn') {
Dockerfile.push(`RUN pip install uvicorn`);
} else if (pythonWSGI?.toLowerCase() === 'uwsgi') { } else if (pythonWSGI?.toLowerCase() === 'uwsgi') {
Dockerfile.push(`RUN apk add --no-cache uwsgi-python3`); Dockerfile.push(`RUN apk add --no-cache uwsgi-python3`);
// Dockerfile.push(`RUN pip install --no-cache-dir uwsgi`) // Dockerfile.push(`RUN pip install --no-cache-dir uwsgi`)
@@ -50,6 +52,8 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
if (pythonWSGI?.toLowerCase() === 'gunicorn') { if (pythonWSGI?.toLowerCase() === 'gunicorn') {
Dockerfile.push(`CMD gunicorn -w=4 -b=0.0.0.0:8000 ${pythonModule}:${pythonVariable}`); Dockerfile.push(`CMD gunicorn -w=4 -b=0.0.0.0:8000 ${pythonModule}:${pythonVariable}`);
} else if (pythonWSGI?.toLowerCase() === 'uvicorn') {
Dockerfile.push(`CMD uvicorn ${pythonModule}:${pythonVariable} --port ${port} --host 0.0.0.0`);
} else if (pythonWSGI?.toLowerCase() === 'uwsgi') { } else if (pythonWSGI?.toLowerCase() === 'uwsgi') {
Dockerfile.push( Dockerfile.push(
`CMD uwsgi --master -p 4 --http-socket 0.0.0.0:8000 --uid uwsgi --plugins python3 --protocol uwsgi --wsgi ${pythonModule}:${pythonVariable}` `CMD uwsgi --master -p 4 --http-socket 0.0.0.0:8000 --uid uwsgi --plugins python3 --protocol uwsgi --wsgi ${pythonModule}:${pythonVariable}`

View File

@@ -62,7 +62,7 @@ export const supportedDatabaseTypesAndVersions = [
name: 'postgresql', name: 'postgresql',
fancyName: 'PostgreSQL', fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql', baseImage: 'bitnami/postgresql',
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0'] versions: ['14.4.0', '13.6.0', '12.10.0', '11.15.0', '10.20.0']
}, },
{ {
name: 'redis', name: 'redis',

View File

@@ -66,8 +66,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
}); });
} }
if (isPnpm) { if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
Dockerfile.push('RUN pnpm add -g pnpm');
} }
if (installCommand) { if (installCommand) {
Dockerfile.push(`COPY .${baseDirectory || ''}/package.json ./`); Dockerfile.push(`COPY .${baseDirectory || ''}/package.json ./`);

View File

@@ -49,7 +49,7 @@ export default async function ({
applicationId applicationId
}); });
await asyncExecShell( await asyncExecShell(
`GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git submodule update --init --recursive && GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git lfs pull && cd .. ` `git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
); );
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', ''); return commit.replace('\n', '');

View File

@@ -31,7 +31,7 @@ export default async function ({
}); });
await asyncExecShell( await asyncExecShell(
`GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git submodule update --init --recursive && GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git lfs pull && cd .. ` `git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
); );
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', ''); return commit.replace('\n', '');

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
export let application; export let application;
export let appId; export let appId;
import Select from 'svelte-select';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
@@ -33,6 +34,10 @@
let showSave = false; let showSave = false;
let autodeploy = application.settings.autodeploy || true; let autodeploy = application.settings.autodeploy || true;
let search = {
project: '',
branch: ''
};
let selected = { let selected = {
group: undefined, group: undefined,
project: undefined, project: undefined,
@@ -84,16 +89,49 @@
}, 100); }, 100);
} }
function selectGroup(event) {
selected.group = event.detail;
selected.project = null;
selected.branch = null;
showSave = false;
loadProjects();
}
async function searchProjects(searchText) {
if (!selected.group) {
return;
}
search.project = searchText;
await loadProjects();
return projects;
}
function selectProject(event) {
selected.project = event.detail;
selected.branch = null;
showSave = false;
loadBranches();
}
async function loadProjects() { async function loadProjects() {
const params = new URLSearchParams({
page: 1,
per_page: 25,
archived: false
});
if (search.project) {
params.append('search', search.project);
}
loading.projects = true; loading.projects = true;
if (username === selected.group.name) { if (username === selected.group.name) {
try { try {
projects = await get( params.append('min_access_level', 40);
`${apiUrl}/v4/users/${selected.group.name}/projects?min_access_level=40&page=1&per_page=25&archived=false`, projects = await get(`${apiUrl}/v4/users/${selected.group.name}/projects?${params}`, {
{ Authorization: `Bearer ${$gitTokens.gitlabToken}`
Authorization: `Bearer ${$gitTokens.gitlabToken}` });
}
);
} catch (error) { } catch (error) {
errorNotification(error); errorNotification(error);
throw new Error(error); throw new Error(error);
@@ -102,12 +140,9 @@
} }
} else { } else {
try { try {
projects = await get( projects = await get(`${apiUrl}/v4/groups/${selected.group.id}/projects?${params}`, {
`${apiUrl}/v4/groups/${selected.group.id}/projects?page=1&per_page=25&archived=false`, Authorization: `Bearer ${$gitTokens.gitlabToken}`
{ });
Authorization: `Bearer ${$gitTokens.gitlabToken}`
}
);
} catch (error) { } catch (error) {
errorNotification(error); errorNotification(error);
throw new Error(error); throw new Error(error);
@@ -117,11 +152,35 @@
} }
} }
async function searchBranches(searchText) {
if (!selected.project) {
return;
}
search.branch = searchText;
await loadBranches();
return branches;
}
function selectBranch(event) {
selected.branch = event.detail;
isBranchAlreadyUsed();
}
async function loadBranches() { async function loadBranches() {
const params = new URLSearchParams({
page: 1,
per_page: 100
});
if (search.branch) {
params.append('search', search.branch);
}
loading.branches = true; loading.branches = true;
try { try {
branches = await get( branches = await get(
`${apiUrl}/v4/projects/${selected.project.id}/repository/branches?per_page=100&page=1`, `${apiUrl}/v4/projects/${selected.project.id}/repository/branches?${params}`,
{ {
Authorization: `Bearer ${$gitTokens.gitlabToken}` Authorization: `Bearer ${$gitTokens.gitlabToken}`
} }
@@ -267,70 +326,79 @@
<form on:submit={handleSubmit}> <form on:submit={handleSubmit}>
<div class="flex flex-col space-y-2 px-4 xl:flex-row xl:space-y-0 xl:space-x-2 "> <div class="flex flex-col space-y-2 px-4 xl:flex-row xl:space-y-0 xl:space-x-2 ">
{#if loading.base} <div class="custom-select-wrapper">
<select name="group" disabled class="w-96"> <Select
<option selected value="">{$t('application.configuration.loading_groups')}</option> placeholder={loading.base
</select> ? $t('application.configuration.loading_groups')
{:else} : $t('application.configuration.select_a_group')}
<select name="group" class="w-96" bind:value={selected.group} on:change={loadProjects}> id="group"
<option value="" disabled selected>{$t('application.configuration.select_a_group')}</option> showIndicator={!loading.base}
{#each groups as group} isWaiting={loading.base}
<option value={group}>{group.full_name}</option> on:select={selectGroup}
{/each} on:clear={() => {
</select> showSave = false;
{/if} projects = [];
{#if loading.projects} branches = [];
<select name="project" disabled class="w-96"> selected.group = null;
<option selected value="">{$t('application.configuration.loading_projects')}</option> selected.project = null;
</select> selected.branch = null;
{:else if !loading.projects && projects.length > 0} }}
<select value={selected.group}
name="project" isDisabled={loading.base}
class="w-96" isClearable={false}
bind:value={selected.project} items={groups}
on:change={loadBranches} labelIdentifier="full_name"
disabled={!selected.group} optionIdentifier="id"
> />
<option value="" disabled selected </div>
>{$t('application.configuration.select_a_project')}</option <div class="custom-select-wrapper">
> <Select
{#each projects as project} placeholder={loading.projects
<option value={project}>{project.name}</option> ? $t('application.configuration.loading_projects')
{/each} : $t('application.configuration.select_a_project')}
</select> noOptionsMessage={$t('application.configuration.no_projects_found')}
{:else} id="project"
<select name="project" disabled class="w-96"> showIndicator={!loading.projects}
<option disabled selected value="" isWaiting={loading.projects}
>{$t('application.configuration.no_projects_found')}</option isDisabled={loading.projects || !selected.group}
> on:select={selectProject}
</select> on:clear={() => {
{/if} showSave = false;
branches = [];
{#if loading.branches} selected.project = null;
<select name="branch" disabled class="w-96"> selected.branch = null;
<option selected value="">{$t('application.configuration.loading_branches')}</option> }}
</select> value={selected.project}
{:else if !loading.branches && branches.length > 0} isClearable={false}
<select items={projects}
name="branch" loadOptions={searchProjects}
class="w-96" labelIdentifier="name"
bind:value={selected.branch} optionIdentifier="id"
on:change={isBranchAlreadyUsed} />
disabled={!selected.project} </div>
> <div class="custom-select-wrapper">
<option value="" disabled selected>{$t('application.configuration.select_a_branch')}</option <Select
> placeholder={loading.branches
{#each branches as branch} ? $t('application.configuration.loading_branches')
<option value={branch}>{branch.name}</option> : $t('application.configuration.select_a_branch')}
{/each} noOptionsMessage={$t('application.configuration.no_branches_found')}
</select> id="branch"
{:else} showIndicator={!loading.branches}
<select name="project" disabled class="w-96"> isWaiting={loading.branches}
<option disabled selected value="" isDisabled={loading.branches || !selected.project}
>{$t('application.configuration.no_branches_found')}</option on:select={selectBranch}
> on:clear={() => {
</select> showSave = false;
{/if} selected.branch = null;
}}
value={selected.branch}
isClearable={false}
items={branches}
loadOptions={searchBranches}
labelIdentifier="name"
optionIdentifier="web_url"
/>
</div>
</div> </div>
<div class="flex flex-col items-center justify-center space-y-4 pt-5"> <div class="flex flex-col items-center justify-center space-y-4 pt-5">
<button <button

View File

@@ -10,7 +10,6 @@
} }
const endpoint = `/applications/${params.id}.json`; const endpoint = `/applications/${params.id}.json`;
const res = await fetch(endpoint); const res = await fetch(endpoint);
if (res.ok) { if (res.ok) {
return { return {
props: { props: {
@@ -18,7 +17,6 @@
} }
}; };
} }
return { return {
status: res.status, status: res.status,
error: new Error(`Could not load ${endpoint}`) error: new Error(`Could not load ${endpoint}`)
@@ -39,7 +37,6 @@
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import Select from 'svelte-select'; import Select from 'svelte-select';
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte'; import Setting from '$lib/components/Setting.svelte';
import type Prisma from '@prisma/client'; import type Prisma from '@prisma/client';
@@ -51,9 +48,7 @@
import { disabledButton, status } from '$lib/store'; import { disabledButton, status } from '$lib/store';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
const { id } = $page.params; const { id } = $page.params;
let domainEl: HTMLInputElement; let domainEl: HTMLInputElement;
let loading = false; let loading = false;
let usageLoading = false; let usageLoading = false;
@@ -69,12 +64,12 @@
let previews = application.settings.previews; let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts; let dualCerts = application.settings.dualCerts;
let autodeploy = application.settings.autodeploy; let autodeploy = application.settings.autodeploy;
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
let isNonWWWDomainOK = false; let isNonWWWDomainOK = false;
let isWWWDomainOK = false; let isWWWDomainOK = false;
$: isDisabled = !$session.isAdmin || $status.application.isRunning; $: isDisabled = !$session.isAdmin || $status.application.isRunning;
let wsgis = [ let wsgis = [
{ {
value: 'None', value: 'None',
@@ -83,6 +78,10 @@
{ {
value: 'Gunicorn', value: 'Gunicorn',
label: 'Gunicorn' label: 'Gunicorn'
},
{
value: 'Uvicorn',
label: 'Uvicorn'
} }
]; ];
function containerClass() { function containerClass() {
@@ -102,7 +101,7 @@
onMount(async () => { onMount(async () => {
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`; application.fqdn = `http://${cuid()}.demo.coolify.io`;
await post(`/applications/${id}.json`, { ...application }); await handleSubmit();
} }
domainEl.focus(); domainEl.focus();
await getUsage(); await getUsage();
@@ -110,7 +109,6 @@
await getUsage(); await getUsage();
}, 1000); }, 1000);
}); });
async function changeSettings(name) { async function changeSettings(name) {
if (name === 'debug') { if (name === 'debug') {
debug = !debug; debug = !debug;
@@ -196,7 +194,6 @@
application.baseBuildImage = event.detail.value; application.baseBuildImage = event.detail.value;
await handleSubmit(); await handleSubmit();
} }
async function isDNSValid(domain, isWWW) { async function isDNSValid(domain, isWWW) {
try { try {
await get(`/applications/${id}/check.json?domain=${domain}`); await get(`/applications/${id}/check.json?domain=${domain}`);
@@ -218,7 +215,6 @@
</div> </div>
<span class="text-xs">{application.name} </span> <span class="text-xs">{application.name} </span>
</div> </div>
{#if application.fqdn} {#if application.fqdn}
<a <a
href={application.fqdn} href={application.fqdn}
@@ -433,7 +429,6 @@
<label for="baseBuildImage" class="text-base font-bold text-stone-100" <label for="baseBuildImage" class="text-base font-bold text-stone-100"
>{$t('application.base_build_image')}</label >{$t('application.base_build_image')}</label
> >
<div class="custom-select-wrapper"> <div class="custom-select-wrapper">
<Select <Select
{isDisabled} {isDisabled}
@@ -530,12 +525,11 @@
</div> </div>
{#if application.buildPack === 'python'} {#if application.buildPack === 'python'}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI</label> <label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
<div class="custom-select-wrapper"> <div class="custom-select-wrapper">
<Select id="wsgi" items={wsgis} on:select={selectWSGI} value={application.pythonWSGI} /> <Select id="wsgi" items={wsgis} on:select={selectWSGI} value={application.pythonWSGI} />
</div> </div>
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="pythonModule" class="text-base font-bold text-stone-100">Module</label> <label for="pythonModule" class="text-base font-bold text-stone-100">Module</label>
<input <input
@@ -544,7 +538,7 @@
id="pythonModule" id="pythonModule"
required required
bind:value={application.pythonModule} bind:value={application.pythonModule}
placeholder={application.pythonWSGI?.toLowerCase() !== 'gunicorn' ? 'main.py' : 'main'} placeholder={application.pythonWSGI?.toLowerCase() !== 'none' ? 'main' : 'main.py'}
/> />
</div> </div>
{#if application.pythonWSGI?.toLowerCase() === 'gunicorn'} {#if application.pythonWSGI?.toLowerCase() === 'gunicorn'}
@@ -560,6 +554,19 @@
/> />
</div> </div>
{/if} {/if}
{#if application.pythonWSGI?.toLowerCase() === 'uvicorn'}
<div class="grid grid-cols-2 items-center">
<label for="pythonVariable" class="text-base font-bold text-stone-100">Variable</label>
<input
readonly={!$session.isAdmin}
name="pythonVariable"
id="pythonVariable"
required
bind:value={application.pythonVariable}
placeholder="default: app"
/>
</div>
{/if}
{/if} {/if}
{#if !staticDeployments.includes(application.buildPack)} {#if !staticDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
@@ -694,7 +701,6 @@
> >
<Explainer text={$t('application.publish_directory_explainer')} /> <Explainer text={$t('application.publish_directory_explainer')} />
</div> </div>
<input <input
readonly={!$session.isAdmin} readonly={!$session.isAdmin}
name="publishDirectory" name="publishDirectory"

View File

@@ -188,7 +188,7 @@
{/each} {/each}
{:else} {:else}
<div class="flex-col"> <div class="flex-col">
<div class="text-center font-bold text-xl"> <div class="text-center text-xl font-bold">
{$t('application.preview.no_previews_available')} {$t('application.preview.no_previews_available')}
</div> </div>
</div> </div>

View File

@@ -12,7 +12,7 @@ export const del: RequestHandler = async (event) => {
const database = await db.getDatabase({ id, teamId }); const database = await db.getDatabase({ id, teamId });
if (database.destinationDockerId) { if (database.destinationDockerId) {
const everStarted = await stopDatabase(database); const everStarted = await stopDatabase(database);
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort); if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
} }
await db.removeDatabase({ id }); await db.removeDatabase({ id });
return { status: 200 }; return { status: 200 };

View File

@@ -29,7 +29,7 @@ export const post: RequestHandler = async (event) => {
} }
} else { } else {
await db.prisma.database.update({ where: { id }, data: { publicPort: null } }); await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
await stopTcpHttpProxy(destinationDocker, oldPublicPort); await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);
} }
} }
return { return {

View File

@@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
try { try {
const database = await db.getDatabase({ id, teamId }); const database = await db.getDatabase({ id, teamId });
const everStarted = await stopDatabase(database); const everStarted = await stopDatabase(database);
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort); if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
await db.setDatabase({ id, isPublic: false }); await db.setDatabase({ id, isPublic: false });
await db.prisma.database.update({ where: { id }, data: { publicPort: null } }); await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
@@ -21,6 +21,7 @@ export const post: RequestHandler = async (event) => {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
console.log(error);
return ErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@@ -74,7 +74,23 @@
onMount(async () => { onMount(async () => {
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) { if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
service.fqdn = `http://${cuid()}.demo.coolify.io`; service.fqdn = `http://${cuid()}.demo.coolify.io`;
await post(`/services/${id}/${service.type}.json`, { ...service }); if (service.type === 'wordpress') {
service.wordpress.mysqlDatabase = 'db';
}
if (service.type === 'plausibleanalytics') {
service.plausibleAnalytics.email = 'noreply@demo.com';
service.plausibleAnalytics.username = 'admin';
}
if (service.type === 'minio') {
service.minio.apiFqdn = `http://${cuid()}.demo.coolify.io`;
}
if (service.type === 'ghost') {
service.ghost.mariadbDatabase = 'db';
}
if (service.type === 'fider') {
service.fider.emailNoreply = 'noreply@demo.com';
}
await handleSubmit();
} }
}); });
</script> </script>

View File

@@ -59,7 +59,7 @@ export const post: RequestHandler = async (event) => {
fider: { fider: {
image: `${image}:${version}`, image: `${image}:${version}`,
environmentVariables: { environmentVariables: {
HOST_DOMAIN: domain, BASE_URL: domain,
DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}?sslmode=disable`, DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}?sslmode=disable`,
JWT_SECRET: `${jwtSecret.replace(/\$/g, '$$$')}`, JWT_SECRET: `${jwtSecret.replace(/\$/g, '$$$')}`,
EMAIL_NOREPLY: emailNoreply, EMAIL_NOREPLY: emailNoreply,

View File

@@ -27,6 +27,7 @@ export const post: RequestHandler = async (event) => {
const config = { const config = {
image: `${image}:${version}`, image: `${image}:${version}`,
volume: `${id}-nc:/usr/app/data`,
environmentVariables: {} environmentVariables: {}
}; };
if (serviceSecret.length > 0) { if (serviceSecret.length > 0) {
@@ -41,6 +42,7 @@ export const post: RequestHandler = async (event) => {
container_name: id, container_name: id,
image: config.image, image: config.image,
networks: [network], networks: [network],
volumes: [config.volume],
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
@@ -59,6 +61,11 @@ export const post: RequestHandler = async (event) => {
[network]: { [network]: {
external: true external: true
} }
},
volumes: {
[config.volume.split(':')[0]]: {
name: config.volume.split(':')[0]
}
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;

View File

@@ -7,7 +7,7 @@ import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
function configureMiddleware( function configureMiddleware(
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }, { id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
traefik traefik
) { ) {
if (isHttps) { if (isHttps) {
@@ -125,6 +125,15 @@ function configureMiddleware(
} }
} }
} }
if (type === 'plausibleanalytics' && scriptName && scriptName !== 'plausible.js') {
if (!traefik.http.routers[`${id}`].middlewares.includes(`${id}-redir`)) {
traefik.http.routers[`${id}`].middlewares.push(`${id}-redir`);
}
if (!traefik.http.routers[`${id}-secure`].middlewares.includes(`${id}-redir`)) {
traefik.http.routers[`${id}-secure`].middlewares.push(`${id}-redir`);
}
}
} }
export const get: RequestHandler = async (event) => { export const get: RequestHandler = async (event) => {
const traefik = { const traefik = {
@@ -176,7 +185,7 @@ export const get: RequestHandler = async (event) => {
} = application; } = application;
if (destinationDockerId) { if (destinationDockerId) {
const { engine, network } = destinationDocker; const { engine, network } = destinationDocker;
const isRunning = await checkContainer(engine, id); const isRunning = true;
if (fqdn) { if (fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
@@ -244,7 +253,7 @@ export const get: RequestHandler = async (event) => {
if (found) { if (found) {
const port = found.ports.main; const port = found.ports.main;
const publicPort = service[type]?.publicPort; const publicPort = service[type]?.publicPort;
const isRunning = await checkContainer(engine, id); const isRunning = true;
if (fqdn) { if (fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
@@ -335,11 +344,6 @@ export const get: RequestHandler = async (event) => {
replacement: '/js/plausible.js' replacement: '/js/plausible.js'
} }
}; };
if (traefik.http.routers[id].middlewares.length > 0) {
traefik.http.routers[id].middlewares.push(`${id}-redir`);
} else {
traefik.http.routers[id].middlewares = [`${id}-redir`];
}
} }
} }
for (const coolify of data.coolify) { for (const coolify of data.coolify) {