78 404 when the application put under nginx subdirectory with proxy pass (#79)
* use http.FileServer insetead of custom middleware * fixed behavior under reverse proxy * enabled reverse proxy subfolder as "domain value" * domain validation * code refactoring * code refactoring * updated translation
This commit is contained in:
@@ -34,6 +34,7 @@ languages:
|
|||||||
clipboardAction: Copied URL to clipboard
|
clipboardAction: Copied URL to clipboard
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may close this window)
|
||||||
restartAppMessage: Needs a page reload to take effect
|
restartAppMessage: Needs a page reload to take effect
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
italian:
|
italian:
|
||||||
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
||||||
statusTitle: Stato
|
statusTitle: Stato
|
||||||
@@ -67,6 +68,7 @@ languages:
|
|||||||
clipboardAction: URL copiato negli appunti
|
clipboardAction: URL copiato negli appunti
|
||||||
playlistCheckbox: Download playlist (richiederà tempo, puoi chiudere la finestra dopo l'inoltro)
|
playlistCheckbox: Download playlist (richiederà tempo, puoi chiudere la finestra dopo l'inoltro)
|
||||||
restartAppMessage: La finestra deve essere ricaricata perché abbia effetto
|
restartAppMessage: La finestra deve essere ricaricata perché abbia effetto
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
chinese:
|
chinese:
|
||||||
urlInput: YouTube 或其他受支持服务的视频网址
|
urlInput: YouTube 或其他受支持服务的视频网址
|
||||||
statusTitle: 状态
|
statusTitle: 状态
|
||||||
@@ -100,6 +102,7 @@ languages:
|
|||||||
archiveTitle: 归档
|
archiveTitle: 归档
|
||||||
clipboardAction: 复制 URL 到剪贴板
|
clipboardAction: 复制 URL 到剪贴板
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
spanish:
|
spanish:
|
||||||
urlInput: URL de YouTube u otro servicio compatible
|
urlInput: URL de YouTube u otro servicio compatible
|
||||||
statusTitle: Estado
|
statusTitle: Estado
|
||||||
@@ -132,6 +135,7 @@ languages:
|
|||||||
archiveTitle: Archive
|
archiveTitle: Archive
|
||||||
clipboardAction: Copied URL to clipboard
|
clipboardAction: Copied URL to clipboard
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
russian:
|
russian:
|
||||||
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
|
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
|
||||||
statusTitle: Статус
|
statusTitle: Статус
|
||||||
@@ -164,6 +168,7 @@ languages:
|
|||||||
archiveTitle: Архив
|
archiveTitle: Архив
|
||||||
clipboardAction: URL скопирован в буфер обмена
|
clipboardAction: URL скопирован в буфер обмена
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
korean:
|
korean:
|
||||||
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
||||||
statusTitle: 상태
|
statusTitle: 상태
|
||||||
@@ -196,6 +201,7 @@ languages:
|
|||||||
archiveTitle: Archive
|
archiveTitle: Archive
|
||||||
clipboardAction: Copied URL to clipboard
|
clipboardAction: Copied URL to clipboard
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
japanese:
|
japanese:
|
||||||
urlInput: YouTubeまたはサポート済み動画のURL
|
urlInput: YouTubeまたはサポート済み動画のURL
|
||||||
statusTitle: 状態
|
statusTitle: 状態
|
||||||
@@ -229,6 +235,7 @@ languages:
|
|||||||
archiveTitle: Archive
|
archiveTitle: Archive
|
||||||
clipboardAction: Copied URL to clipboard
|
clipboardAction: Copied URL to clipboard
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
catalan:
|
catalan:
|
||||||
urlInput: URL de YouTube o d'un altre servei compatible
|
urlInput: URL de YouTube o d'un altre servei compatible
|
||||||
statusTitle: Estat
|
statusTitle: Estat
|
||||||
@@ -261,6 +268,7 @@ languages:
|
|||||||
archiveTitle: Archive
|
archiveTitle: Archive
|
||||||
clipboardAction: Copied URL to clipboard
|
clipboardAction: Copied URL to clipboard
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
ukrainian:
|
ukrainian:
|
||||||
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
|
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
|
||||||
statusTitle: Статус
|
statusTitle: Статус
|
||||||
@@ -293,6 +301,7 @@ languages:
|
|||||||
archiveTitle: Архів
|
archiveTitle: Архів
|
||||||
clipboardAction: URL скопійовано в буфер обміну
|
clipboardAction: URL скопійовано в буфер обміну
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
polish:
|
polish:
|
||||||
urlInput: Adres URL YouTube lub innej obsługiwanej usługi
|
urlInput: Adres URL YouTube lub innej obsługiwanej usługi
|
||||||
statusTitle: Status
|
statusTitle: Status
|
||||||
@@ -325,3 +334,4 @@ languages:
|
|||||||
archiveTitle: Archiwum
|
archiveTitle: Archiwum
|
||||||
clipboardAction: Adres URL zostanie skopiowany do schowka
|
clipboardAction: Adres URL zostanie skopiowany do schowka
|
||||||
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
|
||||||
|
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { atom, selector } from 'recoil'
|
import { atom, selector } from 'recoil'
|
||||||
import { prefersDarkMode } from '../utils'
|
import { prefersDarkMode } from '../utils'
|
||||||
|
|
||||||
export type Language =
|
|
||||||
| 'english'
|
|
||||||
| 'chinese'
|
|
||||||
| 'russian'
|
|
||||||
| 'italian'
|
|
||||||
| 'spanish'
|
|
||||||
| 'korean'
|
|
||||||
| 'japanese'
|
|
||||||
| 'catalan'
|
|
||||||
| 'ukrainian'
|
|
||||||
| 'polish'
|
|
||||||
|
|
||||||
export const languages = [
|
export const languages = [
|
||||||
'english',
|
'english',
|
||||||
'chinese',
|
'chinese',
|
||||||
@@ -26,6 +14,8 @@ export const languages = [
|
|||||||
'polish',
|
'polish',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
export type Language = (typeof languages)[number]
|
||||||
|
|
||||||
export type Theme = 'light' | 'dark' | 'system'
|
export type Theme = 'light' | 'dark' | 'system'
|
||||||
export type ThemeNarrowed = 'light' | 'dark'
|
export type ThemeNarrowed = 'light' | 'dark'
|
||||||
|
|
||||||
@@ -40,6 +30,7 @@ export interface SettingsState {
|
|||||||
pathOverriding: boolean
|
pathOverriding: boolean
|
||||||
enableCustomArgs: boolean
|
enableCustomArgs: boolean
|
||||||
listView: boolean
|
listView: boolean
|
||||||
|
servedFromReverseProxy: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const languageState = atom<Language>({
|
export const languageState = atom<Language>({
|
||||||
@@ -133,9 +124,20 @@ export const listViewState = atom({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const servedFromReverseProxyState = atom({
|
||||||
|
key: 'servedFromReverseProxyState',
|
||||||
|
default: localStorage.getItem('reverseProxy') === "true",
|
||||||
|
effects: [
|
||||||
|
({ onSet }) =>
|
||||||
|
onSet(a => localStorage.setItem('reverseProxy', a.toString()))
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
export const serverAddressAndPortState = selector({
|
export const serverAddressAndPortState = selector({
|
||||||
key: 'serverAddressAndPortState',
|
key: 'serverAddressAndPortState',
|
||||||
get: ({ get }) => `${get(serverAddressState)}:${get(serverPortState)}`
|
get: ({ get }) => get(servedFromReverseProxyState)
|
||||||
|
? `${get(serverAddressState)}`
|
||||||
|
: `${get(serverAddressState)}:${get(serverPortState)}`
|
||||||
})
|
})
|
||||||
|
|
||||||
export const serverURL = selector({
|
export const serverURL = selector({
|
||||||
@@ -184,5 +186,6 @@ export const settingsState = selector<SettingsState>({
|
|||||||
pathOverriding: get(pathOverridingState),
|
pathOverriding: get(pathOverridingState),
|
||||||
enableCustomArgs: get(enableCustomArgsState),
|
enableCustomArgs: get(enableCustomArgsState),
|
||||||
listView: get(listViewState),
|
listView: get(listViewState),
|
||||||
|
servedFromReverseProxy: get(servedFromReverseProxyState),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CircularProgress } from '@mui/material'
|
import { CircularProgress } from '@mui/material'
|
||||||
import { Suspense, lazy } from 'react'
|
import { Suspense, lazy } from 'react'
|
||||||
import { createBrowserRouter } from 'react-router-dom'
|
import { createHashRouter } from 'react-router-dom'
|
||||||
import Layout from './Layout'
|
import Layout from './Layout'
|
||||||
|
|
||||||
const Home = lazy(() => import('./views/Home'))
|
const Home = lazy(() => import('./views/Home'))
|
||||||
@@ -10,7 +10,7 @@ const Settings = lazy(() => import('./views/Settings'))
|
|||||||
|
|
||||||
const ErrorBoundary = lazy(() => import('./components/ErrorBoundary'))
|
const ErrorBoundary = lazy(() => import('./components/ErrorBoundary'))
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createHashRouter([
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
Component: () => <Layout />,
|
Component: () => <Layout />,
|
||||||
|
|||||||
@@ -19,9 +19,13 @@ export function validateIP(ipAddr: string): boolean {
|
|||||||
* @param domainName
|
* @param domainName
|
||||||
* @returns domain validity test
|
* @returns domain validity test
|
||||||
*/
|
*/
|
||||||
export function validateDomain(domainName: string): boolean {
|
export function validateDomain(url: string): boolean {
|
||||||
let domainRegex = /[^@ \t\r\n]+.[^@ \t\r\n]+\.[^@ \t\r\n]+/
|
const urlRegex = /(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
||||||
return domainRegex.test(domainName) || domainName === 'localhost'
|
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
||||||
|
|
||||||
|
const [name, slug] = url.split('/')
|
||||||
|
|
||||||
|
return urlRegex.test(url) || name === 'localhost' && slugRegex.test(slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
Container,
|
Container,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
@@ -36,6 +37,7 @@ import {
|
|||||||
languages,
|
languages,
|
||||||
latestCliArgumentsState,
|
latestCliArgumentsState,
|
||||||
pathOverridingState,
|
pathOverridingState,
|
||||||
|
servedFromReverseProxyState,
|
||||||
serverAddressState,
|
serverAddressState,
|
||||||
serverPortState,
|
serverPortState,
|
||||||
themeState
|
themeState
|
||||||
@@ -48,6 +50,7 @@ import { validateDomain, validateIP } from '../utils'
|
|||||||
|
|
||||||
// NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS
|
// NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
const [reverseProxy, setReverseProxy] = useRecoilState(servedFromReverseProxyState)
|
||||||
const [formatSelection, setFormatSelection] = useRecoilState(formatSelectionState)
|
const [formatSelection, setFormatSelection] = useRecoilState(formatSelectionState)
|
||||||
const [pathOverriding, setPathOverriding] = useRecoilState(pathOverridingState)
|
const [pathOverriding, setPathOverriding] = useRecoilState(pathOverridingState)
|
||||||
const [fileRenaming, setFileRenaming] = useRecoilState(fileRenamingState)
|
const [fileRenaming, setFileRenaming] = useRecoilState(fileRenamingState)
|
||||||
@@ -154,16 +157,27 @@ export default function Settings() {
|
|||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <InputAdornment position="start">ws://</InputAdornment>,
|
startAdornment: <InputAdornment position="start">ws://</InputAdornment>,
|
||||||
}}
|
}}
|
||||||
sx={{ mb: 2 }}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={1}>
|
<Grid item xs={12} md={1}>
|
||||||
<TextField
|
<TextField
|
||||||
|
disabled={reverseProxy}
|
||||||
fullWidth
|
fullWidth
|
||||||
label={i18n.t('serverPortTitle')}
|
label={i18n.t('serverPortTitle')}
|
||||||
defaultValue={serverPort}
|
defaultValue={serverPort}
|
||||||
onChange={(e) => serverPort$.next(e.currentTarget.value)}
|
onChange={(e) => serverPort$.next(e.currentTarget.value)}
|
||||||
error={isNaN(Number(serverPort)) || Number(serverPort) > 65535}
|
error={isNaN(Number(serverPort)) || Number(serverPort) > 65535}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked={reverseProxy}
|
||||||
|
onChange={() => setReverseProxy(state => !state)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n.t('servedFromReverseProxyCheckbox')}
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
import ViteYaml from '@modyfi/vite-plugin-yaml'
|
import ViteYaml from '@modyfi/vite-plugin-yaml'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { resolve } from 'path'
|
|
||||||
|
|
||||||
export default defineConfig(() => {
|
export default defineConfig(() => {
|
||||||
return {
|
return {
|
||||||
@@ -9,10 +8,9 @@ export default defineConfig(() => {
|
|||||||
react(),
|
react(),
|
||||||
ViteYaml(),
|
ViteYaml(),
|
||||||
],
|
],
|
||||||
root: resolve(__dirname, '.'),
|
base: '',
|
||||||
build: {
|
build: {
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
outDir: resolve(__dirname, 'dist'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -22,7 +22,8 @@ var (
|
|||||||
requireAuth bool
|
requireAuth bool
|
||||||
rpcSecret string
|
rpcSecret string
|
||||||
|
|
||||||
//go:embed frontend/dist
|
//go:embed frontend/dist/index.html
|
||||||
|
//go:embed frontend/dist/assets/*
|
||||||
frontend embed.FS
|
frontend embed.FS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ func RunBlocking(port int, frontend fs.FS) {
|
|||||||
mq: mq,
|
mq: mq,
|
||||||
})
|
})
|
||||||
|
|
||||||
// http-post
|
|
||||||
go gracefulShutdown(srv, &db)
|
go gracefulShutdown(srv, &db)
|
||||||
go autoPersist(time.Minute*5, &db)
|
go autoPersist(time.Minute*5, &db)
|
||||||
|
|
||||||
@@ -59,12 +58,9 @@ func newServer(c serverConfig) *http.Server {
|
|||||||
r.Use(cors.AllowAll().Handler)
|
r.Use(cors.AllowAll().Handler)
|
||||||
r.Use(middleware.Logger)
|
r.Use(middleware.Logger)
|
||||||
|
|
||||||
sh := middlewares.NewSpaHandler("index.html", c.frontend)
|
app := http.FileServer(http.FS(c.frontend))
|
||||||
sh.AddClientRoute("/settings")
|
|
||||||
sh.AddClientRoute("/archive")
|
|
||||||
sh.AddClientRoute("/login")
|
|
||||||
|
|
||||||
r.Get("/*", sh.Handler())
|
r.Mount("/", app)
|
||||||
|
|
||||||
// Archive routes
|
// Archive routes
|
||||||
r.Route("/archive", func(r chi.Router) {
|
r.Route("/archive", func(r chi.Router) {
|
||||||
|
|||||||
Reference in New Issue
Block a user