Merge pull request #159 from coollabsio/next

v2.0.18
This commit is contained in:
Andras Bacsai 2022-02-22 20:41:53 +01:00 committed by GitHub
commit 0dfcf9b1e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 858 additions and 746 deletions

View File

@ -1,12 +1,12 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.17",
"version": "2.0.18",
"license": "AGPL-3.0",
"scripts": {
"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:logs": "docker compose -f docker-compose-dev.yaml logs -f --tail 10",
"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:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
"studio": "npx prisma studio",
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
"build": "svelte-kit build",

View File

@ -0,0 +1,20 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Setting" (
"id" TEXT NOT NULL PRIMARY KEY,
"fqdn" TEXT,
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"minPort" INTEGER NOT NULL DEFAULT 9000,
"maxPort" INTEGER NOT NULL DEFAULT 9100,
"proxyPassword" TEXT NOT NULL,
"proxyUser" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
DROP TABLE "Setting";
ALTER TABLE "new_Setting" RENAME TO "Setting";
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -12,6 +12,8 @@ model Setting {
fqdn String? @unique
isRegistrationEnabled Boolean @default(false)
dualCerts Boolean @default(false)
minPort Int @default(9000)
maxPort Int @default(9100)
proxyPassword String
proxyUser String
createdAt DateTime @default(now())

View File

@ -15,16 +15,12 @@
export let placeholder = '';
let disabledClass = 'bg-coolback disabled:bg-coolblack';
let actionsShow = false;
let isHttps = browser && window.location.protocol === 'https:';
function showActions(value) {
actionsShow = value;
}
function copyToClipboard() {
if (isHttps && navigator.clipboard) {
navigator.clipboard.writeText(value);
toast.push('Copied to clipboard');
toast.push('Copied to clipboard.');
}
}
</script>
@ -33,7 +29,7 @@
{#if !isPasswordField || showPassword}
{#if textarea}
<textarea
rows="3"
rows="5"
class={disabledClass}
{placeholder}
type="text"

View File

@ -3,4 +3,4 @@
export let customClass = 'max-w-[24rem]';
</script>
<div class="py-1 text-xs text-stone-400 {customClass}">{@html text}</div>
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>

View File

@ -15,7 +15,12 @@
<Explainer text={description} />
</div>
</div>
<div class:tooltip={dataTooltip} class:text-center={isCenter} data-tooltip={dataTooltip}>
<div
class:tooltip={dataTooltip}
class:text-center={isCenter}
data-tooltip={dataTooltip}
class="flex justify-center"
>
<div
type="button"
on:click

View File

@ -1,16 +1,146 @@
const defaultBuildAndDeploy = {
installCommand: 'yarn install',
buildCommand: 'yarn build',
startCommand: 'yarn start'
};
export const buildPacks = [
{
function defaultBuildAndDeploy(packageManager) {
return {
installCommand:
packageManager === 'npm' ? `${packageManager} run install` : `${packageManager} install`,
buildCommand:
packageManager === 'npm' ? `${packageManager} run build` : `${packageManager} build`,
startCommand:
packageManager === 'npm' ? `${packageManager} run start` : `${packageManager} start`
};
}
export function findBuildPack(pack, packageManager = 'npm') {
const metaData = buildPacks.find((b) => b.name === pack);
if (pack === 'node') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null
};
}
if (pack === 'static') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 80
};
}
if (pack === 'docker') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null
};
}
if (pack === 'svelte') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'public',
port: 80
};
}
if (pack === 'nestjs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
startCommand:
packageManager === 'npm' ? 'npm run start:prod' : `${packageManager} run start:prod`,
publishDirectory: null,
port: 3000
};
}
if (pack === 'react') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'build',
port: 80
};
}
if (pack === 'nextjs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: null,
port: 3000
};
}
if (pack === 'gatsby') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'public',
port: 80
};
}
if (pack === 'vuejs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'dist',
port: 80
};
}
if (pack === 'nuxtjs') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: null,
port: 3000
};
}
if (pack === 'preact') {
return {
...metaData,
...defaultBuildAndDeploy(packageManager),
publishDirectory: 'build',
port: 80
};
}
if (pack === 'php') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 80
};
}
if (pack === 'rust') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 3000
};
}
return {
name: 'node',
fancyName: 'Node.js',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700',
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null,
port: null
};
}
export const buildPacks = [
{
name: 'node',
fancyName: 'Node.js',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
@ -18,103 +148,72 @@ export const buildPacks = [
{
name: 'static',
publishDirectory: 'dist',
port: 80,
fancyName: 'Static',
hoverColor: 'hover:bg-orange-700',
color: 'bg-orange-700'
},
{
name: 'docker',
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null,
fancyName: 'Docker',
hoverColor: 'hover:bg-sky-700',
color: 'bg-sky-700'
},
{
name: 'svelte',
...defaultBuildAndDeploy,
publishDirectory: 'public',
port: 80,
fancyName: 'Svelte',
hoverColor: 'hover:bg-orange-700',
color: 'bg-orange-700'
},
{
name: 'nestjs',
...defaultBuildAndDeploy,
startCommand: 'yarn start:prod',
port: 3000,
fancyName: 'NestJS',
hoverColor: 'hover:bg-red-700',
color: 'bg-red-700'
},
{
name: 'react',
...defaultBuildAndDeploy,
publishDirectory: 'build',
port: 80,
fancyName: 'React',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'nextjs',
...defaultBuildAndDeploy,
port: 3000,
fancyName: 'NextJS',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'gatsby',
...defaultBuildAndDeploy,
publishDirectory: 'public',
port: 80,
fancyName: 'Gatsby',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'vuejs',
...defaultBuildAndDeploy,
publishDirectory: 'dist',
port: 80,
fancyName: 'VueJS',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
},
{
name: 'nuxtjs',
...defaultBuildAndDeploy,
port: 3000,
fancyName: 'NuxtJS',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
},
{
name: 'preact',
...defaultBuildAndDeploy,
publishDirectory: 'build',
port: 80,
fancyName: 'Preact',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
},
{
name: 'php',
port: 80,
fancyName: 'PHP',
hoverColor: 'hover:bg-indigo-700',
color: 'bg-indigo-700'
},
{
name: 'rust',
port: 3000,
fancyName: 'Rust',
hoverColor: 'hover:bg-pink-700',
color: 'bg-pink-700'

View File

@ -1,9 +1,9 @@
import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import * as db from '$lib/database';
import cuid from 'cuid';
import { generatePassword } from '.';
import { prisma, ErrorHandler } from './common';
import getPort from 'get-port';
import getPort, { portNumbers } from 'get-port';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
export async function listDatabases(teamId) {
@ -16,24 +16,9 @@ export async function newDatabase({ name, teamId }) {
const rootUserPassword = encrypt(generatePassword());
const defaultDatabase = cuid();
let publicPort = await getPort();
let i = 0;
do {
const usedPorts = await prisma.database.findMany({ where: { publicPort } });
if (usedPorts.length === 0) break;
publicPort = await getPort();
i++;
} while (i < 10);
if (i === 9) {
throw {
error: 'No free port found!? Is it possible?'
};
}
return await prisma.database.create({
data: {
name,
publicPort,
defaultDatabase,
dbUser,
dbUserPassword,

View File

@ -3,23 +3,24 @@ import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common';
import * as db from '$lib/database';
import cuid from 'cuid';
import getPort from 'get-port';
import getPort, { portNumbers } from 'get-port';
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
try {
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const nakedDomain = domain.replace('www.', '');
const wwwDomain = `www.${nakedDomain}`;
const randomCuid = cuid();
const randomPort = 9080;
const randomPort = await getPort({ port: portNumbers(minPort, maxPort) });
let host;
let dualCerts = false;
if (isCoolify) {
const data = await db.prisma.setting.findFirst();
dualCerts = data.dualCerts;
host = 'unix:///var/run/docker.sock';
} else {
// Check Application
const applicationData = await db.prisma.application.findUnique({
where: { id },
include: { destinationDocker: true, settings: true }

View File

@ -101,7 +101,6 @@ export default async function () {
if (isHttps) await forceSSLOnApplication(domain);
}
} catch (error) {
console.log(error);
throw error;
}
}

View File

@ -4,9 +4,16 @@ import { dockerInstance } from '$lib/docker';
import { forceSSLOnApplication } from '$lib/haproxy';
import * as db from '$lib/database';
import { dev } from '$app/env';
import getPort, { portNumbers } from 'get-port';
import cuid from 'cuid';
export default async function () {
try {
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const randomCuid = cuid();
const destinationDockers = await prisma.destinationDocker.findMany({});
for (const destination of destinationDockers) {
if (destination.isCoolifyProxyUsed) {
@ -30,10 +37,10 @@ export default async function () {
} else {
const host = getEngine(destination.engine);
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:${publicPort} -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 ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
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 -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);
}
@ -52,7 +59,7 @@ export default async function () {
console.log('DEV MODE: SSL is enabled');
} else {
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:${publicPort} -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 ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(

View File

@ -98,7 +98,7 @@
updateStatus.loading = true;
try {
await post(`/update.json`, { type: 'update', latestVersion });
toast.push('Update completed.<br>Waiting for the new version to start...');
toast.push('Update completed.<br><br>Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
@ -444,7 +444,8 @@
</button>
{/if}
{/if}
</div>
<div class="flex flex-col space-y-4 py-2">
<a
sveltekit:prefetch
href="/teams"
@ -519,20 +520,20 @@
<path d="M7 12h14l-3 -3m0 6l3 -3" />
</svg>
</div>
</div>
<div
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
>
<a
class="text-[10px] no-underline"
href={`https://github.com/coollabsio/coolify/releases/tag/v${$session.version}`}
target="_blank">v{$session.version}</a
<div
class="w-full text-center font-bold text-stone-400 hover:bg-coolgray-200 hover:text-white"
>
<a
class="text-[10px] no-underline"
href={`https://github.com/coollabsio/coolify/releases/tag/v${$session.version}`}
target="_blank">v{$session.version}</a
>
</div>
</div>
</div>
</nav>
<select
class="fixed right-0 bottom-0 z-50 m-2 p-2 px-4"
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4"
bind:value={selectedTeamId}
on:change={switchTeam}
>

View File

@ -16,7 +16,7 @@ export const post: RequestHandler = async (event) => {
const found = await db.isDomainConfigured({ id, fqdn });
if (found) {
throw {
message: `Domain ${getDomain(fqdn).replace('www.', '')} is already configured.`
message: `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
};
}
return {

View File

@ -3,6 +3,7 @@
import { page } from '$app/stores';
import { post } from '$lib/api';
import { findBuildPack } from '$lib/components/templates';
import { errorNotification } from '$lib/form';
const { id } = $page.params;
@ -11,10 +12,13 @@
export let buildPack;
export let foundConfig;
export let scanning;
export let packageManager;
async function handleSubmit(name) {
try {
const tempBuildPack = JSON.parse(JSON.stringify(buildPack));
const tempBuildPack = JSON.parse(
JSON.stringify(findBuildPack(buildPack.name, packageManager))
);
delete tempBuildPack.name;
delete tempBuildPack.fancyName;
delete tempBuildPack.color;

View File

@ -29,7 +29,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { buildPacks, scanningTemplates } from '$lib/components/templates';
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/components/templates';
import BuildPack from './_BuildPack.svelte';
import { page, session } from '$app/stores';
import { get } from '$lib/api';
@ -38,6 +38,7 @@
let scanning = true;
let foundConfig = null;
let packageManager = 'npm';
export let apiUrl;
export let projectId;
@ -49,10 +50,11 @@
function checkPackageJSONContents({ key, json }) {
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
}
function checkTemplates({ json }) {
function checkTemplates({ json, packageManager }) {
for (const [key, value] of Object.entries(scanningTemplates)) {
if (checkPackageJSONContents({ key, json })) {
return buildPacks.find((bp) => bp.name === value.buildPack);
foundConfig = findBuildPack(value.buildPack, packageManager);
break;
}
}
}
@ -65,6 +67,10 @@
const packageJson = files.find(
(file) => file.name === 'package.json' && file.type === 'blob'
);
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'blob');
const pnpmLock = files.find(
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'blob'
);
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'blob');
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'blob');
const requirementsTxt = files.find(
@ -72,6 +78,10 @@
);
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
if (yarnLock) packageManager = 'yarn';
if (pnpmLock) packageManager = 'pnpm';
if (dockerfile) {
foundConfig.buildPack = 'docker';
} else if (packageJson) {
@ -83,15 +93,15 @@
}
);
const json = JSON.parse(data) || {};
foundConfig = checkTemplates({ json });
checkTemplates({ json, packageManager });
} else if (cargoToml) {
foundConfig = buildPacks.find((bp) => bp.name === 'rust');
foundConfig = findBuildPack('rust');
} else if (requirementsTxt) {
foundConfig = buildPacks.find((bp) => bp.name === 'python');
foundConfig = findBuildPack('python');
} else if (indexHtml) {
foundConfig = buildPacks.find((bp) => bp.name === 'static');
foundConfig = findBuildPack('static', packageManager);
} else if (indexPHP) {
foundConfig = buildPacks.find((bp) => bp.name === 'php');
foundConfig = findBuildPack('php');
}
} else if (type === 'github') {
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
@ -101,6 +111,10 @@
const packageJson = files.find(
(file) => file.name === 'package.json' && file.type === 'file'
);
const yarnLock = files.find((file) => file.name === 'yarn.lock' && file.type === 'file');
const pnpmLock = files.find(
(file) => file.name === 'pnpm-lock.yaml' && file.type === 'file'
);
const dockerfile = files.find((file) => file.name === 'Dockerfile' && file.type === 'file');
const cargoToml = files.find((file) => file.name === 'Cargo.toml' && file.type === 'file');
const requirementsTxt = files.find(
@ -108,6 +122,10 @@
);
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
if (yarnLock) packageManager = 'yarn';
if (pnpmLock) packageManager = 'pnpm';
if (dockerfile) {
foundConfig.buildPack = 'docker';
} else if (packageJson) {
@ -116,18 +134,19 @@
Accept: 'application/vnd.github.v2.raw'
});
const json = JSON.parse(data) || {};
foundConfig = checkTemplates({ json });
checkTemplates({ json, packageManager });
} else if (cargoToml) {
foundConfig = buildPacks.find((bp) => bp.name === 'rust');
foundConfig = findBuildPack('rust');
} else if (requirementsTxt) {
foundConfig = buildPacks.find((bp) => bp.name === 'python');
foundConfig = findBuildPack('python');
} else if (indexHtml) {
foundConfig = buildPacks.find((bp) => bp.name === 'static');
foundConfig = findBuildPack('static', packageManager);
} else if (indexPHP) {
foundConfig = buildPacks.find((bp) => bp.name === 'php');
foundConfig = findBuildPack('php');
}
}
} catch (error) {
scanning = true;
if (
error.error === 'invalid_token' ||
error.error_description ===
@ -155,11 +174,13 @@
}, 100);
}
}
if (error.message === 'Bad credentials') {
browser && window.location.reload();
}
return errorNotification(error);
} finally {
if (!foundConfig) foundConfig = buildPacks.find((bp) => bp.name === 'node');
scanning = false;
}
if (!foundConfig) foundConfig = findBuildPack('node', packageManager);
scanning = false;
}
onMount(async () => {
await scanRepository();
@ -175,10 +196,16 @@
<div class="text-xl tracking-tight">Scanning repository to suggest a build pack for you...</div>
</div>
{:else}
{#if packageManager === 'yarn' || packageManager === 'pnpm'}
<div class="flex justify-center p-6">
Found lock file for <span class="font-bold text-orange-500 pl-1">{packageManager}</span>.
Using it for predefined commands commands.
</div>
{/if}
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
{#each buildPacks as buildPack}
<div class="p-2">
<BuildPack {buildPack} {scanning} bind:foundConfig />
<BuildPack {buildPack} {scanning} {packageManager} bind:foundConfig />
</div>
{/each}
</div>

View File

@ -30,7 +30,7 @@
import type Prisma from '@prisma/client';
import { page } from '$app/stores';
import { enhance, errorNotification } from '$lib/form';
import { errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';

View File

@ -173,106 +173,96 @@
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2 ">
<div class="mt-2 grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input
readonly={!$session.isAdmin}
name="name"
id="name"
bind:value={application.name}
required
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="gitSource" class="text-base font-bold text-stone-100">Git Source</label>
<a
href={$session.isAdmin
? `/applications/${id}/configuration/source?from=/applications/${id}`
: ''}
class="no-underline"
><input
value={application.gitSource.name}
id="gitSource"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
<div class="grid grid-cols-2 items-center">
<label for="repository" class="text-base font-bold text-stone-100">Git Repository</label>
<a
href={$session.isAdmin
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
: ''}
class="no-underline"
><input
value="{application.repository}/{application.branch}"
id="repository"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
<div class="grid grid-cols-2 items-center">
<label for="buildPack" class="text-base font-bold text-stone-100">Build Pack</label>
<a
href={$session.isAdmin
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
: ''}
class="no-underline "
>
<input
readonly={!$session.isAdmin}
name="name"
id="name"
bind:value={application.name}
required
value={application.buildPack}
id="buildPack"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
<div class="grid grid-cols-2 items-center pb-8">
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
<div class="no-underline">
<input
value={application.destinationDocker.name}
id="destination"
disabled
class="bg-transparent "
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="gitSource">Git Source</label>
<div class="col-span-2">
<a
href={$session.isAdmin
? `/applications/${id}/configuration/source?from=/applications/${id}`
: ''}
class="no-underline"
><input
value={application.gitSource.name}
id="gitSource"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="repository">Git Repository</label>
<div class="col-span-2">
<a
href={$session.isAdmin
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
: ''}
class="no-underline"
><input
value="{application.repository}/{application.branch}"
id="repository"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="buildPack">Build Pack</label>
<div class="col-span-2">
<a
href={$session.isAdmin
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
: ''}
class="no-underline "
>
<input
value={application.buildPack}
id="buildPack"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
</div>
<div class="grid grid-cols-3 items-center pb-8">
<label for="destination">Destination</label>
<div class="col-span-2">
<div class="no-underline">
<input
value={application.destinationDocker.name}
id="destination"
disabled
class="bg-transparent "
/>
</div>
</div>
</div>
</div>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Application</div>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-3">
<label for="fqdn" class="relative pt-2">Domain (FQDN)</label>
<div class="col-span-2">
<input
readonly={!$session.isAdmin || isRunning}
disabled={!$session.isAdmin || isRunning}
bind:this={domainEl}
name="fqdn"
id="fqdn"
bind:value={application.fqdn}
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="eg: https://coollabs.io"
required
/>
<div class="grid grid-cols-2">
<div class="flex-col">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</label>
<Explainer
text="If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
/>
</div>
<input
readonly={!$session.isAdmin || isRunning}
disabled={!$session.isAdmin || isRunning}
bind:this={domainEl}
name="fqdn"
id="fqdn"
bind:value={application.fqdn}
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="eg: https://coollabs.io"
required
/>
</div>
<div class="grid grid-cols-2 items-center pb-8">
<Setting
@ -286,89 +276,88 @@
/>
</div>
{#if !staticDeployments.includes(application.buildPack)}
<div class="grid grid-cols-3 items-center">
<label for="port">Port</label>
<div class="col-span-2">
<input
readonly={!$session.isAdmin}
name="port"
id="port"
bind:value={application.port}
placeholder="default: 3000"
/>
</div>
</div>
{/if}
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-3 items-center">
<label for="installCommand">Install Command</label>
<div class="col-span-2">
<input
readonly={!$session.isAdmin}
name="installCommand"
id="installCommand"
bind:value={application.installCommand}
placeholder="default: yarn install"
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="buildCommand">Build Command</label>
<div class="col-span-2">
<input
readonly={!$session.isAdmin}
name="buildCommand"
id="buildCommand"
bind:value={application.buildCommand}
placeholder="default: yarn build"
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="startCommand" class="">Start Command</label>
<div class="col-span-2">
<input
readonly={!$session.isAdmin}
name="startCommand"
id="startCommand"
bind:value={application.startCommand}
placeholder="default: yarn start"
/>
</div>
</div>
{/if}
<div class="grid grid-cols-3">
<label for="baseDirectory" class="pt-2">Base Directory</label>
<div class="col-span-2">
<div class="grid grid-cols-2 items-center">
<label for="port" class="text-base font-bold text-stone-100">Port</label>
<input
readonly={!$session.isAdmin}
name="baseDirectory"
id="baseDirectory"
bind:value={application.baseDirectory}
placeholder="default: /"
/>
<Explainer
text="Directory to use as the base of all commands. <br> Could be useful with monorepos."
name="port"
id="port"
bind:value={application.port}
placeholder="default: 3000"
/>
</div>
{/if}
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<label for="installCommand" class="text-base font-bold text-stone-100"
>Install Command</label
>
<input
readonly={!$session.isAdmin}
name="installCommand"
id="installCommand"
bind:value={application.installCommand}
placeholder="default: yarn install"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="buildCommand" class="text-base font-bold text-stone-100">Build Command</label>
<input
readonly={!$session.isAdmin}
name="buildCommand"
id="buildCommand"
bind:value={application.buildCommand}
placeholder="default: yarn build"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="startCommand" class="text-base font-bold text-stone-100">Start Command</label>
<input
readonly={!$session.isAdmin}
name="startCommand"
id="startCommand"
bind:value={application.startCommand}
placeholder="default: yarn start"
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
>Base Directory</label
>
<Explainer
text="Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>."
/>
</div>
<input
readonly={!$session.isAdmin}
name="baseDirectory"
id="baseDirectory"
bind:value={application.baseDirectory}
placeholder="default: /"
/>
</div>
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-3">
<label for="publishDirectory" class="pt-2">Publish Directory</label>
<div class="col-span-2">
<input
readonly={!$session.isAdmin}
name="publishDirectory"
id="publishDirectory"
bind:value={application.publishDirectory}
placeholder=" default: /"
/>
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
>Publish Directory</label
>
<Explainer
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-600 font-bold'>dist</span>,<span class='text-green-600 font-bold'>_site</span> or <span class='text-green-600 font-bold'>public</span>."
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>."
/>
</div>
<input
readonly={!$session.isAdmin}
name="publishDirectory"
id="publishDirectory"
bind:value={application.publishDirectory}
placeholder=" default: /"
/>
</div>
{/if}
</div>
@ -400,7 +389,7 @@
bind:setting={debug}
on:click={() => changeSettings('debug')}
title="Debug Logs"
description="Enable debug logs during build phase. <br>(<span class='text-red-500'>sensitive information</span> could be visible in logs)"
description="Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs."
/>
</div>
</div>

View File

@ -59,19 +59,19 @@
}
</script>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<td>
<input
id={isNewSecret ? 'secretName' : 'secretNameNew'}
bind:value={name}
required
placeholder="EXAMPLE_VARIABLE"
class="-mx-2 w-64 border-2 border-transparent"
class=" border border-dashed border-coolgray-300"
readonly={!isNewSecret}
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<td>
<CopyPasswordField
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
@ -81,7 +81,7 @@
placeholder="J$#@UIO%HO#$U%H"
/>
</td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
<td class="text-center">
<div
type="button"
on:click={setSecretValue}
@ -130,21 +130,19 @@
</span>
</div>
</td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<td>
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
</div>
{:else}
<div class="flex-col space-y-2">
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={() => saveSecret(false)}
>Set</button
>
<button class="" on:click={() => saveSecret(false)}>Set</button>
</div>
{#if !isPRMRSecret}
<div class="flex justify-center items-end">
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</div>

View File

@ -41,33 +41,19 @@
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto">
<thead class=" rounded-xl border-b border-coolgray-500">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white">Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
/>
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
<th scope="col" class="w-96 text-center">Action</th>
</tr>
</thead>
<tbody class="">
<tbody>
{#each secrets as secret}
{#key secret.id}
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
<tr>
<Secret
name={secret.name}
value={secret.value}

View File

@ -8,7 +8,7 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label>
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
<CopyPasswordField
required
readonly={database.defaultDatabase}
@ -20,7 +20,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label>
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
<CopyPasswordField
readonly
disabled
@ -31,7 +31,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<CopyPasswordField
readonly
disabled
@ -43,7 +43,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label>
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
<CopyPasswordField
readonly
disabled
@ -54,7 +54,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword">Root's Password</label>
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
<CopyPasswordField
readonly
disabled

View File

@ -56,9 +56,11 @@
appendOnly = !appendOnly;
}
try {
await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
const { publicPort } = await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
if (isPublic) {
database.publicPort = publicPort;
}
databaseUrl = generateUrl();
return;
} catch ({ error }) {
return errorNotification(error);
}
@ -89,7 +91,7 @@
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="name">Name</label>
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input
readonly={!$session.isAdmin}
name="name"
@ -99,7 +101,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="destination">Destination</label>
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
{#if database.destinationDockerId}
<div class="no-underline">
<input
@ -114,14 +116,14 @@
</div>
<div class="grid grid-cols-2 items-center">
<label for="version">Version</label>
<label for="version" class="text-base font-bold text-stone-100">Version</label>
<input value={database.version} readonly disabled class="bg-transparent " />
</div>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-flow-row gap-2 px-10 pt-2">
<div class="grid grid-cols-2 items-center">
<label for="host">Host</label>
<label for="host" class="text-base font-bold text-stone-100">Host</label>
<CopyPasswordField
placeholder="Generated automatically after start"
isPasswordField={false}
@ -133,9 +135,9 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="publicPort">Port</label>
<label for="publicPort" class="text-base font-bold text-stone-100">Port</label>
<CopyPasswordField
placeholder="Generated automatically after start"
placeholder="Generated automatically after set to public"
id="publicPort"
readonly
disabled
@ -157,7 +159,7 @@
<CouchDb bind:database />
{/if}
<div class="grid grid-cols-2 items-center px-10 pb-8">
<label for="url">Connection String</label>
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
<CopyPasswordField
textarea={true}
placeholder="Generated automatically after start"

View File

@ -8,7 +8,7 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label>
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
<CopyPasswordField
placeholder="Generated automatically after start"
id="rootUser"
@ -19,7 +19,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword">Root's Password</label>
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
<CopyPasswordField
placeholder="Generated automatically after start"
isPasswordField={true}

View File

@ -8,7 +8,7 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label>
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
<CopyPasswordField
required
readonly={database.defaultDatabase}
@ -20,7 +20,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label>
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
<CopyPasswordField
readonly
disabled
@ -31,7 +31,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<CopyPasswordField
readonly
disabled
@ -43,7 +43,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser">Root User</label>
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
<CopyPasswordField
readonly
disabled
@ -54,7 +54,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword">Root's Password</label>
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
<CopyPasswordField
readonly
disabled

View File

@ -8,7 +8,7 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase">Default Database</label>
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
<CopyPasswordField
required
readonly={database.defaultDatabase}
@ -20,7 +20,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser">User</label>
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
<CopyPasswordField
readonly
disabled
@ -31,7 +31,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<CopyPasswordField
readonly
disabled

View File

@ -8,7 +8,7 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<CopyPasswordField
disabled
readonly

View File

@ -3,30 +3,39 @@ import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
import getPort, { portNumbers } from 'get-port';
export const post: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const { isPublic, appendOnly = true } = await event.request.json();
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
try {
await db.setDatabase({ id, isPublic, appendOnly });
const database = await db.getDatabase({ id, teamId });
const { destinationDockerId, destinationDocker, publicPort } = database;
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
const { privatePort } = generateDatabaseConfiguration(database);
if (destinationDockerId) {
if (isPublic) {
await db.prisma.database.update({ where: { id }, data: { publicPort } });
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
} else {
await stopTcpHttpProxy(destinationDocker, publicPort);
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
}
}
return {
status: 201
status: 201,
body: {
publicPort
}
};
} catch (error) {
return ErrorHandler(error);

View File

@ -15,6 +15,7 @@ export const post: RequestHandler = async (event) => {
const everStarted = await stopDatabase(database);
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
await db.setDatabase({ id, isPublic: false });
await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
return {
status: 200

View File

@ -61,12 +61,12 @@
<div class="w-full text-center font-bold">Loading...</div>
{:else if app.foundByDomain}
<div class="w-full bg-coolgray-200 text-xs">
<span class="text-red-500">Domain</span> already configured for
<span class="text-red-500">Domain</span> already used for
<span class="text-red-500">{app.foundName}</span>
</div>
{:else if app.foundByRepository}
<div class="w-full bg-coolgray-200 text-xs">
<span class="text-red-500">Repository</span> already configured for
<span class="text-red-500">Repository</span> already used for
<span class="text-red-500">{app.foundName}</span>
</div>
{:else}

View File

@ -122,80 +122,72 @@
}
</script>
<div class="flex justify-center px-6 pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex h-8 items-center space-x-2">
<div class="text-xl font-bold text-white">Configuration</div>
<button
type="submit"
class="bg-sky-600 hover:bg-sky-500"
class:bg-sky-600={!loading}
class:hover:bg-sky-500={!loading}
disabled={loading}
>{loading ? 'Saving...' : 'Save'}
</button>
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
>
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-5">
<div class="title font-bold">Configuration</div>
<button
type="submit"
class="bg-sky-600 hover:bg-sky-500"
class:bg-sky-600={!loading}
class:hover:bg-sky-500={!loading}
disabled={loading}
>{loading ? 'Saving...' : 'Save'}
</button>
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
>
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
>Scan for applications</button
> -->
</div>
<div class="grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2">
<input name="name" placeholder="name" bind:value={destination.name} />
</div>
</div>
</div>
<div class="grid grid-cols-2 items-center px-10 ">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input name="name" placeholder="name" bind:value={destination.name} />
</div>
<div class="grid grid-cols-3 items-center">
<label for="engine">Engine</label>
<div class="col-span-2">
<CopyPasswordField
id="engine"
readonly
disabled
name="engine"
placeholder="eg: /var/run/docker.sock"
value={destination.engine}
/>
</div>
</div>
<!-- <div class="flex items-center">
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
<CopyPasswordField
id="engine"
readonly
disabled
name="engine"
placeholder="eg: /var/run/docker.sock"
value={destination.engine}
/>
</div>
<!-- <div class="flex items-center">
<label for="remoteEngine">Remote Engine?</label>
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
</div> -->
<div class="grid grid-cols-3 items-center">
<label for="network">Network</label>
<div class="col-span-2">
<CopyPasswordField
id="network"
readonly
disabled
name="network"
placeholder="default: coolify"
value={destination.network}
/>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
title="Use Coolify Proxy?"
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
: ''
}`}
/>
</div>
</form>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">Network</label>
<CopyPasswordField
id="network"
readonly
disabled
name="network"
placeholder="default: coolify"
value={destination.network}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
title="Use Coolify Proxy?"
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'
: ''
}`}
/>
</div>
</form>
<!-- <div class="flex justify-center">
{#if payload.isCoolifyProxyUsed}
{#if state}

View File

@ -42,5 +42,6 @@
<span class="arrow-right-applications px-1">></span>
<span class="pr-2">{destination.name}</span>
</div>
<LocalDocker bind:destination {settings} {state} />
<div class="mx-auto max-w-4xl px-6">
<LocalDocker bind:destination {settings} {state} />
</div>

View File

@ -18,7 +18,7 @@
async function handleSubmit() {
loading = true;
try {
const { teamId } = await post(`/login.json`, { email, password });
const { teamId } = await post(`/login.json`, { email: email.toLowerCase(), password });
if (teamId === '0') {
window.location.replace('/settings');
} else {

View File

@ -5,7 +5,7 @@
import { post } from '$lib/api';
import Setting from '$lib/components/Setting.svelte';
import { enhance, errorNotification } from '$lib/form';
import { errorNotification } from '$lib/form';
let loading = false;
@ -24,8 +24,8 @@
<div class="flex justify-center px-6 pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex h-8 items-center space-x-2">
<div class="text-xl font-bold text-white">Configuration</div>
<div class="flex items-center space-x-2 pb-5">
<div class="title font-bold">Configuration</div>
<button
type="submit"
class:bg-sky-600={!loading}
@ -38,24 +38,20 @@
: 'Save'}</button
>
</div>
<div class="grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2">
<input required name="name" placeholder="name" bind:value={payload.name} />
</div>
<div class="mt-2 grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input required name="name" placeholder="name" bind:value={payload.name} />
</div>
<div class="grid grid-cols-3 items-center">
<label for="engine">Engine</label>
<div class="col-span-2">
<input
required
name="engine"
placeholder="eg: /var/run/docker.sock"
bind:value={payload.engine}
/>
<!-- <Explainer text="You can use remote Docker Engine with over SSH." /> -->
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
<input
required
name="engine"
placeholder="eg: /var/run/docker.sock"
bind:value={payload.engine}
/>
<!-- <Explainer text="You can use remote Docker Engine with over SSH." /> -->
</div>
<!-- <div class="flex items-center">
<label for="remoteEngine">Remote Docker Engine?</label>
@ -75,27 +71,17 @@
</div>
</div>
{/if} -->
<div class="grid grid-cols-3 items-center">
<label for="network">Network</label>
<div class="col-span-2">
<input
required
name="network"
placeholder="default: coolify"
bind:value={payload.network}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">Network</label>
<input required name="network" placeholder="default: coolify" bind:value={payload.network} />
</div>
<div class="flex justify-start">
<ul class="mt-2 divide-y divide-stone-800">
<Setting
bind:setting={payload.isCoolifyProxyUsed}
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
isPadding={false}
title="Use Coolify Proxy?"
description="This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker). Databases will have their own proxy."
/>
</ul>
<div class="grid grid-cols-2 items-center">
<Setting
bind:setting={payload.isCoolifyProxyUsed}
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
title="Use Coolify Proxy?"
description="This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy."
/>
</div>
</form>
</div>

View File

@ -24,26 +24,24 @@
}
</script>
<div class="flex justify-center pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex h-8 items-center space-x-2">
<div class="text-xl font-bold text-white">Configuration</div>
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
</div>
<div class="grid grid-cols-3 items-center">
<label for="type">Type</label>
<div class="mx-auto max-w-4xl px-6">
<div class="flex justify-center pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex h-8 items-center space-x-2">
<div class="text-xl font-bold text-white">Configuration</div>
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="type" class="text-base font-bold text-stone-100">Type</label>
<div class="col-span-2">
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
<option value="github">GitHub</option>
<option value="gitlab">GitLab</option>
<option value="bitbucket">BitBucket</option>
</select>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2">
<div class="grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input
name="name"
id="name"
@ -53,11 +51,9 @@
bind:value={gitSource.name}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="htmlUrl">HTML URL</label>
<div class="col-span-2">
<div class="grid grid-cols-2 items-center px-10">
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
<input
type="url"
name="htmlUrl"
@ -67,10 +63,8 @@
bind:value={gitSource.htmlUrl}
/>
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="apiUrl">API URL</label>
<div class="col-span-2">
<div class="grid grid-cols-2 items-center px-10">
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
<input
name="apiUrl"
type="url"
@ -80,10 +74,15 @@
bind:value={gitSource.apiUrl}
/>
</div>
</div>
<div class="grid grid-cols-3">
<label for="organization" class="pt-2">Organization</label>
<div class="col-span-2">
<div class="grid grid-cols-2 px-10">
<div class="flex flex-col">
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
>Organization</label
>
<Explainer
text="Fill it if you would like to use an organization's as your Git Source. Otherwise your user will be used."
/>
</div>
<input
name="organization"
id="organization"
@ -91,11 +90,7 @@
bind:value={gitSource.organization}
bind:this={organizationEl}
/>
<Explainer
text="Fill it if you would like to use an organization's as your Git Source. Otherwise your
user will be used."
/>
</div>
</div>
</form>
</form>
</div>
</div>

View File

@ -27,56 +27,47 @@
<div class="text-xl font-bold text-white">Configuration</div>
<button type="submit" class="bg-orange-600 hover:bg-orange-500">Save</button>
</div>
<div class="grid grid-cols-3 items-center">
<label for="type">Type</label>
<div class="col-span-2">
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
<option value="github">GitHub</option>
<option value="gitlab">GitLab</option>
<option value="bitbucket">BitBucket</option>
</select>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="type" class="text-base font-bold text-stone-100">Type</label>
<select name="type" id="type" class="w-96" bind:value={gitSource.type}>
<option value="github">GitHub</option>
<option value="gitlab">GitLab</option>
<option value="bitbucket">BitBucket</option>
</select>
</div>
<div class="grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2">
<input
name="name"
id="name"
placeholder="GitHub.com"
required
bind:this={nameEl}
bind:value={gitSource.name}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input
name="name"
id="name"
placeholder="GitHub.com"
required
bind:this={nameEl}
bind:value={gitSource.name}
/>
</div>
<div class="grid grid-cols-3 items-center">
<label for="htmlUrl">HTML URL</label>
<div class="col-span-2">
<input
type="url"
name="htmlUrl"
id="htmlUrl"
placeholder="eg: https://github.com"
required
bind:value={gitSource.htmlUrl}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
<input
type="url"
name="htmlUrl"
id="htmlUrl"
placeholder="eg: https://github.com"
required
bind:value={gitSource.htmlUrl}
/>
</div>
<div class="grid grid-cols-3 items-center">
<label for="apiUrl">API URL</label>
<div class="col-span-2">
<input
name="apiUrl"
type="url"
id="apiUrl"
placeholder="eg: https://api.github.com"
required
bind:value={gitSource.apiUrl}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
<input
name="apiUrl"
type="url"
id="apiUrl"
placeholder="eg: https://api.github.com"
required
bind:value={gitSource.apiUrl}
/>
</div>
</form>
</div>

View File

@ -57,7 +57,7 @@
}
</script>
<div class="mx-auto max-w-4xl px-6">
<div class="mx-auto max-w-4xl px-6 pb-12">
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
@ -70,11 +70,7 @@
>
{/if}
{#if service.type === 'plausibleanalytics' && isRunning}
<button
on:click|preventDefault={setEmailsToVerified}
class:bg-pink-600={!loadingVerification}
class:hover:bg-pink-500={!loadingVerification}
disabled={loadingVerification}
<button on:click|preventDefault={setEmailsToVerified} disabled={loadingVerification}
>{loadingVerification ? 'Verifying' : 'Verify emails without SMTP'}</button
>
{/if}
@ -82,7 +78,7 @@
<div class="grid grid-flow-row gap-2">
<div class="mt-2 grid grid-cols-2 items-center px-10">
<label for="name">Name</label>
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<div>
<input
readonly={!$session.isAdmin}
@ -95,7 +91,7 @@
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="destination">Destination</label>
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
<div>
{#if service.destinationDockerId}
<div class="no-underline">
@ -110,22 +106,23 @@
</div>
</div>
<div class="grid grid-cols-2 px-10">
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
<div>
<CopyPasswordField
placeholder="eg: https://analytics.coollabs.io"
readonly={!$session.isAdmin && !isRunning}
disabled={!$session.isAdmin || isRunning}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
bind:value={service.fqdn}
required
/>
<div class="flex-col ">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</label>
<Explainer
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>
<CopyPasswordField
placeholder="eg: https://analytics.coollabs.io"
readonly={!$session.isAdmin && !isRunning}
disabled={!$session.isAdmin || isRunning}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
bind:value={service.fqdn}
required
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<Setting

View File

@ -17,7 +17,7 @@ export const post: RequestHandler = async (event) => {
return {
status: found ? 500 : 200,
body: {
error: found && `Domain ${getDomain(fqdn).replace('www.', '')} is already configured`
error: found && `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
}
};
} catch (error) {

View File

@ -9,10 +9,9 @@ import {
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection,
startHttpProxy,
startTcpProxy
startHttpProxy
} from '$lib/haproxy';
import getPort from 'get-port';
import getPort, { portNumbers } from 'get-port';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
@ -35,14 +34,20 @@ export const post: RequestHandler = async (event) => {
minio: { rootUser, rootUserPassword }
} = service;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const publicPort = await getPort();
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const consolePort = 9001;
const apiPort = 9000;
const { workdir } = await createDirectories({ repository: type, buildId: id });
const config = {

View File

@ -16,7 +16,7 @@ export const post: RequestHandler = async (event) => {
return {
status: found ? 500 : 200,
body: {
error: found && `Domain ${fqdn.replace('www.', '')} is already configured`
error: found && `Domain ${fqdn.replace('www.', '')} is already used.`
}
};
} catch (error) {

View File

@ -78,7 +78,7 @@ export const post: RequestHandler = async (event) => {
};
if (status === 401) return { status, body };
const { fqdn, isRegistrationEnabled, dualCerts } = await event.request.json();
const { fqdn, isRegistrationEnabled, dualCerts, minPort, maxPort } = await event.request.json();
try {
const {
id,
@ -119,6 +119,9 @@ export const post: RequestHandler = async (event) => {
data: { isCoolifyProxyUsed: true }
});
}
if (minPort && maxPort) {
await db.prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
}
return {
status: 201

View File

@ -35,6 +35,9 @@
let isRegistrationEnabled = settings.isRegistrationEnabled;
let dualCerts = settings.dualCerts;
let minPort = settings.minPort;
let maxPort = settings.maxPort;
let fqdn = settings.fqdn;
let isFqdnSet = !!settings.fqdn;
let loading = {
@ -75,7 +78,11 @@
if (fqdn) {
await post(`/settings/check.json`, { fqdn });
await post(`/settings.json`, { fqdn });
return window.location.reload();
}
if (minPort !== settings.minPort || maxPort !== settings.maxPort) {
await post(`/settings.json`, { minPort, maxPort });
settings.minPort = minPort;
settings.maxPort = maxPort;
}
} catch ({ error }) {
return errorNotification(error);
@ -90,9 +97,9 @@
</div>
{#if $session.teamId === '0'}
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit}>
<div class="flex space-x-1 py-6 font-bold">
<div class="title">Global Settings</div>
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 py-6">
<div class="title font-bold">Global Settings</div>
<button
type="submit"
disabled={loading.save}
@ -112,7 +119,12 @@
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2 items-start">
<div class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</div>
<div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</div>
<Explainer
text="If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
/>
</div>
<div class="justify-start text-left">
<input
bind:value={fqdn}
@ -122,10 +134,31 @@
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="eg: https://coolify.io"
required
/>
</div>
</div>
<div class="grid grid-cols-2 items-start py-6">
<div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100">Public Port Range</div>
<Explainer
text="If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
text="Ports used to expose databases/services/internal services.<br> Add them to your firewall (if applicable).<br><br>You can specify a range of ports, eg: <span class='text-yellow-500 font-bold'>9000-9100</span>"
/>
</div>
<div class="mx-auto flex-row items-center justify-center space-y-2">
<input
class="h-8 w-20 px-2"
type="number"
bind:value={minPort}
min="1024"
max={maxPort}
/>
-
<input
class="h-8 w-20 px-2"
type="number"
bind:value={maxPort}
min={minPort}
max="65543"
/>
</div>
</div>
@ -135,7 +168,7 @@
disabled={isFqdnSet}
bind:setting={dualCerts}
title="Generate SSL for www and non-www?"
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-400'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/>
</div>

View File

@ -62,32 +62,28 @@
{#if !source.githubAppId}
<button on:click={newGithubApp}>Create new GitHub App</button>
{:else if source.githubApp?.installationId}
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
<button on:click|preventDefault={() => installRepositories(source)}
>Change GitHub App Settings</button
>
{/if}
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
<button on:click|preventDefault={() => installRepositories(source)}
>Change GitHub App Settings</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2 items-center mt-2">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input name="name" id="name" required bind:value={source.name} />
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2 ">
<input name="name" id="name" required bind:value={source.name} />
</div>
</div>
</div>
</form>
</div>
</div>
</form>
{:else}
<button on:click={() => installRepositories(source)}>Install Repositories</button>
{/if}

View File

@ -90,129 +90,115 @@
}
</script>
<div class="flex flex-col justify-center">
{#if !source.gitlabApp?.appId}
<form class="grid grid-flow-row gap-2 py-4" on:submit|preventDefault={newApp}>
<div class="grid grid-cols-3 items-center">
<label for="type">GitLab Application Type</label>
<div class="col-span-2">
<select name="type" id="type" class="w-96" bind:value={payload.applicationType}>
<option value="user">User owned application</option>
<option value="group">Group owned application</option>
{#if source.htmlUrl !== 'https://gitlab.com'}
<option value="instance">Instance-wide application (self-hosted)</option>
{/if}
</select>
</div>
{#if !source.gitlabApp?.appId}
<form class="grid grid-flow-row gap-2 py-4" on:submit|preventDefault={newApp}>
<div class="grid grid-cols-2 items-center">
<label for="type">GitLab Application Type</label>
<select name="type" id="type" class="w-96" bind:value={payload.applicationType}>
<option value="user">User owned application</option>
<option value="group">Group owned application</option>
{#if source.htmlUrl !== 'https://gitlab.com'}
<option value="instance">Instance-wide application (self-hosted)</option>
{/if}
</select>
</div>
{#if payload.applicationType === 'group'}
<div class="grid grid-cols-2 items-center">
<label for="groupName">Group Name</label>
<input name="groupName" id="groupName" required bind:value={payload.groupName} />
</div>
{#if payload.applicationType === 'group'}
<div class="grid grid-cols-3 items-center">
<label for="groupName">Group Name</label>
<div class="col-span-2">
<input name="groupName" id="groupName" required bind:value={payload.groupName} />
</div>
</div>
{/if}
{/if}
<div class="w-full pt-10 text-center">
<button class="w-96 bg-orange-600 hover:bg-orange-500" type="submit"
>Register new OAuth application on GitLab</button
>
</div>
<div class="w-full pt-10 text-center">
<button class="w-96 bg-orange-600 hover:bg-orange-500" type="submit"
>Register new OAuth application on GitLab</button
>
</div>
<Explainer
customClass="w-full"
text="<span class='font-bold text-base text-white'>Scopes required:</span>
<Explainer
customClass="w-full"
text="<span class='font-bold text-base text-white'>Scopes required:</span>
<br>- <span class='text-orange-500 font-bold'>api</span> (Access the authenticated user's API)
<br>- <span class='text-orange-500 font-bold'>read_repository</span> (Allows read-only access to the repository)
<br>- <span class='text-orange-500 font-bold'>email</span> (Allows read-only access to the user's primary email address using OpenID Connect)
<br>
<br>For extra security, you can set Expire access tokens!
<br><br>Webhook URL: <span class='text-orange-500 font-bold'>{browser
? window.location.origin
: ''}/webhooks/gitlab</span>
? window.location.origin
: ''}/webhooks/gitlab</span>
<br>But if you will set a custom domain name for Coolify, use that instead."
/>
</form>
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4 pt-10">
<div class="flex h-8 items-center space-x-2">
<div class="text-xl font-bold text-white">Configuration</div>
<button
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
</div>
<div class="grid grid-cols-3 items-start">
<label for="oauthId" class="pt-2">OAuth ID</label>
<div class="col-span-2">
<input
on:change={checkOauthId}
bind:this={oauthIdEl}
name="oauthId"
id="oauthId"
type="number"
required
bind:value={payload.oauthId}
/>
<Explainer
text="The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application."
/>
</div>
</div>
{#if payload.applicationType === 'group'}
<div class="grid grid-cols-3 items-center">
<label for="groupName">Group Name</label>
<div class="col-span-2">
<input name="groupName" id="groupName" required bind:value={payload.groupName} />
</div>
</div>
{/if}
<div class="grid grid-cols-3 items-center">
<label for="appId">Application ID</label>
<div class="col-span-2">
<input name="appId" id="appId" required bind:value={payload.appId} />
</div>
</div>
<div class="grid grid-cols-3 items-center">
<label for="appSecret">Secret</label>
<div class="col-span-2">
<input
name="appSecret"
id="appSecret"
type="password"
required
bind:value={payload.appSecret}
/>
</div>
</div>
</form>
{:else}
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmitSave} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
<button on:click|preventDefault={changeSettings}>Change GitLab App Settings</button>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-3 items-center">
<label for="name">Name</label>
<div class="col-span-2 ">
<input name="name" id="name" required bind:value={source.name} />
</div>
</div>
</div>
</form>
/>
</form>
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4 pt-10">
<div class="flex h-8 items-center space-x-2">
<div class="text-xl font-bold text-white">Configuration</div>
<button
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
</div>
{/if}
</div>
<div class="grid grid-cols-2 items-start">
<div class="flex-col">
<label for="oauthId" class="pt-2">OAuth ID</label>
<Explainer
text="The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application."
/>
</div>
<input
on:change={checkOauthId}
bind:this={oauthIdEl}
name="oauthId"
id="oauthId"
type="number"
required
bind:value={payload.oauthId}
/>
</div>
{#if payload.applicationType === 'group'}
<div class="grid grid-cols-2 items-center">
<label for="groupName">Group Name</label>
<input name="groupName" id="groupName" required bind:value={payload.groupName} />
</div>
{/if}
<div class="grid grid-cols-2 items-center">
<label for="appId">Application ID</label>
<input name="appId" id="appId" required bind:value={payload.appId} />
</div>
<div class="grid grid-cols-2 items-center">
<label for="appSecret">Secret</label>
<input
name="appSecret"
id="appSecret"
type="password"
required
bind:value={payload.appSecret}
/>
</div>
</form>
{:else}
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmitSave} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
<button on:click|preventDefault={changeSettings}>Change GitLab App Settings</button>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input name="name" id="name" required bind:value={source.name} />
</div>
</div>
</form>
</div>
{/if}

View File

@ -40,7 +40,7 @@
<span class="pr-2">{source.name}</span>
</div>
<div class="flex justify-center space-x-2 px-6 py-3">
<div class="flex justify-center px-6 pb-8">
{#if source.type === 'github'}
<Github bind:source />
{:else if source.type === 'gitlab'}

View File

@ -47,7 +47,7 @@
await post(`/teams/${id}/invitation/invite.json`, {
teamId: team.id,
teamName: invitation.teamName,
email: invitation.email,
email: invitation.email.toLowerCase(),
permission: invitation.permission
});
return window.location.reload();
@ -98,39 +98,40 @@
<span class="arrow-right-applications px-1 text-cyan-500">></span>
<span class="pr-2">{team.name}</span>
</div>
<div class="mx-auto max-w-4xl">
<form on:submit|preventDefault={handleSubmit}>
<div class="flex space-x-1 p-6 font-bold">
<div class="title">Settings</div>
<div class="text-center">
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit">Save</button>
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class=" py-4">
<div class="flex space-x-1 pb-5">
<div class="title font-bold">Settings</div>
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit">Save</button>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-2">
<div class="flex-col">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
{#if team.id === '0'}
<Explainer
customClass="w-full"
text="This is the <span class='text-red-500 font-bold'>root</span> team. That means members of this group can manage instance wide settings and have all the priviliges in Coolify (imagine like root user on Linux)."
/>
{/if}
</div>
<input id="name" name="name" placeholder="name" bind:value={team.name} />
</div>
</div>
<div class="mx-2 flex items-center space-x-2 px-4 sm:px-6">
<label for="name">Name</label>
<input id="name" name="name" placeholder="name" bind:value={team.name} />
</div>
{#if team.id === '0'}
<div class="px-8 pt-4 text-left">
<Explainer
customClass="w-full"
text="This is the <span class='text-red-500 font-bold'>root</span> team. That means members of this group can manage instance wide settings and have all the priviliges in Coolify (imagine like root user on Linux)."
/>
</div>
{/if}
</form>
<div class="flex space-x-1 py-5 px-6 pt-10 font-bold">
<div class="flex space-x-1 py-5 pt-10 font-bold">
<div class="title">Members</div>
</div>
<div class="px-4 sm:px-6">
<table class="mx-2 w-full table-auto text-left">
<tr class="h-8 border-b border-coolgray-400">
<th scope="col">Email</th>
<th scope="col">Permission</th>
<th scope="col" class="text-center">Actions</th>
</tr>
<table class="w-full border-separate text-left">
<thead>
<tr class="h-8 border-b border-coolgray-400">
<th scope="col">Email</th>
<th scope="col">Permission</th>
<th scope="col" class="text-center">Actions</th>
</tr>
</thead>
{#each permissions as permission}
<tr class="text-xs">
<td class="py-4"
@ -176,25 +177,18 @@
{/each}
</table>
</div>
</div>
{#if $session.isAdmin}
<div class="mx-auto max-w-4xl pt-8">
<form on:submit|preventDefault={sendInvitation}>
<div class="flex space-x-1 p-6">
<div>
{#if $session.isAdmin}
<form on:submit|preventDefault={sendInvitation} class="py-5 pt-10">
<div class="flex space-x-1">
<div class="flex space-x-1">
<div class="title font-bold">Invite new member</div>
<div class="text-left">
<Explainer
customClass="w-56"
text="You can only invite registered users at the moment - will be extended soon."
/>
</div>
</div>
<div class="pt-1 text-center">
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit">Send invitation</button>
</div>
</div>
<div class="flex-col space-y-2 px-4 sm:px-6">
<Explainer
text="You can only invite registered users at the moment - will be extended soon."
/>
<div class="flex-col space-y-2 px-4 pt-5 sm:px-6">
<div class="flex space-x-0">
<input
bind:value={invitation.email}
@ -205,18 +199,20 @@
<div class="flex-1" />
<button
on:click={() => (invitation.permission = 'read')}
class="rounded-none rounded-l"
class="rounded-none rounded-l border border-dashed border-transparent"
type="button"
class:border-coolgray-300={invitation.permission !== 'read'}
class:bg-pink-500={invitation.permission === 'read'}>Read</button
>
<button
on:click={() => (invitation.permission = 'admin')}
class="rounded-none rounded-r"
class="rounded-none rounded-r border border-dashed border-transparent"
type="button"
class:border-coolgray-300={invitation.permission !== 'admin'}
class:bg-red-500={invitation.permission === 'admin'}>Admin</button
>
</div>
</div>
</form>
</div>
{/if}
{/if}
</div>

View File

@ -35,14 +35,14 @@ .main {
}
input {
@apply h-12 w-96 rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
@apply h-12 w-96 rounded border border-transparent bg-transparent bg-coolgray-200 p-2 pr-20 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
}
textarea {
@apply w-96 rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
@apply min-w-[24rem] rounded border border-transparent bg-transparent bg-coolgray-200 p-2 pr-20 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
}
select {
@apply rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
}
label {