diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a99018d9..b9e2c2ac 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -80,6 +80,7 @@ services: ARCHITECTURE: ${ARCHITECTURE} REDIS_HOST: ${REDIS_HOST} DEMO_MODE: ${DEMO_MODE} + LOCAL_DOMAIN: ${LOCAL_DOMAIN} networks: - tipi_main_network ports: @@ -93,6 +94,7 @@ services: - ${PWD}/repos:/runtipi/repos:ro - ${PWD}/apps:/runtipi/apps - ${PWD}/logs:/app/logs + - ${PWD}/traefik:/runtipi/traefik - ${STORAGE_PATH}:/app/storage labels: traefik.enable: true diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index 9f0627f2..90bdf22a 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -76,11 +76,13 @@ services: ARCHITECTURE: ${ARCHITECTURE} REDIS_HOST: ${REDIS_HOST} DEMO_MODE: ${DEMO_MODE} + LOCAL_DOMAIN: ${LOCAL_DOMAIN} volumes: - ${PWD}/state:/runtipi/state - ${PWD}/repos:/runtipi/repos:ro - ${PWD}/apps:/runtipi/apps - ${PWD}/logs:/app/logs + - ${PWD}/traefik:/runtipi/traefik - ${PWD}:/app/storage labels: # Main diff --git a/docker-compose.yml b/docker-compose.yml index dd11190d..7aafb9e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,12 +76,14 @@ services: ARCHITECTURE: ${ARCHITECTURE} REDIS_HOST: ${REDIS_HOST} DEMO_MODE: ${DEMO_MODE} + LOCAL_DOMAIN: ${LOCAL_DOMAIN} volumes: - ${PWD}/.env:/runtipi/.env - ${PWD}/state:/runtipi/state - ${PWD}/repos:/runtipi/repos:ro - ${PWD}/apps:/runtipi/apps - ${PWD}/logs:/app/logs + - ${PWD}/traefik:/runtipi/traefik - ${STORAGE_PATH}:/app/storage labels: # Main diff --git a/src/client/components/ui/Button/Button.tsx b/src/client/components/ui/Button/Button.tsx index 501c37a5..8a5c202c 100644 --- a/src/client/components/ui/Button/Button.tsx +++ b/src/client/components/ui/Button/Button.tsx @@ -6,7 +6,7 @@ interface IProps { type?: 'submit' | 'reset' | 'button'; disabled?: boolean; loading?: boolean; - onClick?: () => void; + onClick?: (e: React.MouseEvent) => void; children: React.ReactNode; width?: number | null; } diff --git a/src/client/messages/en.json b/src/client/messages/en.json index 9a9047ed..bfea2403 100644 --- a/src/client/messages/en.json +++ b/src/client/messages/en.json @@ -247,6 +247,8 @@ "apps-repo-hint": "URL to the apps repository.", "storage-path": "Storage path", "storage-path-hint": "Path to the storage directory. Keep empty for default (runtipi/app-data). Make sure it is an absolute path and that it exists", + "local-domain": "Local domain", + "local-domain-hint": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.", "submit": "Save", "user-settings-title": "User settings", "language": "Language", diff --git a/src/client/modules/Settings/components/SettingsForm/SettingsForm.tsx b/src/client/modules/Settings/components/SettingsForm/SettingsForm.tsx index b127865b..14c03856 100644 --- a/src/client/modules/Settings/components/SettingsForm/SettingsForm.tsx +++ b/src/client/modules/Settings/components/SettingsForm/SettingsForm.tsx @@ -2,9 +2,11 @@ import { LanguageSelector } from '@/components/LanguageSelector'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { IconAdjustmentsAlt, IconUser } from '@tabler/icons-react'; +import clsx from 'clsx'; import { useTranslations } from 'next-intl'; import React, { useEffect } from 'react'; import { useForm } from 'react-hook-form'; +import { Tooltip } from 'react-tooltip'; import validator from 'validator'; export type SettingsFormValues = { @@ -13,6 +15,7 @@ export type SettingsFormValues = { appsRepoUrl?: string; domain?: string; storagePath?: string; + localDomain?: string; }; interface IProps { @@ -29,6 +32,10 @@ export const SettingsForm = (props: IProps) => { const validateFields = (values: SettingsFormValues) => { const errors: { [K in keyof SettingsFormValues]?: string } = {}; + if (values.localDomain && !validator.isFQDN(values.localDomain)) { + errors.localDomain = t('invalid-domain'); + } + if (values.dnsIp && !validator.isIP(values.dnsIp)) { errors.dnsIp = t('invalid-ip'); } @@ -86,6 +93,11 @@ export const SettingsForm = (props: IProps) => { } }; + const downloadCertificate = (e: React.MouseEvent) => { + e.preventDefault(); + window.open('/certificate'); + }; + return ( <>
@@ -100,23 +112,81 @@ export const SettingsForm = (props: IProps) => {

{t('subtitle')}

- - {t('domain-name-hint')} + + {t('domain-name')} + {t('domain-name-hint')} + ? + + } + error={errors.domain?.message} + placeholder="example.com" + />
- - {t('internal-ip-hint')} + + {t('internal-ip')} + {t('internal-ip-hint')} + ? + + } + error={errors.internalIp?.message} + placeholder="192.168.1.100" + />
- - {t('apps-repo-hint')} + + {t('apps-repo')} + {t('apps-repo-hint')} + ? + + } + error={errors.appsRepoUrl?.message} + placeholder="https://github.com/meienberger/runtipi-appstore" + />
- - {t('storage-path-hint')} + + {t('storage-path')} + {t('storage-path-hint')} + ? + + } + error={errors.storagePath?.message} + placeholder={t('storage-path')} + /> +
+
+ + {t('local-domain')} + {t('local-domain-hint')} + ? + + } + error={errors.localDomain?.message} + placeholder="tipi.lan" + /> + +