commit
0dfcf9b1e6
@ -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",
|
||||
|
@ -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;
|
@ -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())
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -1,16 +1,146 @@
|
||||
const defaultBuildAndDeploy = {
|
||||
installCommand: 'yarn install',
|
||||
buildCommand: 'yarn build',
|
||||
startCommand: 'yarn start'
|
||||
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 const buildPacks = [
|
||||
{
|
||||
name: 'node',
|
||||
}
|
||||
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,
|
||||
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
|
||||
};
|
||||
}
|
||||
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'
|
||||
|
@ -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,
|
||||
|
@ -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 }
|
||||
|
@ -101,7 +101,6 @@ export default async function () {
|
||||
if (isHttps) await forceSSLOnApplication(domain);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,7 +520,6 @@
|
||||
<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"
|
||||
>
|
||||
@ -530,9 +530,10 @@
|
||||
>
|
||||
</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}
|
||||
>
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
if (!foundConfig) foundConfig = buildPacks.find((bp) => bp.name === 'node');
|
||||
scanning = false;
|
||||
if (error.message === 'Bad credentials') {
|
||||
browser && window.location.reload();
|
||||
}
|
||||
return errorNotification(error);
|
||||
}
|
||||
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>
|
||||
|
@ -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';
|
||||
|
||||
|
@ -173,9 +173,8 @@
|
||||
{/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"
|
||||
@ -184,10 +183,8 @@
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="gitSource">Git Source</label>
|
||||
<div class="col-span-2">
|
||||
<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}`
|
||||
@ -201,10 +198,8 @@
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="repository">Git Repository</label>
|
||||
<div class="col-span-2">
|
||||
<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`
|
||||
@ -218,10 +213,8 @@
|
||||
/></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="buildPack">Build Pack</label>
|
||||
<div class="col-span-2">
|
||||
<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}`
|
||||
@ -236,10 +229,8 @@
|
||||
/></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="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}
|
||||
@ -250,14 +241,17 @@
|
||||
</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">
|
||||
<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}
|
||||
@ -269,10 +263,6 @@
|
||||
placeholder="eg: https://coollabs.io"
|
||||
required
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center pb-8">
|
||||
<Setting
|
||||
@ -286,9 +276,8 @@
|
||||
/>
|
||||
</div>
|
||||
{#if !staticDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="port">Port</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="port"
|
||||
@ -297,13 +286,13 @@
|
||||
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">
|
||||
<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"
|
||||
@ -312,10 +301,8 @@
|
||||
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">
|
||||
<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"
|
||||
@ -324,10 +311,8 @@
|
||||
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">
|
||||
<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"
|
||||
@ -336,12 +321,17 @@
|
||||
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">
|
||||
<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"
|
||||
@ -349,15 +339,18 @@
|
||||
bind:value={application.baseDirectory}
|
||||
placeholder="default: /"
|
||||
/>
|
||||
<Explainer
|
||||
text="Directory to use as the base of all commands. <br> Could be useful with monorepos."
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<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-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"
|
||||
@ -365,10 +358,6 @@
|
||||
bind:value={application.publishDirectory}
|
||||
placeholder=" default: /"
|
||||
/>
|
||||
<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>."
|
||||
/>
|
||||
</div>
|
||||
</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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -122,10 +122,9 @@
|
||||
}
|
||||
</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>
|
||||
<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"
|
||||
@ -144,16 +143,13 @@
|
||||
>Scan for applications</button
|
||||
> -->
|
||||
</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" placeholder="name" bind:value={destination.name} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="engine">Engine</label>
|
||||
<div class="col-span-2">
|
||||
<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
|
||||
@ -163,14 +159,12 @@
|
||||
value={destination.engine}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<label for="remoteEngine">Remote Engine?</label>
|
||||
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
|
||||
</div> -->
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="network">Network</label>
|
||||
<div class="col-span-2">
|
||||
<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
|
||||
@ -180,7 +174,6 @@
|
||||
value={destination.network}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
disabled={cannotDisable}
|
||||
@ -195,7 +188,6 @@
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- <div class="flex justify-center">
|
||||
{#if payload.isCoolifyProxyUsed}
|
||||
{#if state}
|
||||
|
@ -42,5 +42,6 @@
|
||||
<span class="arrow-right-applications px-1">></span>
|
||||
<span class="pr-2">{destination.name}</span>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
<LocalDocker bind:destination {settings} {state} />
|
||||
</div>
|
||||
|
@ -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 {
|
||||
|
@ -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,16 +38,13 @@
|
||||
: 'Save'}</button
|
||||
>
|
||||
</div>
|
||||
<div class="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 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>
|
||||
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<label for="engine">Engine</label>
|
||||
<div class="col-span-2">
|
||||
<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"
|
||||
@ -56,7 +53,6 @@
|
||||
/>
|
||||
<!-- <Explainer text="You can use remote Docker Engine with over SSH." /> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<label for="remoteEngine">Remote Docker Engine?</label>
|
||||
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
|
||||
@ -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 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>
|
||||
<div class="flex justify-start">
|
||||
<ul class="mt-2 divide-y divide-stone-800">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<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."
|
||||
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."
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -24,26 +24,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<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-3 items-center">
|
||||
<label for="type">Type</label>
|
||||
<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 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>
|
||||
<div class="grid grid-cols-3">
|
||||
<label for="organization" class="pt-2">Organization</label>
|
||||
<div class="col-span-2">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,20 +27,16 @@
|
||||
<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">
|
||||
<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>
|
||||
<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"
|
||||
@ -50,11 +46,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"
|
||||
@ -64,10 +58,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"
|
||||
@ -77,6 +69,5 @@
|
||||
bind:value={gitSource.apiUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -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,8 +106,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
|
||||
<div>
|
||||
<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}
|
||||
@ -122,10 +123,6 @@
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
|
@ -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) {
|
||||
|
@ -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 = {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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="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>
|
||||
|
@ -62,7 +62,6 @@
|
||||
{#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>
|
||||
@ -79,15 +78,12 @@
|
||||
{/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="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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{:else}
|
||||
<button on:click={() => installRepositories(source)}>Install Repositories</button>
|
||||
{/if}
|
||||
|
@ -90,12 +90,10 @@
|
||||
}
|
||||
</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">
|
||||
<div class="grid grid-cols-2 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>
|
||||
@ -104,14 +102,11 @@
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if payload.applicationType === 'group'}
|
||||
<div class="grid grid-cols-3 items-center">
|
||||
<div class="grid grid-cols-2 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="w-full pt-10 text-center">
|
||||
@ -145,9 +140,13 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-start">
|
||||
<div class="grid grid-cols-2 items-start">
|
||||
<div class="flex-col">
|
||||
<label for="oauthId" class="pt-2">OAuth ID</label>
|
||||
<div class="col-span-2">
|
||||
<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}
|
||||
@ -157,28 +156,19 @@
|
||||
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">
|
||||
<div class="grid grid-cols-2 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">
|
||||
<div class="grid grid-cols-2 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">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="appSecret">Secret</label>
|
||||
<div class="col-span-2">
|
||||
<input
|
||||
name="appSecret"
|
||||
id="appSecret"
|
||||
@ -187,7 +177,6 @@
|
||||
bind:value={payload.appSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
@ -205,14 +194,11 @@
|
||||
{/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 name="name" id="name" required bind:value={source.name} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -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'}
|
||||
|
@ -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">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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'}
|
||||
<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}
|
||||
</div>
|
||||
<input id="name" name="name" placeholder="name" bind:value={team.name} />
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<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>
|
||||
<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}
|
||||
</div>
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user