feat(update front-end to support a custom domain api): n
This commit is contained in:
parent
b483a284bd
commit
97bb074ae5
25 changed files with 257 additions and 81 deletions
|
@ -92,6 +92,15 @@ To stop Tipi, run the stop script.
|
|||
sudo ./scripts/stop.sh
|
||||
```
|
||||
|
||||
## Linking a domain to your dashboard
|
||||
If you want to link a domain to your dashboard, you can do so by providing the `--domain` option in the start script.
|
||||
|
||||
```bash
|
||||
sudo ./scripts/start.sh --domain mydomain.com
|
||||
```
|
||||
|
||||
A Let's Encrypt certificate will be generated and installed automatically. Make sure to have port 443 open on your firewall and that your domain has an **A** record pointing to your server IP.
|
||||
|
||||
## ❤️ Contributing
|
||||
|
||||
Tipi is made to be very easy to plug in new apps. We welcome and appreciate new contributions.
|
||||
|
|
|
@ -14,6 +14,7 @@ services:
|
|||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ${PWD}/traefik:/root/.config
|
||||
- ${PWD}/traefik/shared:/shared
|
||||
- ${PWD}/traefik/letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
|
@ -67,8 +68,26 @@ services:
|
|||
POSTGRES_HOST: tipi-db
|
||||
APPS_REPO_ID: ${APPS_REPO_ID}
|
||||
APPS_REPO_URL: ${APPS_REPO_URL}
|
||||
DOMAIN: ${DOMAIN}
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
traefik.enable: true
|
||||
# Web
|
||||
traefik.http.routers.api.rule: PathPrefix(`/api`)
|
||||
traefik.http.routers.api.service: api
|
||||
traefik.http.routers.api.entrypoints: web
|
||||
traefik.http.routers.api.middlewares: api-stripprefix
|
||||
traefik.http.services.api.loadbalancer.server.port: 3001
|
||||
# Websecure
|
||||
traefik.http.routers.api-secure.rule: (Host(`${DOMAIN}`) && PathPrefix(`/api`))
|
||||
traefik.http.routers.api-secure.entrypoints: websecure
|
||||
traefik.http.routers.api-secure.service: api-secure
|
||||
traefik.http.routers.api-secure.tls.certresolver: myresolver
|
||||
traefik.http.routers.api-secure.middlewares: api-stripprefix
|
||||
traefik.http.services.api-secure.loadbalancer.server.port: 3001
|
||||
# Middlewares
|
||||
traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
|
||||
|
||||
dashboard:
|
||||
build:
|
||||
|
@ -82,10 +101,40 @@ services:
|
|||
- tipi_main_network
|
||||
environment:
|
||||
- INTERNAL_IP=${INTERNAL_IP}
|
||||
- DOMAIN=${DOMAIN}
|
||||
volumes:
|
||||
- ${PWD}/packages/dashboard/src:/dashboard/src
|
||||
# - /dashboard/node_modules
|
||||
# - /dashboard/.next
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard-redirect.entrypoints: web
|
||||
traefik.http.routers.dashboard-redirect.middlewares: redirect-middleware
|
||||
traefik.http.routers.dashboard-redirect.service: dashboard
|
||||
traefik.http.services.dashboard-redirect.loadbalancer.server.port: 3000
|
||||
|
||||
traefik.http.routers.dashboard-redirect-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
|
||||
traefik.http.routers.dashboard-redirect-secure.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-redirect-secure.middlewares: redirect-middleware
|
||||
traefik.http.routers.dashboard-redirect-secure.service: dashboard
|
||||
traefik.http.routers.dashboard-redirect-secure.tls.certresolver: myresolver
|
||||
traefik.http.services.dashboard-redirect-secure.loadbalancer.server.port: 3000
|
||||
|
||||
# Web
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/dashboard")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
# Websecure
|
||||
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/dashboard`)
|
||||
traefik.http.routers.dashboard-secure.service: dashboard-secure
|
||||
traefik.http.routers.dashboard-secure.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
|
||||
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
|
||||
# Middlewares
|
||||
traefik.http.middlewares.redirect-middleware.redirectregex.regex: .*
|
||||
traefik.http.middlewares.redirect-middleware.redirectregex.replacement: /dashboard
|
||||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
|
|
|
@ -8,6 +8,7 @@ services:
|
|||
ports:
|
||||
- ${NGINX_PORT-80}:80
|
||||
- ${NGINX_PORT_SSL-443}:443
|
||||
- 8080:8080
|
||||
command: --providers.docker
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
@ -63,10 +64,28 @@ services:
|
|||
NODE_ENV: production
|
||||
APPS_REPO_ID: ${APPS_REPO_ID}
|
||||
APPS_REPO_URL: ${APPS_REPO_URL}
|
||||
DOMAIN: ${DOMAIN}
|
||||
dns:
|
||||
- ${DNS_IP}
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
traefik.enable: true
|
||||
# Web
|
||||
traefik.http.routers.api.rule: PathPrefix(`/api`)
|
||||
traefik.http.routers.api.service: api
|
||||
traefik.http.routers.api.entrypoints: web
|
||||
traefik.http.routers.api.middlewares: api-stripprefix
|
||||
traefik.http.services.api.loadbalancer.server.port: 3001
|
||||
# Websecure
|
||||
traefik.http.routers.api-secure.rule: (Host(`${DOMAIN}`) && PathPrefix(`/api`))
|
||||
traefik.http.routers.api-secure.entrypoints: websecure
|
||||
traefik.http.routers.api-secure.service: api-secure
|
||||
traefik.http.routers.api-secure.tls.certresolver: myresolver
|
||||
traefik.http.routers.api-secure.middlewares: api-stripprefix
|
||||
traefik.http.services.api-secure.loadbalancer.server.port: 3001
|
||||
# Middlewares
|
||||
traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
|
||||
|
||||
dashboard:
|
||||
image: meienberger/runtipi:rc-${TIPI_VERSION}
|
||||
|
@ -79,12 +98,36 @@ services:
|
|||
environment:
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
NODE_ENV: production
|
||||
DOMAIN: ${DOMAIN}
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) &&
|
||||
traefik.http.routers.dashboard.entrypoints: webinsecure
|
||||
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard-redirect.entrypoints: web
|
||||
traefik.http.routers.dashboard-redirect.middlewares: redirect-middleware
|
||||
traefik.http.routers.dashboard-redirect.service: dashboard
|
||||
traefik.http.services.dashboard-redirect.loadbalancer.server.port: 3000
|
||||
|
||||
traefik.http.routers.dashboard-redirect-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
|
||||
traefik.http.routers.dashboard-redirect-secure.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-redirect-secure.middlewares: redirect-middleware
|
||||
traefik.http.routers.dashboard-redirect-secure.service: dashboard
|
||||
traefik.http.routers.dashboard-redirect-secure.tls.certresolver: myresolver
|
||||
traefik.http.services.dashboard-redirect-secure.loadbalancer.server.port: 3000
|
||||
|
||||
# Web
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/dashboard")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
# Websecure
|
||||
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/dashboard`)
|
||||
traefik.http.routers.dashboard-secure.service: dashboard-secure
|
||||
traefik.http.routers.dashboard-secure.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
|
||||
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
|
||||
# Middlewares
|
||||
traefik.http.middlewares.redirect-middleware.redirectregex.regex: .*
|
||||
traefik.http.middlewares.redirect-middleware.redirectregex.replacement: /dashboard
|
||||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
|
|
|
@ -64,10 +64,28 @@ services:
|
|||
NODE_ENV: production
|
||||
APPS_REPO_ID: ${APPS_REPO_ID}
|
||||
APPS_REPO_URL: ${APPS_REPO_URL}
|
||||
DOMAIN: ${DOMAIN}
|
||||
dns:
|
||||
- ${DNS_IP}
|
||||
networks:
|
||||
- tipi_main_network
|
||||
labels:
|
||||
traefik.enable: true
|
||||
# Web
|
||||
traefik.http.routers.api.rule: PathPrefix(`/api`)
|
||||
traefik.http.routers.api.service: api
|
||||
traefik.http.routers.api.entrypoints: web
|
||||
traefik.http.routers.api.middlewares: api-stripprefix
|
||||
traefik.http.services.api.loadbalancer.server.port: 3001
|
||||
# Websecure
|
||||
traefik.http.routers.api-secure.rule: (Host(`${DOMAIN}`) && PathPrefix(`/api`))
|
||||
traefik.http.routers.api-secure.entrypoints: websecure
|
||||
traefik.http.routers.api-secure.service: api-secure
|
||||
traefik.http.routers.api-secure.tls.certresolver: myresolver
|
||||
traefik.http.routers.api-secure.middlewares: api-stripprefix
|
||||
traefik.http.services.api-secure.loadbalancer.server.port: 3001
|
||||
# Middlewares
|
||||
traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
|
||||
|
||||
dashboard:
|
||||
image: meienberger/runtipi:${TIPI_VERSION}
|
||||
|
@ -81,12 +99,36 @@ services:
|
|||
environment:
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
NODE_ENV: production
|
||||
DOMAIN: ${DOMAIN}
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) &&
|
||||
traefik.http.routers.dashboard.entrypoints: webinsecure
|
||||
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard-redirect.entrypoints: web
|
||||
traefik.http.routers.dashboard-redirect.middlewares: redirect-middleware
|
||||
traefik.http.routers.dashboard-redirect.service: dashboard
|
||||
traefik.http.services.dashboard-redirect.loadbalancer.server.port: 3000
|
||||
|
||||
traefik.http.routers.dashboard-redirect-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
|
||||
traefik.http.routers.dashboard-redirect-secure.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-redirect-secure.middlewares: redirect-middleware
|
||||
traefik.http.routers.dashboard-redirect-secure.service: dashboard
|
||||
traefik.http.routers.dashboard-redirect-secure.tls.certresolver: myresolver
|
||||
traefik.http.services.dashboard-redirect-secure.loadbalancer.server.port: 3000
|
||||
|
||||
# Web
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/dashboard")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
# Websecure
|
||||
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/dashboard`)
|
||||
traefik.http.routers.dashboard-secure.service: dashboard-secure
|
||||
traefik.http.routers.dashboard-secure.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
|
||||
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
|
||||
# Middlewares
|
||||
traefik.http.middlewares.redirect-middleware.redirectregex.regex: .*
|
||||
traefik.http.middlewares.redirect-middleware.redirectregex.replacement: /dashboard
|
||||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const { NODE_ENV, INTERNAL_IP } = process.env;
|
||||
const { INTERNAL_IP, DOMAIN } = process.env;
|
||||
|
||||
const nextConfig = {
|
||||
webpackDevMiddleware: (config) => {
|
||||
|
@ -12,7 +12,9 @@ const nextConfig = {
|
|||
reactStrictMode: true,
|
||||
env: {
|
||||
INTERNAL_IP: INTERNAL_IP,
|
||||
NEXT_PUBLIC_DOMAIN: DOMAIN,
|
||||
},
|
||||
basePath: '/dashboard',
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
|||
import { useSytemStore } from '../../state/systemStore';
|
||||
|
||||
const AppLogo: React.FC<{ id: string; size?: number; className?: string; alt?: string }> = ({ id, size = 80, className = '', alt = '' }) => {
|
||||
const { internalIp } = useSytemStore();
|
||||
const logoUrl = `http://${internalIp}:3001/apps/${id}/metadata/logo.jpg`;
|
||||
const { baseUrl } = useSytemStore();
|
||||
const logoUrl = `${baseUrl}/apps/${id}/metadata/logo.jpg`;
|
||||
|
||||
return (
|
||||
<div aria-label={alt} className={`drop-shadow ${className}`} style={{ width: size, height: size }}>
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import Link from 'next/link';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
import { getUrl } from '../../core/helpers/url-helpers';
|
||||
|
||||
interface IProps {
|
||||
onClickMenu: () => void;
|
||||
|
@ -16,7 +17,7 @@ const Header: React.FC<IProps> = ({ onClickMenu }) => {
|
|||
</div>
|
||||
<Flex justifyContent="center" flex="1">
|
||||
<Link href="/" passHref>
|
||||
<img src="/tipi.png" alt="Tipi Logo" width={30} height={30} />
|
||||
<img src={getUrl('tipi.png')} alt="Tipi Logo" width={30} height={30} />
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
|
|
@ -9,6 +9,7 @@ import clsx from 'clsx';
|
|||
import { useRouter } from 'next/router';
|
||||
import { IconType } from 'react-icons';
|
||||
import { useLogoutMutation, useVersionQuery } from '../../generated/graphql';
|
||||
import { getUrl } from '../../core/helpers/url-helpers';
|
||||
|
||||
const SideMenu: React.FC = () => {
|
||||
const router = useRouter();
|
||||
|
@ -45,7 +46,7 @@ const SideMenu: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Box className="flex-1 flex flex-col p-0 md:p-4">
|
||||
<img className="self-center mb-5 logo mt-0 md:mt-5" src="/tipi.png" width={512} height={512} />
|
||||
<img className="self-center mb-5 logo mt-0 md:mt-5" src={getUrl('tipi.png')} width={512} height={512} />
|
||||
<List spacing={3} className="pt-5">
|
||||
{renderMenuItem('Dashboard', '', AiOutlineDashboard)}
|
||||
{renderMenuItem('My Apps', 'apps', AiOutlineAppstore)}
|
||||
|
|
|
@ -12,7 +12,7 @@ const api = async <T = unknown>(fetchParams: IFetchParams): Promise<T> => {
|
|||
const { endpoint, method = 'GET', params, data } = fetchParams;
|
||||
|
||||
const { getState } = useSytemStore;
|
||||
const BASE_URL = `http://${getState().internalIp}:3001`;
|
||||
const BASE_URL = getState().baseUrl;
|
||||
|
||||
const response = await axios.request<T & { error?: string }>({
|
||||
method,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ApolloClient, from, InMemoryCache } from '@apollo/client';
|
||||
import links from './links';
|
||||
|
||||
export const createApolloClient = async (ip: string): Promise<ApolloClient<any>> => {
|
||||
const additiveLink = from([links.errorLink, links.httpLink(ip)]);
|
||||
export const createApolloClient = async (url: string): Promise<ApolloClient<any>> => {
|
||||
const additiveLink = from([links.errorLink, links.httpLink(url)]);
|
||||
|
||||
return new ApolloClient({
|
||||
link: additiveLink,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { HttpLink } from '@apollo/client';
|
||||
|
||||
const httpLink = (ip: string) =>
|
||||
new HttpLink({
|
||||
uri: `http://${ip}:3001/graphql`,
|
||||
const httpLink = (url: string) => {
|
||||
return new HttpLink({
|
||||
uri: `${url}/graphql`,
|
||||
credentials: 'include',
|
||||
});
|
||||
};
|
||||
|
||||
export default httpLink;
|
||||
|
|
|
@ -3,10 +3,9 @@ import axios from 'axios';
|
|||
import { useSytemStore } from '../state/systemStore';
|
||||
|
||||
const fetcher: BareFetcher<any> = (url: string) => {
|
||||
const { getState } = useSytemStore;
|
||||
const BASE_URL = `http://${getState().internalIp}:3001`;
|
||||
const { baseUrl } = useSytemStore.getState();
|
||||
|
||||
return axios.get(url, { baseURL: BASE_URL, withCredentials: true }).then((res) => res.data);
|
||||
return axios.get(url, { baseURL: baseUrl, withCredentials: true }).then((res) => res.data);
|
||||
};
|
||||
|
||||
export default fetcher;
|
||||
|
|
10
packages/dashboard/src/core/helpers/url-helpers.ts
Normal file
10
packages/dashboard/src/core/helpers/url-helpers.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export const getUrl = (url: string) => {
|
||||
const domain = process.env.NEXT_PUBLIC_DOMAIN;
|
||||
let prefix = '';
|
||||
|
||||
if (domain !== 'tipi.localhost') {
|
||||
prefix = 'dashboard';
|
||||
}
|
||||
|
||||
return `/${prefix}/${url}`;
|
||||
};
|
|
@ -4,6 +4,7 @@ import axios from 'axios';
|
|||
import useSWR, { BareFetcher } from 'swr';
|
||||
import { createApolloClient } from '../core/apollo/client';
|
||||
import { useSytemStore } from '../state/systemStore';
|
||||
import { getUrl } from '../core/helpers/url-helpers';
|
||||
|
||||
interface IReturnProps {
|
||||
client?: ApolloClient<unknown>;
|
||||
|
@ -11,18 +12,18 @@ interface IReturnProps {
|
|||
}
|
||||
|
||||
const fetcher: BareFetcher<any> = (url: string) => {
|
||||
return axios.get(url).then((res) => res.data);
|
||||
return axios.get(getUrl(url)).then((res) => res.data);
|
||||
};
|
||||
|
||||
export default function useCachedResources(): IReturnProps {
|
||||
const { data } = useSWR('/api/ip', fetcher);
|
||||
const { internalIp, setInternalIp } = useSytemStore();
|
||||
const { data } = useSWR('api/ip', fetcher);
|
||||
const { baseUrl, setBaseUrl, setInternalIp, setDomain } = useSytemStore();
|
||||
const [isLoadingComplete, setLoadingComplete] = useState(false);
|
||||
const [client, setClient] = useState<ApolloClient<unknown>>();
|
||||
|
||||
async function loadResourcesAndDataAsync(ip: string) {
|
||||
async function loadResourcesAndDataAsync(url: string) {
|
||||
try {
|
||||
const restoredClient = await createApolloClient(ip);
|
||||
const restoredClient = await createApolloClient(url);
|
||||
|
||||
setClient(restoredClient);
|
||||
} catch (error) {
|
||||
|
@ -34,16 +35,24 @@ export default function useCachedResources(): IReturnProps {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.ip && !internalIp) {
|
||||
setInternalIp(data.ip);
|
||||
const { ip, domain } = data || {};
|
||||
if (ip && !baseUrl) {
|
||||
setInternalIp(ip);
|
||||
setDomain(domain);
|
||||
|
||||
if (!domain || domain === 'tipi.localhost') {
|
||||
setBaseUrl(`http://${ip}/api`);
|
||||
} else {
|
||||
setBaseUrl(`https://${domain}/api`);
|
||||
}
|
||||
}
|
||||
}, [data?.ip, internalIp, setInternalIp]);
|
||||
}, [baseUrl, data.ip, data.domain, setBaseUrl, data, setInternalIp, setDomain]);
|
||||
|
||||
useEffect(() => {
|
||||
if (internalIp) {
|
||||
loadResourcesAndDataAsync(internalIp);
|
||||
if (baseUrl) {
|
||||
loadResourcesAndDataAsync(baseUrl);
|
||||
}
|
||||
}, [internalIp]);
|
||||
}, [baseUrl]);
|
||||
|
||||
return { client, isLoadingComplete };
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ const InstallForm: React.FC<IProps> = ({ formFields, onSubmit, initalValues, exp
|
|||
render={({ input, meta }) => <FormInput className="mb-3" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} label="Domain name" {...input} />}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
Make sur the domain contains an <strong>A</strong> record pointing to your IP.
|
||||
Make sure this exact domain contains an <strong>A</strong> record pointing to your IP.
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Container, Flex, SlideFade, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { getUrl } from '../../../core/helpers/url-helpers';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
|
@ -12,7 +13,7 @@ const AuthFormLayout: React.FC<IProps> = ({ children, title, description }) => {
|
|||
<Container maxW="1250px">
|
||||
<Flex flex={1} height="100vh" overflowY="hidden">
|
||||
<SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
|
||||
<img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
|
||||
<img className="self-center mb-5 logo" src={getUrl('tipi.png')} width={512} height={512} />
|
||||
<Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
|
||||
{title}
|
||||
</Text>
|
||||
|
|
|
@ -2,16 +2,18 @@ import React from 'react';
|
|||
import { Html, Head, Main, NextScript } from 'next/document';
|
||||
import { ColorModeScript } from '@chakra-ui/react';
|
||||
import { theme } from '../styles/theme';
|
||||
import { getUrl } from '../core/helpers/url-helpers';
|
||||
|
||||
export default function MyDocument() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<title>Tipi - Dashboard</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={getUrl('apple-touch-icon.png')} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={getUrl('favicon-32x32.png')} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={getUrl('favicon-16x16.png')} />
|
||||
<link rel="manifest" href={getUrl('site.webmanifest')} />
|
||||
<link rel="mask-icon" href={getUrl('safari-pinned-tab.svg')} color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</Head>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export default function ip(_: any, res: any) {
|
||||
const { INTERNAL_IP } = process.env;
|
||||
const { DOMAIN } = process.env;
|
||||
|
||||
res.status(200).json({ ip: INTERNAL_IP });
|
||||
res.status(200).json({ ip: INTERNAL_IP, domain: DOMAIN });
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import create from 'zustand';
|
||||
import api from '../core/api';
|
||||
|
||||
type AppsStore = {
|
||||
internalIp: string;
|
||||
fetchInternalIp: () => void;
|
||||
};
|
||||
|
||||
export const useNetworkStore = create<AppsStore>((set) => ({
|
||||
internalIp: '',
|
||||
fetchInternalIp: async () => {
|
||||
const response = await api.fetch<string>({
|
||||
endpoint: '/network/internal-ip',
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
set({ internalIp: response });
|
||||
},
|
||||
}));
|
|
@ -1,11 +1,19 @@
|
|||
import create from 'zustand';
|
||||
|
||||
type Store = {
|
||||
baseUrl: string;
|
||||
internalIp: string;
|
||||
setInternalIp: (internalIp: string) => void;
|
||||
domain: string;
|
||||
setDomain: (domain: string) => void;
|
||||
setBaseUrl: (url: string) => void;
|
||||
setInternalIp: (ip: string) => void;
|
||||
};
|
||||
|
||||
export const useSytemStore = create<Store>((set) => ({
|
||||
baseUrl: '',
|
||||
internalIp: '',
|
||||
setInternalIp: (internalIp: string) => set((state) => ({ ...state, internalIp })),
|
||||
domain: '',
|
||||
setDomain: (domain: string) => set((state) => ({ ...state, domain })),
|
||||
setBaseUrl: (url: string) => set((state) => ({ ...state, baseUrl: url })),
|
||||
setInternalIp: (ip: string) => set((state) => ({ ...state, internalIp: ip })),
|
||||
}));
|
||||
|
|
|
@ -34,6 +34,7 @@ const {
|
|||
NGINX_PORT = '80',
|
||||
APPS_REPO_ID = '',
|
||||
APPS_REPO_URL = '',
|
||||
DOMAIN = '',
|
||||
} = process.env;
|
||||
|
||||
const config: IConfig = {
|
||||
|
@ -45,7 +46,7 @@ const config: IConfig = {
|
|||
NODE_ENV,
|
||||
ROOT_FOLDER: '/tipi',
|
||||
JWT_SECRET,
|
||||
CLIENT_URLS: ['http://localhost:3000', `http://${INTERNAL_IP}`, `http://${INTERNAL_IP}:${NGINX_PORT}`, `http://${INTERNAL_IP}:3000`],
|
||||
CLIENT_URLS: ['http://localhost:3000', `http://${INTERNAL_IP}`, `http://${INTERNAL_IP}:${NGINX_PORT}`, `http://${INTERNAL_IP}:3000`, `https://${DOMAIN}`],
|
||||
VERSION: TIPI_VERSION,
|
||||
ROOT_FOLDER_HOST,
|
||||
APPS_REPO_ID,
|
||||
|
|
|
@ -18,27 +18,23 @@ import recover from './core/updates/recover-migrations';
|
|||
import { cloneRepo, updateRepo } from './helpers/repo-helpers';
|
||||
import startJobs from './core/jobs/jobs';
|
||||
|
||||
let corsOptions = __prod__
|
||||
? {
|
||||
credentials: true,
|
||||
origin: function (origin: any, callback: any) {
|
||||
// disallow requests with no origin
|
||||
if (!origin) return callback(new Error('Not allowed by CORS'), false);
|
||||
|
||||
if (config.CLIENT_URLS.includes(origin)) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
const message = "The CORS policy for this origin doesn't allow access from the particular origin.";
|
||||
return callback(new Error(message), false);
|
||||
},
|
||||
let corsOptions = {
|
||||
credentials: true,
|
||||
origin: function (origin: any, callback: any) {
|
||||
if (!__prod__) {
|
||||
return callback(null, true);
|
||||
}
|
||||
: {
|
||||
credential: true,
|
||||
origin: function (origin: any, callback: any) {
|
||||
return callback(null, true);
|
||||
},
|
||||
};
|
||||
// disallow requests with no origin
|
||||
if (!origin) return callback(new Error('Not allowed by CORS'), false);
|
||||
|
||||
if (config.CLIENT_URLS.includes(origin)) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
const message = "The CORS policy for this origin doesn't allow access from the particular origin.";
|
||||
return callback(new Error(message), false);
|
||||
},
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
|
|
|
@ -10,6 +10,7 @@ fi
|
|||
|
||||
NGINX_PORT=80
|
||||
PROXY_PORT=8080
|
||||
DOMAIN=tipi.localhost
|
||||
|
||||
while [ -n "$1" ]; do # while loop starts
|
||||
case "$1" in
|
||||
|
@ -37,6 +38,17 @@ while [ -n "$1" ]; do # while loop starts
|
|||
fi
|
||||
shift
|
||||
;;
|
||||
--domain)
|
||||
domain="$2"
|
||||
|
||||
if [[ "${domain}" =~ ^[a-zA-Z0-9.-]+$ ]]; then
|
||||
DOMAIN="${domain}"
|
||||
else
|
||||
echo "--domain must be a valid domain"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift # The double dash makes them parameters
|
||||
break
|
||||
|
@ -58,6 +70,12 @@ if [[ "$(uname)" != "Linux" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# If port is not 80 and domain is not tipi.localhost, we exit
|
||||
if [[ "${NGINX_PORT}" != "80" ]] && [[ "${DOMAIN}" != "tipi.localhost" ]]; then
|
||||
echo "Using a custom domain with a custom port is not supported"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ROOT_FOLDER="$($readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
|
||||
STATE_FOLDER="${ROOT_FOLDER}/state"
|
||||
SED_ROOT_FOLDER="$(echo $ROOT_FOLDER | sed 's/\//\\\//g')"
|
||||
|
@ -154,6 +172,7 @@ for template in ${ENV_FILE}; do
|
|||
sed -i "s/<postgres_password>/${POSTGRES_PASSWORD}/g" "${template}"
|
||||
sed -i "s/<apps_repo_id>/${REPO_ID}/g" "${template}"
|
||||
sed -i "s/<apps_repo_url>/${APPS_REPOSITORY_ESCAPED}/g" "${template}"
|
||||
sed -i "s/<domain>/${DOMAIN}/g" "${template}"
|
||||
done
|
||||
|
||||
mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
|
||||
|
|
|
@ -12,4 +12,5 @@ JWT_SECRET=<jwt_secret>
|
|||
ROOT_FOLDER_HOST=<root_folder>
|
||||
NGINX_PORT=<nginx_port>
|
||||
PROXY_PORT=<proxy_port>
|
||||
POSTGRES_PASSWORD=<postgres_password>
|
||||
POSTGRES_PASSWORD=<postgres_password>
|
||||
DOMAIN=<domain>
|
|
@ -23,4 +23,4 @@ certificatesResolvers:
|
|||
entryPoint: web
|
||||
|
||||
log:
|
||||
level: DEBUG
|
||||
level: ERROR
|
||||
|
|
Loading…
Reference in a new issue