feat: WP could have custom db
This commit is contained in:
parent
ede37d296b
commit
ce52608f19
@ -0,0 +1,32 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Wordpress" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"extraConfig" TEXT,
|
||||
"tablePrefix" TEXT,
|
||||
"ownMysql" BOOLEAN NOT NULL DEFAULT false,
|
||||
"mysqlHost" TEXT,
|
||||
"mysqlPort" INTEGER,
|
||||
"mysqlUser" TEXT NOT NULL,
|
||||
"mysqlPassword" TEXT NOT NULL,
|
||||
"mysqlRootUser" TEXT NOT NULL,
|
||||
"mysqlRootUserPassword" TEXT NOT NULL,
|
||||
"mysqlDatabase" TEXT,
|
||||
"mysqlPublicPort" INTEGER,
|
||||
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"ftpUser" TEXT,
|
||||
"ftpPassword" TEXT,
|
||||
"ftpPublicPort" INTEGER,
|
||||
"ftpHostKey" TEXT,
|
||||
"ftpHostKeyPrivate" TEXT,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
|
||||
DROP TABLE "Wordpress";
|
||||
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
|
||||
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -353,6 +353,9 @@ model Wordpress {
|
||||
id String @id @default(cuid())
|
||||
extraConfig String?
|
||||
tablePrefix String?
|
||||
ownMysql Boolean @default(false)
|
||||
mysqlHost String?
|
||||
mysqlPort Int?
|
||||
mysqlUser String
|
||||
mysqlPassword String
|
||||
mysqlRootUser String
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
<img
|
||||
alt="plausible logo"
|
||||
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
|
||||
class={isAbsolute ? 'w-9 absolute top-0 left-0 -m-4' : 'w-6 mx-auto'}
|
||||
src="/plausible.png"
|
||||
/>
|
||||
|
@ -28,7 +28,7 @@ if (!dev) {
|
||||
}
|
||||
|
||||
export const prisma = new PrismaClient({
|
||||
errorFormat: 'pretty',
|
||||
errorFormat: 'minimal',
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
|
||||
|
@ -419,7 +419,9 @@ export async function updateWordpress({
|
||||
name,
|
||||
exposePort,
|
||||
mysqlDatabase,
|
||||
extraConfig
|
||||
extraConfig,
|
||||
mysqlHost,
|
||||
mysqlPort
|
||||
}: {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
@ -427,10 +429,24 @@ export async function updateWordpress({
|
||||
exposePort?: number;
|
||||
mysqlDatabase: string;
|
||||
extraConfig: string;
|
||||
mysqlHost?: string;
|
||||
mysqlPort?: number;
|
||||
}): Promise<Service> {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, exposePort, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
||||
data: {
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
wordpress: {
|
||||
update: {
|
||||
mysqlDatabase,
|
||||
extraConfig,
|
||||
mysqlHost,
|
||||
mysqlPort
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col flex-wrap justify-center">
|
||||
<div class="flex justify-center">
|
||||
{#if !applications || ownApplications.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
||||
|
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-wrap justify-center">
|
||||
<div class="flex justify-center">
|
||||
{#if !databases || ownDatabases.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
|
||||
|
@ -39,10 +39,13 @@
|
||||
import { t } from '$lib/translations';
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 text-2xl font-bold">
|
||||
<div class="tracking-tight">{$t('application.destination')}</div>
|
||||
<span class="arrow-right-applications px-1">></span>
|
||||
<span class="pr-2">{destination.name}</span>
|
||||
<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">{destination.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-6">
|
||||
|
@ -141,21 +141,21 @@
|
||||
<div class="title font-bold">Server Usage</div>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<Loading />
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{(usage?.memory.totalMemMb).toFixed(0)}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white ">
|
||||
{(usage?.memory.usedMemMb).toFixed(0)}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6" class:bg-red-500={memoryWarning}>
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6" class:bg-red-500={memoryWarning}>
|
||||
<dt class="truncate text-sm font-medium text-white">Free Memory</dt>
|
||||
<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
|
||||
{usage?.memory.freeMemPercentage}%
|
||||
@ -166,19 +166,19 @@
|
||||
</div>
|
||||
</dl>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.count}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-white">Load Average (5/10/30mins)</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.load.join('/')}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6" class:bg-red-500={cpuWarning}>
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6" class:bg-red-500={cpuWarning}>
|
||||
<dt class="truncate text-sm font-medium text-white">CPU Usage</dt>
|
||||
<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
|
||||
{usage?.cpu.usage}%
|
||||
@ -189,19 +189,19 @@
|
||||
</div>
|
||||
</dl>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.totalGb}GB
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
|
||||
<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.usedGb}GB
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6" class:bg-red-500={diskWarning}>
|
||||
<div class="overflow-hidden rounded px-4 py-5 sm:p-6" class:bg-red-500={diskWarning}>
|
||||
<dt class="truncate text-sm font-medium text-white">Free Disk</dt>
|
||||
<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
|
||||
{usage?.disk.freePercentage}%
|
||||
@ -217,7 +217,7 @@
|
||||
<a
|
||||
href="/applications"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded-lg px-4 py-5 text-green-500 no-underline transition-all duration-100 hover:bg-green-500 hover:text-white sm:p-6"
|
||||
class="overflow-hidden rounded px-4 py-5 text-green-500 no-underline transition-all duration-100 hover:bg-green-500 hover:text-white sm:p-6"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.applications')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
@ -227,7 +227,7 @@
|
||||
<a
|
||||
href="/destinations"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded-lg px-4 py-5 text-sky-500 no-underline transition-all duration-100 hover:bg-sky-500 hover:text-white sm:p-6"
|
||||
class="overflow-hidden rounded px-4 py-5 text-sky-500 no-underline transition-all duration-100 hover:bg-sky-500 hover:text-white sm:p-6"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.destinations')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
@ -238,7 +238,7 @@
|
||||
<a
|
||||
href="/sources"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded-lg px-4 py-5 text-orange-500 no-underline transition-all duration-100 hover:bg-orange-500 hover:text-white sm:p-6"
|
||||
class="overflow-hidden rounded px-4 py-5 text-orange-500 no-underline transition-all duration-100 hover:bg-orange-500 hover:text-white sm:p-6"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.git_sources')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold">
|
||||
@ -250,7 +250,7 @@
|
||||
<a
|
||||
href="/databases"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded-lg px-4 py-5 text-purple-500 no-underline transition-all duration-100 hover:bg-purple-500 hover:text-white sm:p-6"
|
||||
class="overflow-hidden rounded px-4 py-5 text-purple-500 no-underline transition-all duration-100 hover:bg-purple-500 hover:text-white sm:p-6"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.databases')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
@ -261,7 +261,7 @@
|
||||
<a
|
||||
href="/services"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded-lg px-4 py-5 text-pink-500 no-underline transition-all duration-100 hover:bg-pink-500 hover:text-white sm:p-6"
|
||||
class="overflow-hidden rounded px-4 py-5 text-pink-500 no-underline transition-all duration-100 hover:bg-pink-500 hover:text-white sm:p-6"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.services')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
@ -272,7 +272,7 @@
|
||||
<a
|
||||
href="/iam"
|
||||
sveltekit:prefetch
|
||||
class="overflow-hidden rounded-lg px-4 py-5 text-cyan-500 no-underline transition-all duration-100 hover:bg-cyan-500 hover:text-white sm:p-6"
|
||||
class="overflow-hidden rounded px-4 py-5 text-cyan-500 no-underline transition-all duration-100 hover:bg-cyan-500 hover:text-white sm:p-6"
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">{$t('index.teams')}</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold ">
|
||||
|
@ -96,16 +96,3 @@
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="grid grid-cols-3 items-center">
|
||||
<label for="postgresqlPublicPort">Public Port</label>
|
||||
<div class="col-span-2 ">
|
||||
<CopyPasswordField
|
||||
placeholder="{ $t('forms.generated_automatically_after_start') }"
|
||||
readonly
|
||||
disabled
|
||||
id="postgresqlPublicPort"
|
||||
name="postgresqlPublicPort"
|
||||
value={service.plausibleAnalytics.postgresqlPublicPort}
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
|
@ -18,6 +18,7 @@
|
||||
let ftpUser = service.wordpress.ftpUser;
|
||||
let ftpPassword = service.wordpress.ftpPassword;
|
||||
let ftpLoading = false;
|
||||
let ownMysql = service.wordpress.ownMysql;
|
||||
|
||||
function generateUrl(publicPort) {
|
||||
return browser
|
||||
@ -40,7 +41,7 @@
|
||||
publicPort,
|
||||
ftpUser: user,
|
||||
ftpPassword: password
|
||||
} = await post(`/services/${id}/wordpress/settings.json`, {
|
||||
} = await post(`/services/${id}/wordpress/ftp.json`, {
|
||||
ftpEnabled
|
||||
});
|
||||
ftpUrl = generateUrl(publicPort);
|
||||
@ -52,6 +53,18 @@
|
||||
} finally {
|
||||
ftpLoading = false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (name === 'ownMysql') {
|
||||
ownMysql = !ownMysql;
|
||||
}
|
||||
await post(`/services/${id}/wordpress/settings.json`, {
|
||||
ownMysql
|
||||
});
|
||||
service.wordpress.ownMysql = ownMysql;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -106,51 +119,95 @@ define('SUBDOMAIN_INSTALL', false);`
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">MySQL</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
bind:setting={service.wordpress.ownMysql}
|
||||
disabled={isRunning}
|
||||
on:click={() => !isRunning && changeSettings('ownMysql')}
|
||||
title="Use your own MySQL server"
|
||||
description="Enables the use of your own MySQL server. If you don't have one, you can use the one provided by Coolify."
|
||||
/>
|
||||
</div>
|
||||
{#if service.wordpress.ownMysql}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlHost">Host</label>
|
||||
<input
|
||||
name="mysqlHost"
|
||||
id="mysqlHost"
|
||||
required
|
||||
readonly={isRunning}
|
||||
disabled={isRunning}
|
||||
bind:value={service.wordpress.mysqlHost}
|
||||
placeholder="{$t('forms.eg')}: db.coolify.io"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlPort">Port</label>
|
||||
<input
|
||||
name="mysqlPort"
|
||||
id="mysqlPort"
|
||||
required
|
||||
readonly={isRunning}
|
||||
disabled={isRunning}
|
||||
bind:value={service.wordpress.mysqlPort}
|
||||
placeholder="{$t('forms.eg')}: 3306"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlDatabase">{$t('index.database')}</label>
|
||||
<input
|
||||
name="mysqlDatabase"
|
||||
id="mysqlDatabase"
|
||||
required
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
readonly={readOnly && !service.wordpress.ownMysql}
|
||||
disabled={readOnly && !service.wordpress.ownMysql}
|
||||
bind:value={service.wordpress.mysqlDatabase}
|
||||
placeholder="{$t('forms.eg')}: wordpress_db"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
|
||||
<input
|
||||
name="mysqlRootUser"
|
||||
id="mysqlRootUser"
|
||||
placeholder="MySQL {$t('forms.root_user')}"
|
||||
value={service.wordpress.mysqlRootUser}
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
|
||||
<CopyPasswordField
|
||||
id="mysqlRootUserPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="mysqlRootUserPassword"
|
||||
value={service.wordpress.mysqlRootUserPassword}
|
||||
/>
|
||||
</div>
|
||||
{#if !service.wordpress.ownMysql}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
|
||||
<input
|
||||
name="mysqlRootUser"
|
||||
id="mysqlRootUser"
|
||||
placeholder="MySQL {$t('forms.root_user')}"
|
||||
value={service.wordpress.mysqlRootUser}
|
||||
readonly={isRunning || !service.wordpress.ownMysq}
|
||||
disabled={isRunning || !service.wordpress.ownMysq}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
|
||||
<CopyPasswordField
|
||||
id="mysqlRootUserPassword"
|
||||
isPasswordField
|
||||
readonly={isRunning || !service.wordpress.ownMysq}
|
||||
disabled={isRunning || !service.wordpress.ownMysq}
|
||||
name="mysqlRootUserPassword"
|
||||
value={service.wordpress.mysqlRootUserPassword}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlUser">{$t('forms.user')}</label>
|
||||
<input name="mysqlUser" id="mysqlUser" value={service.wordpress.mysqlUser} disabled readonly />
|
||||
<input
|
||||
name="mysqlUser"
|
||||
id="mysqlUser"
|
||||
value={service.wordpress.mysqlUser}
|
||||
readonly={isRunning || !service.wordpress.ownMysql}
|
||||
disabled={isRunning || !service.wordpress.ownMysql}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="mysqlPassword">{$t('forms.password')}</label>
|
||||
<CopyPasswordField
|
||||
id="mysqlPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
readonly={isRunning || !service.wordpress.ownMysql}
|
||||
disabled={isRunning || !service.wordpress.ownMysql}
|
||||
name="mysqlPassword"
|
||||
value={service.wordpress.mysqlPassword}
|
||||
/>
|
||||
|
185
src/routes/services/[id]/wordpress/ftp.json.ts
Normal file
185
src/routes/services/[id]/wordpress/ftp.json.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import { dev } from '$app/env';
|
||||
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
|
||||
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import cuid from 'cuid';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body, teamId } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
const { ftpEnabled } = await event.request.json();
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
let ftpUser = cuid();
|
||||
let ftpPassword = generatePassword();
|
||||
|
||||
const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
||||
try {
|
||||
const data = await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpEnabled },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
const {
|
||||
service: { destinationDockerId, destinationDocker },
|
||||
ftpPublicPort: oldPublicPort,
|
||||
ftpUser: user,
|
||||
ftpPassword: savedPassword,
|
||||
ftpHostKey,
|
||||
ftpHostKeyPrivate
|
||||
} = data;
|
||||
if (user) ftpUser = user;
|
||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||
|
||||
const { stdout: password } = await asyncExecShell(
|
||||
`echo ${ftpPassword} | openssl passwd -1 -stdin`
|
||||
);
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
await fs.stat(hostkeyDir);
|
||||
} catch (error) {
|
||||
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
|
||||
}
|
||||
if (!ftpHostKey) {
|
||||
await asyncExecShell(
|
||||
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
||||
);
|
||||
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKey: encrypt(ftpHostKey) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
|
||||
}
|
||||
if (!ftpHostKeyPrivate) {
|
||||
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
|
||||
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
|
||||
}
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
if (ftpEnabled) {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: {
|
||||
ftpPublicPort: publicPort,
|
||||
ftpUser: user ? undefined : ftpUser,
|
||||
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const isRunning = await checkContainer(engine, `${id}-ftp`);
|
||||
if (isRunning) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
//
|
||||
}
|
||||
const volumes = [
|
||||
`${id}-wordpress-data:/home/${ftpUser}`,
|
||||
`${
|
||||
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
|
||||
`${
|
||||
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
|
||||
`${
|
||||
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
||||
];
|
||||
|
||||
const compose: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[`${id}-ftp`]: {
|
||||
image: `atmoz/sftp:alpine`,
|
||||
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
|
||||
extra_hosts: ['host.docker.internal:host-gateway'],
|
||||
container_name: `${id}-ftp`,
|
||||
volumes,
|
||||
networks: [network],
|
||||
depends_on: [],
|
||||
restart: 'always'
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[`${id}-wordpress-data`]: {
|
||||
external: true,
|
||||
name: `${id}-wordpress-data`
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(
|
||||
`${hostkeyDir}/${id}.sh`,
|
||||
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key`
|
||||
);
|
||||
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
|
||||
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
||||
);
|
||||
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
|
||||
} else {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpPublicPort: null }
|
||||
});
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
|
||||
}
|
||||
}
|
||||
if (ftpEnabled) {
|
||||
return {
|
||||
status: 201,
|
||||
body: {
|
||||
publicPort,
|
||||
ftpUser,
|
||||
ftpPassword
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: 200,
|
||||
body: {}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
} finally {
|
||||
await asyncExecShell(
|
||||
`rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
||||
);
|
||||
}
|
||||
};
|
@ -12,13 +12,24 @@ export const post: RequestHandler = async (event) => {
|
||||
name,
|
||||
fqdn,
|
||||
exposePort,
|
||||
wordpress: { extraConfig, mysqlDatabase }
|
||||
wordpress: { extraConfig, mysqlDatabase, mysqlHost, mysqlPort }
|
||||
} = await event.request.json();
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
if (mysqlPort) mysqlPort = Number(mysqlPort);
|
||||
|
||||
try {
|
||||
await db.updateWordpress({ id, fqdn, name, extraConfig, mysqlDatabase, exposePort });
|
||||
await db.updateWordpress({
|
||||
id,
|
||||
fqdn,
|
||||
name,
|
||||
extraConfig,
|
||||
mysqlDatabase,
|
||||
exposePort,
|
||||
mysqlHost,
|
||||
mysqlPort
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
|
@ -16,170 +16,17 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
const { ftpEnabled } = await event.request.json();
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
let ftpUser = cuid();
|
||||
let ftpPassword = generatePassword();
|
||||
|
||||
const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
||||
const { ownMysql } = await event.request.json();
|
||||
try {
|
||||
const data = await db.prisma.wordpress.update({
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpEnabled },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
data: { ownMysql }
|
||||
});
|
||||
const {
|
||||
service: { destinationDockerId, destinationDocker },
|
||||
ftpPublicPort: oldPublicPort,
|
||||
ftpUser: user,
|
||||
ftpPassword: savedPassword,
|
||||
ftpHostKey,
|
||||
ftpHostKeyPrivate
|
||||
} = data;
|
||||
if (user) ftpUser = user;
|
||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||
|
||||
const { stdout: password } = await asyncExecShell(
|
||||
`echo ${ftpPassword} | openssl passwd -1 -stdin`
|
||||
);
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
await fs.stat(hostkeyDir);
|
||||
} catch (error) {
|
||||
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
|
||||
}
|
||||
if (!ftpHostKey) {
|
||||
await asyncExecShell(
|
||||
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
||||
);
|
||||
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKey: encrypt(ftpHostKey) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
|
||||
}
|
||||
if (!ftpHostKeyPrivate) {
|
||||
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
|
||||
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
||||
});
|
||||
} else {
|
||||
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
|
||||
}
|
||||
const { network, engine } = destinationDocker;
|
||||
const host = getEngine(engine);
|
||||
if (ftpEnabled) {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: {
|
||||
ftpPublicPort: publicPort,
|
||||
ftpUser: user ? undefined : ftpUser,
|
||||
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const isRunning = await checkContainer(engine, `${id}-ftp`);
|
||||
if (isRunning) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
//
|
||||
}
|
||||
const volumes = [
|
||||
`${id}-wordpress-data:/home/${ftpUser}`,
|
||||
`${
|
||||
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
|
||||
`${
|
||||
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
|
||||
`${
|
||||
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
||||
];
|
||||
|
||||
const compose: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[`${id}-ftp`]: {
|
||||
image: `atmoz/sftp:alpine`,
|
||||
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
|
||||
extra_hosts: ['host.docker.internal:host-gateway'],
|
||||
container_name: `${id}-ftp`,
|
||||
volumes,
|
||||
networks: [network],
|
||||
depends_on: [],
|
||||
restart: 'always'
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[`${id}-wordpress-data`]: {
|
||||
external: true,
|
||||
name: `${id}-wordpress-data`
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(
|
||||
`${hostkeyDir}/${id}.sh`,
|
||||
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key`
|
||||
);
|
||||
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
|
||||
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
||||
);
|
||||
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
|
||||
} else {
|
||||
await db.prisma.wordpress.update({
|
||||
where: { serviceId: id },
|
||||
data: { ftpPublicPort: null }
|
||||
});
|
||||
try {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
||||
);
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
|
||||
}
|
||||
}
|
||||
if (ftpEnabled) {
|
||||
return {
|
||||
status: 201,
|
||||
body: {
|
||||
publicPort,
|
||||
ftpUser,
|
||||
ftpPassword
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: 200,
|
||||
body: {}
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
} finally {
|
||||
await asyncExecShell(
|
||||
`rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -26,11 +26,14 @@ export const post: RequestHandler = async (event) => {
|
||||
exposePort,
|
||||
wordpress: {
|
||||
mysqlDatabase,
|
||||
mysqlHost,
|
||||
mysqlPort,
|
||||
mysqlUser,
|
||||
mysqlPassword,
|
||||
extraConfig,
|
||||
mysqlRootUser,
|
||||
mysqlRootUserPassword
|
||||
mysqlRootUserPassword,
|
||||
ownMysql
|
||||
}
|
||||
} = service;
|
||||
|
||||
@ -45,7 +48,7 @@ export const post: RequestHandler = async (event) => {
|
||||
image: `${image}:${version}`,
|
||||
volume: `${id}-wordpress-data:/var/www/html`,
|
||||
environmentVariables: {
|
||||
WORDPRESS_DB_HOST: `${id}-mysql`,
|
||||
WORDPRESS_DB_HOST: ownMysql ? `${mysqlHost}:${mysqlPort}` : `${id}-mysql`,
|
||||
WORDPRESS_DB_USER: mysqlUser,
|
||||
WORDPRESS_DB_PASSWORD: mysqlPassword,
|
||||
WORDPRESS_DB_NAME: mysqlDatabase,
|
||||
@ -69,7 +72,7 @@ export const post: RequestHandler = async (event) => {
|
||||
config.wordpress.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
const composeFile: ComposeFile = {
|
||||
let composeFile: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
@ -80,7 +83,6 @@ export const post: RequestHandler = async (event) => {
|
||||
networks: [network],
|
||||
restart: 'always',
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
depends_on: [`${id}-mysql`],
|
||||
labels: makeLabelForServices('wordpress'),
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
@ -90,22 +92,6 @@ export const post: RequestHandler = async (event) => {
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
},
|
||||
[`${id}-mysql`]: {
|
||||
container_name: `${id}-mysql`,
|
||||
image: config.mysql.image,
|
||||
volumes: [config.mysql.volume],
|
||||
environment: config.mysql.environmentVariables,
|
||||
networks: [network],
|
||||
restart: 'always',
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
@ -116,12 +102,32 @@ export const post: RequestHandler = async (event) => {
|
||||
volumes: {
|
||||
[config.wordpress.volume.split(':')[0]]: {
|
||||
name: config.wordpress.volume.split(':')[0]
|
||||
},
|
||||
[config.mysql.volume.split(':')[0]]: {
|
||||
name: config.mysql.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!ownMysql) {
|
||||
composeFile.services[id].depends_on = [`${id}-mysql`];
|
||||
composeFile.services[`${id}-mysql`] = {
|
||||
container_name: `${id}-mysql`,
|
||||
image: config.mysql.image,
|
||||
volumes: [config.mysql.volume],
|
||||
environment: config.mysql.environmentVariables,
|
||||
networks: [network],
|
||||
restart: 'always',
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
composeFile.volumes[config.mysql.volume.split(':')[0]] = {
|
||||
name: config.mysql.volume.split(':')[0]
|
||||
};
|
||||
}
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
|
@ -55,7 +55,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-wrap justify-center">
|
||||
<div class="flex justify-center">
|
||||
{#if !services || ownServices.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
|
||||
|
@ -68,10 +68,48 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
|
||||
<div class="tracking-tight">{$t('application.git_source')}</div>
|
||||
<span class="arrow-right-applications px-1 text-orange-500">></span>
|
||||
<span class="pr-2">{source.name}</span>
|
||||
<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">{source.name}</span>
|
||||
</div>
|
||||
{#if source?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128" class="w-8">
|
||||
<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 source?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128" class="w-8">
|
||||
<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}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center">
|
||||
|
@ -62,7 +62,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col flex-wrap justify-center">
|
||||
<div class="flex justify-center">
|
||||
{#if !sources || ownSources.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
|
||||
@ -74,11 +74,48 @@
|
||||
{#each ownSources as source}
|
||||
<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
|
||||
<div
|
||||
class="box-selection group hover:bg-orange-600"
|
||||
class="box-selection group relative hover:bg-orange-600"
|
||||
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
|
||||
class:border-0={source.gitlabApp && !source.gitlabAppId}
|
||||
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
|
||||
>
|
||||
<div class="absolute top-0 left-0 -m-5 h-10 w-10">
|
||||
{#if source?.type === 'gitlab'}
|
||||
<svg viewBox="0 0 128 128">
|
||||
<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 source?.type === 'github'}
|
||||
<svg viewBox="0 0 128 128">
|
||||
<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}
|
||||
</div>
|
||||
<div class="truncate text-center text-xl font-bold">{source.name}</div>
|
||||
{#if $session.teamId === '0' && otherSources.length > 0}
|
||||
<div class="truncate text-center">{source.teams[0].name}</div>
|
||||
|
@ -356,7 +356,7 @@ a {
|
||||
}
|
||||
|
||||
.box-selection {
|
||||
@apply min-w-[16rem] max-w-[24rem] justify-center rounded border-transparent bg-coolgray-200 p-6 shadow-lg transition duration-150 hover:scale-105 hover:border-transparent hover:bg-coolgray-400;
|
||||
@apply min-w-[16rem] max-w-[24rem] justify-center rounded border-transparent bg-coolgray-200 p-6 hover:border-transparent hover:bg-coolgray-400;
|
||||
}
|
||||
|
||||
._toastBar {
|
||||
|
Loading…
x
Reference in New Issue
Block a user