feat: custom certificate

This commit is contained in:
Andras Bacsai
2022-09-21 15:48:32 +02:00
parent 86ac6461d1
commit 90e639f119
30 changed files with 1000 additions and 367 deletions

View File

@@ -39,7 +39,7 @@ export function getWebhookUrl(type: string) {
async function send({
method,
path,
data = {},
data = null,
headers,
timeout = 120000
}: {
@@ -53,7 +53,7 @@ async function send({
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const opts: any = { method, headers: {}, body: null, signal: controller.signal };
if (Object.keys(data).length > 0) {
if (data && Object.keys(data).length > 0) {
const parsedData = data;
for (const [key, value] of Object.entries(data)) {
if (value === '') {
@@ -85,7 +85,9 @@ async function send({
if (dev && !path.startsWith('https://')) {
path = `${getAPIUrl()}${path}`;
}
if (method === 'POST' && data && !opts.body) {
opts.body = data;
}
const response = await fetch(`${path}`, opts);
clearTimeout(id);
@@ -132,7 +134,7 @@ export function del(
export function post(
path: string,
data: Record<string, unknown>,
data: Record<string, unknown> | FormData,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'POST', path, data, headers });

View File

@@ -4,7 +4,7 @@
export let type = 'info';
function success() {
if (type === 'success') {
return 'bg-gradient-to-r from-purple-500 via-pink-500 to-red-500';
return 'bg-coollabs';
}
}
</script>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { post } from '$lib/api';
let cert: any;
let key: any;
async function submitForm() {
const formData = new FormData();
formData.append('cert', cert[0]);
formData.append('key', key[0]);
await post('/upload', formData);
}
</script>
<form on:submit|preventDefault={submitForm}>
<label for="cert">Certificate</label>
<input id="cert" type="file" required name="cert" bind:files={cert} />
<label for="key">Private Key</label>
<input id="key" type="file" required name="key" bind:files={key} />
<br />
<input type="submit" />
</form>

View File

@@ -226,7 +226,7 @@
<a
id="settings"
sveltekit:prefetch
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
class="icons hover:text-settings"
class:text-settings={$page.url.pathname.startsWith('/settings')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
@@ -393,7 +393,7 @@
<li>
<a
class="no-underline icons hover:text-black hover:bg-settings"
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
class:bg-settings={$page.url.pathname.startsWith('/settings')}
class:text-black={$page.url.pathname.startsWith('/settings')}
>

View File

@@ -218,7 +218,7 @@
id="git"
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank"
class="w-6 h-6"
class="w-6 h-6 lg:w-10 lg:h-10"
>
{#if application.gitSource?.type === 'gitlab'}
<svg viewBox="0 0 128 128" class="icons">

View File

@@ -61,7 +61,7 @@
<div class="pb-2 text-center font-bold">No SSH key found</div>
<div class="flex justify-center">
<a
href="/settings/ssh-keys"
href="/settings/ssh"
sveltekit:prefetch
class="add-icon bg-sky-600 hover:bg-sky-500"
>

View File

@@ -3,19 +3,29 @@
import { appSession } from '$lib/store';
</script>
<div class="flex flex-col pt-4 space-y-6 px-10">
<ul class="menu bg-coolgray-200 rounded lg:w-52">
{#if $appSession.teamId === '0'}
<a
href="/settings/global"
class="sub-menu no-underline w-full"
class:sub-menu-active={$page.routeId === 'settings/global'}
<li
class="hover:bg-coollabs duration-150"
class:bordered={$page.url.pathname === '/settings/coolify'}
class:bg-coolgray-500={$page.url.pathname === '/settings/coolify'}
>
Global Settings
</a>
<a href="/settings/coolify" class="no-underline w-full">Coolify Settings</a>
</li>
{/if}
<a
href="/settings/ssh-keys"
class="sub-menu no-underline w-full"
class:sub-menu-active={$page.routeId === 'settings/ssh-keys'}>SSH Keys</a
<li
class="hover:bg-coollabs duration-150"
class:bordered={$page.url.pathname === '/settings/ssh'}
class:bg-coolgray-500={$page.url.pathname === '/settings/ssh'}
>
</div>
<a href="/settings/ssh" class="no-underline w-full">SSH Keys</a>
</li>
<li
class="hover:bg-coollabs duration-150"
class:bordered={$page.url.pathname === '/settings/certificates'}
class:bg-coolgray-400={$page.url.pathname === '/settings/certificates'}
>
<a href="/settings/certificates" class="no-underline w-full">SSL Certificates</a>
</li>
</ul>

View File

@@ -1,7 +1,8 @@
<script context="module" lang="ts">
import { get } from '$lib/api';
import { page } from '$app/stores';
import type { Load } from '@sveltejs/kit';
import Menu from './_Menu.svelte';
export const load: Load = async () => {
try {
const response = await get(`/settings`);
@@ -19,5 +20,12 @@
};
</script>
<slot />
<div class="flex flex-col lg:flex-row ">
<nav class="header flex flex-col w-full lg:w-52">
<div class="title pb-10">Settings</div>
<Menu />
</nav>
<div class="pt-0 lg:pt-24 px-5 lg:px-0 mx-auto">
<slot />
</div>
</div>

View File

@@ -0,0 +1,144 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
try {
return {
props: {
...stuff
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let certificates: any;
import { del, post } from '$lib/api';
import { errorNotification } from '$lib/common';
let loading = {
save: false
};
let isModalActive = false;
let cert: any = null;
let key: any = null;
async function handleSubmit() {
try {
const formData = new FormData();
formData.append('cert', cert[0]);
formData.append('key', key[0]);
await post('/settings/upload', formData);
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
async function deleteCertificate(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
if (sure) {
try {
if (!id) return;
await del(`/settings/certificate`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
}
</script>
<div class="title font-bold pb-3">SSL Certificates</div>
<div class="w-full lg:w-[50em]">
{#if certificates.length === 0}
<div class="text-sm">No SSL Certificate found</div>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSL Certificate</label
>
{:else}
<div class="mx-auto w-full p-6 bg-coolgray-100 rounded border-coolgray-300 border ">
<table class="table w-full">
<thead>
<tr>
<th>Common Name</th>
<th>CreatedAt</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each certificates as cert}
<tr>
<td>{cert.commonName}</td>
<td>{cert.createdAt}</td>
<td
><button on:click={() => deleteCertificate(cert.id)} class="btn btn-sm bg-error"
>Delete</button
></td
>
</tr>
{/each}
</tbody>
</table>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSL Certificate</label
>
</div>
{/if}
</div>
{#if isModalActive}
<input type="checkbox" id="my-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle ">
<div class="modal-box rounded bg-coolgray-300 max-w-2xl">
<h3 class="font-bold text-lg">Add a new SSL Certificate</h3>
<p class="py-4">
SSL Certificates are used to secure your domain and allow you to use HTTPS. <br /><br />Once
you uploaded your certificate, Coolify will automatically configure it for you in the
background.
</p>
<div class="modal-action">
<form on:submit|preventDefault={handleSubmit} class="w-full">
<div class="flex flex-col justify-center">
<label for="cert">Certificate</label>
<div class="flex-1" />
<input
class="w-full bg-coolgray-100"
id="cert"
type="file"
required
name="cert"
bind:files={cert}
/>
<label for="key" class="pt-10">Private Key</label>
<input
class="w-full bg-coolgray-100"
id="key"
type="file"
required
name="key"
bind:files={key}
/>
</div>
<label for="my-modal">
<button type="submit" class="btn btn-sm bg-settings text-black mt-4">Upload</button
></label
>
<button on:click={() => (isModalActive = false)} type="button" class="btn btn-sm"
>Cancel</button
>
</form>
</div>
</div>
</div>
{/if}

View File

@@ -18,6 +18,7 @@
<script lang="ts">
export let settings: any;
export let certificates: any;
import Setting from '$lib/components/Setting.svelte';
import { del, get, post } from '$lib/api';
import { browser } from '$app/env';
@@ -26,6 +27,7 @@
import { asyncSleep, errorNotification, getDomain } from '$lib/common';
import Menu from './_Menu.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import Upload from '$lib/components/Upload.svelte';
let isAPIDebuggingEnabled = settings.isAPIDebuggingEnabled;
let isRegistrationEnabled = settings.isRegistrationEnabled;
@@ -194,180 +196,187 @@
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
</div>
<div class="mx-auto w-full">
<div class="title font-bold pb-3">Coolify Settings</div>
<div class="mx-auto w-full p-4 bg-coolgray-100 rounded border-coolgray-300 border">
<div class="flex lg:flex-row flex-col">
<Menu />
<form on:submit|preventDefault={handleSubmit}>
<div class="flex flex-col lg:flex-row flex-wrap items-center space-x-3 justify-center lg:justify-start lg:py-0 px-4">
<div class="title font-bold">{$t('index.global_settings')}</div>
<div class="flex lg:flex-row lg:space-x-4 flex-col space-y-2 lg:space-y-0 py-4">
<button
class="btn btn-sm bg-settings text-black"
type="submit"
class:bg-orange-600={forceSave}
class:hover:bg-orange-400={forceSave}
disabled={loading.save}
>{loading.save
? $t('forms.saving')
: forceSave
? $t('forms.confirm_continue')
: $t('forms.save')}</button
>
{#if isFqdnSet}
<button
on:click|preventDefault={removeFqdn}
disabled={loading.remove}
class="btn btn-sm"
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
>
{/if}
<button
on:click={restartCoolify}
class:loading={loading.restart}
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
>
<!-- <Upload />
{#if certificates.length > 0}
{#each certificates as cert}
<div>{cert.commonName}</div>
{/each}
{/if} -->
<form on:submit|preventDefault={handleSubmit}>
<div class="grid grid-flow-row gap-2 lg:px-10 px-2 pr-5">
<div class="grid grid-cols-2 items-center">
<div>
{$t('application.url_fqdn')}
<Explainer position="dropdown-bottom" explanation={$t('setting.ssl_explainer')} />
</div>
</div>
<div class="grid grid-flow-row gap-2 lg:px-10 px-2 pr-5">
<div class="grid grid-cols-2 items-center">
<div>
{$t('application.url_fqdn')}
<Explainer position="dropdown-bottom" explanation={$t('setting.ssl_explainer')} />
</div>
<input
class="w-full"
bind:value={fqdn}
readonly={!$appSession.isAdmin || isFqdnSet}
disabled={!$appSession.isAdmin || isFqdnSet}
on:input={resetView}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="{$t('forms.eg')}: https://coolify.io"
/>
<input
class="w-full"
bind:value={fqdn}
readonly={!$appSession.isAdmin || isFqdnSet}
disabled={!$appSession.isAdmin || isFqdnSet}
on:input={resetView}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="{$t('forms.eg')}: https://coolify.io"
/>
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
<button
class="btn btn-sm bg-success"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-error"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="btn btn-sm bg-success"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-error"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="btn btn-sm bg-success"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-error"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{/if}
</div>
{/if}
</div>
<div class="grid grid-cols-2 items-center">
<div>
{$t('forms.public_port_range')}
<Explainer explanation={$t('forms.public_port_range_explainer')} />
</div>
<div class="flex flex-row items-center space-x-2">
<input
class=" w-full px-2"
type="number"
bind:value={minPort}
min="1024"
max={maxPort}
/>
<p>-</p>
<input
class="w-full px-2"
type="number"
bind:value={maxPort}
min={minPort}
max="65543"
/>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isDNSCheckEnabled"
bind:setting={isDNSCheckEnabled}
title={$t('setting.is_dns_check_enabled')}
description={$t('setting.is_dns_check_enabled_explainer')}
on:click={() => changeSettings('isDNSCheckEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div>
Custom DNS servers <Explainer
explanation="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>
<input class="w-full" placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="dualCerts"
dataTooltip={$t('setting.must_remove_domain_before_changing')}
disabled={isFqdnSet}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('setting.generate_www_non_www_ssl')}
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isRegistrationEnabled"
bind:setting={isRegistrationEnabled}
title={$t('setting.registration_allowed')}
description={$t('setting.registration_allowed_explainer')}
on:click={() => changeSettings('isRegistrationEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isAPIDebuggingEnabled"
bind:setting={isAPIDebuggingEnabled}
title="API Debugging"
description="Enable API debugging. This will log all API requests and responses.<br><br>You need to restart the Coolify for this to take effect."
on:click={() => changeSettings('isAPIDebuggingEnabled')}
/>
</div>
{#if browser && $features.beta}
<div class="grid grid-cols-2 items-center">
<Setting
id="isAutoUpdateEnabled"
bind:setting={isAutoUpdateEnabled}
title={$t('setting.auto_update_enabled')}
description={$t('setting.auto_update_enabled_explainer')}
on:click={() => changeSettings('isAutoUpdateEnabled')}
/>
{/if}
</div>
{/if}
</div>
</form>
<div class="grid grid-cols-2 items-center">
<Setting
id="dualCerts"
dataTooltip={$t('setting.must_remove_domain_before_changing')}
disabled={isFqdnSet}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('setting.generate_www_non_www_ssl')}
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div>
{$t('forms.public_port_range')}
<Explainer explanation={$t('forms.public_port_range_explainer')} />
</div>
<div class="flex flex-row items-center space-x-2">
<input
class=" w-full px-2"
type="number"
bind:value={minPort}
min="1024"
max={maxPort}
/>
<p>-</p>
<input
class="w-full px-2"
type="number"
bind:value={maxPort}
min={minPort}
max="65543"
/>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isDNSCheckEnabled"
bind:setting={isDNSCheckEnabled}
title={$t('setting.is_dns_check_enabled')}
description={$t('setting.is_dns_check_enabled_explainer')}
on:click={() => changeSettings('isDNSCheckEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div>
Custom DNS servers <Explainer
explanation="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>
<input class="w-full" placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isRegistrationEnabled"
bind:setting={isRegistrationEnabled}
title={$t('setting.registration_allowed')}
description={$t('setting.registration_allowed_explainer')}
on:click={() => changeSettings('isRegistrationEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isAPIDebuggingEnabled"
bind:setting={isAPIDebuggingEnabled}
title="API Debugging"
description="Enable API debugging. This will log all API requests and responses.<br><br>You need to restart the Coolify for this to take effect."
on:click={() => changeSettings('isAPIDebuggingEnabled')}
/>
</div>
{#if browser && $features.beta}
<div class="grid grid-cols-2 items-center">
<Setting
id="isAutoUpdateEnabled"
bind:setting={isAutoUpdateEnabled}
title={$t('setting.auto_update_enabled')}
description={$t('setting.auto_update_enabled_explainer')}
on:click={() => changeSettings('isAutoUpdateEnabled')}
/>
</div>
{/if}
</div>
<div
class="flex flex-col lg:flex-row flex-wrap items-center space-x-3 justify-center lg:justify-start lg:py-4 px-4 pb-4 lg:pb-4"
>
<div class="flex lg:flex-row lg:space-x-4 flex-col space-y-2 lg:space-y-0 px-6">
<button
class="btn btn-sm bg-settings text-black"
type="submit"
class:bg-orange-600={forceSave}
class:hover:bg-orange-400={forceSave}
class:loading={loading.save}
disabled={loading.save}
>{loading.save
? $t('forms.saving')
: forceSave
? $t('forms.confirm_continue')
: $t('forms.save')}</button
>
{#if isFqdnSet}
<button
on:click|preventDefault={removeFqdn}
disabled={loading.remove}
class="btn btn-sm"
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
>
{/if}
<button
on:click={restartCoolify}
class:loading={loading.restart}
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
>
</div>
</div>
</form>
</div>
</div>

View File

@@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { appSession } from '$lib/store';
if ($appSession.teamId !== '0') {
goto('/settings/ssh-keys');
goto('/settings/ssh');
}
goto('/settings/global');
goto('/settings/coolify');
</script>

View File

@@ -1,155 +0,0 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
try {
return {
props: {
...stuff
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let sshKeys: any;
import { del, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import Menu from './_Menu.svelte';
let loading = {
save: false
};
let isModalActive = false;
let newSSHKey = {
name: null,
privateKey: null
};
async function handleSubmit() {
try {
await post(`/settings/sshKey`, { ...newSSHKey });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
async function deleteSSHKey(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
if (sure) {
try {
if (!id) return;
await del(`/settings/sshKey`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
</div>
<div class="mx-auto w-full">
<div class="flex lg:flex-row flex-col">
<Menu />
<div class="flex flex-col mt-5">
<div
class="flex flex-col lg:flex-row flex-wrap items-center space-x-3 justify-center lg:justify-start lg:py-0 px-4 pb-4 lg:pb-4"
style="min-width: 83vw"
>
<div class="title font-bold">SSH Keys</div>
<button
on:click={() => (isModalActive = true)}
class="btn btn-sm bg-settings text-black"
disabled={loading.save}>New SSH Key</button
>
</div>
<div class="grid grid-flow-col gap-2 lg:px-10 px-6">
{#if sshKeys.length === 0}
<div class="text-sm ">No SSH keys found</div>
{:else}
{#each sshKeys as key}
<div class="box-selection group relative">
<div class="text-xl font-bold">{key.name}</div>
<div class="py-3 text-stone-600">Added on {key.createdAt}</div>
<button on:click={() => deleteSSHKey(key.id)} class="btn btn-sm bg-error"
>Delete</button
>
</div>
{/each}
{/if}
</div>
</div>
</div>
</div>
{#if isModalActive}
<div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-coolgray-500 bg-opacity-75 transition-opacity" />
<div class="fixed z-10 inset-0 overflow-y-auto text-white">
<div class="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
<form
on:submit|preventDefault={handleSubmit}
class="relative bg-coolblack rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full sm:p-6 border border-coolgray-500"
>
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
<button
on:click={() => (isModalActive = false)}
type="button"
class=" rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<span class="sr-only">Close</span>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium pb-4" id="modal-title">New SSH Key</h3>
<div class="text-xs text-stone-400">Add an SSH key to your Coolify instance.</div>
<div class="mt-2">
<label for="privateKey" class="pb-2">Key</label>
<textarea
id="privateKey"
required
bind:value={newSSHKey.privateKey}
class="w-full"
rows={15}
/>
</div>
<div class="mt-2">
<label for="name" class="pb-2">Name</label>
<input id="name" required bind:value={newSSHKey.name} class="w-full" />
</div>
</div>
</div>
<div class="mt-5 flex space-x-4 justify-end">
<button type="submit" class="btn btn-sm bg-success">Save</button>
<button on:click={() => (isModalActive = false)} type="button" class="btn btn-sm"
>Cancel</button
>
</div>
</form>
</div>
</div>
</div>
{/if}

View File

@@ -0,0 +1,145 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
try {
return {
props: {
...stuff
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let sshKeys: any;
import { del, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import Menu from './_Menu.svelte';
let loading = {
save: false
};
let isModalActive = false;
let newSSHKey = {
name: null,
privateKey: null
};
async function handleSubmit() {
try {
await post(`/settings/sshKey`, { ...newSSHKey });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
async function deleteSSHKey(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
if (sure) {
try {
if (!id) return;
await del(`/settings/sshKey`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
}
</script>
<div class="title font-bold pb-3">SSH Keys</div>
<div class="w-full lg:w-[50em]">
{#if sshKeys.length === 0}
<div class="text-sm">No SSH keys found</div>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSH Key</label
>
{:else}
<div
class="mx-auto w-full p-6 bg-coolgray-100 rounded border-coolgray-300 border "
>
<table class="table w-full">
<thead>
<tr>
<th>Name</th>
<th>CreatedAt</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each sshKeys as key}
<tr>
<td>{key.name}</td>
<td>{key.createdAt}</td>
<td
><button on:click={() => deleteSSHKey(key.id)} class="btn btn-sm bg-error"
>Delete</button
></td
>
</tr>
{/each}
</tbody>
</table>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSH Key</label
>
</div>
{/if}
</div>
{#if isModalActive}
<input type="checkbox" id="my-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle">
<div class="modal-box rounded bg-coolgray-300">
<h3 class="font-bold text-lg">Add a new SSH Key to Coolify</h3>
<p class="py-4">
SSH Keys can be used to authenticate & execute commands on remote servers.
<br /><br />You can generate a new public/private key using the following command:
<br />
<br />
<code class="bg-coolgray-100 p-2 rounded">ssh-keygen -t rsa -b 4096</code>
</p>
<div class="modal-action">
<form on:submit|preventDefault={handleSubmit}>
<label for="name" class="">Name</label>
<input
id="name"
required
bind:value={newSSHKey.name}
class="w-full bg-coolgray-100"
/>
<label for="privateKey" class="pt-4">Private Key</label>
<textarea
id="privateKey"
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
required
bind:value={newSSHKey.privateKey}
class="w-full bg-coolgray-100"
rows={15}
/>
<label for="my-modal">
<button type="submit" class="btn btn-sm bg-settings text-black mt-4">Save</button
></label
>
<button on:click={() => (isModalActive = false)} type="button" class="btn btn-sm"
>Cancel</button
>
</form>
</div>
</div>
</div>
{/if}