2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 20:16:44 +00:00

Fix URL generation error (#8630)

- Implement better helper function for handling URL generation
- Handle errors in said function
This commit is contained in:
Oliver 2024-12-06 14:44:13 +11:00 committed by GitHub
parent 9f120ef76f
commit 82bce192e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 42 additions and 33 deletions

View File

@ -3,8 +3,8 @@ import { IconUserStar } from '@tabler/icons-react';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
import { generateUrl } from '../../functions/urls';
import { useServerApiState } from '../../states/ApiState'; import { useServerApiState } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { ModelInformationDict } from '../render/ModelType'; import { ModelInformationDict } from '../render/ModelType';
import { ActionButton } from './ActionButton'; import { ActionButton } from './ActionButton';
@ -56,16 +56,14 @@ export default function AdminButton(props: Readonly<AdminButtonProps>) {
const openAdmin = useCallback( const openAdmin = useCallback(
(event: any) => { (event: any) => {
const modelDef = ModelInformationDict[props.model]; const modelDef = ModelInformationDict[props.model];
const host = useLocalState.getState().host;
if (!modelDef.admin_url) { if (!modelDef.admin_url) {
return; return;
} }
// Generate the URL for the admin interface // Generate the URL for the admin interface
const url = new URL( const url = generateUrl(
`${server.server.django_admin}${modelDef.admin_url}${props.id}/`, `${server.server.django_admin}${modelDef.admin_url}${props.id}/`
host
); );
if (event?.ctrlKey || event?.shiftKey) { if (event?.ctrlKey || event?.shiftKey) {

View File

@ -8,9 +8,9 @@ import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
import { extractAvailableFields } from '../../functions/forms'; import { extractAvailableFields } from '../../functions/forms';
import { generateUrl } from '../../functions/urls';
import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useCreateApiFormModal } from '../../hooks/UseForm';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { useUserSettingsState } from '../../states/SettingsState'; import { useUserSettingsState } from '../../states/SettingsState';
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
import { ActionDropdown } from '../items/ActionDropdown'; import { ActionDropdown } from '../items/ActionDropdown';
@ -28,8 +28,6 @@ export function PrintingActions({
enableReports?: boolean; enableReports?: boolean;
modelType?: ModelType; modelType?: ModelType;
}) { }) {
const { host } = useLocalState.getState();
const userSettings = useUserSettingsState(); const userSettings = useUserSettingsState();
const enabled = useMemo(() => items.length > 0, [items]); const enabled = useMemo(() => items.length > 0, [items]);
@ -116,7 +114,7 @@ export function PrintingActions({
if (response.output) { if (response.output) {
// An output file was generated // An output file was generated
const url = new URL(response.output, host); const url = generateUrl(response.output);
window.open(url.toString(), '_blank'); window.open(url.toString(), '_blank');
} }
} }
@ -154,7 +152,7 @@ export function PrintingActions({
if (response.output) { if (response.output) {
// An output file was generated // An output file was generated
const url = new URL(response.output, host); const url = generateUrl(response.output);
window.open(url.toString(), '_blank'); window.open(url.toString(), '_blank');
} }
} }

View File

@ -6,6 +6,7 @@
import { Image, type ImageProps, Skeleton, Stack } from '@mantine/core'; import { Image, type ImageProps, Skeleton, Stack } from '@mantine/core';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { generateUrl } from '../../functions/urls';
import { useLocalState } from '../../states/LocalState'; import { useLocalState } from '../../states/LocalState';
interface ApiImageProps extends ImageProps { interface ApiImageProps extends ImageProps {
@ -19,7 +20,7 @@ export function ApiImage(props: Readonly<ApiImageProps>) {
const { host } = useLocalState.getState(); const { host } = useLocalState.getState();
const imageUrl = useMemo(() => { const imageUrl = useMemo(() => {
return new URL(props.src, host).toString(); return generateUrl(props.src, host);
}, [host, props.src]); }, [host, props.src]);
return ( return (

View File

@ -10,8 +10,7 @@ import {
IconPhoto IconPhoto
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { type ReactNode, useMemo } from 'react'; import { type ReactNode, useMemo } from 'react';
import { generateUrl } from '../../functions/urls';
import { useLocalState } from '../../states/LocalState';
/** /**
* Return an icon based on the provided filename * Return an icon based on the provided filename
@ -61,16 +60,13 @@ export function AttachmentLink({
}>): ReactNode { }>): ReactNode {
const text = external ? attachment : attachment.split('/').pop(); const text = external ? attachment : attachment.split('/').pop();
const host = useLocalState((s) => s.host);
const url = useMemo(() => { const url = useMemo(() => {
if (external) { if (external) {
return attachment; return attachment;
} }
const u = new URL(attachment, host); return generateUrl(attachment);
return u.toString(); }, [attachment, external]);
}, [host, attachment, external]);
return ( return (
<Group justify='left' gap='sm' wrap='nowrap'> <Group justify='left' gap='sm' wrap='nowrap'>

View File

@ -15,8 +15,8 @@ import { useQuery } from '@tanstack/react-query';
import { api } from '../../App'; import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { generateUrl } from '../../functions/urls';
import { apiUrl, useServerApiState } from '../../states/ApiState'; import { apiUrl, useServerApiState } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { CopyButton } from '../buttons/CopyButton'; import { CopyButton } from '../buttons/CopyButton';
import { StylishText } from '../items/StylishText'; import { StylishText } from '../items/StylishText';
@ -35,7 +35,6 @@ export function AboutInvenTreeModal({
modalBody: string; modalBody: string;
}>) { }>) {
const [user] = useUserState((state) => [state.user]); const [user] = useUserState((state) => [state.user]);
const { host } = useLocalState.getState();
const [server] = useServerApiState((state) => [state.server]); const [server] = useServerApiState((state) => [state.server]);
if (user?.is_staff != true) if (user?.is_staff != true)
@ -137,7 +136,7 @@ export function AboutInvenTreeModal({
{ {
ref: 'api', ref: 'api',
title: <Trans>API Version</Trans>, title: <Trans>API Version</Trans>,
link: new URL('/api-doc/', host).toString(), link: generateUrl('/api-doc/'),
copy: true copy: true
}, },
{ {

View File

@ -1,11 +1,9 @@
import { useLocalState } from '../../states/LocalState'; import { generateUrl } from '../../functions/urls';
/* /*
* Load an external plugin source from a URL. * Load an external plugin source from a URL.
*/ */
export async function loadExternalPluginSource(source: string) { export async function loadExternalPluginSource(source: string) {
const host = useLocalState.getState().host;
source = source.trim(); source = source.trim();
// If no source is provided, clear the plugin content // If no source is provided, clear the plugin content
@ -13,7 +11,7 @@ export async function loadExternalPluginSource(source: string) {
return null; return null;
} }
const url = new URL(source, host).toString(); const url = generateUrl(source);
const module = await import(/* @vite-ignore */ url) const module = await import(/* @vite-ignore */ url)
.catch((error) => { .catch((error) => {

View File

@ -126,6 +126,7 @@ const icons = {
units: IconRulerMeasure, units: IconRulerMeasure,
keywords: IconTag, keywords: IconTag,
status: IconInfoCircle, status: IconInfoCircle,
status_custom_key: IconInfoCircle,
edit: IconEdit, edit: IconEdit,
info: IconInfoCircle, info: IconInfoCircle,
exclamation: IconExclamationCircle, exclamation: IconExclamationCircle,
@ -291,6 +292,3 @@ export function InvenTreeIcon(props: Readonly<InvenTreeIconProps>) {
return <Icon {...props.iconProps} />; return <Icon {...props.iconProps} />;
} }
function IconShapes(props: TablerIconProps): Element {
throw new Error('Function not implemented.');
}

View File

@ -1,6 +1,7 @@
import { ModelInformationDict } from '../components/render/ModelType'; import { ModelInformationDict } from '../components/render/ModelType';
import type { ModelType } from '../enums/ModelType'; import type { ModelType } from '../enums/ModelType';
import { base_url } from '../main'; import { base_url } from '../main';
import { useLocalState } from '../states/LocalState';
/** /**
* Returns the detail view URL for a given model type * Returns the detail view URL for a given model type
@ -30,3 +31,26 @@ export function getDetailUrl(
console.error(`No detail URL found for model ${model} <${pk}>`); console.error(`No detail URL found for model ${model} <${pk}>`);
return ''; return '';
} }
/**
* Returns the edit view URL for a given model type
*/
export function generateUrl(url: string | URL, base?: string): string {
const { host } = useLocalState.getState();
let newUrl: string | URL = url;
try {
if (base) {
newUrl = new URL(url, base).toString();
} else if (host) {
newUrl = new URL(url, host).toString();
} else {
newUrl = url.toString();
}
} catch (e: any) {
console.error(`ERR: generateURL failed. url='${url}', base='${base}'`);
}
return newUrl.toString();
}

View File

@ -2,8 +2,8 @@ import { create } from 'zustand';
import { api } from '../App'; import { api } from '../App';
import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ApiEndpoints } from '../enums/ApiEndpoints';
import { generateUrl } from '../functions/urls';
import { apiUrl } from './ApiState'; import { apiUrl } from './ApiState';
import { useLocalState } from './LocalState';
type IconPackage = { type IconPackage = {
name: string; name: string;
@ -34,8 +34,6 @@ export const useIconState = create<IconState>()((set, get) => ({
fetchIcons: async () => { fetchIcons: async () => {
if (get().hasLoaded) return; if (get().hasLoaded) return;
const host = useLocalState.getState().host;
const packs = await api.get(apiUrl(ApiEndpoints.icons)); const packs = await api.get(apiUrl(ApiEndpoints.icons));
await Promise.all( await Promise.all(
@ -43,8 +41,7 @@ export const useIconState = create<IconState>()((set, get) => ({
const fontName = `inventree-icon-font-${pack.prefix}`; const fontName = `inventree-icon-font-${pack.prefix}`;
const src = Object.entries(pack.fonts as Record<string, string>) const src = Object.entries(pack.fonts as Record<string, string>)
.map( .map(
([format, url]) => ([format, url]) => `url(${generateUrl(url)}) format("${format}")`
`url(${new URL(url, host).toString()}) format("${format}")`
) )
.join(',\n'); .join(',\n');
const font = new FontFace(fontName, `${src};`); const font = new FontFace(fontName, `${src};`);