feat: custom dns servers

This commit is contained in:
Andras Bacsai
2022-08-17 10:43:57 +02:00
parent 1bd08cb2db
commit 727133e28b
13 changed files with 320 additions and 248 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "DNSServers" TEXT;

View File

@@ -20,6 +20,7 @@ model Setting {
proxyHash String? proxyHash String?
isAutoUpdateEnabled Boolean @default(false) isAutoUpdateEnabled Boolean @default(false)
isDNSCheckEnabled Boolean @default(true) isDNSCheckEnabled Boolean @default(true)
DNSServers String?
isTraefikUsed Boolean @default(true) isTraefikUsed Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt

View File

@@ -307,6 +307,10 @@ export async function checkDoubleBranch(branch: string, projectId: number): Prom
} }
export async function isDNSValid(hostname: any, domain: string): Promise<any> { export async function isDNSValid(hostname: any, domain: string): Promise<any> {
const { isIP } = await import('is-ip'); const { isIP } = await import('is-ip');
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let resolves = []; let resolves = [];
try { try {
if (isIP(hostname)) { if (isIP(hostname)) {
@@ -320,7 +324,6 @@ export async function isDNSValid(hostname: any, domain: string): Promise<any> {
try { try {
let ipDomainFound = false; let ipDomainFound = false;
dns.setServers(['1.1.1.1', '8.8.8.8']);
const dnsResolve = await dns.resolve4(domain); const dnsResolve = await dns.resolve4(domain);
if (dnsResolve.length > 0) { if (dnsResolve.length > 0) {
for (const ip of dnsResolve) { for (const ip of dnsResolve) {
@@ -412,7 +415,12 @@ export async function checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }): P
const { isIP } = await import('is-ip'); const { isIP } = await import('is-ip');
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`; const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
dns.setServers(['1.1.1.1', '8.8.8.8']);
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let resolves = []; let resolves = [];
try { try {
if (isIP(hostname)) { if (isIP(hostname)) {
@@ -1553,7 +1561,7 @@ export async function configureServiceType({
}); });
} else if (type === 'appwrite') { } else if (type === 'appwrite') {
const opensslKeyV1 = encrypt(generatePassword()); const opensslKeyV1 = encrypt(generatePassword());
const executorSecret = encrypt(generatePassword()); const executorSecret = encrypt(generatePassword());
const redisPassword = encrypt(generatePassword()); const redisPassword = encrypt(generatePassword());
const mariadbHost = `${id}-mariadb` const mariadbHost = `${id}-mariadb`
const mariadbUser = cuid(); const mariadbUser = cuid();

View File

@@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
const { teamId } = request.user const { teamId } = request.user
const applications = await prisma.application.findMany({ const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true, destinationDocker: true } include: { teams: true, destinationDocker: true, settings: true }
}); });
const settings = await prisma.setting.findFirst() const settings = await prisma.setting.findFirst()
return { return {

View File

@@ -4,7 +4,7 @@ import axios from 'axios';
import compare from 'compare-versions'; import compare from 'compare-versions';
import cuid from 'cuid'; import cuid from 'cuid';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, prisma, uniqueName, version } from '../../../lib/common'; import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, listSettings, prisma, uniqueName, version } from '../../../lib/common';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import type { Login, Update } from '.'; import type { Login, Update } from '.';
@@ -97,7 +97,8 @@ export async function showDashboard(request: FastifyRequest) {
const userId = request.user.userId; const userId = request.user.userId;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const applications = await prisma.application.findMany({ const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true }
}); });
const databases = await prisma.database.findMany({ const databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
@@ -105,10 +106,12 @@ export async function showDashboard(request: FastifyRequest) {
const services = await prisma.service.findMany({ const services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
}); });
const settings = await listSettings();
return { return {
applications, applications,
databases, databases,
services, services,
settings,
}; };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })

View File

@@ -33,12 +33,13 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
minPort, minPort,
maxPort, maxPort,
isAutoUpdateEnabled, isAutoUpdateEnabled,
isDNSCheckEnabled isDNSCheckEnabled,
DNSServers
} = request.body } = request.body
const { id } = await listSettings(); const { id } = await listSettings();
await prisma.setting.update({ await prisma.setting.update({
where: { id }, where: { id },
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled } data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers }
}); });
if (fqdn) { if (fqdn) {
await prisma.setting.update({ where: { id }, data: { fqdn } }); await prisma.setting.update({ where: { id }, data: { fqdn } });
@@ -54,6 +55,10 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) { export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
try { try {
const { fqdn } = request.body const { fqdn } = request.body
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let ip; let ip;
try { try {
ip = await dns.resolve(fqdn); ip = await dns.resolve(fqdn);

View File

@@ -8,7 +8,8 @@ export interface SaveSettings {
minPort: number, minPort: number,
maxPort: number, maxPort: number,
isAutoUpdateEnabled: boolean, isAutoUpdateEnabled: boolean,
isDNSCheckEnabled: boolean isDNSCheckEnabled: boolean,
DNSServers: string
} }
} }
export interface DeleteDomain { export interface DeleteDomain {

View File

@@ -1,3 +1,4 @@
import { dev } from '$app/env';
import cuid from 'cuid'; import cuid from 'cuid';
import { writable, readable, type Writable } from 'svelte/store'; import { writable, readable, type Writable } from 'svelte/store';
@@ -73,7 +74,7 @@ export const location: Writable<null | string> = writable(null)
export const setLocation = (resource: any, settings?: any) => { export const setLocation = (resource: any, settings?: any) => {
if (resource.settings.isBot && resource.exposePort) { if (resource.settings.isBot && resource.exposePort) {
disabledButton.set(false); disabledButton.set(false);
return location.set(`http://${settings.ipv4}:${resource.exposePort}`) return location.set(`http://${dev ? 'localhost' : settings.ipv4}:${resource.exposePort}`)
} }
if (GITPOD_WORKSPACE_URL && resource.exposePort) { if (GITPOD_WORKSPACE_URL && resource.exposePort) {
const { href } = new URL(GITPOD_WORKSPACE_URL); const { href } = new URL(GITPOD_WORKSPACE_URL);

View File

@@ -141,8 +141,8 @@
if ( if (
application.gitSourceId && application.gitSourceId &&
application.destinationDockerId && application.destinationDockerId &&
application.fqdn && (application.fqdn ||
!application.settings.isBot application.settings.isBot)
) { ) {
await getStatus(); await getStatus();
statusInterval = setInterval(async () => { statusInterval = setInterval(async () => {
@@ -409,37 +409,39 @@
</svg> </svg>
</button></a </button></a
> >
<a {#if !application.settings.isBot}
href={!$disabledButton ? `/applications/${id}/previews` : null} <a
sveltekit:prefetch href={!$disabledButton ? `/applications/${id}/previews` : null}
class="hover:text-orange-500 rounded" sveltekit:prefetch
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`} class="hover:text-orange-500 rounded"
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`} class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
> class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Previews"
> >
<svg <button
xmlns="http://www.w3.org/2000/svg" disabled={$disabledButton}
class="w-6 h-6" class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
viewBox="0 0 24 24" data-tip="Previews"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <svg
<circle cx="7" cy="18" r="2" /> xmlns="http://www.w3.org/2000/svg"
<circle cx="7" cy="6" r="2" /> class="w-6 h-6"
<circle cx="17" cy="12" r="2" /> viewBox="0 0 24 24"
<line x1="7" y1="8" x2="7" y2="16" /> stroke-width="1.5"
<path d="M7 8a4 4 0 0 0 4 4h4" /> stroke="currentColor"
</svg></button fill="none"
></a stroke-linecap="round"
> stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="7" cy="18" r="2" />
<circle cx="7" cy="6" r="2" />
<circle cx="17" cy="12" r="2" />
<line x1="7" y1="8" x2="7" y2="16" />
<path d="M7 8a4 4 0 0 0 4 4h4" />
</svg></button
></a
>
{/if}
<div class="border border-coolgray-500 h-8" /> <div class="border border-coolgray-500 h-8" />
<a <a
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null} href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}

View File

@@ -779,15 +779,17 @@
description={$t('application.enable_auto_deploy_webhooks')} description={$t('application.enable_auto_deploy_webhooks')}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> {#if !application.settings.isBot}
<Setting <div class="grid grid-cols-2 items-center">
isCenter={false} <Setting
bind:setting={previews} isCenter={false}
on:click={() => changeSettings('previews')} bind:setting={previews}
title={$t('application.enable_mr_pr_previews')} on:click={() => changeSettings('previews')}
description={$t('application.enable_preview_deploy_mr_pr_requests')} title={$t('application.enable_mr_pr_previews')}
/> description={$t('application.enable_preview_deploy_mr_pr_requests')}
</div> />
</div>
{/if}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
isCenter={false} isCenter={false}

View File

@@ -98,7 +98,7 @@
<div class="truncate text-center font-bold text-red-500 group-hover:text-white"> <div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Destination Missing Destination Missing
</div> </div>
{:else if !application.fqdn} {:else if !application.fqdn && !application.settings.isBot}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white"> <div class="truncate text-center font-bold text-red-500 group-hover:text-white">
URL Missing URL Missing
</div> </div>

View File

@@ -20,35 +20,38 @@
</script> </script>
<script lang="ts"> <script lang="ts">
export let applications: any;
export let databases: any;
export let services: any;
export let settings: any;
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import Usage from '$lib/components/Usage.svelte'; import Usage from '$lib/components/Usage.svelte';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification, asyncSleep } from '$lib/common'; import { errorNotification, asyncSleep } from '$lib/common';
import { addToast, appSession } from '$lib/store'; import { addToast, appSession } from '$lib/store';
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte'; import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte'; import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte'; import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
import { dev } from '$app/env';
let loading = { let loading = {
cleanup: false cleanup: false
}; };
export let applications: any; let numberOfGetStatus = 0;
export let databases: any;
export let services: any; function getRndInteger(min: number, max: number) {
let numberOfGetStatus = 0; return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRndInteger(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) ) + min;
}
async function getStatus(resources: any) { async function getStatus(resources: any) {
while (numberOfGetStatus > 1){ while (numberOfGetStatus > 1) {
await asyncSleep(getRndInteger(100,200)); await asyncSleep(getRndInteger(100, 200));
} }
try { try {
numberOfGetStatus++; numberOfGetStatus++;
const { id, buildPack, dualCerts } = resources; const { id, buildPack, dualCerts } = resources;
let isRunning = false; let isRunning = false;
if (buildPack) { if (buildPack) {
@@ -69,8 +72,8 @@
} catch (error) { } catch (error) {
return 'Error'; return 'Error';
} finally { } finally {
numberOfGetStatus--; numberOfGetStatus--;
} }
} }
async function manuallyCleanupStorage() { async function manuallyCleanupStorage() {
try { try {
@@ -98,40 +101,90 @@
<div class="mx-auto px-10"> <div class="mx-auto px-10">
<div class="flex flex-col justify-center xl:flex-row"> <div class="flex flex-col justify-center xl:flex-row">
{#if applications.length > 0} {#if applications.length > 0}
<div> <div>
<div class="title">Resources</div> <div class="title">Resources</div>
<div class="flex items-start justify-center p-8"> <div class="flex items-start justify-center p-8">
<table class="rounded-none text-base"> <table class="rounded-none text-base">
<tbody> <tbody>
{#each applications as application} {#each applications as application}
<tr> <tr>
<td class="space-x-2 items-center tracking-tight font-bold"> <td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(application)} {#await getStatus(application)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" /> <div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status} {:then status}
{#if status === 'Running'} {#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" /> <div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else} {:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" /> <div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{application.name}</div>
</td>
<td class="px-10 inline-flex">
<ApplicationsIcons {application} isAbsolute={false} />
</td>
<td class="px-10">
<div
class="badge badge-outline text-xs border-applications rounded text-white"
>
Application
{#if application.settings.isBot}
| BOT
{/if}
</div></td
>
<td class="flex justify-end">
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><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}
{#if application.settings.isBot && application.exposePort}
<a
href={`http://${dev ? 'localhost' : settings.ipv4}:${
application.exposePort
}`}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><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} {/if}
{/await}
<div class="inline-flex">{application.name}</div>
</td>
<td class="px-10 inline-flex">
<ApplicationsIcons {application} isAbsolute={false} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-applications rounded text-white">
Application
</div></td
>
<td class="flex justify-end">
{#if application.fqdn}
<a <a
href={application.fqdn} href={`/applications/${application.id}`}
target="_blank"
class="icons bg-transparent text-sm inline-flex" class="icons bg-transparent text-sm inline-flex"
><svg >
<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -142,72 +195,72 @@
stroke-linejoin="round" stroke-linejoin="round"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <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" /> <rect x="4" y="8" width="4" height="4" />
<line x1="10" y1="14" x2="20" y2="4" /> <line x1="6" y1="4" x2="6" y2="8" />
<polyline points="15 4 20 4 20 9" /> <line x1="6" y1="12" x2="6" y2="20" />
</svg></a <rect x="10" y="14" width="4" height="4" />
> <line x1="12" y1="4" x2="12" y2="14" />
{/if} <line x1="12" y1="18" x2="12" y2="20" />
<a <rect x="16" y="5" width="4" height="4" />
href={`/applications/${application.id}`} <line x1="18" y1="4" x2="18" y2="5" />
class="icons bg-transparent text-sm inline-flex" <line x1="18" y1="9" x2="18" y2="20" />
> </svg>
<svg </a>
xmlns="http://www.w3.org/2000/svg" </td>
class="h-6 w-6" </tr>
viewBox="0 0 24 24" {/each}
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
{#each services as service} {#each services as service}
<tr> <tr>
<td class="space-x-2 items-center tracking-tight font-bold"> <td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(service)} {#await getStatus(service)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" /> <div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status} {:then status}
{#if status === 'Running'} {#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" /> <div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else} {:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" /> <div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{service.name}</div>
</td>
<td class="px-10 inline-flex">
<ServiceIcons type={service.type} isAbsolute={false} />
</td>
<td class="px-10"
><div class="badge badge-outline text-xs border-services rounded text-white">
Service
</div>
</td>
<td class="flex justify-end">
{#if service.fqdn}
<a
href={service.fqdn}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><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} {/if}
{/await}
<div class="inline-flex">{service.name}</div>
</td>
<td class="px-10 inline-flex">
<ServiceIcons type={service.type} isAbsolute={false} />
</td>
<td class="px-10"
><div class="badge badge-outline text-xs border-services rounded text-white">
Service
</div>
</td>
<td class="flex justify-end">
{#if service.fqdn}
<a <a
href={service.fqdn} href={`/services/${service.id}`}
target="_blank"
class="icons bg-transparent text-sm inline-flex" class="icons bg-transparent text-sm inline-flex"
><svg >
<svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6" class="h-6 w-6"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -218,97 +271,76 @@
stroke-linejoin="round" stroke-linejoin="round"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <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" /> <rect x="4" y="8" width="4" height="4" />
<line x1="10" y1="14" x2="20" y2="4" /> <line x1="6" y1="4" x2="6" y2="8" />
<polyline points="15 4 20 4 20 9" /> <line x1="6" y1="12" x2="6" y2="20" />
</svg></a <rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
{#each databases as database}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(database)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{database.name}</div>
</td>
<td class="px-10 inline-flex">
<DatabaseIcons type={database.type} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-databases rounded text-white">
Database
</div>
</td>
<td class="flex justify-end">
<a
href={`/databases/${database.id}`}
class="icons bg-transparent text-sm inline-flex ml-11"
> >
{/if} <svg
<a xmlns="http://www.w3.org/2000/svg"
href={`/services/${service.id}`} class="h-6 w-6"
class="icons bg-transparent text-sm inline-flex" viewBox="0 0 24 24"
> stroke-width="1.5"
<svg stroke="currentColor"
xmlns="http://www.w3.org/2000/svg" fill="none"
class="h-6 w-6" stroke-linecap="round"
viewBox="0 0 24 24" stroke-linejoin="round"
stroke-width="1.5" >
stroke="currentColor" <path stroke="none" d="M0 0h24v24H0z" fill="none" />
fill="none" <rect x="4" y="8" width="4" height="4" />
stroke-linecap="round" <line x1="6" y1="4" x2="6" y2="8" />
stroke-linejoin="round" <line x1="6" y1="12" x2="6" y2="20" />
> <rect x="10" y="14" width="4" height="4" />
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <line x1="12" y1="4" x2="12" y2="14" />
<rect x="4" y="8" width="4" height="4" /> <line x1="12" y1="18" x2="12" y2="20" />
<line x1="6" y1="4" x2="6" y2="8" /> <rect x="16" y="5" width="4" height="4" />
<line x1="6" y1="12" x2="6" y2="20" /> <line x1="18" y1="4" x2="18" y2="5" />
<rect x="10" y="14" width="4" height="4" /> <line x1="18" y1="9" x2="18" y2="20" />
<line x1="12" y1="4" x2="12" y2="14" /> </svg>
<line x1="12" y1="18" x2="12" y2="20" /> </a>
<rect x="16" y="5" width="4" height="4" /> </td>
<line x1="18" y1="4" x2="18" y2="5" /> </tr>
<line x1="18" y1="9" x2="18" y2="20" /> {/each}
</svg> </tbody>
</a> </table>
</td> </div>
</tr>
{/each}
{#each databases as database}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(database)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{database.name}</div>
</td>
<td class="px-10 inline-flex">
<DatabaseIcons type={database.type} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-databases rounded text-white">
Database
</div>
</td>
<td class="flex justify-end">
<a
href={`/databases/${database.id}`}
class="icons bg-transparent text-sm inline-flex ml-11"
>
<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" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
</tbody>
</table>
</div> </div>
</div>
{/if} {/if}
{#if $appSession.teamId === '0'} {#if $appSession.teamId === '0'}
<Usage /> <Usage />

View File

@@ -31,7 +31,7 @@
let dualCerts = settings.dualCerts; let dualCerts = settings.dualCerts;
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled; let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
let isDNSCheckEnabled = settings.isDNSCheckEnabled; let isDNSCheckEnabled = settings.isDNSCheckEnabled;
let DNSServers = settings.DNSServers;
let minPort = settings.minPort; let minPort = settings.minPort;
let maxPort = settings.maxPort; let maxPort = settings.maxPort;
@@ -105,6 +105,10 @@
settings.minPort = minPort; settings.minPort = minPort;
settings.maxPort = maxPort; settings.maxPort = maxPort;
} }
if (DNSServers !== settings.DNSServers) {
await post(`/settings`, { DNSServers });
settings.DNSServers = DNSServers;
}
forceSave = false; forceSave = false;
return addToast({ return addToast({
message: 'Configuration saved.', message: 'Configuration saved.',
@@ -275,6 +279,17 @@
on:click={() => changeSettings('isDNSCheckEnabled')} on:click={() => changeSettings('isDNSCheckEnabled')}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100">
Custom DNS servers
</div>
<Explainer text="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used." />
</div>
<div class="mx-auto flex-row items-center justify-center space-y-2">
<input placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
</div>
</div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
dataTooltip={$t('setting.must_remove_domain_before_changing')} dataTooltip={$t('setting.must_remove_domain_before_changing')}