mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-10-20 19:39:05 +00:00
partial working subpath
client side relative urls are not working yet
This commit is contained in:
parent
a6090cf987
commit
36b7a6c61d
11 changed files with 52 additions and 60 deletions
|
|
@ -7,9 +7,9 @@
|
|||
content="Dashboard for Multi Scrobbler"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="apple-touch-icon" href="%BASE_URL%logo192.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="%BASE_URL%icon.svg" />
|
||||
<link rel="manifest" href="%BASE_URL%manifest.json" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Multi Scrobbler</title>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,17 @@ export const initServer = async (args: ServerArgs, opts: ServerOptions = {}): Pr
|
|||
});
|
||||
|
||||
try {
|
||||
app.use(router.getRouter());
|
||||
const root = getRoot();
|
||||
// pathname the app is mounted under (e.g. '/myapp', or '/' at root)
|
||||
// decided at runtime from BASE_URL, not baked in at build time. See
|
||||
// vite.config.ts (relative asset base) and src/client/App.tsx (router mode).
|
||||
const local = root.get('localUrl');
|
||||
const localDefined = root.get('hasDefinedBaseUrl');
|
||||
const basePath = localDefined && local.pathname !== '/' ? local.pathname : '/';
|
||||
|
||||
// mount API routes under the same prefix the frontend's relative
|
||||
// `api/...` fetches resolve against
|
||||
app.use(basePath, router.getRouter());
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
|
|
@ -68,8 +78,6 @@ export const initServer = async (args: ServerArgs, opts: ServerOptions = {}): Pr
|
|||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
const root = getRoot();
|
||||
|
||||
if(root.get('disableWeb')) {
|
||||
logger.warn('API and Dashboard have been DISABLED. Note that any ingress sources (Webscrobbler, Listenbrainz/Lastfm Endpoint Sources, etc...) will be unusable');
|
||||
return;
|
||||
|
|
@ -83,8 +91,6 @@ export const initServer = async (args: ServerArgs, opts: ServerOptions = {}): Pr
|
|||
|
||||
const isProd = root.get('isProd');
|
||||
const port = root.get('port');
|
||||
const local = root.get('localUrl');
|
||||
const localDefined = root.get('hasDefinedBaseUrl');
|
||||
|
||||
const addy = getAddress();
|
||||
const addresses: string[] = [];
|
||||
|
|
@ -113,26 +119,33 @@ export const initServer = async (args: ServerArgs, opts: ServerOptions = {}): Pr
|
|||
|
||||
app.use('/docs', express.static(path.resolve(projectRootDir, `./docsite/build`)));
|
||||
|
||||
if(process.env.USE_HASH_ROUTER === undefined) {
|
||||
process.env.USE_HASH_ROUTER = root.get('isSubPath').toString();
|
||||
}
|
||||
|
||||
app.get(/^\/next$/, (_, res) => {
|
||||
res.redirect("/next/")
|
||||
});
|
||||
|
||||
const useHashRouter = root.get('isSubPath');
|
||||
const viteExpressOptions: Parameters<typeof ViteExpress.config>[0] = {
|
||||
mode: isProd ? 'production' : 'development',
|
||||
inlineViteConfig: {
|
||||
base: localDefined && local.pathname !== '/' ? local.toString() : '/'
|
||||
}
|
||||
base: basePath
|
||||
},
|
||||
// tell the frontend at request time whether to use HashRouter --
|
||||
// it can't be a build-time constant since the same build is
|
||||
// shipped for every deployment (root or subpath)
|
||||
transformer: (html) => html.replace(
|
||||
'<head>',
|
||||
`<head><script>window.__MS_RUNTIME__=${JSON.stringify({useHashRouter})};</script>`
|
||||
)
|
||||
};
|
||||
|
||||
if(!isProd) {
|
||||
const conf = await ViteExpress.getViteConfig();
|
||||
viteExpressOptions.inlineViteConfig = {
|
||||
...viteExpressOptions.inlineViteConfig,
|
||||
...conf
|
||||
...conf,
|
||||
// conf.base is the frontend's relative asset base ('./'), not
|
||||
// the Express mount prefix -- keep our runtime-computed one
|
||||
base: basePath
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ const routesNested: RouteObject[] = [
|
|||
];
|
||||
|
||||
const genRouter = () => {
|
||||
const useHashRouter = __USE_HASH_ROUTER__ === 'true';
|
||||
const useHashRouter = window.__MS_RUNTIME__?.useHashRouter ?? false;
|
||||
return useHashRouter ? createHashRouter(routesNested) : createBrowserRouter(routesNested);
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ const router = genRouter();
|
|||
|
||||
export const sseProviderOptions = {
|
||||
key: ['events'],
|
||||
url: '/api/events?next=true'
|
||||
url: 'api/events?next=true'
|
||||
}
|
||||
|
||||
function App() {
|
||||
|
|
|
|||
|
|
@ -469,15 +469,15 @@ export const ActivityStateActions = (props: {activity: PlayApiCommonDetailed, co
|
|||
mutationKey: ['playAction', props.activity.uid],
|
||||
mutationFn: (data: {action: string, context?: QueueContext}) => {
|
||||
if(data.action === 'queue') {
|
||||
return ky.post(`/api/components/${props.activity.componentId}/plays/${props.activity.uid}/queue`,{
|
||||
return ky.post(`api/components/${props.activity.componentId}/plays/${props.activity.uid}/queue`,{
|
||||
json: data.context ?? {}
|
||||
});
|
||||
}
|
||||
if(data.action === 'delete') {
|
||||
return ky.delete(`/api/components/${props.activity.componentId}/plays/${props.activity.uid}`);
|
||||
return ky.delete(`api/components/${props.activity.componentId}/plays/${props.activity.uid}`);
|
||||
}
|
||||
const realAction = data.action === 'cancel' ? 'queue' : data.action;
|
||||
return ky.delete(`/api/components/${props.activity.componentId}/plays/${props.activity.uid}/${realAction}`);
|
||||
return ky.delete(`api/components/${props.activity.componentId}/plays/${props.activity.uid}/${realAction}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const LogsFetchable = (props: {settings?: LogOutputConfig, streamable?: b
|
|||
|
||||
const connection = useSSE<{stream: MinLogInfo}>({
|
||||
key: ['stream'],
|
||||
url: "/api/logs/stream",
|
||||
url: "api/logs/stream",
|
||||
enabled: streamable,
|
||||
events: {
|
||||
stream: (data) => {
|
||||
|
|
@ -84,7 +84,7 @@ export const LogsFetchable = (props: {settings?: LogOutputConfig, streamable?: b
|
|||
});
|
||||
|
||||
const mutateLogSettings = useMutation({
|
||||
mutationFn: (settings: Partial<LogOutputConfig>) => ky.put('/api/logs', {json: settings}),
|
||||
mutationFn: (settings: Partial<LogOutputConfig>) => ky.put('api/logs', {json: settings}),
|
||||
onSuccess: (sentData, variables) => {
|
||||
if(variables.level !== undefined) {
|
||||
setLogLevel(variables.level);
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const dialog = createOverlay<AuthDialogProps>((props) => {
|
|||
});
|
||||
const {mutate, isPending: mutateIsPending, isSuccess} = useMutation({
|
||||
mutationKey: ['authChange', data.id],
|
||||
mutationFn: () => ky.post(`/api/components/${data.id}/auth`)
|
||||
mutationFn: () => ky.post(`api/components/${data.id}/auth`)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -193,7 +193,7 @@ export const ComponentStateBadgeActionable = (props: Omit<ComponentProps<typeof
|
|||
|
||||
const {mutate, isPending, variables, isSuccess} = useMutation({
|
||||
mutationKey: ['stateChange', componentId],
|
||||
mutationFn: (action: ComponentStateBody['state']) => ky.post(`/api/components/${componentId}/state`,{
|
||||
mutationFn: (action: ComponentStateBody['state']) => ky.post(`api/components/${componentId}/state`,{
|
||||
json: {state: action, reason: 'User initiated from UI'}
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ export const ListContainerFilterable = (props: { componentId: number, componentT
|
|||
mutationKey: ['retryBulk', props.componentId, filters],
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
mutationFn: (data: { action: string, context?: QueueContext }) => {
|
||||
return ky.post<{ filters: QueryPlaysOptsJsonRefreshable, context?: QueueContext }>(`/api/components/${props.componentId}/plays/queue`, {
|
||||
return ky.post<{ filters: QueryPlaysOptsJsonRefreshable, context?: QueueContext }>(`api/components/${props.componentId}/plays/queue`, {
|
||||
json: {
|
||||
filters: {
|
||||
...filters,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const SettingsContainer = () => {
|
|||
},[setTheme]);
|
||||
|
||||
const {mutate, isPending, variables, isSuccess} = useMutation({
|
||||
mutationFn: (clearType: CacheClearType) => ky.delete(`/api/cache/${clearType}`)
|
||||
mutationFn: (clearType: CacheClearType) => ky.delete(`api/cache/${clearType}`)
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -78,4 +78,4 @@ export const isoToHuman = (iso?: string | null, opts?: DateFormatOptions) => {
|
|||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export const baseUrl = '/api/';
|
||||
export const baseUrl = 'api/';
|
||||
7
src/client/vite-env.d.ts
vendored
7
src/client/vite-env.d.ts
vendored
|
|
@ -1,3 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
declare const __USE_HASH_ROUTER__: boolean
|
||||
|
||||
// Injected server-side per-request (src/backend/server/index.ts) since the
|
||||
// hash-vs-browser router choice depends on the runtime BASE_URL, not the build.
|
||||
interface Window {
|
||||
__MS_RUNTIME__?: { useHashRouter: boolean };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,15 @@
|
|||
import react,{ reactCompilerPreset } from '@vitejs/plugin-react';
|
||||
import babel from '@rolldown/plugin-babel';
|
||||
import normalizeUrl from "normalize-url";
|
||||
import { defineConfig } from 'vite';
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
||||
const QUOTES_UNWRAP_REGEX: RegExp = new RegExp(/^"(.*)"$/);
|
||||
export const generateBaseURL = (userUrl: string | undefined): URL => {
|
||||
let cleanUserUrl = userUrl.trim();
|
||||
if (QUOTES_UNWRAP_REGEX.test(cleanUserUrl)) {
|
||||
const results = cleanUserUrl.match(QUOTES_UNWRAP_REGEX);
|
||||
cleanUserUrl = results[1];
|
||||
}
|
||||
const base = normalizeUrl(cleanUserUrl, { removeSingleSlash: true });
|
||||
const u = new URL(base);
|
||||
if (u.port === '') {
|
||||
if (u.protocol === 'https:') {
|
||||
u.port = '443';
|
||||
} else if (userUrl.includes(`${u.hostname}:80`)) {
|
||||
u.port = '80';
|
||||
}
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
// Relative base so the built assets work from any mount path (root or a
|
||||
// runtime-chosen subpath) without needing to know it at build time
|
||||
//
|
||||
// the server picks the actual mount path/router mode at runtime, see
|
||||
// src/backend/server/index.ts and src/client/App.tsx.
|
||||
export default defineConfig(() => {
|
||||
let baseUrlStr = '/';
|
||||
if (process.env.BASE_URL !== undefined && process.env.BASE_URL !== '') {
|
||||
const baseUrl = generateBaseURL(process.env.BASE_URL);
|
||||
if (baseUrl.pathname !== '/') {
|
||||
baseUrlStr = baseUrl.toString();
|
||||
}
|
||||
}
|
||||
console.debug(`[VITE] BASE_URL ENV: ${process.env.BASE_URL} | Base Url String: ${baseUrlStr}`);
|
||||
return {
|
||||
server: {
|
||||
allowedHosts: (true as true),
|
||||
|
|
@ -43,7 +20,7 @@ export default defineConfig(() => {
|
|||
esbuild: {
|
||||
minifyIdentifiers: false
|
||||
},
|
||||
base: baseUrlStr,
|
||||
base: './',
|
||||
plugins: [
|
||||
react(),
|
||||
babel({
|
||||
|
|
@ -63,9 +40,6 @@ export default defineConfig(() => {
|
|||
sourcemap: true
|
||||
},
|
||||
},
|
||||
define: {
|
||||
"__USE_HASH_ROUTER__": JSON.stringify((process.env.USE_HASH_ROUTER ?? false))
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue