Merge branch 'restray_i18n' of https://github.com/restray/coolify into restray-restray_i18n

This commit is contained in:
Andras Bacsai 2022-04-21 09:51:29 +02:00
commit fe3702847a
82 changed files with 1506 additions and 472 deletions

11
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"i18n-ally.localesPaths": ["src/lib/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.extract.ignoredByFiles": {
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
},
"i18n-ally.sourceLanguage": "en",
"i18n-ally.enabledFrameworks": ["svelte"],
"i18n-ally.enabledParsers": ["js", "ts", "json"],
"i18n-ally.extract.autoDetect": true
}

View File

@ -1,14 +1,26 @@
# Welcome
# 👋 Welcome
First of all, thank you for considering contributing to my project! It means a lot 💜.
# Technical skills required
## 🙋 Want to help?
- Node.js / Javascript
- Svelte / SvelteKit
- Prisma.io / SQL
If you begin in GitHub contribution, you can find the [first contribution](https://github.com/firstcontributions/first-contributions) and follow this guide.
# Recommended Pull Request Guideline
Follow the [introduction](#introduction) to get started then start contributing!
This is a little list of what you can do to help the project:
- [🧑‍💻 Develop your own ideas](#developer-contribution)
- [🌐 Translate the project](#translation)
- [📄 Help sorting out the issues](#help-sorting-out-the-issues)
- [🎯 Test Pull Requests](#test-pull-requests)
- [✒️ Help with the documentation](#help-with-the-documentation)
## 👋 Introduction
🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS.
#### Recommended Pull Request Guideline
- Fork the project
- Clone your fork repo to local
@ -26,7 +38,7 @@ # How to start after you set up your local fork?
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
## Setup development environment
#### Setup a local development environment
- Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool.
- Install dependencies with `pnpm install`.
@ -35,13 +47,27 @@ ## Setup development environment
- Seed the database with base entities with `pnpm db:seed`
- You can start coding after starting `pnpm dev`.
## Database migrations
#### How to start after you set up your local fork?
This repository works better with [pnpm](https://pnpm.io) due to the lock file. I recommend you to give it a try and use `pnpm` as well because it is cool and efficient!
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
## 🧑‍💻 Developer contribution
### Technical skills required
- **Languages**: Node.js / Javascript / Typescript
- **Framework JS/TS**: Svelte / SvelteKit
- **Database ORM**: Prisma.io
### Database migrations
During development, if you change the database layout, you need to run `pnpm db:push` to migrate the database and create types for Prisma. You also need to restart the development process.
If the schema is finalized, you need to create a migration file with `pnpm db:migrate <nameOfMigration>` where `nameOfMigration` is given by you. Make it sense. :)
## Tricky parts
### Tricky parts
- BullMQ, the queue system Coolify uses, cannot be hot reloaded. So if you change anything in the files related to it, you need to restart the development process. I'm actively looking for a different queue/scheduler library. I'm open to discussion!
@ -115,3 +141,42 @@ ## Frontend
If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component to [src/routes/services/[id]/\_Services](src/routes/services/[id]/_Services) with an underscore. For example, see other files in that folder.
You also need to add the new inputs to the `index.json.ts` file of the specific service, like for MinIO here: [src/routes/services/[id]/minio/index.json.ts](src/routes/services/[id]/minio/index.json.ts)
## 🌐 Translate the project
The project use [sveltekit-i18n](https://github.com/sveltekit-i18n/lib) to translate the project.
It follows the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to name languages.
### Installation
You must have gone throw all the [intro](#introduction) steps before you can start translating.
It's only an advice, but I recommend you to use:
- Visual Studio Code
- [i18n Ally for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally): ideal to see the progress of the translation.
- [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode): to get the syntax color for the project
### Adding a language
If your language doesn't appear in the [locales folder list](src/lib/locales/), follow the step below:
1. In `src/lib/locales/`, Copy paste `en.json` and rename it with your language (eg: `cz.json`).
2. In the [lang.json](src/lib/lang.json) file, add a line after the first bracket (`{`) with `"ISO of your language": "Language",` (eg: `"cz": "Czech",`).
3. Have fun translating!
### Additionnal pull requests steps
Please add the emoji 🌐 to your pull request title to indicate that it is a translation.
## 📄 Help sorting out the issues
ToDo
## 🎯 Test Pull Requests
ToDo
## ✒️ Help with the documentation
ToDo

View File

@ -56,6 +56,7 @@
"svelte-preprocess": "4.10.6",
"svelte-select": "4.4.7",
"tailwindcss": "3.0.24",
"sveltekit-i18n": "2.1.2",
"ts-node": "10.7.0",
"tslib": "2.3.1",
"typescript": "4.6.3"

View File

@ -49,6 +49,7 @@ specifiers:
svelte-preprocess: 4.10.6
svelte-select: 4.4.7
tailwindcss: 3.0.24
sveltekit-i18n: 2.1.2
tailwindcss-scrollbar: 0.1.0
ts-node: 10.7.0
tslib: 2.3.1
@ -109,6 +110,7 @@ devDependencies:
svelte-select: 4.4.7
tailwindcss: 3.0.24_ts-node@10.7.0
ts-node: 10.7.0_de7c86b0cde507c63a0402da5b982bd3
sveltekit-i18n: 2.1.2_svelte@3.46.4
tslib: 2.3.1
typescript: 4.6.3
@ -422,6 +424,26 @@ packages:
- supports-color
dev: true
/@sveltekit-i18n/base/1.1.1_svelte@3.46.4:
resolution:
{
integrity: sha512-J/sMU0OwS3dCLOuilHMBqu8vZHuuXiNV9vFJx8Nb4/b5BlR/KCZ4bCXI8wZR02GHeCOYKZxWus07CM1scxa/jw==
}
peerDependencies:
svelte: ^3.x
dependencies:
svelte: 3.46.4
optionalDependencies:
'@sveltekit-i18n/parser-default': 1.0.3
dev: true
/@sveltekit-i18n/parser-default/1.0.3:
resolution:
{
integrity: sha512-HheveklTjp3hxpYQhoHfyA6B4bQaUeSV5MQf2usIv/58UF2jY/YqhCAWj9bDBjufbuZc5pSz4BXvdX3WVT+viA==
}
dev: true
/@szmarczak/http-timer/5.0.1:
resolution:
{
@ -4955,6 +4977,19 @@ packages:
engines: { node: '>= 8' }
dev: true
/sveltekit-i18n/2.1.2_svelte@3.46.4:
resolution:
{
integrity: sha512-s5YxcbNd2EWNZaZR1A4Drt8s53E4fpUkN4XIWd3VRpw1pihZVWssqmBW1qkjQ6AB0kiu1Qwule+vt1HkbQOjrg==
}
peerDependencies:
svelte: ^3.x
dependencies:
'@sveltekit-i18n/base': 1.1.1_svelte@3.46.4
'@sveltekit-i18n/parser-default': 1.0.3
svelte: 3.46.4
dev: true
/table/6.7.2:
resolution:
{

1
src/app.d.ts vendored
View File

@ -31,6 +31,7 @@ interface SessionData {
userId?: string | null;
teamId?: string | null;
permission?: string;
lang?: string;
isAdmin?: boolean;
expires?: string | null;
}

View File

@ -6,6 +6,7 @@ import { getUserDetails, sentry } from '$lib/common';
import { version } from '$lib/common';
import cookie from 'cookie';
import { dev } from '$app/env';
import { locales } from '$lib/translations';
const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true';
const whiteLabelDetails = {
@ -20,6 +21,24 @@ export const handle = handleSession(
},
async function ({ event, resolve }) {
let response;
const { url, request } = event;
// Get defined locales
const supportedLocales = locales.get();
let locale;
if (event.locals.cookies['lang']) {
locale = event.locals.cookies['lang'];
} else if (!locale) {
locale = `${`${request.headers.get('accept-language')}`.match(
/[a-zA-Z]+?(?=-|_|,|;)/
)}`.toLowerCase();
}
// Set default locale if user preferred locale does not match
if (!supportedLocales.includes(locale)) locale = 'en';
try {
if (event.locals.cookies) {
if (event.locals.cookies['kit.session']) {
@ -29,7 +48,8 @@ export const handle = handleSession(
teamId,
permission,
isAdmin: permission === 'admin' || permission === 'owner',
expires: event.locals.session.data.expires
expires: event.locals.session.data.expires,
lang: locale
};
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {
@ -39,12 +59,14 @@ export const handle = handleSession(
}
response = await resolve(event, {
ssr: !event.url.pathname.startsWith('/webhooks/success')
ssr: !event.url.pathname.startsWith('/webhooks/success'),
transformPage: ({ html }) => html.replace(/<html.*>/, `<html lang="${locale}">`)
});
} catch (error) {
console.log(error);
response = await resolve(event, {
ssr: !event.url.pathname.startsWith('/webhooks/success')
ssr: !event.url.pathname.startsWith('/webhooks/success'),
transformPage: ({ html }) => html.replace(/<html.*>/, `<html lang="${locale}">`)
});
response.headers.append(
'Set-Cookie',
@ -67,14 +89,34 @@ export const handle = handleSession(
expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT')
})
);
} finally {
return response;
}
response.headers.append(
'Set-Cookie',
cookie.serialize('lang', locale, {
path: '/',
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60
})
);
return response;
}
);
export const getSession: GetSession = function ({ locals }) {
// Get defined locales
const supportedLocales = locales.get();
let locale;
if (locals.cookies) {
locale = supportedLocales.find(
(l) => `${l}`.toLowerCase() === `${locals.cookies['lang']}`.toLowerCase()
);
}
return {
lang: locale,
version,
whiteLabeled,
whiteLabelDetails,

4
src/lib/lang.json Normal file
View File

@ -0,0 +1,4 @@
{
"fr": "Français",
"en": "English"
}

326
src/lib/locales/en.json Normal file
View File

@ -0,0 +1,326 @@
{
"layout": {
"update_done": "Update completed.",
"wait_new_version_startup": "Waiting for the new version to start...",
"new_version": "New version reachable. Reloading...",
"switch_to_a_different_team": "Switch to a different team...",
"update_available": "Update available"
},
"error": {
"you_can_find_your_way_back": "You can find your way back",
"here": "here",
"you_are_lost": "Ooops you are lost! But don't be afraid!"
},
"index": {
"dashboard": "Dashboard",
"applications": "Applications",
"destinations": "Destinations",
"git_sources": "Git Sources",
"databases": "Databases",
"services": "Services",
"teams": "Teams",
"not_implemented_yet": "Not implemented yet",
"database": "Database",
"settings": "Settings",
"global_settings": "Global Settings",
"secret": "Secret",
"team": "Team",
"logout": "Logout"
},
"login": {
"already_logged_in": "Already logged in...",
"authenticating": "Authenticating...",
"login": "Login"
},
"forms": {
"password": "Password",
"email": "Email address",
"passwords_not_match": "Passwords do not match.",
"password_again": "Password again",
"save": "Save",
"saving": "Saving...",
"name": "Name",
"value": "Value",
"action": "Action",
"is_required": "is required.",
"add": "Add",
"set": "Set",
"remove": "Remove",
"path": "Path",
"confirm_continue": "Are you sure to continue?",
"must_be_stopped_to_modify": "Must be stopped to modify.",
"port": "Port",
"default": "default",
"base_directory": "Base Directory",
"publish_directory": "Publish Directory",
"generated_automatically_after_start": "Generated automatically after start",
"roots_password": "Root's Password",
"root_user": "Root User",
"eg": "eg",
"user": "User",
"loading": "Loading...",
"version": "Version",
"host": "Host",
"already_used_for": "<span class=\"text-red-500\">{{type}}</span> already used for",
"configuration": "Configuration",
"engine": "Engine",
"network": "Network",
"ip_address": "IP Address",
"ssh_private_key": "SSH Private Key",
"type": "Type",
"html_url": "HTML URL",
"api_url": "API URL",
"organization": "Organization",
"new_password": "New password",
"super_secure_new_password": "Super secure new password",
"submit": "Submit",
"default_email_address": "Default Email Address",
"default_password": "Default Password",
"username": "Username",
"root_db_user": "Root DB User",
"root_db_password": "Root DB Password",
"api_port": "API Port",
"verifying": "Verifying",
"verify_emails_without_smtp": "Verify emails without SMTP",
"extra_config": "Extra Config",
"select_a_service": "Select a Service",
"select_a_service_version": "Select a Service version",
"removing": "Removing...",
"remove_domain": "Remove domain",
"public_port_range": "Public Port Range",
"public_port_range_explainer": "Ports used to expose databases/services/internal services.<br> Add them to your firewall (if applicable).<br><br>You can specify a range of ports, eg: <span class='text-yellow-500 font-bold'>9000-9100</span>",
"no_actions_available": "No actions available",
"admin_api_key": "Admin API key"
},
"register": {
"register": "Register",
"registering": "Registering...",
"first_user": "You are registering the first user. It will be the administrator of your Coolify instance."
},
"reset": {
"reset_password": "Reset",
"invalid_secret_key": "Invalid secret key.",
"secret_key": "Secret Key",
"find_path_secret_key": "You can find it in ~/coolify/.env (COOLIFY_SECRET_KEY)"
},
"application": {
"configuration": {
"buildpack": {
"choose_this_one": "Choose this one..."
},
"branch_already_in_use": "This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?",
"no_repositories_configured": "No repositories configured for your Git Application.",
"configure_it_now": "Configure it now",
"loading_repositories": "Loading repositories ...",
"select_a_repository": "Please select a repository",
"loading_branches": "Loading branches ...",
"select_a_repository_first": "Please select a repository first",
"select_a_branch": "Please select a branch",
"loading_groups": "Loading groups...",
"select_a_group": "Please select a group",
"loading_projects": "Loading projects...",
"select_a_project": "Please select a project",
"no_projects_found": "No projects found",
"no_branches_found": "No branches found",
"configure_build_pack": "Configure Build Pack",
"scanning_repository_suggest_build_pack": "Scanning repository to suggest a build pack for you...",
"found_lock_file": "Found lock file for <span class=\"font-bold text-orange-500 pl-1\">{{packageManager}}</span>. Using it for predefined commands commands.",
"configure_destination": "Configure Destination",
"no_configurable_destination": "No configurable Destination found",
"select_a_repository_project": "Select a Repository / Project",
"select_a_git_source": "Select a Git Source",
"no_configurable_git": "No configurable Git Source found",
"configuration_missing": "Configuration missing"
},
"build": {
"queued_waiting_exec": "Queued and waiting for execution.",
"build_logs_of": "Build logs of",
"running": "Running",
"queued": "Queued",
"finished_in": "Finished in",
"load_more": "Load More",
"no_logs": "No logs found",
"waiting_logs": "Waiting for the logs..."
},
"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. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
"redeploy": "Redeploy",
"no_previews_available": "No previews available"
},
"secrets": {
"secret_saved": "Secret saved.",
"use_isbuildsecret": "Use isBuildSecret",
"secrets_for": "Secrets for"
},
"storage": {
"path_is_required": "Path is required.",
"storage_saved": "Storage saved.",
"storage_updated": "Storage updated.",
"storage_deleted": "Storage deleted.",
"persistent_storage_explainer": "You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache."
},
"deployment_queued": "Deployment queued.",
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
"stop_application": "Stop application",
"permission_denied_stop_application": "You do not have permission to stop the application.",
"rebuild_application": "Rebuild application",
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
"build_and_start_application": "Build and start application",
"permission_denied_build_and_start_application": "You do not have permission to Build and start application.",
"configurations": "Configurations",
"secret": "Secrets",
"persistent_storage": "Persistent Storage",
"previews": "Previews",
"logs": "Application Logs",
"build_logs": "Build Logs",
"delete_application": "Delete application",
"permission_denied_delete_application": "You do not have permission to delete this application",
"domain_already_in_use": "Domain {{domain}} is already used.",
"dns_not_set_error": "DNS not set or propogated for {{domain}}.<br><br>Please check your DNS settings.",
"settings_saved": "Settings saved.",
"dns_not_set_partial_error": "DNS not set",
"git_source": "Git Source",
"git_repository": "Git Repository",
"build_pack": "Build Pack",
"destination": "Destination",
"application": "Application",
"url_fqdn": "URL (FQDN)",
"domain_fqdn": "Domain (FQDN)",
"https_explainer": "If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>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.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>.",
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>.",
"features": "Features",
"enable_automatic_deployment": "Enable Automatic Deployment",
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",
"enable_mr_pr_previews": "Enable MR/PR Previews",
"enable_preview_deploy_mr_pr_requests": "Enable preview deployments from pull or merge requests.",
"debug_logs": "Debug Logs",
"enable_debug_log_during_build": "Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs.",
"cant_activate_auto_deploy_without_repo": "Cannot activate automatic deployments until only one application is defined for this repository / branch.",
"no_applications_found": "No applications found",
"secret__batch_dot_env": "Paste .env file",
"batch_secrets": "Batch add secrets"
},
"general": "General",
"database": {
"default_database": "Default Database",
"generated_automatically_after_set_to_public": "Generated automatically after set to public",
"connection_string": "Connection String",
"set_public": "Set it public",
"warning_database_public": "Your database will be reachable over the internet. <br>Take security seriously in this case!",
"change_append_only_mode": "Change append only mode",
"warning_append_only": "Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>",
"select_database_type": "Select a Database type",
"select_database_version": "Select a Database version",
"confirm_stop": "Are you sure you would like to stop {{name}}?",
"stop_database": "Stop database",
"permission_denied_stop_database": "You do not have permission to stop the database.",
"start_database": "Start database",
"permission_denied_start_database": "You do not have permission to start the database.",
"delete_database": "Delete Database",
"permission_denied_delete_database": "You do not have permission to delete a Database",
"no_databases_found": "No databases found"
},
"destination": {
"delete_destination": "Delete Destination",
"permission_denied_delete_destination": "You do not have permission to delete this destination",
"add_to_coolify": "Add to Coolify",
"coolify_proxy_stopped": "Coolify Proxy stopped!",
"coolify_proxy_started": "Coolify Proxy started!",
"confirm_restart_proxy": "Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.",
"coolify_proxy_restarting": "Coolify Proxy restarting...",
"restarting_please_wait": "Restarting... please wait...",
"force_restart_proxy": "Force restart proxy",
"use_coolify_proxy": "Use Coolify Proxy?",
"no_destination_found": "No destination found",
"new_error_network_already_exists": "Network {{network}} already configured for another team!",
"new": {
"saving_and_configuring_proxy": "Saving and configuring proxy...",
"install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy.",
"add_new_destination": "Add New Destination",
"predefined_destinations": "Predefined destinations"
}
},
"sources": {
"local_docker": "Local Docker",
"remote_docker": "Remote Docker",
"organization_explainer": "Fill it if you would like to use an organization's as your Git Source. Otherwise your user will be used."
},
"source": {
"new": {
"git_source": "Add New Git Source",
"official_providers": "Official providers"
},
"no_git_sources_found": "No git sources found",
"delete_git_source": "Delete Git Source",
"permission_denied": "You do not have permission to delete a Git Source",
"create_new_app": "Create new {{name}} App",
"change_app_settings": "Change {{name}} App Settings",
"install_repositories": "Install Repositories",
"application_id": "Application ID",
"group_name": "Group Name",
"oauth_id": "OAuth ID",
"oauth_id_explainer": "The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application.",
"register_oauth_gitlab": "Register new OAuth application on GitLab",
"gitlab": {
"self_hosted": "Instance-wide application (self-hosted)",
"user_owned": "User owned application",
"group_owned": "Group owned application",
"gitlab_application_type": "GitLab Application Type",
"already_configured": "GitLab App is already configured."
},
"github": {
"redirecting": "Redirecting to Github..."
}
},
"services": {
"all_email_verified": "All email verified. You can login now.",
"generate_www_non_www_ssl": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-pink-600'>both DNS entries</span> set in advance.<br><br>Service needs to be restarted."
},
"service": {
"stop_service": "Stop Service",
"permission_denied_stop_service": "You do not have permission to stop the service.",
"start_service": "Start Service",
"permission_denied_start_service": "You do not have permission to start the service.",
"delete_service": "Delete Service",
"permission_denied_delete_service": "You do not have permission to delete a service.",
"no_service": "No services found"
},
"setting": {
"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 <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa.",
"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. <br>It's turned off after the first registration.",
"coolify_proxy_settings": "Coolify Proxy Settings",
"credential_stat_explainer": "Credentials for <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">stats</a> page."
},
"team": {
"pending_invitations": "Pending invitations",
"accept": "Accept",
"delete": "Delete",
"member": "member(s)",
"root": "(root)",
"invited_with_permissions": "Invited to <span class=\"font-bold text-pink-600\">{{teamName}}</span> with <span class=\"font-bold text-rose-600\">{{permission}}</span> permission.",
"members": "Members",
"root_team_explainer": "This is the <span class='text-red-500 font-bold'>root</span> team. That means members of this group can manage instance wide settings and have all the priviliges in Coolify (imagine like root user on Linux).",
"permission": "Permission",
"you": "(You)",
"promote_to": "Promote to {{grade}}",
"revoke_invitation": "Revoke invitation",
"pending_invitation": "Pending invitation",
"invite_new_member": "Invite new member",
"send_invitation": "Send invitation",
"invite_only_register_explainer": "You can only invite registered users at the moment - will be extended soon.",
"admin": "Admin",
"read": "Read"
}
}

322
src/lib/locales/fr.json Normal file
View File

@ -0,0 +1,322 @@
{
"application": {
"application": "Application",
"build": {
"build_logs_of": "Créer des journaux de",
"finished_in": "Fini en",
"load_more": "Charger plus",
"no_logs": "Aucun journal trouvé",
"queued": "En file d'attente",
"queued_waiting_exec": "En file d'attente et en attente d'exécution.",
"running": "Fonctionnement",
"waiting_logs": "En attente des logs..."
},
"build_and_start_application": "Build et démarrer l'application",
"build_command": "Commande Build",
"build_logs": "Créer des journaux",
"build_pack": "Pack de Build",
"cant_activate_auto_deploy_without_repo": "Impossible d'activer les déploiements automatiques tant qu'une seule application n'est pas définie pour ce dépôt/branche.",
"configuration": {
"branch_already_in_use": "Cette branche est déjà utilisée par une autre application. \nLes webhooks ne fonctionneront pas dans ce cas pour les deux applications. \nÊtes-vous sûr de vouloir l'utiliser ?",
"buildpack": {
"choose_this_one": "Choisir celui-ci..."
},
"configuration_missing": "Configuration manquante",
"configure_build_pack": "Configurer le pack de build",
"configure_destination": "Configurer la destination",
"configure_it_now": "Configurez-le maintenant",
"found_lock_file": "Fichier .lock trouvé pour <span class=\"font-bold text-orange-500 pl-1\">{{packageManager}}</span>. \nL'utiliser pour les commandes prédéfinies.",
"loading_branches": "Chargement des branches...",
"loading_groups": "Chargement des groupes...",
"loading_projects": "Chargement des projets...",
"loading_repositories": "Chargement des dépôts Git...",
"no_branches_found": "Aucune branche trouvée",
"no_configurable_destination": "Aucune destination configurable trouvée",
"no_configurable_git": "Aucune source Git configurable trouvée",
"no_projects_found": "Aucun projet trouvé",
"no_repositories_configured": "Aucun dépôt Git configuré pour votre application.",
"scanning_repository_suggest_build_pack": "Analyse du dépôt pour vous suggérer un pack de Build...",
"select_a_branch": "Veuillez sélectionner une branche",
"select_a_git_source": "Sélectionnez une source Git",
"select_a_group": "Veuillez sélectionner un groupe",
"select_a_project": "Veuillez sélectionner un projet",
"select_a_repository": "Veuillez sélectionner un dépôt",
"select_a_repository_first": "Veuillez d'abord sélectionner un dépôt",
"select_a_repository_project": "Sélectionnez un dépôt / projet"
},
"configurations": "Configurations",
"confirm_to_delete": "Voulez-vous vraiment supprimer '{{name}}'?",
"debug_logs": "Journaux de débogage",
"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.<br>Pourrait être utile avec <span class='text-green-500 font-bold'>monorepos</span>.",
"dns_not_set_error": "DNS non défini ou propagé pour {{domain}}.<br><br>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é.",
"domain_fqdn": "Domaine (FQDN)",
"url_fqdn": "URL (FQDN)",
"enable_auto_deploy_webhooks": "Activez le déploiement automatique via des webhooks.",
"enable_automatic_deployment": "Activer le déploiement automatique",
"enable_debug_log_during_build": "Activez les journaux de débogage pendant la phase de build.<br><span class='text-red-500 font-bold'>Les informations sensibles</span> peuvent être visibles et enregistrées dans les journaux.",
"enable_mr_pr_previews": "Activer les aperçus MR/PR",
"enable_preview_deploy_mr_pr_requests": "Activez les déploiements de prévisualisation à partir de demandes d'extraction ou de fusion.",
"features": "Caractéristiques",
"git_repository": "Dépôt Git",
"git_source": "Source Git",
"https_explainer": "Si vous spécifiez <span class='text-green-500 font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-green-500 font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
"install_command": "Commande d'installation",
"logs": "Journaux des applications",
"no_applications_found": "Aucune application trouvée",
"permission_denied_build_and_start_application": "Vous n'êtes pas autorisé à créer et à démarrer l'application.",
"permission_denied_delete_application": "Vous n'êtes pas autorisé à supprimer cette application",
"permission_denied_rebuild_application": "Vous n'êtes pas autorisé à re-build l'application.",
"permission_denied_stop_application": "Vous n'êtes pas autorisé à arrêter l'application.",
"persistent_storage": "Stockage persistant",
"preview": {
"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<br>Utile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>.",
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>."
},
"previews": "Aperçus",
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> ou <span \nclass='text-green-500 font-bold'>public</span>.",
"rebuild_application": "Re-build l'application",
"secret": "secrets",
"secrets": {
"secret_saved": "Secret enregistré.",
"secrets_for": "secrets pour",
"use_isbuildsecret": "Utiliser isBuildSecret"
},
"settings_saved": "Paramètres sauvegardés.",
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-green-500'>les deux entrées DNS</span> définies à l'avance.<br><br>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",
"storage": {
"path_is_required": "Le chemin est requis.",
"persistent_storage_explainer": "Vous pouvez spécifier n'importe quel dossier que vous souhaitez conserver dans les déploiements. \n<br>Ceci est utile pour stocker des données telles qu'une base de données (SQLite) ou un cache.",
"storage_deleted": "Stockage supprimé.",
"storage_saved": "Stockage enregistré.",
"storage_updated": "Stockage mis à jour."
}
},
"database": {
"change_append_only_mode": "Changer le mode d'ajout uniquement",
"confirm_stop": "Êtes-vous sûr de vouloir arrêter {{name}} ?",
"connection_string": "Connexion string",
"default_database": "Base de données par défaut",
"delete_database": "Supprimer la base de données",
"generated_automatically_after_set_to_public": "Généré automatiquement après avoir été défini sur public",
"no_databases_found": "Aucune base de données trouvée",
"permission_denied_delete_database": "Vous n'êtes pas autorisé à supprimer une base de données",
"permission_denied_start_database": "Vous n'êtes pas autorisé à démarrer la base de données.",
"permission_denied_stop_database": "Vous n'êtes pas autorisé à arrêter la base de données.",
"select_database_type": "Sélectionnez un type de base de données",
"select_database_version": "Sélectionnez une version de la base de données",
"set_public": "Rendre public",
"start_database": "Démarrer la base de données",
"stop_database": "Arrêter la base de données",
"warning_append_only": "Utile si vous souhaitez restaurer des données Redis à partir d'une sauvegarde.<br><span class='font-bold text-white'>Le redémarrage de la base de données est nécessaire.</span>",
"warning_database_public": "Votre base de données sera accessible depuis Internet. \n<br>Prenez la sécurité au sérieux dans ce cas!"
},
"destination": {
"add_to_coolify": "Ajouter à Coolify",
"confirm_restart_proxy": "Voulez-vous vraiment redémarrer le proxy? \nTout sera reconfiguré en ~10 secondes.",
"coolify_proxy_restarting": "Redémarrage du Proxy Coolify...",
"coolify_proxy_started": "Proxy Coolify démarré!",
"coolify_proxy_stopped": "Proxy Coolify arrêté!",
"delete_destination": "Supprimer le destinataire",
"force_restart_proxy": "Forcer le redémarrage du proxy",
"new": {
"add_new_destination": "Ajouter une nouvelle destination",
"install_proxy": "Cela installera un proxy sur la destination pour vous permettre d'accéder à vos applications et services sans aucune configuration manuelle (recommandé pour Docker).<br><br>Les bases de données auront leur propre proxy.",
"predefined_destinations": "Destinations prédéfinies",
"saving_and_configuring_proxy": "Enregistrement et configuration du proxy..."
},
"new_error_network_already_exists": "Réseau {{network}} déjà configuré pour une autre équipe !",
"no_destination_found": "Aucune destination trouvée",
"permission_denied_delete_destination": "Vous n'êtes pas autorisé à supprimer cette destination",
"restarting_please_wait": "Redémarrage... veuillez patienter...",
"use_coolify_proxy": "Utiliser le Proxy Coolify ?"
},
"error": {
"here": "ici",
"you_are_lost": "Oups vous êtes perdu ! \nMais n'ayez pas peur !",
"you_can_find_your_way_back": "Tu peux retrouver ton chemin"
},
"forms": {
"action": "action",
"add": "Ajouter",
"already_used_for": "<span class=\"text-red-500\">{{type}}</span> déjà utilisé pour",
"api_port": "Port API",
"api_url": "URL de l'API",
"base_directory": "Répertoire de base",
"configuration": "Configuration",
"confirm_continue": "Êtes-vous sûr de continuer ?",
"default": "défaut",
"default_email_address": "Adresse e-mail par défaut",
"default_password": "Mot de passe par défaut",
"eg": "ex",
"email": "Adresse e-mail",
"engine": "Moteur",
"extra_config": "Configuration supplémentaire",
"generated_automatically_after_start": "Généré automatiquement après le démarrage",
"host": "Hôte",
"html_url": "URL HTML",
"ip_address": "Adresse IP",
"is_required": "est requis.",
"loading": "Chargement...",
"must_be_stopped_to_modify": "Doit être arrêté pour être modifié.",
"name": "Nom",
"network": "Réseau",
"new_password": "Nouveau mot de passe",
"no_actions_available": "Aucune action disponible",
"organization": "Organisation",
"password": "Mot de passe",
"password_again": "Mot de passe à nouveau",
"passwords_not_match": "Les mots de passe ne correspondent pas.",
"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.<br> Ajoutez-les à votre pare-feu (le cas échéant).<br><br>Vous pouvez spécifier une plage de ports, par exemple : <span class='text-yellow-500 \nfont-bold'>9000-9100</span>",
"publish_directory": "Publier le répertoire",
"remove": "Retirer",
"remove_domain": "Supprimer le domaine",
"removing": "Suppression...",
"root_db_password": "Mot de passe root de la base de données",
"root_db_user": "Utilisateur root de la base de données",
"root_user": "Utilisateur root",
"roots_password": "Mot de passe de l'utilisateur root",
"save": "sauvegarder",
"saving": "Sauvegarde...",
"select_a_service": "Sélectionnez un service",
"select_a_service_version": "Sélectionnez une version de service",
"set": "Régler",
"ssh_private_key": "Clé privée SSH",
"submit": "Nous faire parvenir",
"super_secure_new_password": "Nouveau mot de passe super sécurisé",
"type": "Taper",
"user": "Utilisateur",
"username": "Nom d'utilisateur",
"value": "Valeur",
"verify_emails_without_smtp": "Vérifier les e-mails sans SMTP",
"verifying": "Vérification",
"version": "Version"
},
"general": "Général",
"index": {
"applications": "Applications",
"dashboard": "Tableau de bord",
"database": "Base de données",
"databases": "Bases de données",
"destinations": "Destinations",
"git_sources": "Sources Git",
"global_settings": "Paramètres globaux",
"logout": "Se déconnecter",
"not_implemented_yet": "Pas encore implémenté",
"secret": "Secret",
"services": "Services",
"settings": "Réglages",
"team": "Équipe",
"teams": "Équipes"
},
"layout": {
"new_version": "Nouvelle version accessible. \nRechargement...",
"switch_to_a_different_team": "Changer d'équipe...",
"update_available": "Mise à jour disponible",
"update_done": "Mise à jour terminée.",
"wait_new_version_startup": "En attendant le lancement de la nouvelle version..."
},
"login": {
"already_logged_in": "Déjà connecté...",
"authenticating": "Authentification...",
"login": "Connexion"
},
"register": {
"first_user": "Vous enregistrez le premier utilisateur. \nCe sera l'administrateur de votre instance Coolify.",
"register": "S'inscrire"
},
"reset": {
"find_path_secret_key": "Vous pouvez le trouver dans ~/coolify/.env (COOLIFY_SECRET_KEY)",
"invalid_secret_key": "Clé secrète invalide.",
"reset_password": "Réinitialiser",
"secret_key": "Clef secrète"
},
"service": {
"delete_service": "Supprimer le service",
"no_service": "Aucun service trouvé",
"permission_denied_delete_service": "Vous n'êtes pas autorisé à supprimer un service.",
"permission_denied_start_service": "Vous n'êtes pas autorisé à démarrer le service.",
"permission_denied_stop_service": "Vous n'êtes pas autorisé à arrêter le service.",
"start_service": "Démarrer le service",
"stop_service": "Stopper le service"
},
"services": {
"all_email_verified": "Tous les e-mails sont vérifiés. \nVous pouvez vous connecter maintenant.",
"generate_www_non_www_ssl": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-pink-600'>les deux entrées DNS</span> définies à l'avance.<br><br>Le service devra être redémarré."
},
"setting": {
"coolify_proxy_settings": "Paramètres du proxy Coolify",
"credential_stat_explainer": "Identifiants pour la page <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">statistiques</a>.",
"domain_removed": "Domaine supprimé",
"must_remove_domain_before_changing": "Vous devez supprimer le domaine avant de pouvoir modifier ce paramètre.",
"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<br>Il est désactivé après la première inscription.",
"ssl_explainer": "Si vous spécifiez <span class='text-yellow-500 font-bold'>https</span>, Coolify sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-yellow-500 font-bold'>www</span>, Coolify sera redirigé (302) à partir de non-www et vice versa."
},
"source": {
"application_id": "ID d'application",
"change_app_settings": "Modifier les paramètres de l'application {{name}}",
"create_new_app": "Créer une nouvelle application {{name}}",
"delete_git_source": "Supprimer la source Git",
"github": {
"redirecting": "Redirection vers Github..."
},
"gitlab": {
"already_configured": "L'application GitLab est déjà configurée.",
"gitlab_application_type": "Type d'application GitLab",
"group_owned": "Application détenue par le groupe",
"self_hosted": "Application à l'échelle de l'instance (auto-hébergée)",
"user_owned": "Application appartenant à l'utilisateur"
},
"group_name": "Nom de groupe",
"install_repositories": "Installer les dépôts",
"new": {
"git_source": "Ajouter une nouvelle source Git",
"official_providers": "Fournisseurs officiels"
},
"no_git_sources_found": "Aucune source git trouvée",
"oauth_id": "ID OAuth",
"oauth_id_explainer": "L'identifiant OAuth est l'identifiant unique de l'application GitLab. \n<br>Vous pouvez le trouver <span class='font-bold text-orange-600' >dans l'URL</span> de votre application GitLab OAuth.",
"permission_denied": "Vous n'êtes pas autorisé à supprimer une source Git",
"register_oauth_gitlab": "Enregistrer une nouvelle application OAuth sur GitLab"
},
"sources": {
"local_docker": "Docker local",
"organization_explainer": "Remplissez-le si vous souhaitez utiliser une organisation comme source Git. \nSinon, votre utilisateur sera utilisé.",
"remote_docker": "Station d'accueil à distance"
},
"team": {
"accept": "J'accepte",
"admin": "Administrateur",
"delete": "Supprimer",
"invite_new_member": "Inviter un nouveau membre",
"invite_only_register_explainer": "Vous ne pouvez inviter que des utilisateurs enregistrés pour le moment - sera bientôt prolongé.",
"invited_with_permissions": "Invité à <span class=\"font-bold text-pink-600\">{{teamName}}</span> avec <span class=\"font-bold text-rose-600\">{{permission}}</span \n> autorisation.",
"member": "membre(s)",
"members": "Membres",
"pending_invitation": "Invitation en attente",
"pending_invitations": "Invitations en attente",
"permission": "Autorisation",
"promote_to": "Promouvoir à {{grade}}",
"read": "Lire",
"revoke_invitation": "Révoquer l'invitation",
"root": "(suprême)",
"root_team_explainer": "Il s'agit de l'équipe <span class='text-red-500 font-bold'>suprême</span>. \nCela signifie que les membres de ce groupe peuvent gérer les paramètres à l'échelle de l'instance et avoir tous les privilèges dans Coolify (imaginez comme un utilisateur root sous Linux).",
"send_invitation": "Envoyer une invitation",
"you": "(Toi)"
}
}

26
src/lib/translations.ts Normal file
View File

@ -0,0 +1,26 @@
import i18n from 'sveltekit-i18n';
import lang from './lang.json';
/** @type {import('sveltekit-i18n').Config} */
export const config = {
fallbackLocale: 'en',
translations: {
en: { lang },
fr: { lang }
},
loaders: [
{
locale: 'en',
key: '',
loader: async () => (await import('./locales/en.json')).default
},
{
locale: 'fr',
key: '',
loader: async () => (await import('./locales/fr.json')).default
}
]
};
export const { t, loading, locales, locale, loadTranslations } = new i18n(config);
loading.subscribe(($loading) => $loading && console.log('Loading translations...'));

View File

@ -12,15 +12,18 @@
</script>
<script>
import { t } from '$lib/translations';
export let status;
export let error;
</script>
<div class="mx-auto flex h-screen flex-col items-center justify-center px-4">
<div class="pb-10 text-7xl font-bold">{status}</div>
<div class="text-3xl font-bold">Ooops you are lost! But don't be afraid!</div>
<div class="text-3xl font-bold">{$t('error.you_are_lost')}</div>
<div class="text-xl">
You can find your way back <a href="/" class="font-bold uppercase text-sky-400">here</a>
{$t('error.you_can_find_your_way_back')}
<a href="/" class="font-bold uppercase text-sky-400">{$t('error.here')}</a>
</div>
<div class="py-10 text-xs font-bold">
<pre

View File

@ -81,6 +81,7 @@
import { toast } from '@zerodevx/svelte-toast';
import { disabledButton } from '$lib/store';
import { onDestroy, onMount } from 'svelte';
import { t } from '$lib/translations';
if (githubToken) $gitTokens.githubToken = githubToken;
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
@ -99,7 +100,7 @@
async function handleDeploySubmit() {
try {
const { buildId } = await post(`/applications/${id}/deploy.json`, { ...application });
toast.push('Deployment queued.');
toast.push($t('application.deployment_queued'));
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
} else {
@ -113,7 +114,7 @@
}
async function deleteApplication(name) {
const sure = confirm(`Are you sure you would like to delete '${name}'?`);
const sure = confirm($t('application.confirm_to_delete', { name }));
if (sure) {
loading = true;
try {
@ -186,8 +187,8 @@
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
data-tooltip={$session.isAdmin
? 'Stop application'
: 'You do not have permission to stop the application.'}
? $t('application.stop_application')
: $t('application.permission_denied_stop_application')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -400,10 +401,10 @@
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
>
<button
title="Application Logs"
title={$t('application.build_logs')}
disabled={$disabledButton}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip="Application Logs"
data-tooltip={$t('application.build_logs')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -463,14 +464,14 @@
<button
on:click={() => deleteApplication(application.name)}
title="Delete application"
title={$t('application.delete_application')}
type="submit"
disabled={!$session.isAdmin}
class:hover:text-red-500={$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$session.isAdmin
? 'Delete application'
: 'You do not have permission to delete this application'}
? $t('application.delete_application')
: $t('application.permission_denied_delete_application')}
>
<DeleteIcon />
</button>

View File

@ -4,6 +4,7 @@ import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
import { promises as dns } from 'dns';
import { t } from '$lib/translations';
export const post: RequestHandler = async (event) => {
const { status, body } = await getUserDetails(event);
@ -18,7 +19,9 @@ export const post: RequestHandler = async (event) => {
const found = await db.isDomainConfigured({ id, fqdn });
if (found) {
throw {
message: `Domain ${getDomain(fqdn).replace('www.', '')} is already used.`
message: t.get('application.domain_already_in_use', {
domain: getDomain(fqdn).replace('www.', '')
})
};
}
if (!dev && !forceSave) {
@ -36,7 +39,7 @@ export const post: RequestHandler = async (event) => {
if (localIp?.length > 0) {
if (ip?.length === 0 || !ip.includes(localIp[0])) {
throw {
message: `DNS not set or propogated for ${domain}.<br><br>Please check your DNS settings.`
message: t.get('application.dns_not_set_error', { domain: domain })
};
}
}

View File

@ -5,6 +5,7 @@
import { post } from '$lib/api';
import { findBuildPack } from '$lib/components/templates';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -43,7 +44,9 @@
buildPack.name && buildPack.color}"
><span>{buildPack.fancyName}</span>
{#if !scanning && foundConfig?.name === buildPack.name}
<span class="absolute bottom-0 pb-2 text-xs">Choose this one...</span>
<span class="absolute bottom-0 pb-2 text-xs"
>{$t('application.configuration.buildpack.choose_this_one')}</span
>
{/if}
</button>
</form>

View File

@ -7,6 +7,7 @@
import { errorNotification } from '$lib/form';
import { onMount } from 'svelte';
import { gitTokens } from '$lib/store';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -95,9 +96,7 @@
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
);
if (data.used) {
const sure = confirm(
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
);
const sure = confirm($t('application.configuration.branch_already_in_use'));
if (sure) {
selected.autodeploy = false;
showSave = true;
@ -171,8 +170,10 @@
{#if repositories.length === 0 && loading.repositories === false}
<div class="flex-col text-center">
<div class="pb-4">No repositories configured for your Git Application.</div>
<a href={`/sources/${application.gitSource.id}`}><button>Configure it now</button></a>
<div class="pb-4">{$t('application.configuration.no_repositories_configured')}</div>
<a href={`/sources/${application.gitSource.id}`}
><button>{$t('application.configuration.configure_it_now')}</button></a
>
</div>
{:else}
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
@ -181,8 +182,8 @@
<div class="custom-select-wrapper">
<Select
placeholder={loading.repositories
? 'Loading repositories...'
: 'Please select a repository'}
? $t('application.configuration.loading_repositories')
: $t('application.configuration.select_a_repository')}
id="repository"
showIndicator={true}
isWaiting={loading.repositories}
@ -196,10 +197,10 @@
<div class="custom-select-wrapper">
<Select
placeholder={loading.branches
? 'Loading branches...'
? $t('application.configuration.loading_branches')
: !selected.repository
? 'Please select a repository first'
: 'Please select a branch'}
? $t('application.configuration.select_a_repository_first')
: $t('application.configuration.select_a_branch')}
isWaiting={loading.branches}
showIndicator={selected.repository}
id="branches"
@ -217,7 +218,7 @@
type="submit"
disabled={!showSave}
class:bg-orange-600={showSave}
class:hover:bg-orange-500={showSave}>Save</button
class:hover:bg-orange-500={showSave}>{$t('forms.save')}</button
>
</div>
</form>

View File

@ -9,6 +9,7 @@
import { goto } from '$app/navigation';
import { del, get, post, put } from '$lib/api';
import { gitTokens } from '$lib/store';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -139,9 +140,7 @@
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
);
if (data.used) {
const sure = confirm(
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
);
const sure = confirm($t('application.configuration.branch_already_in_use'));
if (sure) {
autodeploy = false;
showSave = true;
@ -270,11 +269,11 @@
<div class="flex flex-col space-y-2 px-4 xl:flex-row xl:space-y-0 xl:space-x-2 ">
{#if loading.base}
<select name="group" disabled class="w-96">
<option selected value="">Loading groups...</option>
<option selected value="">{$t('application.configuration.loading_groups')}</option>
</select>
{:else}
<select name="group" class="w-96" bind:value={selected.group} on:change={loadProjects}>
<option value="" disabled selected>Please select a group</option>
<option value="" disabled selected>{$t('application.configuration.select_a_group')}</option>
{#each groups as group}
<option value={group}>{group.full_name}</option>
{/each}
@ -282,7 +281,7 @@
{/if}
{#if loading.projects}
<select name="project" disabled class="w-96">
<option selected value="">Loading projects...</option>
<option selected value="">{$t('application.configuration.loading_projects')}</option>
</select>
{:else if !loading.projects && projects.length > 0}
<select
@ -292,20 +291,24 @@
on:change={loadBranches}
disabled={!selected.group}
>
<option value="" disabled selected>Please select a project</option>
<option value="" disabled selected
>{$t('application.configuration.select_a_project')}</option
>
{#each projects as project}
<option value={project}>{project.name}</option>
{/each}
</select>
{:else}
<select name="project" disabled class="w-96">
<option disabled selected value="">No projects found</option>
<option disabled selected value=""
>{$t('application.configuration.no_projects_found')}</option
>
</select>
{/if}
{#if loading.branches}
<select name="branch" disabled class="w-96">
<option selected value="">Loading branches...</option>
<option selected value="">{$t('application.configuration.loading_branches')}</option>
</select>
{:else if !loading.branches && branches.length > 0}
<select
@ -315,14 +318,17 @@
on:change={isBranchAlreadyUsed}
disabled={!selected.project}
>
<option value="" disabled selected>Please select a branch</option>
<option value="" disabled selected>{$t('application.configuration.select_a_branch')}</option
>
{#each branches as branch}
<option value={branch}>{branch.name}</option>
{/each}
</select>
{:else}
<select name="project" disabled class="w-96">
<option disabled selected value="">No branches found</option>
<option disabled selected value=""
>{$t('application.configuration.no_branches_found')}</option
>
</select>
{/if}
</div>
@ -334,7 +340,7 @@
disabled={!showSave || loading.save}
class:bg-orange-600={showSave && !loading.save}
class:hover:bg-orange-500={showSave && !loading.save}
>{loading.save ? 'Saving...' : 'Save'}</button
>{loading.save ? $t('forms.saving') : $t('forms.save')}</button
>
</div>
</form>

View File

@ -36,6 +36,7 @@
import { errorNotification } from '$lib/form';
import { gitTokens } from '$lib/store';
import { browser } from '$app/env';
import { t } from '$lib/translations';
const { id } = $page.params;
@ -204,18 +205,21 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Configure Build Pack</div>
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.configure_build_pack')}
</div>
</div>
{#if scanning}
<div class="flex justify-center space-x-1 p-6 font-bold">
<div class="text-xl tracking-tight">Scanning repository to suggest a build pack for you...</div>
<div class="text-xl tracking-tight">
{$t('application.configuration.scanning_repository_suggest_build_pack')}
</div>
</div>
{:else}
{#if packageManager === 'yarn' || packageManager === 'pnpm'}
<div class="flex justify-center p-6">
Found lock file for <span class="font-bold text-orange-500 pl-1">{packageManager}</span>.
Using it for predefined commands commands.
{@html $t('application.configuration.found_lock_file', { packageManager })}
</div>
{/if}
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">

View File

@ -33,6 +33,7 @@
import { errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -60,12 +61,14 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.configure_destination')}
</div>
</div>
<div class="flex flex-col justify-center">
{#if !destinations || ownDestinations.length === 0}
<div class="flex-col">
<div class="pb-2">No configurable Destination found</div>
<div class="pb-2">{$t('application.configuration.no_configurable_destination')}</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg

View File

@ -18,6 +18,8 @@
</script>
<script lang="ts">
import { t } from '$lib/translations';
export let application;
export let appId;
@ -26,7 +28,9 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Repository / Project</div>
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.select_a_repository_project')}
</div>
</div>
<div class="flex flex-wrap justify-center">
{#if application.gitSource.type === 'github'}

View File

@ -33,6 +33,7 @@
import { errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -72,12 +73,14 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.select_a_git_source')}
</div>
</div>
<div class="flex flex-col justify-center">
{#if !filteredSources || ownSources.length === 0}
<div class="flex-col">
<div class="pb-2 text-center">No configurable Git Source found</div>
<div class="pb-2">{$t('application.configuration.no_configurable_git')}</div>
<div class="flex justify-center">
<button on:click={newSource} class="add-icon bg-orange-600 hover:bg-orange-500">
<svg
@ -139,7 +142,7 @@
<div class="font-bold text-xl text-center truncate">{source.name}</div>
{#if source.gitlabApp && !source.gitlabAppId}
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
Configuration missing
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</button>

View File

@ -49,6 +49,7 @@
import cuid from 'cuid';
import { browser } from '$app/env';
import { disabledButton } from '$lib/store';
import { t } from '$lib/translations';
const { id } = $page.params;
let domainEl: HTMLInputElement;
@ -101,7 +102,7 @@
branch: application.branch,
projectId: application.projectId
});
return toast.push('Settings saved.');
return toast.push($t('application.settings_saved'));
} catch ({ error }) {
if (name === 'debug') {
debug = !debug;
@ -126,7 +127,7 @@
$disabledButton = false;
return toast.push('Configurations saved.');
} catch ({ error }) {
if (error?.startsWith('DNS not set')) {
if (error?.startsWith($t('application.dns_not_set_partial_error'))) {
forceSave = true;
}
return errorNotification(error);
@ -216,7 +217,7 @@
<!-- svelte-ignore missing-declaration -->
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
<div class="title">{$t('general')}</div>
{#if $session.isAdmin}
<button
type="submit"
@ -225,13 +226,17 @@
class:hover:bg-green-500={!loading}
class:hover:bg-orange-400={forceSave}
disabled={loading}
>{loading ? 'Saving...' : forceSave ? 'Are you sure to continue?' : 'Save'}</button
>{loading
? $t('forms.saving')
: forceSave
? $t('forms.confirm_continue')
: $t('forms.save')}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input
readonly={!$session.isAdmin}
name="name"
@ -241,7 +246,9 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="gitSource" class="text-base font-bold text-stone-100">Git Source</label>
<label for="gitSource" class="text-base font-bold text-stone-100"
>{$t('application.git_source')}</label
>
<a
href={$session.isAdmin
? `/applications/${id}/configuration/source?from=/applications/${id}`
@ -256,7 +263,9 @@
>
</div>
<div class="grid grid-cols-2 items-center">
<label for="repository" class="text-base font-bold text-stone-100">Git Repository</label>
<label for="repository" class="text-base font-bold text-stone-100"
>{$t('application.git_repository')}</label
>
<a
href={$session.isAdmin
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
@ -271,7 +280,9 @@
>
</div>
<div class="grid grid-cols-2 items-center">
<label for="buildPack" class="text-base font-bold text-stone-100">Build Pack</label>
<label for="buildPack" class="text-base font-bold text-stone-100"
>{$t('application.build_pack')}</label
>
<a
href={$session.isAdmin
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
@ -287,7 +298,9 @@
>
</div>
<div class="grid grid-cols-2 items-center pb-8">
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
<label for="destination" class="text-base font-bold text-stone-100"
>{$t('application.destination')}</label
>
<div class="no-underline">
<input
value={application.destinationDocker.name}
@ -299,20 +312,20 @@
</div>
</div>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Application</div>
<div class="title">{$t('application.application')}</div>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2">
<div class="flex-col">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">URL (FQDN)</label>
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
>{$t('application.url_fqdn')}</label
>
{#if browser && window.location.hostname === 'demo.coolify.io'}
<Explainer
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
/>
{/if}
<Explainer
text="If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the url, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>"
/>
<Explainer text={$t('application.https_explainer')} />
</div>
<input
readonly={!$session.isAdmin || isRunning}
@ -328,12 +341,12 @@
</div>
<div class="grid grid-cols-2 items-center pb-8">
<Setting
dataTooltip="Must be stopped to modify."
dataTooltip={$t('forms.must_be_stopped_to_modify')}
disabled={isRunning}
isCenter={false}
bind:setting={dualCerts}
title="Generate SSL for www and non-www?"
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
title={$t('application.ssl_www_and_non_www')}
description={$t('application.ssl_explainer')}
on:click={() => !isRunning && changeSettings('dualCerts')}
/>
</div>
@ -372,13 +385,13 @@
{/if}
{#if !staticDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<label for="port" class="text-base font-bold text-stone-100">Port</label>
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
<input
readonly={!$session.isAdmin}
name="port"
id="port"
bind:value={application.port}
placeholder={application.buildPack === 'python' ? '8000' : '3000'}
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
/>
</div>
{/if}
@ -386,34 +399,38 @@
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<label for="installCommand" class="text-base font-bold text-stone-100"
>Install Command</label
>{$t('application.install_command')}</label
>
<input
readonly={!$session.isAdmin}
name="installCommand"
id="installCommand"
bind:value={application.installCommand}
placeholder="default: yarn install"
placeholder="{$t('forms.default')}: yarn install"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="buildCommand" class="text-base font-bold text-stone-100">Build Command</label>
<label for="buildCommand" class="text-base font-bold text-stone-100"
>{$t('application.build_command')}</label
>
<input
readonly={!$session.isAdmin}
name="buildCommand"
id="buildCommand"
bind:value={application.buildCommand}
placeholder="default: yarn build"
placeholder="{$t('forms.default')}: yarn build"
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="startCommand" class="text-base font-bold text-stone-100">Start Command</label>
<label for="startCommand" class="text-base font-bold text-stone-100"
>{$t('application.start_command')}</label
>
<input
readonly={!$session.isAdmin}
name="startCommand"
id="startCommand"
bind:value={application.startCommand}
placeholder="default: yarn start"
placeholder="{$t('forms.default')}: yarn start"
/>
</div>
{/if}
@ -462,29 +479,25 @@
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
>Base Directory</label
>{$t('forms.base_directory')}</label
>
<Explainer
text="Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>."
/>
<Explainer text={$t('application.directory_to_use_explainer')} />
</div>
<input
readonly={!$session.isAdmin}
name="baseDirectory"
id="baseDirectory"
bind:value={application.baseDirectory}
placeholder="default: /"
placeholder="{$t('forms.default')}: /"
/>
</div>
{#if !notNodeDeployments.includes(application.buildPack)}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="publishDirectory" class="pt-2 text-base font-bold text-stone-100"
>Publish Directory</label
>{$t('forms.publish_directory')}</label
>
<Explainer
text="Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>."
/>
<Explainer text={$t('application.publish_directory_explainer')} />
</div>
<input
@ -492,14 +505,14 @@
name="publishDirectory"
id="publishDirectory"
bind:value={application.publishDirectory}
placeholder=" default: /"
placeholder=" {$t('forms.default')}: /"
/>
</div>
{/if}
</div>
</form>
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div>
<div class="title">{$t('application.features')}</div>
</div>
<div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
@ -507,8 +520,8 @@
isCenter={false}
bind:setting={autodeploy}
on:click={() => changeSettings('autodeploy')}
title="Enable Automatic Deployment"
description="Enable automatic deployment through webhooks."
title={$t('application.enable_automatic_deployment')}
description={$t('application.enable_auto_deploy_webhooks')}
/>
</div>
<div class="grid grid-cols-2 items-center">
@ -516,8 +529,8 @@
isCenter={false}
bind:setting={previews}
on:click={() => changeSettings('previews')}
title="Enable MR/PR Previews"
description="Enable preview deployments from pull or merge requests."
title={$t('application.enable_mr_pr_previews')}
description={$t('application.enable_preview_deploy_mr_pr_requests')}
/>
</div>
<div class="grid grid-cols-2 items-center">
@ -525,8 +538,8 @@
isCenter={false}
bind:setting={debug}
on:click={() => changeSettings('debug')}
title="Debug Logs"
description="Enable debug logs during build phase.<br><span class='text-red-500 font-bold'>Sensitive information</span> could be visible and saved in logs."
title={$t('application.debug_logs')}
description={$t('application.enable_debug_log_during_build')}
/>
</div>
</div>

View File

@ -10,6 +10,7 @@
import LoadingLogs from '../_Loading.svelte';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
let logs = [];
let loading = true;
@ -84,7 +85,7 @@
<LoadingLogs />
{/if}
{#if currentStatus === 'queued'}
<div class="text-center font-bold text-xl">Queued and waiting for execution.</div>
<div class="text-center font-bold text-xl">{$t('application.build.queued_waiting_exec')}</div>
{:else}
<div class="flex justify-end sticky top-0 p-2">
<button

View File

@ -26,6 +26,7 @@
import BuildLog from './_BuildLog.svelte';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
export let builds;
export let application;
@ -86,7 +87,9 @@
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">Build Logs</div>
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
$t('application.build_logs')}
</div>
<span class="text-xs">{application.name} </span>
</div>
@ -183,12 +186,14 @@
<div class="w-48 text-center text-xs">
{#if build.status === 'running'}
<div class="font-bold">Running</div>
<div class="font-bold">{$t('application.build.running')}</div>
{:else if build.status === 'queued'}
<div class="font-bold">Queued</div>
<div class="font-bold">{$t('application.build.queued')}</div>
{:else}
<div>{build.since}</div>
<div>Finished in <span class="font-bold">{build.took}s</span></div>
<div>
{$t('application.build.finished_in')} <span class="font-bold">{build.took}s</span>
</div>
{/if}
</div>
</div>
@ -197,7 +202,8 @@
{#if !noMoreBuilds}
{#if buildCount > 5}
<div class="flex space-x-2">
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}>Load More</button
<button disabled={noMoreBuilds} class="w-full" on:click={loadMoreBuilds}
>{$t('application.build.load_more')}</button
>
</div>
{/if}
@ -212,5 +218,5 @@
</div>
</div>
{#if buildCount === 0}
<div class="text-center text-xl font-bold">No logs found</div>
<div class="text-center text-xl font-bold">{$t('application.build.no_logs')}</div>
{/if}

View File

@ -26,6 +26,7 @@
import LoadingLogs from './_Loading.svelte';
import { get } from '$lib/api';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
let loadLogsInterval = null;
let allLogs = {
@ -176,7 +177,7 @@
</div>
<div class="flex flex-row justify-center space-x-2 px-10 pt-6">
{#if logs.length === 0}
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
<div class="text-xl font-bold tracking-tighter">{$t('application.build.waiting_logs')}</div>
{:else}
<div class="relative w-full">
<div class="text-right " />

View File

@ -30,6 +30,7 @@
import Explainer from '$lib/components/Explainer.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
const { id } = $page.params;
async function refreshSecrets() {
@ -134,10 +135,12 @@
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
<th scope="col" class="w-96 text-center">Action</th>
<th scope="col">{$t('forms.name')}</th>
<th scope="col">{$t('forms.value')}</th>
<th scope="col" class="w-64 text-center"
>{$t('application.preview.need_during_buildtime')}</th
>
<th scope="col" class="w-96 text-center">{$t('forms.action')}</th>
</tr>
</thead>
<tbody>
@ -171,13 +174,15 @@
</a>
<div class="flex items-center justify-center">
<button class="bg-coollabs hover:bg-coollabs-100" on:click={() => redeploy(container)}
>Redeploy</button
>{$t('application.preview.redeploy')}</button
>
</div>
{/each}
{:else}
<div class="flex-col">
<div class="text-center font-bold text-xl">No previews available</div>
<div class="text-center font-bold text-xl">
{$t('application.preview.no_previews_available')}
</div>
</div>
{/if}
</div>

View File

@ -6,6 +6,7 @@
import { saveSecret } from './utils';
import pLimit from 'p-limit';
import { createEventDispatcher } from 'svelte';
import { t } from '$lib/translations';
const dispatch = createEventDispatcher();
let batchSecrets = '';
@ -38,11 +39,11 @@
}
</script>
<h2 class="title my-6 font-bold">Paste .env file</h2>
<h2 class="title my-6 font-bold">{$t('application.secret__batch_dot_env')}</h2>
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
<button
class="bg-green-600 hover:bg-green-500 disabled:text-white disabled:opacity-40"
type="submit">Batch add secrets</button
type="submit">{$t('application.batch_secrets')}</button
>
</form>

View File

@ -12,6 +12,7 @@
import { del } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
import { toast } from '@zerodevx/svelte-toast';
import { createEventDispatcher } from 'svelte';
import { saveSecret } from './utils';
@ -104,7 +105,7 @@
class:cursor-not-allowed={isPRMRSecret}
class:cursor-pointer={!isPRMRSecret}
>
<span class="sr-only">Use isBuildSecret</span>
<span class="sr-only">{$t('application.secrets.use_isbuildsecret')}</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:translate-x-5={isBuildSecret}
@ -145,17 +146,19 @@
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => createSecret(true)}
>Add</button
>{$t('forms.add')}</button
>
</div>
{:else}
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => createSecret(false)}>Set</button>
<button class="" on:click={() => createSecret(false)}>{$t('forms.set')}</button>
</div>
{#if !isPRMRSecret}
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}
>{$t('forms.remove')}</button
>
</div>
{/if}
</div>

View File

@ -25,6 +25,7 @@
import pLimit from 'p-limit';
import Secret from './_Secret.svelte';
import { page } from '$app/stores';
import { t } from '$lib/translations';
import { get } from '$lib/api';
import { saveSecret } from './utils';
import { toast } from '@zerodevx/svelte-toast';
@ -65,7 +66,9 @@
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">Secrets</div>
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
{$t('application.secret')}
</div>
<span class="text-xs">{application.name} </span>
</div>
@ -137,10 +140,12 @@
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
<th scope="col" class="w-96 text-center">Action</th>
<th scope="col">{$t('forms.name')}</th>
<th scope="col">{$t('forms.value')}</th>
<th scope="col" class="w-64 text-center"
>{$t('application.preview.need_during_buildtime')}</th
>
<th scope="col" class="w-96 text-center">{$t('forms.action')}</th>
</tr>
</thead>
<tbody>

View File

@ -1,6 +1,7 @@
import { toast } from '@zerodevx/svelte-toast';
import { errorNotification } from '$lib/form';
import { post } from '$lib/api';
import { t } from '$lib/translations';
type Props = {
isNew: boolean;
@ -21,8 +22,8 @@ export async function saveSecret({
isNewSecret,
applicationId
}: Props): Promise<void> {
if (!name) return errorNotification('Name is required.');
if (!value) return errorNotification('Value is required.');
if (!name) return errorNotification(`${t.get('forms.name')} ${t.get('forms.is_required')}`);
if (!value) return errorNotification(`${t.get('forms.value')} ${t.get('forms.is_required')}`);
try {
await post(`/applications/${applicationId}/secrets.json`, {
name,

View File

@ -1,6 +1,7 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { t } from '$lib/translations';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@ -14,8 +15,7 @@ export const post: RequestHandler = async (event) => {
const isDouble = await db.checkDoubleBranch(branch, projectId);
if (isDouble && autodeploy) {
throw {
message:
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
message: t.get('application.cant_activate_auto_deploy_without_repo')
};
}
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });

View File

@ -10,12 +10,13 @@
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
const { id } = $page.params;
const dispatch = createEventDispatcher();
async function saveStorage(newStorage = false) {
try {
if (!storage.path) return errorNotification('Path is required.');
if (!storage.path) return errorNotification($t('application.storage.path_is_required'));
storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`;
storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path;
storage.path.replace(/\/\//g, '/');
@ -29,8 +30,8 @@
storage.path = null;
storage.id = null;
}
if (newStorage) toast.push('Storage saved.');
else toast.push('Storage updated.');
if (newStorage) toast.push($t('application.storage.storage_saved'));
else toast.push($t('application.storage.storage_updated'));
} catch ({ error }) {
return errorNotification(error);
}
@ -39,7 +40,7 @@
try {
await del(`/applications/${id}/storage.json`, { path: storage.path });
dispatch('refresh');
toast.push('Storage deleted.');
toast.push($t('application.storage.storage_deleted'));
} catch ({ error }) {
return errorNotification(error);
}
@ -57,16 +58,19 @@
<td>
{#if isNew}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}>Add</button
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}
>{$t('forms.add')}</button
>
</div>
{:else}
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => saveStorage(false)}>Set</button>
<button class="" on:click={() => saveStorage(false)}>{$t('forms.set')}</button>
</div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}>Remove</button>
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}
>{$t('forms.remove')}</button
>
</div>
</div>
{/if}

View File

@ -28,6 +28,7 @@
import Storage from './_Storage.svelte';
import { get } from '$lib/api';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
const { id } = $page.params;
async function refreshStorage() {
@ -111,15 +112,12 @@
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<div class="flex justify-center py-4 text-center">
<Explainer
customClass="w-full"
text={'You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache.'}
/>
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
</div>
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Path</th>
<th scope="col">{$t('forms.path')}</th>
</tr>
</thead>
<tbody>

View File

@ -3,6 +3,8 @@
import { session } from '$app/stores';
import { post } from '$lib/api';
import { goto } from '$app/navigation';
import { t } from '$lib/translations';
import { getDomain } from '$lib/components/common';
import Rust from '$lib/components/svg/applications/Rust.svelte';
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
@ -20,7 +22,6 @@
import Astro from '$lib/components/svg/applications/Astro.svelte';
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
import Deno from '$lib/components/svg/applications/Deno.svelte';
import { getDomain } from '$lib/components/common';
async function newApplication() {
const { id } = await post('/applications/new', {});
@ -39,7 +40,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl ">Applications</div>
<div class="mr-4 text-2xl ">{$t('index.applications')}</div>
{#if $session.isAdmin}
<div on:click={newApplication} class="add-icon cursor-pointer bg-green-600 hover:bg-green-500">
<svg
@ -61,7 +62,7 @@
<div class="flex flex-col flex-wrap justify-center">
{#if !applications || ownApplications.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">No applications found</div>
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
</div>
{/if}
{#if ownApplications.length > 0 || otherApplications.length > 0}

View File

@ -1,6 +1,7 @@
<script>
export let database;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
@ -8,34 +9,38 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="eg: mydb"
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
@ -43,22 +48,24 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="rootUserPassword"
name="rootUserPassword"

View File

@ -18,6 +18,7 @@
import { post } from '$lib/api';
import { getDomain } from '$lib/components/common';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
const { id } = $page.params;
@ -59,7 +60,7 @@
: window.location.hostname
: database.id
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`
: 'Loading...');
: $t('forms.loading');
}
async function changeSettings(name) {
@ -110,20 +111,20 @@
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
<div class="title">{$t('general')}</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-purple-600={!loading}
class:hover:bg-purple-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
disabled={loading}>{loading ? $t('forms.saving') : $t('forms.save')}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input
readonly={!$session.isAdmin}
name="name"
@ -133,7 +134,9 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
<label for="destination" class="text-base font-bold text-stone-100"
>{$t('application.destination')}</label
>
{#if database.destinationDockerId}
<div class="no-underline">
<input
@ -148,16 +151,17 @@
</div>
<div class="grid grid-cols-2 items-center">
<label for="version" class="text-base font-bold text-stone-100">Version</label>
<label for="version" class="text-base font-bold text-stone-100">{$t('forms.version')}</label
>
<input value={database.version} readonly disabled class="bg-transparent " />
</div>
</div>
<div class="grid grid-flow-row gap-2 px-10 pt-2">
<div class="grid grid-cols-2 items-center">
<label for="host" class="text-base font-bold text-stone-100">Host</label>
<label for="host" class="text-base font-bold text-stone-100">{$t('forms.host')}</label>
<CopyPasswordField
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField={false}
readonly
disabled
@ -167,9 +171,10 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="publicPort" class="text-base font-bold text-stone-100">Port</label>
<label for="publicPort" class="text-base font-bold text-stone-100">{$t('forms.port')}</label
>
<CopyPasswordField
placeholder="Generated automatically after set to public"
placeholder={$t('database.generated_automatically_after_set_to_public')}
id="publicPort"
readonly
disabled
@ -191,10 +196,12 @@
<CouchDb {database} />
{/if}
<div class="grid grid-cols-2 items-center px-10 pb-8">
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
<label for="url" class="text-base font-bold text-stone-100"
>{$t('database.connection_string')}</label
>
<CopyPasswordField
textarea={true}
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField={false}
id="url"
name="url"
@ -206,7 +213,7 @@
</div>
</form>
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div>
<div class="title">{$t('application.features')}</div>
</div>
<div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
@ -214,8 +221,8 @@
loading={publicLoading}
bind:setting={isPublic}
on:click={() => changeSettings('isPublic')}
title="Set it public"
description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
title={$t('database.set_public')}
description={$t('database.warning_database_public')}
disabled={!isRunning}
/>
</div>
@ -224,8 +231,8 @@
<Setting
bind:setting={appendOnly}
on:click={() => changeSettings('appendOnly')}
title="Change append only mode"
description="Useful if you would like to restore redis data from a backup.<br><span class='font-bold text-white'>Database restart is required.</span>"
title={$t('database.change_append_only_mode')}
description={$t('database.warning_append_only')}
/>
</div>
{/if}

View File

@ -3,6 +3,7 @@
export let isRunning;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
@ -10,9 +11,9 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
readonly
disabled
@ -21,11 +22,13 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
disabled={!isRunning}
readonly={!isRunning}
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField={true}
id="rootUserPassword"
name="rootUserPassword"

View File

@ -3,6 +3,7 @@
export let isRunning;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
@ -10,34 +11,38 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="eg: mydb"
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!isRunning}
readonly={!isRunning}
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"
@ -46,22 +51,24 @@
<Explainer text="Could be changed while the database is running." />
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
<label for="rootUser" class="text-base font-bold text-stone-100">{$t('forms.root_user')}</label>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
id="rootUser"
name="rootUser"
value={database.rootUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
<label for="rootUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.roots_password')}</label
>
<CopyPasswordField
disabled={!isRunning}
readonly={!isRunning}
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="rootUserPassword"
name="rootUserPassword"

View File

@ -3,6 +3,7 @@
export let isRunning;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
@ -10,12 +11,14 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="defaultDatabase" class="text-base font-bold text-stone-100">Default Database</label>
<label for="defaultDatabase" class="text-base font-bold text-stone-100"
>{$t('database.default_database')}</label
>
<CopyPasswordField
required
readonly={database.defaultDatabase}
disabled={database.defaultDatabase}
placeholder="eg: mydb"
placeholder="{$t('forms.eg')}: mydb"
id="defaultDatabase"
name="defaultDatabase"
bind:value={database.defaultDatabase}
@ -37,22 +40,24 @@
<Explainer text="Could be changed while the database is running." />
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
<label for="dbUser" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
id="dbUser"
name="dbUser"
value={database.dbUser}
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!isRunning}
readonly={!isRunning}
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"

View File

@ -3,6 +3,7 @@
export let isRunning;
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 py-5 font-bold">
@ -10,11 +11,13 @@
</div>
<div class="space-y-2 px-10">
<div class="grid grid-cols-2 items-center">
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
<label for="dbUserPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
disabled={!isRunning}
readonly={!isRunning}
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
isPasswordField
id="dbUserPassword"
name="dbUserPassword"

View File

@ -63,6 +63,7 @@
import Loading from '$lib/components/Loading.svelte';
import { del, post } from '$lib/api';
import { goto } from '$app/navigation';
import { t } from '$lib/translations';
export let database;
export let isRunning;
@ -83,7 +84,7 @@
}
}
async function stopDatabase() {
const sure = confirm(`Are you sure you would like to stop '${database.name}'?`);
const sure = confirm($t('database.confirm_stop', { name: database.name }));
if (sure) {
loading = true;
try {
@ -113,13 +114,13 @@
{#if isRunning}
<button
on:click={stopDatabase}
title="Stop database"
title={$t('database.stop_database')}
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
data-tooltip={$session.isAdmin
? 'Stop database'
: 'You do not have permission to stop the database.'}
? $t('database.stop_database')
: $t('database.permission_denied_stop_database')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -139,13 +140,13 @@
{:else}
<button
on:click={startDatabase}
title="Start database"
title={$t('database.start_database')}
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
data-tooltip={$session.isAdmin
? 'Start database'
: 'You do not have permission to start the database.'}
? $t('database.start_database')
: $t('database.permission_denied_start_database')}
><svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
@ -164,14 +165,14 @@
{/if}
<button
on:click={deleteDatabase}
title="Delete Database"
title={$t('database.delete_database')}
type="submit"
disabled={!$session.isAdmin}
class:hover:text-red-500={$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$session.isAdmin
? 'Delete Database'
: 'You do not have permission to delete a Database'}><DeleteIcon /></button
? $t('database.delete_database')
: $t('database.permission_denied_delete_database')}><DeleteIcon /></button
>
{/if}
</nav>

View File

@ -34,6 +34,7 @@
import { errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -52,12 +53,14 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.configure_destination')}
</div>
</div>
<div class="flex justify-center">
{#if !destinations || destinations.length === 0}
<div class="flex-col">
<div class="pb-2">No configurable Destination found</div>
<div class="pb-2">{$t('application.configuration.no_configurable_destination')}</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg

View File

@ -42,6 +42,7 @@
import Redis from '$lib/components/svg/databases/Redis.svelte';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { t } from '$lib/translations';
async function handleSubmit(type) {
try {
await post(`/databases/${id}/configuration/type.json`, { type });
@ -53,7 +54,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Database type</div>
<div class="mr-4 text-2xl tracking-tight">{$t('database.select_database_type')}</div>
</div>
<div class="flex flex-wrap justify-center">

View File

@ -31,6 +31,7 @@
import { enhance, errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -47,7 +48,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Database version</div>
<div class="mr-4 text-2xl tracking-tight">{$t('database.select_database_version')}</div>
</div>
<div class="flex flex-wrap justify-center">

View File

@ -9,6 +9,7 @@
import { post } from '$lib/api';
import { goto } from '$app/navigation';
import { session } from '$app/stores';
import { t } from '$lib/translations';
async function newDatabase() {
const { id } = await post('/databases/new', {});
@ -27,7 +28,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Databases</div>
<div class="mr-4 text-2xl tracking-tight">{$t('index.databases')}</div>
<div on:click={newDatabase} class="add-icon cursor-pointer bg-purple-600 hover:bg-purple-500">
<svg
class="w-6"
@ -48,7 +49,7 @@
<div class="flex flex-col flex-wrap justify-center">
{#if !databases || ownDatabases.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">No databases found</div>
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
</div>
{/if}
{#if ownDatabases.length > 0 || otherDatabases.length > 0}
@ -78,7 +79,7 @@
{/if}
{#if !database.type}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</div>

View File

@ -2,6 +2,7 @@
export let app;
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { t } from '$lib/translations';
const { id } = $page.params;
let loading = true;
async function checkApp() {
@ -58,20 +59,20 @@
<div class="box-selection hover:border-transparent hover:bg-coolgray-200">
<div class="truncate pb-2 text-center text-xl font-bold">{app.domain}</div>
{#if loading}
<div class="w-full text-center font-bold">Loading...</div>
<div class="w-full text-center font-bold">{$t('forms.loading')}</div>
{:else if app.foundByDomain}
<div class="w-full bg-coolgray-200 text-xs">
<span class="text-red-500">Domain</span> already used for
{@html $t('forms.already_used_for', { type: 'Domains' })}
<span class="text-red-500">{app.foundName}</span>
</div>
{:else if app.foundByRepository}
<div class="w-full bg-coolgray-200 text-xs">
<span class="text-red-500">Repository</span> already used for
{@html $t('forms.already_used_for', { type: 'Repository' })}
<span class="text-red-500">{app.foundName}</span>
</div>
{:else}
<button class="bg-green-600 hover:bg-green-500 w-full" on:click={addToCoolify}
>Add to Coolify</button
>{$t('destination.add_to_coolify')}</button
>
{/if}
</div>

View File

@ -10,6 +10,7 @@
import { post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { onMount } from 'svelte';
import { t } from '$lib/translations';
const { id } = $page.params;
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
let loading = false;
@ -85,7 +86,7 @@
async function stopProxy() {
try {
await post(`/destinations/${id}/stop.json`, { engine: destination.engine });
return toast.push('Coolify Proxy stopped!');
return toast.push($t('destination.coolify_proxy_stopped'));
} catch ({ error }) {
return errorNotification(error);
}
@ -93,19 +94,17 @@
async function startProxy() {
try {
await post(`/destinations/${id}/start.json`, { engine: destination.engine });
return toast.push('Coolify Proxy started!');
return toast.push($t('destination.coolify_proxy_started'));
} catch ({ error }) {
return errorNotification(error);
}
}
async function forceRestartProxy() {
const sure = confirm(
'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
);
const sure = confirm($t('destination.confirm_restart_proxy'));
if (sure) {
try {
restarting = true;
toast.push('Coolify Proxy restarting...');
toast.push($t('destination.coolify_proxy_restarting'));
await post(`/destinations/${id}/restart.json`, {
engine: destination.engine,
fqdn: settings.fqdn
@ -123,7 +122,7 @@
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-5">
<div class="title font-bold">Configuration</div>
<div class="title font-bold">{$t('forms.configuration')}</div>
{#if $session.isAdmin}
<button
type="submit"
@ -131,13 +130,15 @@
class:bg-sky-600={!loading}
class:hover:bg-sky-500={!loading}
disabled={loading}
>{loading ? 'Saving...' : 'Save'}
>{loading ? $t('forms.saving') : $t('forms.save')}
</button>
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
>{restarting
? $t('destination.restarting_please_wait')
: $t('destination.force_restart_proxy')}</button
>
{/if}
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
@ -145,10 +146,10 @@
> -->
</div>
<div class="grid grid-cols-2 items-center px-10 ">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input
name="name"
placeholder="name"
placeholder={$t('forms.name')}
disabled={!$session.isAdmin}
readonly={!$session.isAdmin}
bind:value={destination.name}
@ -156,13 +157,13 @@
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
<CopyPasswordField
id="engine"
readonly
disabled
name="engine"
placeholder="eg: /var/run/docker.sock"
placeholder="{$t('forms.eg')}: /var/run/docker.sock"
value={destination.engine}
/>
</div>
@ -171,13 +172,13 @@
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
</div> -->
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">Network</label>
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<CopyPasswordField
id="network"
readonly
disabled
name="network"
placeholder="default: coolify"
placeholder="{$t('forms.default')}: coolify"
value={destination.network}
/>
</div>
@ -188,7 +189,7 @@
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
title="Use Coolify Proxy?"
title={$t('destination.use_coolify_proxy')}
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'

View File

@ -11,6 +11,7 @@
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { onMount } from 'svelte';
import { generateRemoteEngine } from '$lib/components/common';
import { t } from '$lib/translations';
const { id } = $page.params;
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
// let scannedApps = [];
@ -90,7 +91,7 @@
try {
const engine = generateRemoteEngine(destination);
await post(`/destinations/${id}/stop.json`, { engine });
return toast.push('Coolify Proxy stopped!');
return toast.push($t('destination.coolify_proxy_stopped'));
} catch ({ error }) {
return errorNotification(error);
}
@ -99,19 +100,17 @@
try {
const engine = generateRemoteEngine(destination);
await post(`/destinations/${id}/start.json`, { engine });
return toast.push('Coolify Proxy started!');
return toast.push($t('destination.coolify_proxy_started'));
} catch ({ error }) {
return errorNotification(error);
}
}
async function forceRestartProxy() {
const sure = confirm(
'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
);
const sure = confirm($t('destination.confirm_restart_proxy'));
if (sure) {
try {
restarting = true;
toast.push('Coolify Proxy restarting...');
toast.push($t('destination.coolify_proxy_restarting'));
await post(`/destinations/${id}/restart.json`, {
engine: destination.engine,
fqdn: settings.fqdn
@ -127,7 +126,7 @@
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-5">
<div class="title font-bold">Configuration</div>
<div class="title font-bold">{$t('forms.configuration')}</div>
{#if $session.isAdmin}
<button
type="submit"
@ -135,13 +134,15 @@
class:bg-sky-600={!loading}
class:hover:bg-sky-500={!loading}
disabled={loading}
>{loading ? 'Saving...' : 'Save'}
>{loading ? $t('forms.saving') : $t('forms.save')}
</button>
<button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting}
on:click|preventDefault={forceRestartProxy}
>{restarting ? 'Restarting... please wait...' : 'Force restart proxy'}</button
>{restarting
? $t('destination.restarting_please_wait')
: $t('destination.force_restart_proxy')}</button
>
{/if}
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
@ -149,10 +150,10 @@
> -->
</div>
<div class="grid grid-cols-2 items-center px-10 ">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input
name="name"
placeholder="name"
placeholder={$t('forms.name')}
disabled={!$session.isAdmin}
readonly={!$session.isAdmin}
bind:value={destination.name}
@ -160,13 +161,13 @@
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
<CopyPasswordField
id="engine"
readonly
disabled
name="engine"
placeholder="eg: /var/run/docker.sock"
placeholder="{$t('forms.eg')}: /var/run/docker.sock"
value={destination.engine}
/>
</div>
@ -175,13 +176,13 @@
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
</div> -->
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">Network</label>
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<CopyPasswordField
id="network"
readonly
disabled
name="network"
placeholder="default: coolify"
placeholder="{$t('forms.default')}: coolify"
value={destination.network}
/>
</div>
@ -190,7 +191,7 @@
disabled={cannotDisable}
bind:setting={destination.isCoolifyProxyUsed}
on:click={changeProxySetting}
title="Use Coolify Proxy?"
title={$t('destination.use_coolify_proxy')}
description={`This will install a proxy on the destination to allow you to access your applications and services without any manual configuration. Databases will have their own proxy. <br><br>${
cannotDisable
? '<span class="font-bold text-white">You cannot disable this proxy as FQDN is configured for Coolify.</span>'

View File

@ -37,10 +37,11 @@
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import { del } from '$lib/api';
import { goto } from '$app/navigation';
import { t } from '$lib/translations';
export let destination;
async function deleteDestination(destination) {
const sure = confirm(`Are you sure you would like to delete '${destination.name}'?`);
const sure = confirm($t('application.confirm_to_delete', { name: destination.name }));
if (sure) {
try {
await del(`/destinations/${destination.id}.json`, { id: destination.id });
@ -55,14 +56,14 @@
<nav class="nav-side">
<button
on:click={() => deleteDestination(destination)}
title="Delete Destination"
title={$t('destination.delete_destination')}
type="submit"
disabled={!$session.isAdmin}
class:hover:text-red-500={$session.isAdmin}
class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip={$session.isAdmin
? 'Delete Destination'
: 'You do not have permission to delete this destination'}><DeleteIcon /></button
? $t('destination.delete_destination')
: $t('destination.permission_denied_delete_destination')}><DeleteIcon /></button
>
</nav>
<slot />

View File

@ -36,10 +36,11 @@
import type Prisma from '@prisma/client';
import LocalDocker from './_LocalDocker.svelte';
import RemoteDocker from './_RemoteDocker.svelte';
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 p-6 text-2xl font-bold">
<div class="tracking-tight">Destination</div>
<div class="tracking-tight">{$t('application.destination')}</div>
<span class="arrow-right-applications px-1">></span>
<span class="pr-2">{destination.name}</span>
</div>

View File

@ -23,6 +23,8 @@
import type Prisma from '@prisma/client';
import { session } from '$app/stores';
import { t } from '$lib/translations';
export let destinations: Prisma.DestinationDocker[];
const ownDestinations = destinations.filter((destination) => {
if (destination.teams[0].id === $session.teamId) {
@ -37,7 +39,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Destinations</div>
<div class="mr-4 text-2xl tracking-tight">{$t('index.destinations')}</div>
{#if $session.isAdmin}
<a href="/new/destination" class="add-icon bg-sky-600 hover:bg-sky-500">
<svg
@ -59,7 +61,7 @@
<div class="flex justify-center">
{#if !destinations || ownDestinations.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">No destination found</div>
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>
</div>
{/if}
{#if ownDestinations.length > 0 || otherDestinations.length > 0}

View File

@ -27,6 +27,7 @@
import Explainer from '$lib/components/Explainer.svelte';
import { errorNotification } from '$lib/form';
import { post } from '$lib/api';
import { t } from '$lib/translations';
const { id } = $page.params;
let invitation = {
teamName: team.name,
@ -94,25 +95,22 @@
</script>
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
<div class="tracking-tight">Team</div>
<div class="tracking-tight">{$t('index.team')}</div>
<span class="arrow-right-applications px-1 text-cyan-500">></span>
<span class="pr-2">{team.name}</span>
</div>
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class=" py-4">
<div class="flex space-x-1 pb-5">
<div class="title font-bold">Settings</div>
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit">Save</button>
<div class="title font-bold">{$t('index.settings')}</div>
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit">{$t('forms.save')}</button>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="mt-2 grid grid-cols-2">
<div class="flex-col">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
{#if team.id === '0'}
<Explainer
customClass="w-full"
text="This is the <span class='text-red-500 font-bold'>root</span> team. That means members of this group can manage instance wide settings and have all the priviliges in Coolify (imagine like root user on Linux)."
/>
<Explainer customClass="w-full" text={$t('team.root_team_explainer')} />
{/if}
</div>
<input id="name" name="name" placeholder="name" bind:value={team.name} />
@ -121,22 +119,23 @@
</form>
<div class="flex space-x-1 py-5 pt-10 font-bold">
<div class="title">Members</div>
<div class="title">{$t('team.members')}</div>
</div>
<div class="px-4 sm:px-6">
<table class="w-full border-separate text-left">
<thead>
<tr class="h-8 border-b border-coolgray-400">
<th scope="col">Email</th>
<th scope="col">Permission</th>
<th scope="col" class="text-center">Actions</th>
<th scope="col">{$t('forms.email')}</th>
<th scope="col">{$t('team.permission')}</th>
<th scope="col" class="text-center">{$t('forms.action')}</th>
</tr>
</thead>
{#each permissions as permission}
<tr class="text-xs">
<td class="py-4"
>{permission.user.email}
<span class="font-bold">{permission.user.id === $session.userId ? '(You)' : ''}</span
<span class="font-bold"
>{permission.user.id === $session.userId ? $t('team.you') : ''}</span
></td
>
<td class="py-4">{permission.permission}</td>
@ -144,17 +143,21 @@
<td class="flex flex-col items-center justify-center space-y-2 py-4 text-center">
<button
class="w-52 bg-red-600 hover:bg-red-500"
on:click={() => removeFromTeam(permission.user.id)}>Remove</button
on:click={() => removeFromTeam(permission.user.id)}>{$t('forms.remove')}</button
>
<button
class="w-52"
on:click={() =>
changePermission(permission.user.id, permission.id, permission.permission)}
>Promote to {permission.permission === 'admin' ? 'read' : 'admin'}</button
>{$t('team.promote_to', {
grade: permission.permission === 'admin' ? 'read' : 'admin'
})}</button
>
</td>
{:else}
<td class="text-center py-4 flex-col space-y-2"> No actions available </td>
<td class="text-center py-4 flex-col space-y-2">
{$t('forms.no_actions_available')}
</td>
{/if}
</tr>
{/each}
@ -167,11 +170,12 @@
<td class="flex-col space-y-2 py-4 text-center">
<button
class="w-52 bg-red-600 hover:bg-red-500"
on:click={() => revokeInvitation(invitation.id)}>Revoke invitation</button
on:click={() => revokeInvitation(invitation.id)}
>{$t('team.revoke_invitation')}</button
>
</td>
{:else}
<td class="text-center py-4 flex-col space-y-2">Pending invitation</td>
<td class="text-center py-4 flex-col space-y-2">{$t('team.pending_invitation')}</td>
{/if}
</tr>
{/each}
@ -181,18 +185,18 @@
<form on:submit|preventDefault={sendInvitation} class="py-5 pt-10">
<div class="flex space-x-1">
<div class="flex space-x-1">
<div class="title font-bold">Invite new member</div>
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit">Send invitation</button>
<div class="title font-bold">{$t('team.invite_new_member')}</div>
<button class="bg-cyan-600 hover:bg-cyan-500" type="submit"
>{$t('team.send_invitation')}</button
>
</div>
</div>
<Explainer
text="You can only invite registered users at the moment - will be extended soon."
/>
<Explainer text={$t('team.invite_only_register_explainer')} />
<div class="flex-col space-y-2 px-4 pt-5 sm:px-6">
<div class="flex space-x-0">
<input
bind:value={invitation.email}
placeholder="Email address"
placeholder={$t('forms.email')}
class="mr-2 w-full"
required
/>
@ -202,14 +206,14 @@
class="rounded-none rounded-l border border-dashed border-transparent"
type="button"
class:border-coolgray-300={invitation.permission !== 'read'}
class:bg-pink-500={invitation.permission === 'read'}>Read</button
class:bg-pink-500={invitation.permission === 'read'}>{$t('team.read')}</button
>
<button
on:click={() => (invitation.permission = 'admin')}
class="rounded-none rounded-r border border-dashed border-transparent"
type="button"
class:border-coolgray-300={invitation.permission !== 'admin'}
class:bg-red-500={invitation.permission === 'admin'}>Admin</button
class:bg-red-500={invitation.permission === 'admin'}>{$t('team.admin')}</button
>
</div>
</div>

View File

@ -20,6 +20,8 @@
</script>
<script lang="ts">
import { t } from '$lib/translations';
export let applicationsCount: number;
export let sourcesCount: number;
export let destinationsCount: number;
@ -29,7 +31,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Dashboard</div>
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
</div>
<div class="mt-10 pb-12 tracking-tight sm:pb-16">
@ -44,7 +46,7 @@
class="flex cursor-pointer flex-col rounded p-6 text-center text-green-500 no-underline transition duration-150 hover:bg-green-500 hover:text-white"
>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
Applications
{$t('index.applications')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">
{applicationsCount}
@ -56,7 +58,7 @@
class="flex cursor-pointer flex-col rounded p-6 text-center text-sky-500 no-underline transition duration-150 hover:bg-sky-500 hover:text-white"
>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
Destinations
{$t('index.destinations')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">
{destinationsCount}
@ -68,7 +70,7 @@
class="flex cursor-pointer flex-col rounded p-6 text-center text-orange-500 no-underline transition duration-150 hover:bg-orange-500 hover:text-white"
>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
Git Sources
{$t('index.git_sources')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">
{sourcesCount}
@ -79,7 +81,9 @@
sveltekit:prefetch
class="flex cursor-pointer flex-col rounded p-6 text-center text-purple-500 no-underline transition duration-150 hover:bg-purple-500 hover:text-white"
>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">Databases</dt>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
{$t('index.databases')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">{databasesCount}</dd>
</a>
<a
@ -87,7 +91,9 @@
sveltekit:prefetch
class="flex cursor-pointer flex-col rounded p-6 text-center text-pink-500 no-underline transition duration-150 hover:bg-pink-500 hover:text-white"
>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">Services</dt>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
{$t('index.services')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">{servicesCount}</dd>
</a>
@ -96,7 +102,9 @@
sveltekit:prefetch
class="flex cursor-pointer flex-col rounded p-6 text-center text-cyan-500 no-underline transition duration-150 hover:bg-cyan-500 hover:text-white"
>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">Teams</dt>
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
{$t('index.teams')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">
{teamsCount}
</dd>

View File

@ -4,6 +4,7 @@
import { session } from '$app/stores';
import { post } from '$lib/api';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
import { onMount } from 'svelte';
let loading = false;
let emailEl;
@ -37,9 +38,13 @@
}
</script>
<svelt:head>
<title>{$t('login.login')}</title>
</svelt:head>
<div class="flex h-screen flex-col items-center justify-center">
{#if $session.userId}
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
<div class="flex justify-center px-4 text-xl font-bold">{$t('login.already_logged_in')}</div>
{:else}
<div class="flex justify-center px-4">
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
@ -55,7 +60,7 @@
<input
type="email"
name="email"
placeholder="Email"
placeholder={$t('forms.email')}
autocomplete="off"
required
bind:this={emailEl}
@ -64,7 +69,7 @@
<input
type="password"
name="password"
placeholder="Password"
placeholder={$t('forms.password')}
bind:value={password}
required
/>
@ -76,12 +81,14 @@
class="hover:opacity-90 text-white"
class:bg-transparent={loading}
class:text-stone-600={loading}
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
class:bg-coollabs={!loading}
>{loading ? $t('login.authenticating') : $t('login.login')}</button
>
<button
on:click|preventDefault={() => goto('/register')}
class="bg-transparent hover:bg-coolgray-300 text-white ">Register</button
class="bg-transparent hover:bg-coolgray-300 text-white "
>{$t('register.register')}</button
>
</div>
</form>

View File

@ -7,6 +7,7 @@
import { post } from '$lib/api';
import Setting from '$lib/components/Setting.svelte';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
let loading = false;
@ -28,7 +29,7 @@
<div class="flex justify-center px-6 pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex items-center space-x-2 pb-5">
<div class="title font-bold">Configuration</div>
<div class="title font-bold">{$t('forms.configuration')}</div>
<button
type="submit"
class:bg-sky-600={!loading}
@ -36,36 +37,41 @@
disabled={loading}
>{loading
? payload.isCoolifyProxyUsed
? 'Saving and configuring proxy...'
: 'Saving...'
: 'Save'}</button
? $t('destination.new.saving_and_configuring_proxy')
: $t('forms.saving')
: $t('forms.save')}</button
>
</div>
<div class="mt-2 grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input required name="name" placeholder="name" bind:value={payload.name} />
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input required name="name" placeholder={$t('forms.name')} bind:value={payload.name} />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">Engine</label>
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
<input
required
name="engine"
placeholder="eg: /var/run/docker.sock"
placeholder="{$t('forms.eg')}: /var/run/docker.sock"
bind:value={payload.engine}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">Network</label>
<input required name="network" placeholder="default: coolify" bind:value={payload.network} />
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<input
required
name="network"
placeholder="{$t('forms.default')}: coolify"
bind:value={payload.network}
/>
</div>
{#if $session.teamId === '0'}
<div class="grid grid-cols-2 items-center">
<Setting
bind:setting={payload.isCoolifyProxyUsed}
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
title="Use Coolify Proxy?"
description="This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy."
title={$t('destination.use_coolify_proxy')}
description={$t('destination.new.install_proxy')}
/>
</div>
{/if}

View File

@ -7,6 +7,7 @@
import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
let loading = false;
@ -25,7 +26,7 @@
<div class="flex justify-center px-6 pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex items-center space-x-2 pb-5">
<div class="title font-bold">Configuration</div>
<div class="title font-bold">{$t('forms.configuration')}</div>
<button
type="submit"
class:bg-sky-600={!loading}
@ -33,57 +34,66 @@
disabled={loading}
>{loading
? payload.isCoolifyProxyUsed
? 'Saving and configuring proxy...'
: 'Saving...'
: 'Save'}</button
? $t('destination.new.saving_and_configuring_proxy')
: $t('forms.saving')
: $t('forms.save')}</button
>
</div>
<div class="mt-2 grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<input required name="name" placeholder="name" bind:value={payload.name} />
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input required name="name" placeholder={$t('forms.name')} bind:value={payload.name} />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="ipAddress" class="text-base font-bold text-stone-100">IP Address</label>
<label for="ipAddress" class="text-base font-bold text-stone-100"
>{$t('forms.ip_address')}</label
>
<input
required
name="ipAddress"
placeholder="eg: 192.168..."
placeholder="{$t('forms.eg')}: 192.168..."
bind:value={payload.ipAddress}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="user" class="text-base font-bold text-stone-100">User</label>
<input required name="user" placeholder="eg: root" bind:value={payload.user} />
<label for="user" class="text-base font-bold text-stone-100">{$t('forms.user')}</label>
<input required name="user" placeholder="{$t('forms.eg')}: root" bind:value={payload.user} />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="port" class="text-base font-bold text-stone-100">Port</label>
<input required name="port" placeholder="eg: 22" bind:value={payload.port} />
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
<input required name="port" placeholder="{$t('forms.eg')}: 22" bind:value={payload.port} />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="sshPrivateKey" class="text-base font-bold text-stone-100">SSH Private Key</label>
<label for="sshPrivateKey" class="text-base font-bold text-stone-100"
>{$t('forms.ssh_private_key')}</label
>
<textarea
rows="10"
class="resize-none"
required
name="sshPrivateKey"
placeholder="eg: -----BEGIN...."
placeholder="{$t('forms.eg')}: -----BEGIN...."
bind:value={payload.sshPrivateKey}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">Network</label>
<input required name="network" placeholder="default: coolify" bind:value={payload.network} />
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<input
required
name="network"
placeholder="{$t('forms.default')}: coolify"
bind:value={payload.network}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
bind:setting={payload.isCoolifyProxyUsed}
on:click={() => (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)}
title="Use Coolify Proxy?"
description="This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).<br><br>Databases will have their own proxy."
title={$t('destination.use_coolify_proxy')}
description={$t('destination.new.install_proxy')}
/>
</div>
</form>

View File

@ -1,5 +1,6 @@
import { getUserDetails } from '$lib/common';
import { isDockerNetworkExists, ErrorHandler } from '$lib/database';
import { t } from '$lib/translations';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@ -9,9 +10,10 @@ export const post: RequestHandler = async (event) => {
const { network } = await event.request.json();
try {
const found = await isDockerNetworkExists({ network });
if (found) {
throw {
error: `Network ${network} already configured for another team!`
error: t.get('destination.new_error_network_already_exists', { network: network })
};
}
return {

View File

@ -2,6 +2,7 @@
import LocalDocker from './_LocalDocker.svelte';
import cuid from 'cuid';
import RemoteDocker from './_RemoteDocker.svelte';
import { t } from '$lib/translations';
let payload = {};
let selected = 'localDocker';
@ -10,7 +11,7 @@
switch (type) {
case 'localDocker':
payload = {
name: 'Local Docker',
name: t.get('sources.local_docker'),
engine: '/var/run/docker.sock',
remoteEngine: false,
network: cuid(),
@ -19,7 +20,7 @@
break;
case 'remoteDocker':
payload = {
name: 'Remote Docker',
name: $t('sources.remote_docker'),
remoteEngine: true,
ipAddress: null,
user: 'root',
@ -36,12 +37,14 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Add New Destination</div>
<div class="mr-4 text-2xl tracking-tight">{$t('destination.new.add_new_destination')}</div>
</div>
<div class="flex-col space-y-2 pb-10 text-center">
<div class="text-xl font-bold text-white">Predefined destinations</div>
<div class="text-xl font-bold text-white">{$t('destination.new.predefined_destinations')}</div>
<div class="flex justify-center space-x-2">
<button class="w-32" on:click={() => setPredefined('localDocker')}>Local Docker</button>
<button class="w-32" on:click={() => setPredefined('localDocker')}
>{$t('sources.local_docker')}</button
>
<!-- <button class="w-32" on:click={() => setPredefined('remoteDocker')}>Remote Docker</button> -->
<button class="w-32" on:click={() => setPredefined('kubernetes')}>Kubernetes</button>
</div>
@ -51,5 +54,5 @@
{:else if selected === 'remoteDocker'}
<RemoteDocker {payload} />
{:else}
<div class="text-center font-bold text-4xl py-10">Not implemented yet</div>
<div class="text-center font-bold text-4xl py-10">{$t('index.not_implemented_yet')}</div>
{/if}

View File

@ -6,6 +6,7 @@
import { session } from '$app/stores';
import { post } from '$lib/api';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
import { onMount } from 'svelte';
let loading = false;
@ -23,7 +24,7 @@
if (loading) return;
if (password !== passwordCheck) {
return errorNotification('Passwords do not match.');
return errorNotification($t('forms.passwords_not_match'));
}
loading = true;
try {
@ -60,7 +61,7 @@
</div>
<div class="flex h-screen flex-col items-center justify-center">
{#if $session.userId}
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
<div class="flex justify-center px-4 text-xl font-bold">{$t('login.already_logged_in')}</div>
{:else}
<div class="flex justify-center px-4">
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
@ -76,7 +77,7 @@
<input
type="email"
name="email"
placeholder="Email"
placeholder={$t('forms.email')}
autocomplete="off"
required
bind:this={emailEl}
@ -85,14 +86,14 @@
<input
type="password"
name="password"
placeholder="Password"
placeholder={$t('forms.password')}
bind:value={password}
required
/>
<input
type="password"
name="passwordCheck"
placeholder="Password again"
placeholder={$t('forms.password_again')}
bind:value={passwordCheck}
required
/>
@ -104,17 +105,15 @@
disabled={loading}
class:bg-transparent={loading}
class:text-stone-600={loading}
class:bg-coollabs={!loading}>{loading ? 'Registering...' : 'Register'}</button
class:bg-coollabs={!loading}
>{loading ? $t('register.registering') : $t('register.register')}</button
>
</div>
</form>
</div>
{#if userCount === 0}
<div class="pt-5">
You are registering the first user. It will be the administrator of your Coolify instance.
<br />
It will take a while, because Coolify will configure itself, the proxy and other docker related
stuff.
{$t('register.first_user')}
</div>
{/if}
{/if}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
export let readOnly;
export let service;
</script>
@ -8,19 +9,19 @@
<div class="title">Ghost</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="email">Default Email Address</label>
<label for="email">{$t('forms.default_email_address')}</label>
<input
name="email"
id="email"
disabled
readonly
placeholder="Email address"
placeholder={$t('forms.email')}
value={service.ghost.defaultEmail}
required
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultPassword">Default Password</label>
<label for="defaultPassword">{$t('forms.default_password')}</label>
<CopyPasswordField
id="defaultPassword"
isPasswordField
@ -34,7 +35,7 @@
<div class="title">MariaDB</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbUser">Username</label>
<label for="mariadbUser">{$t('forms.username')}</label>
<CopyPasswordField
name="mariadbUser"
id="mariadbUser"
@ -44,7 +45,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPassword">Password</label>
<label for="mariadbPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="mariadbPassword"
isPasswordField
@ -55,7 +56,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbDatabase">Database</label>
<label for="mariadbDatabase">{$t('index.database')}</label>
<input
name="mariadbDatabase"
id="mariadbDatabase"
@ -63,11 +64,11 @@
readonly={readOnly}
disabled={readOnly}
bind:value={service.ghost.mariadbDatabase}
placeholder="eg: ghost_db"
placeholder="{$t('forms.eg')}: ghost_db"
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUser">Root DB User</label>
<label for="mariadbRootUser">{$t('forms.root_db_user')}</label>
<CopyPasswordField
id="mariadbRootUser"
isPasswordField
@ -78,7 +79,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUserPassword">Root DB Password</label>
<label for="mariadbRootUserPassword">{$t('forms.root_db_password')}</label>
<CopyPasswordField
id="mariadbRootUserPassword"
isPasswordField

View File

@ -1,5 +1,6 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
export let service;
</script>
@ -7,7 +8,7 @@
<div class="title">MeiliSearch</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="masterKey">Admin API key</label>
<label for="masterKey">{$t('forms.admin_api_key')}</label>
<CopyPasswordField
id="masterKey"
isPasswordField

View File

@ -1,25 +1,26 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
export let service;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MinIO Server</div>
<div class="title">MinIO</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="rootUser">Root User</label>
<label for="rootUser">{$t('forms.root_user')}</label>
<input
name="rootUser"
id="rootUser"
placeholder="User to login"
placeholder={$t('forms.username')}
value={service.minio.rootUser}
disabled
readonly
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="rootUserPassword">Root's Password</label>
<label for="rootUserPassword">{$t('forms.roots_password')}</label>
<CopyPasswordField
id="rootUserPassword"
isPasswordField
@ -30,13 +31,13 @@
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="publicPort">API Port</label>
<label for="publicPort">{$t('forms.api_port')}</label>
<input
name="publicPort"
id="publicPort"
value={service.minio.publicPort}
disabled
readonly
placeholder="Generated automatically after start"
placeholder={$t('forms.generated_automatically_after_start')}
/>
</div>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
export let service;
export let readOnly;
</script>
@ -8,31 +9,31 @@
<div class="title">Plausible Analytics</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="email">Email Address</label>
<label for="email">{$t('forms.email')}</label>
<input
name="email"
id="email"
disabled={readOnly}
readonly={readOnly}
placeholder="Email address"
placeholder={$t('forms.email')}
bind:value={service.plausibleAnalytics.email}
required
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="username">Username</label>
<label for="username">{$t('forms.username')}</label>
<CopyPasswordField
name="username"
id="username"
disabled={readOnly}
readonly={readOnly}
placeholder="User to login"
placeholder={$t('forms.username')}
bind:value={service.plausibleAnalytics.username}
required
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="password">Password</label>
<label for="password">{$t('forms.password')}</label>
<CopyPasswordField
id="password"
isPasswordField
@ -46,7 +47,7 @@
<div class="title">PostgreSQL</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="postgresqlUser">Username</label>
<label for="postgresqlUser">{$t('forms.username')}</label>
<CopyPasswordField
name="postgresqlUser"
id="postgresqlUser"
@ -56,7 +57,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="postgresqlPassword">Password</label>
<label for="postgresqlPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="postgresqlPassword"
isPasswordField
@ -67,7 +68,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="postgresqlDatabase">Database</label>
<label for="postgresqlDatabase">{$t('index.database')}</label>
<CopyPasswordField
name="postgresqlDatabase"
id="postgresqlDatabase"
@ -80,7 +81,7 @@
<label for="postgresqlPublicPort">Public Port</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="Generated automatically after start"
placeholder="{ $t('forms.generated_automatically_after_start') }"
readonly
disabled
id="postgresqlPublicPort"

View File

@ -10,6 +10,7 @@
import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte';
import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
import { toast } from '@zerodevx/svelte-toast';
import Ghost from './_Ghost.svelte';
import MeiliSearch from './_MeiliSearch.svelte';
@ -40,7 +41,7 @@
loadingVerification = true;
try {
await post(`/services/${id}/${service.type}/activate.json`, { id: service.id });
toast.push('All email verified. You can login now.');
toast.push(t.get('services.all_email_verified'));
} catch ({ error }) {
return errorNotification(error);
} finally {
@ -53,7 +54,7 @@
dualCerts = !dualCerts;
}
await post(`/services/${id}/settings.json`, { dualCerts });
return toast.push('Settings saved.');
return toast.push(t.get('application.settings_saved'));
} catch ({ error }) {
return errorNotification(error);
}
@ -63,25 +64,27 @@
<div class="mx-auto max-w-4xl px-6 pb-12">
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
<div class="title">{$t('general')}</div>
{#if $session.isAdmin}
<button
type="submit"
class:bg-pink-600={!loading}
class:hover:bg-pink-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
disabled={loading}>{loading ? $t('forms.saving') : $t('forms.save')}</button
>
{/if}
{#if service.type === 'plausibleanalytics' && isRunning}
<button on:click|preventDefault={setEmailsToVerified} disabled={loadingVerification}
>{loadingVerification ? 'Verifying' : 'Verify emails without SMTP'}</button
>{loadingVerification
? $t('forms.verifying')
: $t('forms.verify_emails_without_smtp')}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2">
<div class="mt-2 grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<div>
<input
readonly={!$session.isAdmin}
@ -109,7 +112,9 @@
>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="destination" class="text-base font-bold text-stone-100">Destination</label>
<label for="destination" class="text-base font-bold text-stone-100"
>{$t('application.destination')}</label
>
<div>
{#if service.destinationDockerId}
<div class="no-underline">
@ -125,10 +130,10 @@
</div>
<div class="grid grid-cols-2 px-10">
<div class="flex-col ">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">URL (FQDN)</label>
<Explainer
text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the url, you must first stop the application."
/>
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
>{$t('application.url_fqdn')}</label
>
<Explainer text={$t('application.https_explainer')} />
</div>
<CopyPasswordField
@ -145,10 +150,10 @@
<div class="grid grid-cols-2 items-center px-10">
<Setting
disabled={isRunning}
dataTooltip="Must be stopped to modify."
dataTooltip={$t('forms.must_be_stopped_to_modify')}
bind:setting={dualCerts}
title="Generate SSL for www and non-www?"
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-pink-600'>both DNS entries</span> set in advance.<br><br>Service needs to be restarted."
title={$t('application.ssl_www_and_non_www')}
description={$t('services.generate_www_non_www_ssl')}
on:click={() => !isRunning && changeSettings('dualCerts')}
/>
</div>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
export let service;
</script>
@ -8,7 +9,7 @@
<div class="title">VSCode Server</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="password">Password</label>
<label for="password">{$t('forms.password')}</label>
<CopyPasswordField
id="password"
isPasswordField

View File

@ -6,6 +6,7 @@
import { errorNotification } from '$lib/form';
import { browser } from '$app/env';
import { getDomain } from '$lib/components/common';
import { t } from '$lib/translations';
export let service;
export let isRunning;
@ -60,7 +61,7 @@
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="extraConfig">Extra Config</label>
<label for="extraConfig">{$t('forms.extra_config')}</label>
<textarea
bind:value={service.wordpress.extraConfig}
disabled={isRunning}
@ -70,7 +71,7 @@
name="extraConfig"
id="extraConfig"
placeholder={!isRunning
? `eg:
? `${$t('forms.eg')}:
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
@ -106,7 +107,7 @@ define('SUBDOMAIN_INSTALL', false);`
<div class="title">MySQL</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlDatabase">Database</label>
<label for="mysqlDatabase">{$t('index.database')}</label>
<input
name="mysqlDatabase"
id="mysqlDatabase"
@ -114,22 +115,22 @@ define('SUBDOMAIN_INSTALL', false);`
readonly={readOnly}
disabled={readOnly}
bind:value={service.wordpress.mysqlDatabase}
placeholder="eg: wordpress_db"
placeholder="{$t('forms.eg')}: wordpress_db"
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlRootUser">Root User</label>
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
<input
name="mysqlRootUser"
id="mysqlRootUser"
placeholder="MySQL Root User"
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">Root's Password</label>
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
<CopyPasswordField
id="mysqlRootUserPassword"
isPasswordField
@ -140,11 +141,11 @@ define('SUBDOMAIN_INSTALL', false);`
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlUser">User</label>
<label for="mysqlUser">{$t('forms.user')}</label>
<input name="mysqlUser" id="mysqlUser" value={service.wordpress.mysqlUser} disabled readonly />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlPassword">Password</label>
<label for="mysqlPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="mysqlPassword"
isPasswordField

View File

@ -65,6 +65,7 @@
import Loading from '$lib/components/Loading.svelte';
import { del, post } from '$lib/api';
import { goto } from '$app/navigation';
import { t } from '$lib/translations';
const { id } = $page.params;
export let service;
@ -73,7 +74,7 @@
let loading = false;
async function deleteService() {
const sure = confirm(`Are you sure you would like to delete '${service.name}'?`);
const sure = confirm($t('application.confirm_to_delete', { name: service.name }));
if (sure) {
loading = true;
try {
@ -88,7 +89,7 @@
}
}
async function stopService() {
const sure = confirm(`Are you sure you would like to stop '${service.name}'?`);
const sure = confirm($t('database.confirm_stop', { name: service.name }));
if (sure) {
loading = true;
try {
@ -122,13 +123,13 @@
{#if isRunning}
<button
on:click={stopService}
title="Stop Service"
title={$t('service.stop_service')}
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
data-tooltip={$session.isAdmin
? 'Stop Service'
: 'You do not have permission to stop the service.'}
? $t('service.stop_service')
: $t('service.permission_denied_stop_service')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -148,13 +149,13 @@
{:else}
<button
on:click={startService}
title="Start Service"
title={$t('service.start_service')}
type="submit"
disabled={!$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
data-tooltip={$session.isAdmin
? 'Start Service'
: 'You do not have permission to start the service.'}
? $t('service.start_service')
: $t('service.permission_denied_start_service')}
><svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
@ -181,9 +182,9 @@
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
>
<button
title="Configurations"
title={$t('application.configurations')}
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Configurations"
data-tooltip={$t('application.configurations')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -216,9 +217,9 @@
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
>
<button
title="Secrets"
title={$t('application.secret')}
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Secrets"
data-tooltip={$t('application.secret')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -272,14 +273,14 @@
{/if}
<button
on:click={deleteService}
title="Delete Service"
title={$t('service.delete_service')}
type="submit"
disabled={!$session.isAdmin}
class:hover:text-red-500={$session.isAdmin}
class="icons bg-transparent tooltip-bottom text-sm"
data-tooltip={$session.isAdmin
? 'Delete Service'
: 'You do not have permission to delete a service.'}><DeleteIcon /></button
? $t('service.delete_service')
: $t('service.permission_denied_delete_service')}><DeleteIcon /></button
>
{/if}
</nav>

View File

@ -34,6 +34,7 @@
import { enhance, errorNotification } from '$lib/form';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -50,12 +51,14 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
<div class="mr-4 text-2xl tracking-tight">
{$t('application.configuration.configure_destination')}
</div>
</div>
<div class="flex justify-center">
{#if !destinations || destinations.length === 0}
<div class="flex-col">
<div class="pb-2">No configurable Destination found</div>
<div class="pb-2">{$t('application.configuration.no_configurable_destination')}</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg

View File

@ -41,6 +41,7 @@
import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import { t } from '$lib/translations';
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
const { id } = $page.params;
@ -59,7 +60,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Service</div>
<div class="mr-4 text-2xl tracking-tight">{$t('forms.select_a_service')}</div>
</div>
<div class="flex flex-wrap justify-center">

View File

@ -32,6 +32,7 @@
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import { supportedServiceTypesAndVersions } from '$lib/components/common';
import { t } from '$lib/translations';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -52,7 +53,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Select a Service version</div>
<div class="mr-4 text-2xl tracking-tight">{$t('forms.select_a_service_version')}</div>
</div>
{#if from}
<div class="pb-10 text-center">

View File

@ -26,6 +26,7 @@
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
import { t } from '$lib/translations';
import ServiceLinks from '$lib/components/ServiceLinks.svelte';
const { id } = $page.params;
@ -42,7 +43,9 @@
class:p-6={!service.fqdn}
>
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">Secrets</div>
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
{$t('application.secret')}
</div>
<span class="text-xs">{service.name}</span>
</div>
{#if service.fqdn}
@ -74,9 +77,9 @@
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-96 text-center">Action</th>
<th scope="col">{$t('forms.name')}</th>
<th scope="col">{$t('forms.value')}</th>
<th scope="col" class="w-96 text-center">{$t('forms.action')}</th>
</tr>
</thead>
<tbody>

View File

@ -11,6 +11,7 @@
import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import { t } from '$lib/translations';
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
import { session } from '$app/stores';
import { getDomain } from '$lib/components/common';
@ -33,7 +34,7 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Services</div>
<div class="mr-4 text-2xl tracking-tight">{$t('index.services')}</div>
<div on:click={newService} class="add-icon cursor-pointer bg-pink-600 hover:bg-pink-500">
<svg
class="w-6"
@ -54,7 +55,7 @@
<div class="flex flex-col flex-wrap justify-center">
{#if !services || ownServices.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">No services found</div>
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
</div>
{/if}
{#if ownServices.length > 0 || otherServices.length > 0}
@ -97,7 +98,7 @@
{/if}
{#if !service.type || !service.fqdn}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</div>

View File

@ -1,6 +1,7 @@
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { t } from '$lib/translations';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@ -16,7 +17,8 @@ export const post: RequestHandler = async (event) => {
return {
status: found ? 500 : 200,
body: {
error: found && `Domain ${fqdn.replace('www.', '')} is already used.`
error:
found && t.get('application.domain_already_in_use', { domain: fqdn.replace('www.', '') })
}
};
} catch (error) {

View File

@ -1,6 +1,7 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { listSettings, ErrorHandler } from '$lib/database';
import { t } from '$lib/translations';
import type { RequestHandler } from '@sveltejs/kit';
import { promises as dns } from 'dns';
@ -27,7 +28,7 @@ export const del: RequestHandler = async (event) => {
return {
status: 401,
body: {
message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
message: t.get('setting.permission_denied')
}
};
if (status === 401) return { status, body };
@ -44,7 +45,7 @@ export const del: RequestHandler = async (event) => {
return {
status: 200,
body: {
message: 'Domain removed',
message: t.get('setting.domain_removed'),
redirect: ip ? `http://${ip[0]}:3000/settings` : undefined
}
};
@ -58,7 +59,7 @@ export const post: RequestHandler = async (event) => {
return {
status: 401,
body: {
message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
message: t.get('setting.permission_denied')
}
};
if (status === 401) return { status, body };

View File

@ -36,6 +36,7 @@
import { browser } from '$app/env';
import { getDomain } from '$lib/components/common';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
let isRegistrationEnabled = settings.isRegistrationEnabled;
let dualCerts = settings.dualCerts;
@ -72,7 +73,7 @@
dualCerts = !dualCerts;
}
await post(`/settings.json`, { isRegistrationEnabled, dualCerts });
return toast.push('Settings saved.');
return toast.push(t.get('application.settings_saved'));
} catch ({ error }) {
return errorNotification(error);
}
@ -99,19 +100,19 @@
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Settings</div>
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
</div>
{#if $session.teamId === '0'}
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-6">
<div class="title font-bold">Global Settings</div>
<div class="title font-bold">{$t('index.global_settings')}</div>
<button
type="submit"
disabled={loading.save}
class:bg-yellow-500={!loading.save}
class:hover:bg-yellow-400={!loading.save}
class="mx-2 ">{loading.save ? 'Saving...' : 'Save'}</button
class="mx-2 ">{loading.save ? $t('forms.saving') : $t('forms.save')}</button
>
{#if isFqdnSet}
<button
@ -119,17 +120,17 @@
disabled={loading.remove}
class:bg-red-600={!loading.remove}
class:hover:bg-red-500={!loading.remove}
>{loading.remove ? 'Removing...' : 'Remove domain'}</button
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2 items-start">
<div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100">URL (FQDN)</div>
<Explainer
text="If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
/>
<div class="pt-2 text-base font-bold text-stone-100">
{$t('application.url_fqdn')}
</div>
<Explainer text={$t('setting.ssl_explainer')} />
</div>
<div class="justify-start text-left">
<input
@ -139,16 +140,16 @@
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="eg: https://coolify.io"
placeholder="{$t('forms.eg')}: https://coolify.io"
/>
</div>
</div>
<div class="grid grid-cols-2 items-start py-6">
<div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100">Public Port Range</div>
<Explainer
text="Ports used to expose databases/services/internal services.<br> Add them to your firewall (if applicable).<br><br>You can specify a range of ports, eg: <span class='text-yellow-500 font-bold'>9000-9100</span>"
/>
<div class="pt-2 text-base font-bold text-stone-100">
{$t('forms.public_port_range')}
</div>
<Explainer text={$t('forms.public_port_range_explainer')} />
</div>
<div class="mx-auto flex-row items-center justify-center space-y-2">
<input
@ -170,40 +171,40 @@
</div>
<div class="grid grid-cols-2 items-center">
<Setting
dataTooltip="Must remove the domain before you can change this setting."
dataTooltip={$t('setting.must_remove_domain_before_changing')}
disabled={isFqdnSet}
bind:setting={dualCerts}
title="Generate SSL for www and non-www?"
description="It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both."
title={$t('application.ssl_www_and_non_www')}
description={$t('services.generate_www_non_www_ssl')}
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
bind:setting={isRegistrationEnabled}
title="Registration allowed?"
description="Allow further registrations to the application. <br>It's turned off after the first registration. "
title={$t('setting.registration_allowed')}
description={$t('setting.registration_allowed_explainer')}
on:click={() => changeSettings('isRegistrationEnabled')}
/>
</div>
</div>
</form>
<div class="flex space-x-1 pt-6 font-bold">
<div class="title">Coolify Proxy Settings</div>
<div class="title">{$t('setting.coolify_proxy_settings')}</div>
</div>
<Explainer
text={`Credentials for <a class="text-white font-bold" href=${
fqdn
text={$t('setting.credential_stat_explainer', {
link: fqdn
? `http://${settings.proxyUser}:${settings.proxyPassword}@` + getDomain(fqdn) + ':8404'
: browser &&
`http://${settings.proxyUser}:${settings.proxyPassword}@` +
window.location.hostname +
':8404'
} target="_blank">stats</a> page.`}
})}
/>
<div class="space-y-2 px-10 py-5">
<div class="grid grid-cols-2 items-center">
<label for="proxyUser">User</label>
<label for="proxyUser">{$t('forms.user')}</label>
<CopyPasswordField
readonly
disabled
@ -213,7 +214,7 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="proxyPassword">Password</label>
<label for="proxyPassword">{$t('forms.password')}</label>
<CopyPasswordField
readonly
disabled

View File

@ -5,6 +5,7 @@
import Explainer from '$lib/components/Explainer.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
const { id } = $page.params;
let loading = false;
@ -125,7 +126,7 @@
{:else if source.githubApp?.installationId}
<form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">General</div>
<div class="title">{$t('general')}</div>
{#if $session.isAdmin}
<button
type="submit"
@ -134,14 +135,14 @@
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
<button on:click|preventDefault={() => installRepositories(source)}
>Change GitHub App Settings</button
>{$t('source.change_app_settings', { name: 'GitHub' })}</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-flow-row gap-2">
<div class="mt-2 grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input name="name" id="name" required bind:value={source.name} />
</div>
</div>

View File

@ -10,6 +10,7 @@
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations';
const { id } = $page.params;
let url = browser ? (settings.fqdn ? settings.fqdn : window.location.origin) : '';
@ -130,10 +131,12 @@
type="submit"
class:bg-orange-600={!loading}
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
disabled={loading}>{loading ? $t('forms.saving') : $t('forms.save')}</button
>
{#if source.gitlabAppId}
<button on:click|preventDefault={changeSettings}>Change GitLab App Settings</button>
<button on:click|preventDefault={changeSettings}
>{$t('source.change_app_settings', { name: 'GitLab' })}</button
>
{/if}
{/if}
</div>
@ -144,10 +147,10 @@
>GitLab Application Type</label
>
<select name="type" id="type" class="w-96" bind:value={applicationType}>
<option value="user">User owned application</option>
<option value="group">Group owned application</option>
<option value="user">{$t('source.gitlab.user_owned')}</option>
<option value="group">{$t('source.gitlab.group_owned')}</option>
{#if source.htmlUrl !== 'https://gitlab.com'}
<option value="instance">Instance-wide application (self-hosted)</option>
<option value="instance">{$t('source.gitlab.self_hosted')}</option>
{/if}
</select>
</div>
@ -167,13 +170,15 @@
<div class="grid grid-flow-row gap-2">
<div class="mt-2 grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">Name</label>
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input name="name" id="name" required bind:value={source.name} />
</div>
</div>
{#if source.gitlabApp.groupName}
<div class="grid grid-cols-2 items-center">
<label for="groupName" class="text-base font-bold text-stone-100">Group Name</label>
<label for="groupName" class="text-base font-bold text-stone-100"
>{$t('source.group_name')}</label
>
<input
name="groupName"
id="groupName"
@ -194,11 +199,11 @@
</div>
<div class="grid grid-cols-2 items-start">
<div class="flex-col">
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100">OAuth ID</label>
<label for="oauthId" class="pt-2 text-base font-bold text-stone-100"
>{$t('source.oauth_id')}</label
>
{#if !source.gitlabAppId}
<Explainer
text="The OAuth ID is the unique identifier of the GitLab application. <br>You can find it <span class='font-bold text-orange-600' >in the URL</span> of your GitLab OAuth Application."
/>
<Explainer text={$t('source.oauth_id_explainer')} />
{/if}
</div>
<input
@ -215,7 +220,9 @@
</div>
<div class="grid grid-cols-2 items-center">
<label for="appId" class="text-base font-bold text-stone-100">Application ID</label>
<label for="appId" class="text-base font-bold text-stone-100"
>{$t('source.application_id')}</label
>
<input
name="appId"
id="appId"
@ -226,7 +233,9 @@
/>
</div>
<div class="grid grid-cols-2 items-center">
<label for="appSecret" class="text-base font-bold text-stone-100">Secret</label>
<label for="appSecret" class="text-base font-bold text-stone-100"
>{$t('index.secret')}</label
>
<CopyPasswordField
disabled={source.gitlabAppId}
readonly={source.gitlabAppId}

View File

@ -34,10 +34,11 @@
import { page, session } from '$app/stores';
import { errorNotification } from '$lib/form';
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import { t } from '$lib/translations';
const { id } = $page.params;
async function deleteSource(name) {
const sure = confirm(`Are you sure you would like to delete '${name}'?`);
const sure = confirm($t('application.confirm_to_delete', { name: name }));
if (sure) {
const response = await fetch(`/sources/${id}.json`, {
method: 'delete'
@ -55,14 +56,14 @@
<nav class="nav-side">
<button
on:click={() => deleteSource(source.name)}
title="Delete Git Source"
title={$t('source.delete_git_source')}
type="submit"
disabled={!$session.isAdmin}
class:hover:text-red-500={$session.isAdmin}
class="icons tooltip-bottom bg-transparent text-sm"
data-tooltip={$session.isAdmin
? 'Delete Git Source'
: 'You do not have permission to delete a Git Source'}><DeleteIcon /></button
? $t('source.delete_git_source')
: $t('source.permission_denied')}><DeleteIcon /></button
>
</nav>
<slot />

View File

@ -1,6 +1,7 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { t } from '$lib/translations';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@ -11,7 +12,7 @@ export const post: RequestHandler = async (event) => {
const found = await db.prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
if (found) {
throw {
message: `GitLab App is already configured.`
message: t.get('source.gitlab.already_configured')
};
}
return { status: 200 };

View File

@ -30,6 +30,8 @@
<script lang="ts">
export let source: Prisma.GitSource;
export let settings;
import { t } from '$lib/translations';
import type Prisma from '@prisma/client';
import Github from './_Github.svelte';
import Gitlab from './_Gitlab.svelte';
@ -67,7 +69,7 @@
</script>
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
<div class="tracking-tight">Git Source</div>
<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>

View File

@ -31,6 +31,7 @@
<script>
import { dev } from '$app/env';
import { getDomain, dashify } from '$lib/components/common';
import { t } from '$lib/translations';
export let source;
export let settings;
@ -84,5 +85,5 @@
</script>
<div class="flex h-screen items-center justify-center text-3xl font-bold">
Redirecting to Github...
{$t('source.github.redirecting')}
</div>

View File

@ -39,10 +39,11 @@
const { id } = await post('/sources/new', {});
return await goto(`/sources/${id}`, { replaceState: true });
}
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">Git Sources</div>
<div class="mr-4 text-2xl tracking-tight">{$t('index.git_sources')}</div>
{#if $session.isAdmin}
<button on:click={newSource} class="add-icon bg-orange-600 hover:bg-orange-500">
<svg
@ -64,7 +65,7 @@
<div class="flex flex-col flex-wrap justify-center">
{#if !sources || ownSources.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">No git sources found</div>
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
</div>
{/if}
{#if ownSources.length > 0 || otherSources.length > 0}
@ -85,7 +86,7 @@
{#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && source.githubApp?.installationId === null)}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
{$t('application.configuration.configuration_missing')}
</div>
{:else}
<div class="truncate text-center">{getDomain(source.htmlUrl) || ''}</div>
@ -112,7 +113,7 @@
{/if}
{#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && source.githubApp?.installationId === null)}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Configuration missing
{$t('application.configuration.configuration_missing')}
</div>
{:else}
<div class="truncate text-center">{source.htmlUrl}</div>

View File

@ -12,6 +12,11 @@ const config = {
vite: {
optimizeDeps: {
exclude: ['svelte-kit-cookie-session']
},
server: {
fs: {
allow: ['./src/lib/locales/']
}
}
}
}