diff --git a/TODO.md b/TODO.md index e72027afc..bb0730f10 100644 --- a/TODO.md +++ b/TODO.md @@ -1,2 +1,6 @@ -# Low -- Create previews model in Coolify DB \ No newline at end of file +- Destination/new is not working? +- Check GitLab new repo +- Services new empty secret errors +- Services new empty storage errors +- New SSH key modal redesign +- Change license every package.json \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index 32a012685..006c03a99 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,5 +1,5 @@ { - "name": "coolify-api", + "name": "api", "description": "Coolify's Fastify API", "license": "AGPL-3.0", "scripts": { diff --git a/apps/ui/package.json b/apps/ui/package.json index 10b2d5219..123632795 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -1,5 +1,5 @@ { - "name": "coolify-ui", + "name": "ui", "description": "Coolify's SvelteKit UI", "license": "AGPL-3.0", "scripts": { @@ -41,6 +41,7 @@ "@sveltejs/adapter-static": "1.0.0-next.37", "@zerodevx/svelte-toast": "0.7.2", "cuid": "2.1.8", + "daisyui": "2.22.0", "js-cookie": "3.0.1", "p-limit": "4.0.0", "svelte-select": "4.4.7", diff --git a/apps/ui/src/app.d.ts b/apps/ui/src/app.d.ts index ed5f4e197..ebc7c2668 100644 --- a/apps/ui/src/app.d.ts +++ b/apps/ui/src/app.d.ts @@ -21,4 +21,5 @@ declare namespace App { } declare const GITPOD_WORKSPACE_URL: string + \ No newline at end of file diff --git a/apps/ui/src/app.html b/apps/ui/src/app.html index af82b3042..16d104bc7 100644 --- a/apps/ui/src/app.html +++ b/apps/ui/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index e0396f19c..f45cf6a52 100644 --- a/apps/ui/src/lib/common.ts +++ b/apps/ui/src/lib/common.ts @@ -1,4 +1,4 @@ -import { toast } from '@zerodevx/svelte-toast'; +import { addToast } from '$lib/store'; export const supportedServiceTypesAndVersions = [ { @@ -167,12 +167,20 @@ export const asyncSleep = (delay: number) => export function errorNotification(error: any): void { if (error.message) { if (error.message === 'Cannot read properties of undefined (reading \'postMessage\')') { - toast.push('Currently there is background process in progress. Please try again later.'); - return; + return addToast({ + message: 'Currently there is background process in progress. Please try again later.', + type: 'error', + }); } - toast.push(error.message); + addToast({ + message: error.message, + type: 'error', + }); } else { - toast.push('Ooops, something is not okay, are you okay?'); + addToast({ + message: 'Ooops, something is not okay, are you okay?', + type: 'error', + }); } console.error(JSON.stringify(error)); } diff --git a/apps/ui/src/lib/components/CopyPasswordField.svelte b/apps/ui/src/lib/components/CopyPasswordField.svelte index 103065601..12033c7ba 100644 --- a/apps/ui/src/lib/components/CopyPasswordField.svelte +++ b/apps/ui/src/lib/components/CopyPasswordField.svelte @@ -1,5 +1,6 @@ diff --git a/apps/ui/src/lib/components/Toast.svelte b/apps/ui/src/lib/components/Toast.svelte new file mode 100644 index 000000000..cfda1d4d5 --- /dev/null +++ b/apps/ui/src/lib/components/Toast.svelte @@ -0,0 +1,52 @@ + + +
+ {#if type === 'success'} + + {:else if type === 'error'} + + {:else if type === 'info'} + + {/if} + +
diff --git a/apps/ui/src/lib/components/Toasts.svelte b/apps/ui/src/lib/components/Toasts.svelte new file mode 100644 index 000000000..2fdec7190 --- /dev/null +++ b/apps/ui/src/lib/components/Toasts.svelte @@ -0,0 +1,22 @@ + + +{#if $toasts} +
+ +
+{/if} + + diff --git a/apps/ui/src/lib/components/UpdateAvailable.svelte b/apps/ui/src/lib/components/UpdateAvailable.svelte index 4171a1c5f..9076f7da7 100644 --- a/apps/ui/src/lib/components/UpdateAvailable.svelte +++ b/apps/ui/src/lib/components/UpdateAvailable.svelte @@ -1,7 +1,7 @@ @@ -132,7 +144,7 @@
{usage?.disk.usedGb}GB
- diff --git a/apps/ui/src/lib/locales/en.json b/apps/ui/src/lib/locales/en.json index df84a976b..b913c0658 100644 --- a/apps/ui/src/lib/locales/en.json +++ b/apps/ui/src/lib/locales/en.json @@ -88,7 +88,7 @@ "removing": "Removing...", "remove_domain": "Remove domain", "public_port_range": "Public Port Range", - "public_port_range_explainer": "Ports used to expose databases/services/internal services.
Add them to your firewall (if applicable).

You can specify a range of ports, eg: 9000-9100", + "public_port_range_explainer": "Ports used to expose databases/services/internal services.
Add them to your firewall (if applicable).

You can specify a range of ports, eg: 9000-9100", "no_actions_available": "No actions available", "admin_api_key": "Admin API key" }, @@ -144,8 +144,8 @@ }, "preview": { "need_during_buildtime": "Need during buildtime?", - "setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first.
Useful for creating staging environments.", - "values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating staging environments.", + "setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first.
Useful for creating staging environments.", + "values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating staging environments.", "redeploy": "Redeploy", "no_previews_available": "No previews available" }, @@ -194,14 +194,14 @@ "application": "Application", "url_fqdn": "URL (FQDN)", "domain_fqdn": "Domain (FQDN)", - "https_explainer": "If you specify https, the application will be accessible only over https. SSL certificate will be generated for you.
If you specify www, the application will be redirected (302) from non-www and vice versa.

To modify the domain, you must first stop the application.

You must set your DNS to point to the server IP in advance.", + "https_explainer": "If you specify https, the application will be accessible only over https. SSL certificate will be generated for you.
If you specify www, the application will be redirected (302) from non-www and vice versa.

To modify the domain, you must first stop the application.

You must set your DNS to point to the server IP in advance.", "ssl_www_and_non_www": "Generate SSL for www and non-www?", - "ssl_explainer": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.

Useful if you expect to have visitors on both.", + "ssl_explainer": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.

Useful if you expect to have visitors on both.", "install_command": "Install Command", "build_command": "Build Command", "start_command": "Start Command", - "directory_to_use_explainer": "Directory to use as the base for all commands.
Could be useful with monorepos.", - "publish_directory_explainer": "Directory containing all the assets for deployment.
For example: dist,_site or public.", + "directory_to_use_explainer": "Directory to use as the base for all commands.
Could be useful with monorepos.", + "publish_directory_explainer": "Directory containing all the assets for deployment.
For example: dist,_site or public.", "features": "Features", "enable_automatic_deployment": "Enable Automatic Deployment", "enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.", @@ -306,7 +306,7 @@ "change_language": "Change Language", "permission_denied": "You do not have permission to do this. \\nAsk an admin to modify your permissions.", "domain_removed": "Domain removed", - "ssl_explainer": "If you specify https, Coolify will be accessible only over https. SSL certificate will be generated for you.
If you specify www, Coolify will be redirected (302) from non-www and vice versa.

WARNING: If you change an already set domain, it will brake webhooks and other integrations! You need to manually update them.", + "ssl_explainer": "If you specify https, Coolify will be accessible only over https. SSL certificate will be generated for you.
If you specify www, Coolify will be redirected (302) from non-www and vice versa.

WARNING: If you change an already set domain, it will brake webhooks and other integrations! You need to manually update them.", "must_remove_domain_before_changing": "Must remove the domain before you can change this setting.", "registration_allowed": "Registration allowed?", "registration_allowed_explainer": "Allow further registrations to the application.
It's turned off after the first registration.", @@ -314,7 +314,7 @@ "credential_stat_explainer": "Credentials for stats page.", "auto_update_enabled": "Auto update enabled?", "auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running.", - "generate_www_non_www_ssl": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.", + "generate_www_non_www_ssl": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.", "is_dns_check_enabled": "DNS check enabled?", "is_dns_check_enabled_explainer": "You can disable DNS check before creating SSL certificates.

Turning it off is useful when Coolify is behind a reverse proxy or tunnel." }, diff --git a/apps/ui/src/lib/locales/fr.json b/apps/ui/src/lib/locales/fr.json index 242ac6fce..418f72526 100644 --- a/apps/ui/src/lib/locales/fr.json +++ b/apps/ui/src/lib/locales/fr.json @@ -50,7 +50,7 @@ "delete_application": "Supprimer l'application", "deployment_queued": "Déploiement en file d'attente.", "destination": "Destination", - "directory_to_use_explainer": "Répertoire à utiliser comme base pour toutes les commandes.
Pourrait être utile avec monorepos.", + "directory_to_use_explainer": "Répertoire à utiliser comme base pour toutes les commandes.
Pourrait être utile avec monorepos.", "dns_not_set_error": "DNS non défini ou propagé pour {{domain}}.

Veuillez vérifier vos paramètres DNS.", "dns_not_set_partial_error": "DNS non défini", "domain_already_in_use": "Le domaine {{domain}} est déjà utilisé.", @@ -65,7 +65,7 @@ "features": "Caractéristiques", "git_repository": "Dépôt Git", "git_source": "Source Git", - "https_explainer": "Si vous spécifiez https, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.
Si vous spécifiez www, l'application sera redirigée (302) à partir de non-www et vice versa \n.

Pour modifier le domaine, vous devez d'abord arrêter l'application.

Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.", + "https_explainer": "Si vous spécifiez https, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.
Si vous spécifiez www, l'application sera redirigée (302) à partir de non-www et vice versa \n.

Pour modifier le domaine, vous devez d'abord arrêter l'application.

Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.", "install_command": "Commande d'installation", "logs": "Journaux des applications", "no_applications_found": "Aucune application trouvée", @@ -78,11 +78,11 @@ "need_during_buildtime": "Besoin pendant la build ?", "no_previews_available": "Aucun aperçu disponible", "redeploy": "Redéployer", - "setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n
Utile pour créer des environnements de mise en scène.", - "values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements de mise en scène." + "setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n
Utile pour créer des environnements de mise en scène.", + "values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements de mise en scène." }, "previews": "Aperçus", - "publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n
Par exemple : dist,_site ou public.", + "publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n
Par exemple : dist,_site ou public.", "rebuild_application": "Re-build l'application", "secret": "secrets", "secrets": { @@ -91,7 +91,7 @@ "use_isbuildsecret": "Utiliser isBuildSecret" }, "settings_saved": "Paramètres sauvegardés.", - "ssl_explainer": "Il générera des certificats pour www et non-www. \n
Vous devez avoir les deux entrées DNS définies à l'avance.

Utile si vous prévoyez d'avoir des visiteurs sur les deux.", + "ssl_explainer": "Il générera des certificats pour www et non-www. \n
Vous devez avoir les deux entrées DNS définies à l'avance.

Utile si vous prévoyez d'avoir des visiteurs sur les deux.", "ssl_www_and_non_www": "Générer SSL pour www et non-www ?", "start_command": "Démarrer la commande", "stop_application": "Arrêter l'application", @@ -181,7 +181,7 @@ "path": "Chemin", "port": "Port", "public_port_range": "Gamme de ports publics", - "public_port_range_explainer": "Ports utilisés pour exposer les bases de données/services/services internes.
Ajoutez-les à votre pare-feu (le cas échéant).

Vous pouvez spécifier une plage de ports, par exemple : 9000-9100", + "public_port_range_explainer": "Ports utilisés pour exposer les bases de données/services/services internes.
Ajoutez-les à votre pare-feu (le cas échéant).

Vous pouvez spécifier une plage de ports, par exemple : 9000-9100", "publish_directory": "Publier le répertoire", "remove": "Retirer", "remove_domain": "Supprimer le domaine", @@ -266,7 +266,7 @@ "permission_denied": "Vous n'avez pas la permission de faire cela. \n\\nDemandez à un administrateur de modifier vos autorisations.", "registration_allowed": "Inscription autorisée ?", "registration_allowed_explainer": "Autoriser d'autres inscriptions à l'application. \n
Il est désactivé après la première inscription.", - "ssl_explainer": "Si vous spécifiez https, Coolify sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.
Si vous spécifiez www, Coolify sera redirigé (302) à partir de non-www et vice versa." + "ssl_explainer": "Si vous spécifiez https, Coolify sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.
Si vous spécifiez www, Coolify sera redirigé (302) à partir de non-www et vice versa." }, "source": { "application_id": "ID d'application", diff --git a/apps/ui/src/lib/store.ts b/apps/ui/src/lib/store.ts index 627885385..9b77a7e8b 100644 --- a/apps/ui/src/lib/store.ts +++ b/apps/ui/src/lib/store.ts @@ -17,6 +17,11 @@ interface AppSession { gitlab: string | null, } } +interface AddToast { + type?: "info" | "success" | "error", + message: string, + timeout?: number | undefined + } export const loginEmail: Writable = writable() export const appSession: Writable = writable({ ipv4: null, @@ -74,4 +79,30 @@ export const setLocation = (resource: any) => { } else { location.set(resource.fqdn) } +} + +export const toasts: any = writable([]) + +export const dismissToast = (id: number) => { + toasts.update((all: any) => all.filter((t: any) => t.id !== id)) +} + +export const addToast = (toast: AddToast) => { + // Create a unique ID so we can easily find/remove it + // if it is dismissible/has a timeout. + const id = Math.floor(Math.random() * 10000) + + // Setup some sensible defaults for a toast. + const defaults = { + id, + type: 'info', + timeout: 2000, + } + + // Push the toast to the top of the list of toasts + const t = { ...defaults, ...toast } + toasts.update((all: any) => [t, ...all]) + + // If toast is dismissible, dismiss it after "timeout" amount of time. + if (t.timeout) setTimeout(() => dismissToast(id), t.timeout) } \ No newline at end of file diff --git a/apps/ui/src/routes/__layout.svelte b/apps/ui/src/routes/__layout.svelte index 4df7f920b..b785d10c3 100644 --- a/apps/ui/src/routes/__layout.svelte +++ b/apps/ui/src/routes/__layout.svelte @@ -86,6 +86,7 @@ import PageLoader from '$lib/components/PageLoader.svelte'; import { errorNotification } from '$lib/common'; import { appSession } from '$lib/store'; + import Toasts from '$lib/components/Toasts.svelte'; if (userId) $appSession.userId = userId; if (teamId) $appSession.teamId = teamId; @@ -110,7 +111,8 @@ {/if} - + + {#if $navigating}
diff --git a/apps/ui/src/routes/applications/[id]/_Secret.svelte b/apps/ui/src/routes/applications/[id]/_Secret.svelte index 52a2e2e77..fda9e33e6 100644 --- a/apps/ui/src/routes/applications/[id]/_Secret.svelte +++ b/apps/ui/src/routes/applications/[id]/_Secret.svelte @@ -12,8 +12,8 @@ import { del } from '$lib/api'; import { errorNotification } from '$lib/common'; import CopyPasswordField from '$lib/components/CopyPasswordField.svelte'; + import { addToast } from '$lib/store'; import { t } from '$lib/translations'; - import { toast } from '@zerodevx/svelte-toast'; import { createEventDispatcher } from 'svelte'; import { saveSecret } from './utils'; @@ -28,7 +28,10 @@ value = ''; isBuildSecret = false; } - toast.push('Secret removed.'); + addToast({ + message: 'Secret removed.', + type: 'success' + }); } catch (error) { return errorNotification(error); } @@ -36,6 +39,7 @@ async function createSecret(isNew: any) { try { + if (!name || !value) return await saveSecret({ isNew, name, @@ -51,9 +55,12 @@ isBuildSecret = false; } dispatch('refresh'); - toast.push('Secret saved.'); + addToast({ + message: 'Secret removed.', + type: 'success' + }); } catch (error) { - console.log(error) + console.log(error); return errorNotification(error); } } @@ -71,7 +78,10 @@ isNewSecret, applicationId: id }); - toast.push('Secret saved.'); + addToast({ + message: 'Secret removed.', + type: 'success' + }); } } } @@ -100,8 +110,7 @@ /> -
-
+ {#if isNewSecret}
-
{:else}
- +
{#if !isPRMRSecret}
-
diff --git a/apps/ui/src/routes/applications/[id]/_Storage.svelte b/apps/ui/src/routes/applications/[id]/_Storage.svelte index 803b1ed9d..76d20c2c3 100644 --- a/apps/ui/src/routes/applications/[id]/_Storage.svelte +++ b/apps/ui/src/routes/applications/[id]/_Storage.svelte @@ -11,6 +11,7 @@ import { toast } from '@zerodevx/svelte-toast'; import { t } from '$lib/translations'; import { errorNotification } from '$lib/common'; + import { addToast } from '$lib/store'; const { id } = $page.params; const dispatch = createEventDispatcher(); @@ -30,8 +31,17 @@ storage.path = null; storage.id = null; } - if (newStorage) toast.push($t('application.storage.storage_saved')); - else toast.push($t('application.storage.storage_updated')); + if (newStorage) { + addToast({ + message: $t('application.storage.storage_saved'), + type: 'success' + }); + } else { + addToast({ + message: $t('application.storage.storage_updated'), + type: 'success' + }); + } } catch (error) { return errorNotification(error); } @@ -40,7 +50,10 @@ try { await del(`/applications/${id}/storages`, { path: storage.path }); dispatch('refresh'); - toast.push($t('application.storage.storage_deleted')); + addToast({ + message: $t('application.storage.storage_deleted'), + type: 'success' + }); } catch (error) { return errorNotification(error); } @@ -58,17 +71,19 @@ {#if isNew}
-
{:else}
- +
-
diff --git a/apps/ui/src/routes/applications/[id]/__layout.svelte b/apps/ui/src/routes/applications/[id]/__layout.svelte index 8d1f52015..bb9a81ba5 100644 --- a/apps/ui/src/routes/applications/[id]/__layout.svelte +++ b/apps/ui/src/routes/applications/[id]/__layout.svelte @@ -60,7 +60,7 @@ import { toast } from '@zerodevx/svelte-toast'; import { onDestroy, onMount } from 'svelte'; import { t } from '$lib/translations'; - import { appSession, disabledButton, status, location, setLocation } from '$lib/store'; + import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store'; import { errorNotification, handlerNotFoundLoad } from '$lib/common'; import Loading from '$lib/components/Loading.svelte'; @@ -80,7 +80,10 @@ async function handleDeploySubmit() { try { const { buildId } = await post(`/applications/${id}/deploy`, { ...application }); - toast.push($t('application.deployment_queued')); + addToast({ + message: $t('application.deployment_queued'), + type: 'success' + }); if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) { return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`); } else { diff --git a/apps/ui/src/routes/applications/[id]/configuration/_GithubRepositories.svelte b/apps/ui/src/routes/applications/[id]/configuration/_GithubRepositories.svelte index cff6769d0..95a307ef7 100644 --- a/apps/ui/src/routes/applications/[id]/configuration/_GithubRepositories.svelte +++ b/apps/ui/src/routes/applications/[id]/configuration/_GithubRepositories.svelte @@ -190,11 +190,11 @@
{$t('forms.save')}
diff --git a/apps/ui/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte b/apps/ui/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte index a91ebcc27..02fa103bc 100644 --- a/apps/ui/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte +++ b/apps/ui/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte @@ -413,11 +413,10 @@
{#if tryAgain} diff --git a/apps/ui/src/routes/applications/[id]/index.svelte b/apps/ui/src/routes/applications/[id]/index.svelte index 05cc368c6..229a45d8e 100644 --- a/apps/ui/src/routes/applications/[id]/index.svelte +++ b/apps/ui/src/routes/applications/[id]/index.svelte @@ -35,13 +35,14 @@ import { get, post } from '$lib/api'; import cuid from 'cuid'; import { browser } from '$app/env'; - import { appSession, disabledButton, setLocation, status } from '$lib/store'; + import { addToast, appSession, disabledButton, setLocation, status } from '$lib/store'; import { t } from '$lib/translations'; import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common'; import Setting from './_Setting.svelte'; const { id } = $page.params; - $: isDisabled = !$appSession.isAdmin || $status.application.isRunning || $status.application.initialLoading; + $: isDisabled = + !$appSession.isAdmin || $status.application.isRunning || $status.application.initialLoading; let domainEl: HTMLInputElement; @@ -138,7 +139,10 @@ branch: application.branch, projectId: application.projectId }); - return toast.push($t('application.settings_saved')); + return addToast({ + message: $t('application.settings_saved'), + type: 'success' + }); } catch (error) { if (name === 'debug') { debug = !debug; @@ -169,10 +173,13 @@ exposePort: application.exposePort }); await post(`/applications/${id}`, { ...application }); - setLocation(application) + setLocation(application); $disabledButton = false; forceSave = false; - return toast.push('Configurations saved.'); + addToast({ + message: 'Configuration saved.', + type: 'success', + }); } catch (error) { console.log(error); //@ts-ignore @@ -215,7 +222,10 @@ async function isDNSValid(domain: any, isWWW: any) { try { await get(`/applications/${id}/check?domain=${domain}`); - toast.push('DNS configuration is valid.'); + addToast({ + message: 'DNS configuration is valid.', + type: 'success' + }); isWWW ? (isWWWDomainOK = true) : (isNonWWWDomainOK = true); return true; } catch (error) { @@ -312,29 +322,21 @@
{$t('general')}
{#if $appSession.isAdmin} {$t('forms.save')} {/if}
- +