mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-21 01:36:46 +00:00
Add error page
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/proxy/internal/roundtrip"
|
"github.com/netbirdio/netbird/proxy/internal/roundtrip"
|
||||||
|
"github.com/netbirdio/netbird/proxy/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReverseProxy struct {
|
type ReverseProxy struct {
|
||||||
@@ -30,9 +31,14 @@ func NewReverseProxy(transport http.RoundTripper) *ReverseProxy {
|
|||||||
func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
target, serviceId, accountID, exists := p.findTargetForRequest(r)
|
target, serviceId, accountID, exists := p.findTargetForRequest(r)
|
||||||
if !exists {
|
if !exists {
|
||||||
// No mapping found so return an error here.
|
web.ServeHTTP(w, r, map[string]any{
|
||||||
// TODO: prettier error page.
|
"page": "error",
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
"error": map[string]any{
|
||||||
|
"code": 404,
|
||||||
|
"title": "Service Not Found",
|
||||||
|
"message": "The requested service could not be found. Please check the URL, try refreshing, or check if the peer is running. If that doesn't work, see our documentation for help.",
|
||||||
|
},
|
||||||
|
}, http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
proxy/web/dist/assets/index-BQ7jeUNq.js
vendored
9
proxy/web/dist/assets/index-BQ7jeUNq.js
vendored
File diff suppressed because one or more lines are too long
1
proxy/web/dist/assets/style-B08XFatU.css
vendored
1
proxy/web/dist/assets/style-B08XFatU.css
vendored
File diff suppressed because one or more lines are too long
6
proxy/web/dist/index.html
vendored
6
proxy/web/dist/index.html
vendored
@@ -4,10 +4,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/x-icon" href="/assets/favicon-Cv-2QvSV.ico" />
|
<link rel="icon" type="image/x-icon" href="/assets/favicon-Cv-2QvSV.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Authentication Required</title>
|
<title>NetBird Service</title>
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
<script type="module" crossorigin src="/assets/index-BQ7jeUNq.js"></script>
|
<script type="module" crossorigin src="/assets/index-ClfM9m3s.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/style-B08XFatU.css">
|
<link rel="stylesheet" crossorigin href="/assets/style-B1NSEbha.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Go template variables injected here -->
|
<!-- Go template variables injected here -->
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Authentication Required</title>
|
<title>NetBird Service</title>
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
13
proxy/web/package-lock.json
generated
13
proxy/web/package-lock.json
generated
@@ -63,6 +63,7 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -1709,6 +1710,7 @@
|
|||||||
"integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==",
|
"integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -1719,6 +1721,7 @@
|
|||||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -1778,6 +1781,7 @@
|
|||||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.54.0",
|
"@typescript-eslint/scope-manager": "8.54.0",
|
||||||
"@typescript-eslint/types": "8.54.0",
|
"@typescript-eslint/types": "8.54.0",
|
||||||
@@ -2029,6 +2033,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2134,6 +2139,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -2388,6 +2394,7 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3384,6 +3391,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3445,6 +3453,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -3678,6 +3687,7 @@
|
|||||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.27.0",
|
"esbuild": "~0.27.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
@@ -3711,6 +3721,7 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -3797,6 +3808,7 @@
|
|||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -3918,6 +3930,7 @@
|
|||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import {Loader2, Lock, Binary, LogIn} from "lucide-react";
|
import {Loader2, Lock, Binary, LogIn} from "lucide-react";
|
||||||
import { getData, type Data } from "@/data";
|
import { getData, type Data } from "@/data";
|
||||||
import Button from "@/components/Button";
|
import Button from "@/components/Button";
|
||||||
@@ -22,6 +22,10 @@ const methods: NonNullable<Data["methods"]> =
|
|||||||
: { password:"password", pin: "pin", oidc: "/auth/oidc" };
|
: { password:"password", pin: "pin", oidc: "/auth/oidc" };
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = "Authentication Required - NetBird Service";
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [submitting, setSubmitting] = useState<string | null>(null);
|
const [submitting, setSubmitting] = useState<string | null>(null);
|
||||||
const [pin, setPin] = useState("");
|
const [pin, setPin] = useState("");
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ import { NetBirdLogo } from "./NetBirdLogo";
|
|||||||
|
|
||||||
export function PoweredByNetBird() {
|
export function PoweredByNetBird() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center mt-8 gap-2 group cursor-pointer">
|
<a
|
||||||
|
href="https://netbird.io?utm_source=netbird-proxy&utm_medium=web&utm_campaign=powered_by"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center justify-center mt-8 gap-2 group cursor-pointer"
|
||||||
|
>
|
||||||
<span className="text-sm text-nb-gray-400 font-light text-center group-hover:opacity-80 transition-all">
|
<span className="text-sm text-nb-gray-400 font-light text-center group-hover:opacity-80 transition-all">
|
||||||
Powered by
|
Powered by
|
||||||
</span>
|
</span>
|
||||||
<NetBirdLogo size="small" mobile={false} />
|
<NetBirdLogo size="small" mobile={false} />
|
||||||
</div>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
// Auth method types matching Go
|
// Auth method types matching Go
|
||||||
export type AuthMethod = 'pin' | 'password' | 'oidc' | "link"
|
export type AuthMethod = 'pin' | 'password' | 'oidc' | "link"
|
||||||
|
|
||||||
|
// Page types
|
||||||
|
export type PageType = 'auth' | 'error'
|
||||||
|
|
||||||
|
// Error data structure
|
||||||
|
export interface ErrorData {
|
||||||
|
code: number
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
// Data injected by Go templates
|
// Data injected by Go templates
|
||||||
export interface Data {
|
export interface Data {
|
||||||
|
page?: PageType
|
||||||
methods?: Partial<Record<AuthMethod, string>>
|
methods?: Partial<Record<AuthMethod, string>>
|
||||||
|
error?: ErrorData
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -2,9 +2,17 @@ import { StrictMode } from 'react'
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
import { ErrorPage } from './pages/ErrorPage.tsx'
|
||||||
|
import { getData } from '@/data'
|
||||||
|
|
||||||
|
const data = getData()
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
{data.page === 'error' && data.error ? (
|
||||||
|
<ErrorPage {...data.error} />
|
||||||
|
) : (
|
||||||
|
<App />
|
||||||
|
)}
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
42
proxy/web/src/pages/ErrorPage.tsx
Normal file
42
proxy/web/src/pages/ErrorPage.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { BookText, RotateCw } from "lucide-react";
|
||||||
|
import { Title } from "@/components/Title";
|
||||||
|
import { Description } from "@/components/Description";
|
||||||
|
import { PoweredByNetBird } from "@/components/PoweredByNetBird";
|
||||||
|
import { Card } from "@/components/Card";
|
||||||
|
import Button from "@/components/Button";
|
||||||
|
import type { ErrorData } from "@/data";
|
||||||
|
|
||||||
|
export function ErrorPage({ code, title, message }: ErrorData) {
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = `${title} - NetBird Service`;
|
||||||
|
}, [title]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col items-center mt-40 px-4 max-w-xl mx-auto">
|
||||||
|
<Card className="text-center">
|
||||||
|
<div className="text-5xl font-bold text-nb-gray-200 mb-4">{code}</div>
|
||||||
|
<Title>{title}</Title>
|
||||||
|
<Description className="mt-2">{message}</Description>
|
||||||
|
<div className="mt-6 flex gap-3 justify-center">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
<RotateCw size={16} />
|
||||||
|
Refresh Page
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => window.open("https://docs.netbird.io", "_blank")}
|
||||||
|
>
|
||||||
|
<BookText size={16} />
|
||||||
|
Documentation
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<PoweredByNetBird />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -37,7 +37,8 @@ func init() {
|
|||||||
|
|
||||||
// ServeHTTP serves the web UI. For static assets it serves them directly,
|
// ServeHTTP serves the web UI. For static assets it serves them directly,
|
||||||
// for other paths it renders the page with the provided data.
|
// for other paths it renders the page with the provided data.
|
||||||
func ServeHTTP(w http.ResponseWriter, r *http.Request, data any) {
|
// Optional statusCode can be passed to set a custom HTTP status code (default 200).
|
||||||
|
func ServeHTTP(w http.ResponseWriter, r *http.Request, data any, statusCode ...int) {
|
||||||
if initErr != nil {
|
if initErr != nil {
|
||||||
http.Error(w, initErr.Error(), http.StatusInternalServerError)
|
http.Error(w, initErr.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -101,5 +102,8 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, data any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
if len(statusCode) > 0 {
|
||||||
|
w.WriteHeader(statusCode[0])
|
||||||
|
}
|
||||||
w.Write(buf.Bytes())
|
w.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user