mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
Add error page
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/netbirdio/netbird/proxy/internal/roundtrip"
|
||||
"github.com/netbirdio/netbird/proxy/web"
|
||||
)
|
||||
|
||||
type ReverseProxy struct {
|
||||
@@ -30,9 +31,14 @@ func NewReverseProxy(transport http.RoundTripper) *ReverseProxy {
|
||||
func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
target, serviceId, accountID, exists := p.findTargetForRequest(r)
|
||||
if !exists {
|
||||
// No mapping found so return an error here.
|
||||
// TODO: prettier error page.
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
web.ServeHTTP(w, r, map[string]any{
|
||||
"page": "error",
|
||||
"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
|
||||
}
|
||||
|
||||
|
||||
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" />
|
||||
<link rel="icon" type="image/x-icon" href="/assets/favicon-Cv-2QvSV.ico" />
|
||||
<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" />
|
||||
<script type="module" crossorigin src="/assets/index-BQ7jeUNq.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/style-B08XFatU.css">
|
||||
<script type="module" crossorigin src="/assets/index-ClfM9m3s.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/style-B1NSEbha.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Go template variables injected here -->
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico" />
|
||||
<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" />
|
||||
</head>
|
||||
<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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1709,6 +1710,7 @@
|
||||
"integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -1719,6 +1721,7 @@
|
||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1778,6 +1781,7 @@
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
@@ -2029,6 +2033,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2134,6 +2139,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -2388,6 +2394,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -3384,6 +3391,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3445,6 +3453,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3678,6 +3687,7 @@
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -3711,6 +3721,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -3797,6 +3808,7 @@
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -3918,6 +3930,7 @@
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"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 { getData, type Data } from "@/data";
|
||||
import Button from "@/components/Button";
|
||||
@@ -22,6 +22,10 @@ const methods: NonNullable<Data["methods"]> =
|
||||
: { password:"password", pin: "pin", oidc: "/auth/oidc" };
|
||||
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
document.title = "Authentication Required - NetBird Service";
|
||||
}, []);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState<string | null>(null);
|
||||
const [pin, setPin] = useState("");
|
||||
|
||||
@@ -2,11 +2,16 @@ import { NetBirdLogo } from "./NetBirdLogo";
|
||||
|
||||
export function PoweredByNetBird() {
|
||||
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">
|
||||
Powered by
|
||||
</span>
|
||||
<NetBirdLogo size="small" mobile={false} />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
// Auth method types matching Go
|
||||
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
|
||||
export interface Data {
|
||||
page?: PageType
|
||||
methods?: Partial<Record<AuthMethod, string>>
|
||||
error?: ErrorData
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -2,9 +2,17 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { ErrorPage } from './pages/ErrorPage.tsx'
|
||||
import { getData } from '@/data'
|
||||
|
||||
const data = getData()
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
{data.page === 'error' && data.error ? (
|
||||
<ErrorPage {...data.error} />
|
||||
) : (
|
||||
<App />
|
||||
)}
|
||||
</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,
|
||||
// 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 {
|
||||
http.Error(w, initErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -101,5 +102,8 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, data any) {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
if len(statusCode) > 0 {
|
||||
w.WriteHeader(statusCode[0])
|
||||
}
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user