Merge branch 'main' into restray_i18n
This commit is contained in:
commit
edef4bd4a0
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
pnpm lint-staged
|
||||
|
@ -35,7 +35,8 @@ This is a little list of what you can do to help the project:
|
||||
- Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
- Install dependencies with `pnpm install`.
|
||||
- Need to create a local SQlite database with `pnpm db:push`.
|
||||
- This will apply all migrations and seed the database at `db/dev.db`.
|
||||
- This will apply all migrations at `db/dev.db`.
|
||||
- Seed the database with base entities with `pnpm db:seed`
|
||||
- You can start coding after starting `pnpm dev`.
|
||||
|
||||
#### How to start after you set up your local fork?
|
||||
|
@ -6,7 +6,7 @@ An open-source & self-hostable Heroku / Netlify alternative.
|
||||
|
||||
https://demo.coolify.io/
|
||||
|
||||
(If it is unresponsible, that means someone overloaded the server. 🙃)
|
||||
(If it is unresponsive, that means someone overloaded the server. 🙃)
|
||||
|
||||
## How to install
|
||||
|
||||
|
17
package.json
17
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.2.7",
|
||||
"version": "2.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",
|
||||
@ -30,14 +30,14 @@
|
||||
"@sveltejs/kit": "1.0.0-next.303",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/js-cookie": "3.0.1",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node-forge": "1.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||
"@typescript-eslint/parser": "4.31.1",
|
||||
"@zerodevx/svelte-toast": "0.7.1",
|
||||
"autoprefixer": "10.4.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-env": "7.0.3",
|
||||
"cross-var": "1.1.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
@ -52,8 +52,8 @@
|
||||
"svelte": "3.46.4",
|
||||
"svelte-check": "2.4.6",
|
||||
"svelte-preprocess": "4.10.4",
|
||||
"svelte-select": "^4.4.7",
|
||||
"sveltekit-i18n": "^2.1.2",
|
||||
"svelte-select": "4.4.7",
|
||||
"sveltekit-i18n": "2.1.2",
|
||||
"tailwindcss": "3.0.23",
|
||||
"ts-node": "10.7.0",
|
||||
"tslib": "2.3.1",
|
||||
@ -68,7 +68,7 @@
|
||||
"bullmq": "1.78.1",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.4.2",
|
||||
"cooltipz-css": "^2.1.0",
|
||||
"cooltipz-css": "2.1.0",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.0",
|
||||
"dockerode": "3.3.1",
|
||||
@ -79,10 +79,11 @@
|
||||
"js-cookie": "3.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"mustache": "^4.2.0",
|
||||
"mustache": "4.2.0",
|
||||
"node-forge": "1.3.0",
|
||||
"p-limit": "4.0.0",
|
||||
"svelte-kit-cookie-session": "2.1.2",
|
||||
"tailwindcss-scrollbar": "^0.1.0",
|
||||
"tailwindcss-scrollbar": "0.1.0",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"prisma": {
|
||||
|
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@ -8,7 +8,7 @@ specifiers:
|
||||
'@sveltejs/kit': 1.0.0-next.303
|
||||
'@types/bcrypt': 5.0.0
|
||||
'@types/js-cookie': 3.0.1
|
||||
'@types/js-yaml': ^4.0.5
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/node': 17.0.23
|
||||
'@types/node-forge': 1.0.1
|
||||
'@typescript-eslint/eslint-plugin': 4.31.1
|
||||
@ -19,8 +19,8 @@ specifiers:
|
||||
bullmq: 1.78.1
|
||||
compare-versions: 4.1.3
|
||||
cookie: 0.4.2
|
||||
cooltipz-css: ^2.1.0
|
||||
cross-env: ^7.0.3
|
||||
cooltipz-css: 2.1.0
|
||||
cross-env: 7.0.3
|
||||
cross-var: 1.1.0
|
||||
cuid: 2.1.8
|
||||
dayjs: 1.11.0
|
||||
@ -37,8 +37,9 @@ specifiers:
|
||||
js-yaml: 4.1.0
|
||||
jsonwebtoken: 8.5.1
|
||||
lint-staged: 12.3.7
|
||||
mustache: ^4.2.0
|
||||
mustache: 4.2.0
|
||||
node-forge: 1.3.0
|
||||
p-limit: 4.0.0
|
||||
postcss: 8.4.12
|
||||
prettier: 2.6.1
|
||||
prettier-plugin-svelte: 2.6.0
|
||||
@ -48,10 +49,10 @@ specifiers:
|
||||
svelte-check: 2.4.6
|
||||
svelte-kit-cookie-session: 2.1.2
|
||||
svelte-preprocess: 4.10.4
|
||||
svelte-select: ^4.4.7
|
||||
sveltekit-i18n: ^2.1.2
|
||||
svelte-select: 4.4.7
|
||||
sveltekit-i18n: 2.1.2
|
||||
tailwindcss: 3.0.23
|
||||
tailwindcss-scrollbar: ^0.1.0
|
||||
tailwindcss-scrollbar: 0.1.0
|
||||
ts-node: 10.7.0
|
||||
tslib: 2.3.1
|
||||
typescript: 4.6.3
|
||||
@ -78,6 +79,7 @@ dependencies:
|
||||
jsonwebtoken: 8.5.1
|
||||
mustache: 4.2.0
|
||||
node-forge: 1.3.0
|
||||
p-limit: 4.0.0
|
||||
svelte-kit-cookie-session: 2.1.2
|
||||
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.23
|
||||
unique-names-generator: 4.7.1
|
||||
@ -4270,6 +4272,16 @@ packages:
|
||||
engines: { node: '>=12.20' }
|
||||
dev: false
|
||||
|
||||
/p-limit/4.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==
|
||||
}
|
||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||
dependencies:
|
||||
yocto-queue: 1.0.0
|
||||
dev: false
|
||||
|
||||
/p-map/2.1.0:
|
||||
resolution:
|
||||
{
|
||||
@ -5714,3 +5726,11 @@ packages:
|
||||
}
|
||||
engines: { node: '>=6' }
|
||||
dev: true
|
||||
|
||||
/yocto-queue/1.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
||||
}
|
||||
engines: { node: '>=12.20' }
|
||||
dev: false
|
||||
|
4
prisma/migrations/20220402135305_python/migration.sql
Normal file
4
prisma/migrations/20220402135305_python/migration.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "pythonModule" TEXT;
|
||||
ALTER TABLE "Application" ADD COLUMN "pythonVariable" TEXT;
|
||||
ALTER TABLE "Application" ADD COLUMN "pythonWSGI" TEXT;
|
12
prisma/migrations/20220402210645_meilisearch/migration.sql
Normal file
12
prisma/migrations/20220402210645_meilisearch/migration.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "MeiliSearch" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"masterKey" TEXT NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "MeiliSearch_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "MeiliSearch_serviceId_key" ON "MeiliSearch"("serviceId");
|
@ -87,6 +87,9 @@ model Application {
|
||||
baseDirectory String?
|
||||
publishDirectory String?
|
||||
phpModules String?
|
||||
pythonWSGI String?
|
||||
pythonModule String?
|
||||
pythonVariable String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
settings ApplicationSettings?
|
||||
@ -280,6 +283,7 @@ model Service {
|
||||
wordpress Wordpress?
|
||||
ghost Ghost?
|
||||
serviceSecret ServiceSecret[]
|
||||
meiliSearch MeiliSearch?
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
@ -349,3 +353,12 @@ model Ghost {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model MeiliSearch {
|
||||
id String @id @default(cuid())
|
||||
masterKey String
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
@ -100,10 +100,14 @@ export const setDefaultConfiguration = async (data) => {
|
||||
if (buildPack === 'static') port = 80;
|
||||
else if (buildPack === 'node') port = 3000;
|
||||
else if (buildPack === 'php') port = 80;
|
||||
else if (buildPack === 'python') port = 8000;
|
||||
}
|
||||
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
|
||||
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
||||
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
||||
if (template) {
|
||||
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
|
||||
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
||||
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
||||
}
|
||||
|
||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||
if (baseDirectory) {
|
||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
@ -136,7 +140,11 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
`
|
||||
);
|
||||
await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`);
|
||||
saveBuildLog({ line: 'Copied default configuration file for PHP.', buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: 'Copied default configuration file for PHP.',
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
} else if (staticDeployments.includes(buildPack)) {
|
||||
await fs.writeFile(
|
||||
`${workdir}/nginx.conf`,
|
||||
@ -190,7 +198,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
||||
}
|
||||
`
|
||||
);
|
||||
saveBuildLog({ line: 'Copied default configuration file.', buildId, applicationId });
|
||||
await saveBuildLog({ line: 'Copied default configuration file.', buildId, applicationId });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@ -12,6 +12,7 @@ import php from './php';
|
||||
import rust from './rust';
|
||||
import astro from './static';
|
||||
import eleventy from './static';
|
||||
import python from './python';
|
||||
|
||||
export {
|
||||
node,
|
||||
@ -27,5 +28,6 @@ export {
|
||||
php,
|
||||
rust,
|
||||
astro,
|
||||
eleventy
|
||||
eleventy,
|
||||
python
|
||||
};
|
||||
|
71
src/lib/buildPacks/python.ts
Normal file
71
src/lib/buildPacks/python.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { buildImage } from '$lib/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const {
|
||||
workdir,
|
||||
port,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
} = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.image=true`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
||||
Dockerfile.push(`RUN pip install gunicorn`);
|
||||
} else if (pythonWSGI?.toLowerCase() === 'uwsgi') {
|
||||
Dockerfile.push(`RUN apk add --no-cache uwsgi-python3`);
|
||||
// Dockerfile.push(`RUN pip install --no-cache-dir uwsgi`)
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.stat(`${workdir}${baseDirectory || ''}/requirements.txt`);
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''}/requirements.txt ./`);
|
||||
Dockerfile.push(`RUN pip install --no-cache-dir -r .${baseDirectory || ''}/requirements.txt`);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
||||
Dockerfile.push(`CMD gunicorn -w=4 -b=0.0.0.0:8000 ${pythonModule}:${pythonVariable}`);
|
||||
} else if (pythonWSGI?.toLowerCase() === 'uwsgi') {
|
||||
Dockerfile.push(
|
||||
`CMD uwsgi --master -p 4 --http-socket 0.0.0.0:8000 --uid uwsgi --plugins python3 --protocol uwsgi --wsgi ${pythonModule}:${pythonVariable}`
|
||||
);
|
||||
} else {
|
||||
Dockerfile.push(`CMD python ${pythonModule}`);
|
||||
}
|
||||
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
export default async function (data) {
|
||||
try {
|
||||
const image = 'python:3-alpine';
|
||||
await createDockerfile(data, image);
|
||||
await buildImage(data);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ export const getTeam = (event) => {
|
||||
|
||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||
const teamId = getTeam(event);
|
||||
const userId = event.locals.session.data.userId || null;
|
||||
const userId = event?.locals?.session?.data?.userId || null;
|
||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
||||
where: { teamId, userId },
|
||||
select: { permission: true },
|
||||
|
25
src/lib/components/DatabaseLinks.svelte
Normal file
25
src/lib/components/DatabaseLinks.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
export let database;
|
||||
import Clickhouse from './svg/databases/Clickhouse.svelte';
|
||||
import CouchDb from './svg/databases/CouchDB.svelte';
|
||||
import MongoDb from './svg/databases/MongoDB.svelte';
|
||||
import MySql from './svg/databases/MySQL.svelte';
|
||||
import PostgreSql from './svg/databases/PostgreSQL.svelte';
|
||||
import Redis from './svg/databases/Redis.svelte';
|
||||
</script>
|
||||
|
||||
<span class="relative">
|
||||
{#if database.type === 'clickhouse'}
|
||||
<Clickhouse />
|
||||
{:else if database.type === 'couchdb'}
|
||||
<CouchDb />
|
||||
{:else if database.type === 'mongodb'}
|
||||
<MongoDb />
|
||||
{:else if database.type === 'mysql'}
|
||||
<MySql />
|
||||
{:else if database.type === 'postgresql'}
|
||||
<PostgreSql />
|
||||
{:else if database.type === 'redis'}
|
||||
<Redis />
|
||||
{/if}
|
||||
</span>
|
55
src/lib/components/ServiceLinks.svelte
Normal file
55
src/lib/components/ServiceLinks.svelte
Normal file
@ -0,0 +1,55 @@
|
||||
<script>
|
||||
export let service;
|
||||
import Ghost from './svg/services/Ghost.svelte';
|
||||
import LanguageTool from './svg/services/LanguageTool.svelte';
|
||||
import MinIo from './svg/services/MinIO.svelte';
|
||||
import N8n from './svg/services/N8n.svelte';
|
||||
import NocoDb from './svg/services/NocoDB.svelte';
|
||||
import PlausibleAnalytics from './svg/services/PlausibleAnalytics.svelte';
|
||||
import UptimeKuma from './svg/services/UptimeKuma.svelte';
|
||||
import VaultWarden from './svg/services/VaultWarden.svelte';
|
||||
import VsCodeServer from './svg/services/VSCodeServer.svelte';
|
||||
import Wordpress from './svg/services/Wordpress.svelte';
|
||||
</script>
|
||||
|
||||
{#if service.type === 'plausibleanalytics'}
|
||||
<a href="https://plausible.io" target="_blank">
|
||||
<PlausibleAnalytics />
|
||||
</a>
|
||||
{:else if service.type === 'nocodb'}
|
||||
<a href="https://nocodb.com" target="_blank">
|
||||
<NocoDb />
|
||||
</a>
|
||||
{:else if service.type === 'minio'}
|
||||
<a href="https://min.io" target="_blank">
|
||||
<MinIo />
|
||||
</a>
|
||||
{:else if service.type === 'vscodeserver'}
|
||||
<a href="https://coder.com" target="_blank">
|
||||
<VsCodeServer />
|
||||
</a>
|
||||
{:else if service.type === 'wordpress'}
|
||||
<a href="https://wordpress.org" target="_blank">
|
||||
<Wordpress />
|
||||
</a>
|
||||
{:else if service.type === 'vaultwarden'}
|
||||
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
|
||||
<VaultWarden />
|
||||
</a>
|
||||
{:else if service.type === 'languagetool'}
|
||||
<a href="https://languagetool.org/dev" target="_blank">
|
||||
<LanguageTool />
|
||||
</a>
|
||||
{:else if service.type === 'n8n'}
|
||||
<a href="https://n8n.io" target="_blank">
|
||||
<N8n />
|
||||
</a>
|
||||
{:else if service.type === 'uptimekuma'}
|
||||
<a href="https://github.com/louislam/uptime-kuma" target="_blank">
|
||||
<UptimeKuma />
|
||||
</a>
|
||||
{:else if service.type === 'ghost'}
|
||||
<a href="https://ghost.org" target="_blank">
|
||||
<Ghost />
|
||||
</a>
|
||||
{/if}
|
@ -19,7 +19,7 @@ export const staticDeployments = [
|
||||
'astro',
|
||||
'eleventy'
|
||||
];
|
||||
export const notNodeDeployments = ['php', 'docker', 'rust'];
|
||||
export const notNodeDeployments = ['php', 'docker', 'rust', 'python'];
|
||||
|
||||
export function getDomain(domain) {
|
||||
return domain?.replace('https://', '').replace('http://', '');
|
||||
@ -37,3 +37,9 @@ export function dashify(str: string, options?: any): string {
|
||||
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function changeQueryParams(buildId) {
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
queryParams.set('buildId', buildId);
|
||||
return history.pushState(null, null, '?' + queryParams.toString());
|
||||
}
|
||||
|
45
src/lib/components/svg/services/MeiliSearch.svelte
Normal file
45
src/lib/components/svg/services/MeiliSearch.svelte
Normal file
@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 127 74"
|
||||
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M.825 73.993l23.244-59.47A21.85 21.85 0 0144.42.625h14.014L35.19 60.096a21.85 21.85 0 01-20.352 13.897H.825z"
|
||||
fill="url(#meilisearch_logo_svg__paint0_linear_0_6)"
|
||||
/><path
|
||||
d="M34.925 73.993l23.243-59.47A21.85 21.85 0 0178.52.626h14.013L69.29 60.096a21.85 21.85 0 01-20.351 13.897H34.925z"
|
||||
fill="url(#meilisearch_logo_svg__paint1_linear_0_6)"
|
||||
/><path
|
||||
d="M69.026 73.993l23.244-59.47A21.85 21.85 0 01112.621.626h14.014l-23.244 59.47a21.851 21.851 0 01-20.352 13.897H69.026z"
|
||||
fill="url(#meilisearch_logo_svg__paint2_linear_0_6)"
|
||||
/><defs
|
||||
><linearGradient
|
||||
id="meilisearch_logo_svg__paint0_linear_0_6"
|
||||
x1="126.635"
|
||||
y1="-4.978"
|
||||
x2="0.825"
|
||||
y2="66.098"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
|
||||
><linearGradient
|
||||
id="meilisearch_logo_svg__paint1_linear_0_6"
|
||||
x1="126.635"
|
||||
y1="-4.978"
|
||||
x2="0.825"
|
||||
y2="66.098"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
|
||||
><linearGradient
|
||||
id="meilisearch_logo_svg__paint2_linear_0_6"
|
||||
x1="126.635"
|
||||
y1="-4.978"
|
||||
x2="0.825"
|
||||
y2="66.098"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
|
||||
></defs
|
||||
></svg
|
||||
>
|
@ -146,6 +146,13 @@ export function findBuildPack(pack, packageManager = 'npm') {
|
||||
port: 80
|
||||
};
|
||||
}
|
||||
if (pack === 'python') {
|
||||
return {
|
||||
...metaData,
|
||||
startCommand: null,
|
||||
port: 8000
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: 'node',
|
||||
fancyName: 'Node.js',
|
||||
@ -249,6 +256,12 @@ export const buildPacks = [
|
||||
fancyName: 'Rust',
|
||||
hoverColor: 'hover:bg-pink-700',
|
||||
color: 'bg-pink-700'
|
||||
},
|
||||
{
|
||||
name: 'python',
|
||||
fancyName: 'Python',
|
||||
hoverColor: 'hover:bg-green-700',
|
||||
color: 'bg-green-700'
|
||||
}
|
||||
];
|
||||
export const scanningTemplates = {
|
||||
|
@ -214,11 +214,15 @@ export async function configureApplication({
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
publishDirectory,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
}) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
buildPack,
|
||||
fqdn,
|
||||
port,
|
||||
@ -227,7 +231,9 @@ export async function configureApplication({
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
name
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ export function ErrorHandler(e) {
|
||||
if (e.message?.includes('git clone')) {
|
||||
truncatedError.message = 'git clone failed';
|
||||
}
|
||||
sentry.captureException(truncatedError);
|
||||
if (!e.message?.includes('Coolify Proxy is not running')) {
|
||||
sentry.captureException(truncatedError);
|
||||
}
|
||||
const payload = {
|
||||
status: truncatedError.status || 500,
|
||||
body: {
|
||||
@ -195,6 +197,16 @@ export const supportedServiceTypesAndVersions = [
|
||||
ports: {
|
||||
main: 2368
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'meilisearch',
|
||||
fancyName: 'Meilisearch',
|
||||
baseImage: 'getmeili/meilisearch',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
ports: {
|
||||
main: 7700
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -22,7 +22,8 @@ export async function getService({ id, teamId }) {
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
serviceSecret: true
|
||||
serviceSecret: true,
|
||||
meiliSearch: true
|
||||
}
|
||||
});
|
||||
|
||||
@ -50,6 +51,8 @@ export async function getService({ id, teamId }) {
|
||||
body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
|
||||
if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
|
||||
|
||||
if (body.meiliSearch?.masterKey) body.meiliSearch.masterKey = decrypt(body.meiliSearch.masterKey);
|
||||
|
||||
if (body?.serviceSecret.length > 0) {
|
||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
@ -165,6 +168,15 @@ export async function configureServiceType({ id, type }) {
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'meilisearch') {
|
||||
const masterKey = encrypt(generatePassword(32));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
meiliSearch: { create: { masterKey } }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function setServiceVersion({ id, version }) {
|
||||
@ -191,6 +203,9 @@ export async function updateService({ id, fqdn, name }) {
|
||||
export async function updateLanguageToolService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateMeiliSearchService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateVaultWardenService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
@ -214,6 +229,7 @@ export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
|
||||
}
|
||||
|
||||
export async function removeService({ id }) {
|
||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
|
@ -88,12 +88,12 @@ export async function buildImage({
|
||||
debug = false
|
||||
}) {
|
||||
if (isCache) {
|
||||
saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
||||
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
||||
} else {
|
||||
saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
}
|
||||
if (!debug && isCache) {
|
||||
saveBuildLog({
|
||||
await saveBuildLog({
|
||||
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
||||
buildId,
|
||||
applicationId
|
||||
@ -126,13 +126,17 @@ export async function streamEvents({ stream, docker, buildId, applicationId, deb
|
||||
if (err) reject(err);
|
||||
resolve(res);
|
||||
}
|
||||
function onProgress(event) {
|
||||
async function onProgress(event) {
|
||||
if (event.error) {
|
||||
reject(event.error);
|
||||
} else if (event.stream) {
|
||||
if (event.stream !== '\n') {
|
||||
if (debug)
|
||||
saveBuildLog({ line: `${event.stream.replace('\n', '')}`, buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: `${event.stream.replace('\n', '')}`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ export async function configureHAProxy() {
|
||||
isRunning,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain : 'www.' + domain,
|
||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
});
|
||||
}
|
||||
@ -199,7 +199,7 @@ export async function configureHAProxy() {
|
||||
isRunning,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? previewDomain : 'www.' + previewDomain,
|
||||
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
});
|
||||
}
|
||||
@ -242,7 +242,7 @@ export async function configureHAProxy() {
|
||||
isRunning,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain : 'www.' + domain,
|
||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
||||
updatedAt: updatedAt.getTime()
|
||||
});
|
||||
}
|
||||
@ -262,7 +262,7 @@ export async function configureHAProxy() {
|
||||
domain,
|
||||
isHttps,
|
||||
redirectValue,
|
||||
redirectTo: isWWW ? domain : 'www.' + domain
|
||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
||||
});
|
||||
}
|
||||
const output = mustache.render(template, data);
|
||||
|
@ -10,11 +10,14 @@ export default async function ({
|
||||
workdir,
|
||||
githubAppId,
|
||||
repository,
|
||||
apiUrl,
|
||||
htmlUrl,
|
||||
branch,
|
||||
buildId
|
||||
}): Promise<any> {
|
||||
try {
|
||||
saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||
|
||||
@ -27,20 +30,20 @@ export default async function ({
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
const { token } = await got
|
||||
.post(`https://api.github.com/app/installations/${installationId}/access_tokens`, {
|
||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwtToken}`,
|
||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
saveBuildLog({
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@github.com/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..`
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..`
|
||||
);
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
return commit.replace('\n', '');
|
||||
|
@ -1,28 +1,28 @@
|
||||
import { asyncExecShell, saveBuildLog } from '$lib/common';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
|
||||
export default async function ({
|
||||
applicationId,
|
||||
debug,
|
||||
workdir,
|
||||
repodir,
|
||||
htmlUrl,
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
privateSshKey
|
||||
}): Promise<any> {
|
||||
saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
||||
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
||||
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
||||
|
||||
saveBuildLog({
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} git@gitlab.com:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..`
|
||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..`
|
||||
);
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
return commit.replace('\n', '');
|
||||
|
@ -51,7 +51,10 @@ export default async function (job) {
|
||||
pullmergeRequestId = null,
|
||||
sourceBranch = null,
|
||||
settings,
|
||||
persistentStorage
|
||||
persistentStorage,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
} = job.data;
|
||||
const { debug } = settings;
|
||||
|
||||
@ -114,6 +117,7 @@ export default async function (job) {
|
||||
branch,
|
||||
buildId,
|
||||
apiUrl: gitSource.apiUrl,
|
||||
htmlUrl: gitSource.htmlUrl,
|
||||
projectId,
|
||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
|
||||
@ -127,7 +131,7 @@ export default async function (job) {
|
||||
}
|
||||
|
||||
try {
|
||||
db.prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
await db.prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
@ -157,7 +161,7 @@ export default async function (job) {
|
||||
});
|
||||
deployNeeded = true;
|
||||
if (configHash) {
|
||||
saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||
}
|
||||
} else {
|
||||
deployNeeded = false;
|
||||
@ -200,16 +204,19 @@ export default async function (job) {
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
phpModules
|
||||
phpModules,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
});
|
||||
else {
|
||||
saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
throw new Error(`Build pack ${buildPack} not found.`);
|
||||
}
|
||||
deployNeeded = true;
|
||||
} else {
|
||||
deployNeeded = false;
|
||||
saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
|
||||
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
|
||||
}
|
||||
|
||||
// Deploy to Docker Engine
|
||||
@ -259,15 +266,7 @@ export default async function (job) {
|
||||
//
|
||||
}
|
||||
try {
|
||||
saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
// for await (const volume of volumes) {
|
||||
// const id = volume.split(':')[0];
|
||||
// try {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`);
|
||||
// } catch (error) {
|
||||
// await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`);
|
||||
// }
|
||||
// }
|
||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
@ -300,19 +299,12 @@ export default async function (job) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
||||
);
|
||||
|
||||
// const { stderr } = await asyncExecShell(
|
||||
// `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
|
||||
// ' '
|
||||
// )} --name ${imageId} --network ${docker.network} --restart always ${volumes.length > 0 ? volumes : ''
|
||||
// } -d ${applicationId}:${tag}`
|
||||
// );
|
||||
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
} catch (error) {
|
||||
saveBuildLog({ line: error, buildId, applicationId });
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
sentry.captureException(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
}
|
||||
}
|
||||
|
@ -135,12 +135,12 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
||||
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||
}
|
||||
saveBuildLog({
|
||||
await saveBuildLog({
|
||||
line: 'Failed to deploy!',
|
||||
buildId: job.data.build_id,
|
||||
applicationId: job.data.id
|
||||
});
|
||||
saveBuildLog({
|
||||
await saveBuildLog({
|
||||
line: `Reason: ${failedReason.toString()}`,
|
||||
buildId: job.data.build_id,
|
||||
applicationId: job.data.id
|
||||
|
@ -89,7 +89,6 @@
|
||||
try {
|
||||
const { buildId } = await post(`/applications/${id}/deploy.json`, { ...application });
|
||||
toast.push($t('application.deployment_queued'));
|
||||
console.log($page.url);
|
||||
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
|
||||
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
} else {
|
||||
|
@ -5,6 +5,7 @@ import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { get as getRequest } from '$lib/api';
|
||||
import { setDefaultConfiguration } from '$lib/buildPacks/common';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
@ -52,12 +53,23 @@ export const post: RequestHandler = async (event) => {
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
publishDirectory,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
} = await event.request.json();
|
||||
|
||||
if (port) port = Number(port);
|
||||
|
||||
try {
|
||||
const defaultConfiguration = await setDefaultConfiguration({
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
});
|
||||
await db.configureApplication({
|
||||
id,
|
||||
buildPack,
|
||||
@ -68,7 +80,11 @@ export const post: RequestHandler = async (event) => {
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
publishDirectory,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
...defaultConfiguration
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
|
@ -38,6 +38,7 @@
|
||||
import { page, session } from '$app/stores';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
@ -58,6 +59,23 @@
|
||||
let previews = application.settings.previews;
|
||||
let dualCerts = application.settings.dualCerts;
|
||||
let autodeploy = application.settings.autodeploy;
|
||||
|
||||
let wsgis = [
|
||||
{
|
||||
value: 'None',
|
||||
label: 'None'
|
||||
},
|
||||
{
|
||||
value: 'Gunicorn',
|
||||
label: 'Gunicorn'
|
||||
}
|
||||
// },
|
||||
// {
|
||||
// value: 'uWSGI',
|
||||
// label: 'uWSGI'
|
||||
// }
|
||||
];
|
||||
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
application.fqdn = `http://${cuid()}.demo.coolify.io`;
|
||||
}
|
||||
@ -112,7 +130,7 @@
|
||||
await post(`/applications/${id}.json`, { ...application });
|
||||
return window.location.reload();
|
||||
} catch ({ error }) {
|
||||
if (error.startsWith($t('application.dns_not_set_partial_error'))) {
|
||||
if (error?.startsWith($t('application.dns_not_set_partial_error'))) {
|
||||
forceSave = true;
|
||||
}
|
||||
return errorNotification(error);
|
||||
@ -120,12 +138,19 @@
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
async function selectWSGI(event) {
|
||||
application.pythonWSGI = event.detail.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
{application.name}
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Configuration
|
||||
</div>
|
||||
<span class="text-xs">{application.name} </span>
|
||||
</div>
|
||||
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
@ -300,7 +325,7 @@
|
||||
>
|
||||
{#if browser && window.location.hostname === 'demo.coolify.io'}
|
||||
<Explainer
|
||||
text="<span class='text-white font-bold'>You can use the predefined random domain name or enter your own domain name.</span>"
|
||||
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
|
||||
/>
|
||||
{/if}
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
@ -328,6 +353,39 @@
|
||||
on:click={() => !isRunning && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
{#if application.buildPack === 'python'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI</label>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select id="wsgi" items={wsgis} on:select={selectWSGI} value={application.pythonWSGI} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="pythonModule" class="text-base font-bold text-stone-100">Module</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="pythonModule"
|
||||
id="pythonModule"
|
||||
required
|
||||
bind:value={application.pythonModule}
|
||||
placeholder={application.pythonWSGI?.toLowerCase() !== 'gunicorn' ? 'main.py' : 'main'}
|
||||
/>
|
||||
</div>
|
||||
{#if application.pythonWSGI?.toLowerCase() === 'gunicorn'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="pythonVariable" class="text-base font-bold text-stone-100">Variable</label>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="pythonVariable"
|
||||
id="pythonVariable"
|
||||
required
|
||||
bind:value={application.pythonVariable}
|
||||
placeholder="default: app"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if !staticDeployments.includes(application.buildPack)}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
|
||||
@ -336,7 +394,7 @@
|
||||
name="port"
|
||||
id="port"
|
||||
bind:value={application.port}
|
||||
placeholder="{$t('forms.default')}: 3000"
|
||||
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { dateOptions, getDomain } from '$lib/components/common';
|
||||
import { changeQueryParams, dateOptions, getDomain } from '$lib/components/common';
|
||||
|
||||
import BuildLog from './_BuildLog.svelte';
|
||||
import { get } from '$lib/api';
|
||||
@ -80,17 +80,81 @@
|
||||
noMoreBuilds = true;
|
||||
}
|
||||
}
|
||||
async function loadBuild(build) {
|
||||
function loadBuild(build) {
|
||||
buildId = build;
|
||||
await goto(`/applications/${id}/logs/build?buildId=${buildId}`);
|
||||
return changeQueryParams(buildId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
{$t('application.build.build_logs_of')}
|
||||
<a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">$t('application.build_logs')}</div>
|
||||
<span class="text-xs">{application.name} </span>
|
||||
</div>
|
||||
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||
target="_blank"
|
||||
class="w-10"
|
||||
>
|
||||
{#if application.gitSource?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<path
|
||||
fill="#FC6D26"
|
||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||
fill="#FC6D26"
|
||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||
/><path
|
||||
fill="#FCA326"
|
||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||
fill="#FCA326"
|
||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if application.gitSource?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<g fill="#ffffff"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||
/><path
|
||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
<div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex">
|
||||
<div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">
|
||||
@ -134,11 +198,14 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}
|
||||
>{$t('application.build.load_more')}</button
|
||||
>
|
||||
</div>
|
||||
{#if !noMoreBuilds}
|
||||
{#if buildCount > 5}
|
||||
<div class="flex space-x-2">
|
||||
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}>{$t('application.build.load_more')}</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-1 md:w-96">
|
||||
{#if buildId}
|
||||
|
@ -69,16 +69,83 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Application logs of <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Application Logs
|
||||
</div>
|
||||
<span class="text-xs">{application.name} </span>
|
||||
</div>
|
||||
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||
target="_blank"
|
||||
class="w-10"
|
||||
>
|
||||
{#if application.gitSource?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<path
|
||||
fill="#FC6D26"
|
||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||
fill="#FC6D26"
|
||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||
/><path
|
||||
fill="#FCA326"
|
||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||
fill="#FCA326"
|
||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if application.gitSource?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<g fill="#ffffff"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||
/><path
|
||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex flex-row justify-center space-x-2 px-10 pt-6">
|
||||
{#if logs.length === 0}
|
||||
<div class="text-xl font-bold tracking-tighter">{$t('application.build.waiting_logs')}</div>
|
||||
{:else}
|
||||
<div class="relative">
|
||||
<div class="relative w-full">
|
||||
<LoadingLogs />
|
||||
<div class="flex justify-end sticky top-0 p-2">
|
||||
<button
|
||||
@ -106,7 +173,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||
bind:this={logsEl}
|
||||
>
|
||||
<div class="px-2">
|
||||
|
@ -11,7 +11,6 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
@ -51,14 +50,88 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Previews for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Preview Deployments
|
||||
</div>
|
||||
<span class="text-xs">{application.name} </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if applicationSecrets.length !== 0}
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||
target="_blank"
|
||||
class="w-10"
|
||||
>
|
||||
{#if application.gitSource?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<path
|
||||
fill="#FC6D26"
|
||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||
fill="#FC6D26"
|
||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||
/><path
|
||||
fill="#FCA326"
|
||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||
fill="#FCA326"
|
||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if application.gitSource?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<g fill="#ffffff"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||
/><path
|
||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mx-auto max-w-6xl px-6 pt-4">
|
||||
<div class="flex justify-center py-4 text-center">
|
||||
<Explainer
|
||||
customClass="w-full"
|
||||
text={applicationSecrets.length === 0
|
||||
? "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||
: "These values overwrite application secrets in PR/MR deployments. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
|
||||
/>
|
||||
</div>
|
||||
{#if applicationSecrets.length !== 0}
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
@ -87,16 +160,9 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-center py-4 text-center">
|
||||
<Explainer
|
||||
customClass="w-full"
|
||||
text={applicationSecrets.length === 0
|
||||
? $t('application.preview.setup_secret_app_first')
|
||||
: $t('application.preview.values_overwriting_app_secrets')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl py-10">
|
||||
<div class="flex flex-wrap justify-center space-x-2">
|
||||
{#if containers.length > 0}
|
||||
|
48
src/routes/applications/[id]/secrets/_BatchSecrets.svelte
Normal file
48
src/routes/applications/[id]/secrets/_BatchSecrets.svelte
Normal file
@ -0,0 +1,48 @@
|
||||
<script>
|
||||
export let secrets;
|
||||
export let refreshSecrets;
|
||||
export let id;
|
||||
|
||||
import { saveSecret } from './utils';
|
||||
import pLimit from 'p-limit';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let batchSecrets = '';
|
||||
function setBatchValue(event) {
|
||||
batchSecrets = event.target?.value;
|
||||
}
|
||||
const limit = pLimit(1);
|
||||
async function getValues(e) {
|
||||
e.preventDefault();
|
||||
const eachValuePair = batchSecrets.split('\n');
|
||||
const batchSecretsPairs = eachValuePair
|
||||
.filter((secret) => !secret.startsWith('#') && secret)
|
||||
.map((secret) => {
|
||||
const [name, value] = secret.split('=');
|
||||
const cleanValue = value?.replaceAll('"', '') || '';
|
||||
return {
|
||||
name,
|
||||
value: cleanValue,
|
||||
isNew: !secrets.find((secret) => name === secret.name)
|
||||
};
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
batchSecretsPairs.map(({ name, value, isNew }) =>
|
||||
limit(() => saveSecret({ name, value, applicationId: id, isNew, dispatch }))
|
||||
)
|
||||
);
|
||||
batchSecrets = '';
|
||||
refreshSecrets();
|
||||
}
|
||||
</script>
|
||||
|
||||
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
||||
<form on:submit={getValues} class="mb-12 w-full">
|
||||
<textarea value={batchSecrets} on:change={setBatchValue} class="mb-2 min-h-[200px] w-full" />
|
||||
<button
|
||||
class="bg-green-600 hover:bg-green-500 disabled:text-white disabled:opacity-40"
|
||||
type="submit">Batch add secrets</button
|
||||
>
|
||||
</form>
|
@ -9,12 +9,11 @@
|
||||
if (isPRMRSecret) value = PRMRSecret.value;
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { del, post } from '$lib/api';
|
||||
import { del } from '$lib/api';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { t } from '$lib/translations';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { saveSecret } from './utils';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const { id } = $page.params;
|
||||
@ -31,28 +30,20 @@
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function saveSecret(isNew = false) {
|
||||
if (!name) return errorNotification(`${$t('forms.name')} ${$t('forms.is_required')}`);
|
||||
if (!value) return errorNotification(`${$t('forms.value')} ${$t('forms.is_required')}`);
|
||||
try {
|
||||
await post(`/applications/${id}/secrets.json`, {
|
||||
name,
|
||||
value,
|
||||
isBuildSecret,
|
||||
isPRMRSecret,
|
||||
isNew
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
isBuildSecret = false;
|
||||
}
|
||||
toast.push($t('application.secrets.secret_saved'));
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
|
||||
async function createSecret(isNew) {
|
||||
saveSecret({
|
||||
isNew,
|
||||
name,
|
||||
value,
|
||||
isBuildSecret,
|
||||
isPRMRSecret,
|
||||
isNewSecret,
|
||||
applicationId: id,
|
||||
dispatch
|
||||
});
|
||||
}
|
||||
|
||||
function setSecretValue() {
|
||||
if (isNewSecret) {
|
||||
isBuildSecret = !isBuildSecret;
|
||||
@ -134,14 +125,14 @@
|
||||
<td>
|
||||
{#if isNewSecret}
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => createSecret(true)}
|
||||
>{$t('forms.add')}</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="" on:click={() => saveSecret(false)}>{$t('forms.set')}</button>
|
||||
<button class="" on:click={() => createSecret(false)}>{$t('forms.set')}</button>
|
||||
</div>
|
||||
{#if !isPRMRSecret}
|
||||
<div class="flex justify-center items-end">
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { getTeam, getUserDetails } from '$lib/common';
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
@ -24,7 +24,7 @@ export const get: RequestHandler = async (event) => {
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
@ -53,7 +53,7 @@ export const post: RequestHandler = async (event) => {
|
||||
}
|
||||
};
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
@ -25,8 +25,9 @@
|
||||
import Secret from './_Secret.svelte';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import BatchSecrets from './_BatchSecrets.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
@ -36,13 +37,77 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
{$t('application.secrets.secrets_for')}
|
||||
<a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">{$t('application.secret')}</div>
|
||||
<span class="text-xs">{application.name} </span>
|
||||
</div>
|
||||
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||
target="_blank"
|
||||
class="w-10"
|
||||
>
|
||||
{#if application.gitSource?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<path
|
||||
fill="#FC6D26"
|
||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||
fill="#FC6D26"
|
||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||
/><path
|
||||
fill="#FCA326"
|
||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||
fill="#FCA326"
|
||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if application.gitSource?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<g fill="#ffffff"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||
/><path
|
||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<div class="mx-auto max-w-6xl px-6 pt-4">
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
@ -72,4 +137,6 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<BatchSecrets {secrets} {id} {refreshSecrets} />
|
||||
</div>
|
||||
|
46
src/routes/applications/[id]/secrets/utils.ts
Normal file
46
src/routes/applications/[id]/secrets/utils.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { post } from '$lib/api';
|
||||
|
||||
type Props = {
|
||||
isNew: boolean;
|
||||
name: string;
|
||||
value: string;
|
||||
isBuildSecret?: boolean;
|
||||
isPRMRSecret?: boolean;
|
||||
isNewSecret?: boolean;
|
||||
applicationId: string;
|
||||
dispatch: (name: string) => void;
|
||||
};
|
||||
|
||||
export async function saveSecret({
|
||||
isNew,
|
||||
name,
|
||||
value,
|
||||
isBuildSecret,
|
||||
isPRMRSecret,
|
||||
isNewSecret,
|
||||
applicationId,
|
||||
dispatch
|
||||
}: Props): Promise<void> {
|
||||
if (!name) return errorNotification('Name is required.');
|
||||
if (!value) return errorNotification('Value is required.');
|
||||
try {
|
||||
await post(`/applications/${applicationId}/secrets.json`, {
|
||||
name,
|
||||
value,
|
||||
isBuildSecret,
|
||||
isPRMRSecret,
|
||||
isNew: isNew || false
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
isBuildSecret = false;
|
||||
}
|
||||
toast.push('Secret saved.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
@ -37,12 +37,77 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
Persistent storage for <a href={application.fqdn} target="_blank"
|
||||
>{getDomain(application.fqdn)}</a
|
||||
>
|
||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Persistent Storage
|
||||
</div>
|
||||
<span class="text-xs">{application.name} </span>
|
||||
</div>
|
||||
|
||||
{#if application.fqdn}
|
||||
<a
|
||||
href={application.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
|
||||
target="_blank"
|
||||
class="w-10"
|
||||
>
|
||||
{#if application.gitSource?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<path
|
||||
fill="#FC6D26"
|
||||
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
|
||||
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
|
||||
fill="#FC6D26"
|
||||
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
|
||||
/><path
|
||||
fill="#FCA326"
|
||||
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
|
||||
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
|
||||
fill="#FCA326"
|
||||
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
|
||||
/><path
|
||||
fill="#E24329"
|
||||
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if application.gitSource?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="icons">
|
||||
<g fill="#ffffff"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
|
||||
/><path
|
||||
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
|
@ -31,37 +31,20 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Clickhouse from '$lib/components/svg/databases/Clickhouse.svelte';
|
||||
import CouchDb from '$lib/components/svg/databases/CouchDB.svelte';
|
||||
import MongoDb from '$lib/components/svg/databases/MongoDB.svelte';
|
||||
import MySql from '$lib/components/svg/databases/MySQL.svelte';
|
||||
import PostgreSql from '$lib/components/svg/databases/PostgreSQL.svelte';
|
||||
import Redis from '$lib/components/svg/databases/Redis.svelte';
|
||||
|
||||
import DatabaseLinks from '$lib/components/DatabaseLinks.svelte';
|
||||
export let database;
|
||||
export let settings;
|
||||
export let privatePort;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-2 p-6 text-2xl font-bold">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:block md:text-2xl">
|
||||
{database.name}
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Configuration
|
||||
</div>
|
||||
<span class="text-xs">{database.name}</span>
|
||||
</div>
|
||||
<span class="relative">
|
||||
{#if database.type === 'clickhouse'}
|
||||
<Clickhouse />
|
||||
{:else if database.type === 'couchdb'}
|
||||
<CouchDb />
|
||||
{:else if database.type === 'mongodb'}
|
||||
<MongoDb />
|
||||
{:else if database.type === 'mysql'}
|
||||
<MySql />
|
||||
{:else if database.type === 'postgresql'}
|
||||
<PostgreSql />
|
||||
{:else if database.type === 'redis'}
|
||||
<Redis />
|
||||
{/if}
|
||||
</span>
|
||||
<DatabaseLinks {database} />
|
||||
</div>
|
||||
|
||||
<Databases bind:database {privatePort} {settings} />
|
||||
|
@ -20,6 +20,9 @@
|
||||
emailEl.focus();
|
||||
});
|
||||
async function handleSubmit() {
|
||||
// Prevent double submission
|
||||
if (loading) return;
|
||||
|
||||
if (password !== passwordCheck) {
|
||||
return errorNotification($t('forms.passwords_not_match'));
|
||||
}
|
||||
@ -89,8 +92,13 @@
|
||||
/>
|
||||
|
||||
<div class="flex space-x-2 h-8 items-center justify-center pt-8">
|
||||
<button type="submit" class="hover:bg-coollabs-100 text-white bg-coollabs"
|
||||
>{$t('register.register')}</button
|
||||
<button
|
||||
type="submit"
|
||||
class="hover:bg-coollabs-100 text-white"
|
||||
disabled={loading}
|
||||
class:bg-transparent={loading}
|
||||
class:text-stone-600={loading}
|
||||
class:bg-coollabs={!loading}>{loading ? $t('register.registering') : $t('register.register')}</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
|
19
src/routes/services/[id]/_Services/_MeiliSearch.svelte
Normal file
19
src/routes/services/[id]/_Services/_MeiliSearch.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
export let service;
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">MeiliSearch</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="masterKey">Admin API key</label>
|
||||
<CopyPasswordField
|
||||
id="masterKey"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="masterKey"
|
||||
value={service.meiliSearch.masterKey}
|
||||
/>
|
||||
</div>
|
@ -12,6 +12,7 @@
|
||||
import { t } from '$lib/translations';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Ghost from './_Ghost.svelte';
|
||||
import MeiliSearch from './_MeiliSearch.svelte';
|
||||
import MinIo from './_MinIO.svelte';
|
||||
import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
|
||||
import VsCodeServer from './_VSCodeServer.svelte';
|
||||
@ -113,9 +114,7 @@
|
||||
</div>
|
||||
<div class="grid grid-cols-2 px-10">
|
||||
<div class="flex-col ">
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
|
||||
>{$t('application.domain_fqdn')}</label
|
||||
>
|
||||
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">{$t('application.url_fqdn')}</label>
|
||||
<Explainer text={$t('application.https_explainer')} />
|
||||
</div>
|
||||
|
||||
@ -150,6 +149,8 @@
|
||||
<Wordpress bind:service {isRunning} {readOnly} />
|
||||
{:else if service.type === 'ghost'}
|
||||
<Ghost bind:service {readOnly} />
|
||||
{:else if service.type === 'meilisearch'}
|
||||
<MeiliSearch bind:service />
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -42,6 +42,7 @@
|
||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
@ -87,6 +88,8 @@
|
||||
<UptimeKuma isAbsolute />
|
||||
{:else if type.name === 'ghost'}
|
||||
<Ghost isAbsolute />
|
||||
{:else if type.name === 'meilisearch'}
|
||||
<MeiliSearch isAbsolute />
|
||||
{/if}{type.fancyName}
|
||||
</button>
|
||||
</form>
|
||||
|
@ -29,19 +29,10 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import PlausibleAnalytics from '$lib/components/svg/services/PlausibleAnalytics.svelte';
|
||||
import NocoDb from '$lib/components/svg/services/NocoDB.svelte';
|
||||
import MinIo from '$lib/components/svg/services/MinIO.svelte';
|
||||
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
|
||||
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
|
||||
import Services from './_Services/_Services.svelte';
|
||||
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
|
||||
import cuid from 'cuid';
|
||||
import { browser } from '$app/env';
|
||||
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
|
||||
import N8n from '$lib/components/svg/services/N8n.svelte';
|
||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||
import ServiceLinks from '$lib/components/ServiceLinks.svelte';
|
||||
import Services from './_Services/_Services.svelte';
|
||||
|
||||
export let service;
|
||||
export let isRunning;
|
||||
@ -52,13 +43,12 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center space-x-3 px-6 text-2xl font-bold"
|
||||
class:p-5={service.fqdn}
|
||||
class:p-6={!service.fqdn}
|
||||
>
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
{service.name}
|
||||
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Configuration
|
||||
</div>
|
||||
<span class="text-xs">{service.name}</span>
|
||||
</div>
|
||||
{#if service.fqdn}
|
||||
<a
|
||||
@ -83,49 +73,7 @@
|
||||
>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
{#if service.type === 'plausibleanalytics'}
|
||||
<a href="https://plausible.io" target="_blank">
|
||||
<PlausibleAnalytics />
|
||||
</a>
|
||||
{:else if service.type === 'nocodb'}
|
||||
<a href="https://nocodb.com" target="_blank">
|
||||
<NocoDb />
|
||||
</a>
|
||||
{:else if service.type === 'minio'}
|
||||
<a href="https://min.io" target="_blank">
|
||||
<MinIo />
|
||||
</a>
|
||||
{:else if service.type === 'vscodeserver'}
|
||||
<a href="https://coder.com" target="_blank">
|
||||
<VsCodeServer />
|
||||
</a>
|
||||
{:else if service.type === 'wordpress'}
|
||||
<a href="https://wordpress.org" target="_blank">
|
||||
<Wordpress />
|
||||
</a>
|
||||
{:else if service.type === 'vaultwarden'}
|
||||
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
|
||||
<VaultWarden />
|
||||
</a>
|
||||
{:else if service.type === 'languagetool'}
|
||||
<a href="https://languagetool.org/dev" target="_blank">
|
||||
<LanguageTool />
|
||||
</a>
|
||||
{:else if service.type === 'n8n'}
|
||||
<a href="https://n8n.io" target="_blank">
|
||||
<N8n />
|
||||
</a>
|
||||
{:else if service.type === 'uptimekuma'}
|
||||
<a href="https://github.com/louislam/uptime-kuma" target="_blank">
|
||||
<UptimeKuma />
|
||||
</a>
|
||||
{:else if service.type === 'ghost'}
|
||||
<a href="https://ghost.org" target="_blank">
|
||||
<Ghost />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
<ServiceLinks {service} />
|
||||
</div>
|
||||
|
||||
<Services bind:service {isRunning} {readOnly} />
|
||||
|
@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateLanguageToolService({ id, fqdn, name });
|
||||
await db.updateMeiliSearchService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
21
src/routes/services/[id]/meilisearch/index.json.ts
Normal file
21
src/routes/services/[id]/meilisearch/index.json.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateLanguageToolService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
83
src/routes/services/[id]/meilisearch/start.json.ts
Normal file
83
src/routes/services/[id]/meilisearch/start.json.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const {
|
||||
meiliSearch: { masterKey }
|
||||
} = service;
|
||||
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
const image = getServiceImage(type);
|
||||
|
||||
const config = {
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-datams:/data.ms`,
|
||||
environmentVariables: {
|
||||
MEILI_MASTER_KEY: masterKey
|
||||
}
|
||||
};
|
||||
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.image,
|
||||
networks: [network],
|
||||
environment: config.environmentVariables,
|
||||
restart: 'always',
|
||||
volumes: [config.volume],
|
||||
labels: makeLabelForServices('meilisearch')
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
35
src/routes/services/[id]/meilisearch/stop.json.ts
Normal file
35
src/routes/services/[id]/meilisearch/stop.json.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer } from '$lib/haproxy';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -27,6 +27,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import ServiceLinks from '$lib/components/ServiceLinks.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
|
||||
@ -36,13 +37,39 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl tracking-tight">
|
||||
{$t('application.secret')}
|
||||
{#if service.fqdn}
|
||||
<a href={service.fqdn} target="_blank">{getDomain(service.fqdn)}</a>
|
||||
{/if}
|
||||
<div
|
||||
class="flex items-center space-x-2 p-5 px-6 font-bold"
|
||||
class:p-5={service.fqdn}
|
||||
class:p-6={!service.fqdn}
|
||||
>
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">{$t('application.secret')}</div>
|
||||
<span class="text-xs">{service.name}</span>
|
||||
</div>
|
||||
{#if service.fqdn}
|
||||
<a
|
||||
href={service.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
|
||||
<ServiceLinks {service} />
|
||||
</div>
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<table class="mx-auto border-separate text-left">
|
||||
|
@ -12,6 +12,7 @@
|
||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
||||
|
||||
export let services;
|
||||
async function newService() {
|
||||
@ -68,6 +69,8 @@
|
||||
<UptimeKuma isAbsolute />
|
||||
{:else if service.type === 'ghost'}
|
||||
<Ghost isAbsolute />
|
||||
{:else if service.type === 'meilisearch'}
|
||||
<MeiliSearch isAbsolute />
|
||||
{/if}
|
||||
<div class="font-bold text-xl text-center truncate">
|
||||
{service.name}
|
||||
|
@ -123,7 +123,7 @@
|
||||
<div class="grid grid-cols-2 items-start">
|
||||
<div class="flex-col">
|
||||
<div class="pt-2 text-base font-bold text-stone-100">
|
||||
{$t('application.domain_fqdn')}
|
||||
{$t('application.url_fqdn')}
|
||||
</div>
|
||||
<Explainer text={$t('setting.ssl_explainer')} />
|
||||
</div>
|
||||
|
@ -54,10 +54,10 @@ textarea {
|
||||
}
|
||||
|
||||
#svelte .item.hover {
|
||||
@apply bg-coolgray-100 text-white;
|
||||
@apply bg-coollabs text-white !important;
|
||||
}
|
||||
#svelte .item.active {
|
||||
@apply bg-coollabs text-white;
|
||||
@apply bg-coolgray-100 text-white;
|
||||
}
|
||||
|
||||
select {
|
||||
|
Loading…
x
Reference in New Issue
Block a user