mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-17 18:26:32 +00:00
Plugin source URLs (#10000)
* Better handling of URLs when loading plugin source - Handle complex URLs more cleanly - Support loading from actual external host - Support loading with specified port * Fix URL rendering - handle "local" and "remote" components * Use default host if not provided * Simplify code
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { generateUrl } from '../../functions/urls';
|
import { generateUrl } from '../../functions/urls';
|
||||||
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load an external plugin source from a URL.
|
* Load an external plugin source from a URL.
|
||||||
@@ -32,13 +33,22 @@ export async function findExternalPluginFunction(
|
|||||||
source: string,
|
source: string,
|
||||||
functionName: string
|
functionName: string
|
||||||
): Promise<Function | null> {
|
): Promise<Function | null> {
|
||||||
// The source URL may also include the function name divided by a colon
|
const { getHost } = useLocalState.getState();
|
||||||
// otherwise the provided function name will be used
|
|
||||||
if (source.includes(':')) {
|
// Extract pathstring from the source URL
|
||||||
[source, functionName] = source.split(':');
|
// Use the specified host unless the source is already a full URL
|
||||||
|
const url = new URL(source, getHost());
|
||||||
|
|
||||||
|
// If the pathname contains a ':' character, it indicates a function name
|
||||||
|
// but we need to remove it for the URL lookup to work correctly
|
||||||
|
if (url.pathname.includes(':')) {
|
||||||
|
const parts = url.pathname.split(':');
|
||||||
|
source = parts[0]; // Use the first part as the source URL
|
||||||
|
functionName = parts[1] || functionName; // Use the second part as the
|
||||||
|
url.pathname = source; // Update the pathname to the source URL
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = await loadExternalPluginSource(source);
|
const module = await loadExternalPluginSource(url.toString());
|
||||||
|
|
||||||
if (module?.[functionName]) {
|
if (module?.[functionName]) {
|
||||||
return module[functionName];
|
return module[functionName];
|
||||||
|
@@ -9,6 +9,7 @@ import { type Root, createRoot } from 'react-dom/client';
|
|||||||
import { api, queryClient } from '../../App';
|
import { api, queryClient } from '../../App';
|
||||||
import { ApiProvider } from '../../contexts/ApiContext';
|
import { ApiProvider } from '../../contexts/ApiContext';
|
||||||
import { LanguageContext } from '../../contexts/LanguageContext';
|
import { LanguageContext } from '../../contexts/LanguageContext';
|
||||||
|
import { useLocalState } from '../../states/LocalState';
|
||||||
import { Boundary } from '../Boundary';
|
import { Boundary } from '../Boundary';
|
||||||
import { findExternalPluginFunction } from './PluginSource';
|
import { findExternalPluginFunction } from './PluginSource';
|
||||||
|
|
||||||
@@ -43,19 +44,17 @@ export default function RemoteComponent({
|
|||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
const sourceFile = useMemo(() => {
|
const func: string = useMemo(() => {
|
||||||
return source.split(':')[0];
|
// Attempt to extract the function name from the source
|
||||||
}, [source]);
|
const { getHost } = useLocalState.getState();
|
||||||
|
const url = new URL(source, getHost());
|
||||||
|
|
||||||
// Determine the function to call in the external plugin source
|
if (url.pathname.includes(':')) {
|
||||||
const functionName = useMemo(() => {
|
const parts = url.pathname.split(':');
|
||||||
// The "source" string may contain a function name, e.g. "source.js:myFunction"
|
return parts[1] || defaultFunctionName; // Use the second part as the function name, or fallback to default
|
||||||
if (source.includes(':')) {
|
} else {
|
||||||
return source.split(':')[1];
|
return defaultFunctionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// By default, return the default function name
|
|
||||||
return defaultFunctionName;
|
|
||||||
}, [source, defaultFunctionName]);
|
}, [source, defaultFunctionName]);
|
||||||
|
|
||||||
const reloadPluginContent = useCallback(() => {
|
const reloadPluginContent = useCallback(() => {
|
||||||
@@ -68,8 +67,8 @@ export default function RemoteComponent({
|
|||||||
reloadContent: reloadPluginContent
|
reloadContent: reloadPluginContent
|
||||||
};
|
};
|
||||||
|
|
||||||
if (sourceFile && functionName) {
|
if (source && defaultFunctionName) {
|
||||||
findExternalPluginFunction(sourceFile, functionName)
|
findExternalPluginFunction(source, func)
|
||||||
.then((func) => {
|
.then((func) => {
|
||||||
if (!!func) {
|
if (!!func) {
|
||||||
try {
|
try {
|
||||||
@@ -99,30 +98,28 @@ export default function RemoteComponent({
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setRenderingError(`${sourceFile}:${functionName}`);
|
setRenderingError(`${source} / ${func}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((_error) => {
|
.catch((_error) => {
|
||||||
console.error(
|
console.error(
|
||||||
`ERR: Failed to load remove plugin function: ${sourceFile}:${functionName}`
|
`ERR: Failed to load remote plugin function: ${source} /${func}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setRenderingError(
|
setRenderingError(
|
||||||
`${t`Invalid source or function name`} - ${sourceFile}:${functionName}`
|
`${t`Invalid source or function name`} - ${source} /${func}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [componentRef, rootElement, sourceFile, functionName, context]);
|
}, [componentRef, rootElement, source, defaultFunctionName, context]);
|
||||||
|
|
||||||
// Reload the plugin content dynamically
|
// Reload the plugin content dynamically
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reloadPluginContent();
|
reloadPluginContent();
|
||||||
}, [sourceFile, functionName, context, rootElement]);
|
}, [source, func, context, rootElement]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Boundary
|
<Boundary label={identifierString(`RemoteComponent-${func}`)}>
|
||||||
label={identifierString(`RemoteComponent-${sourceFile}-${functionName}`)}
|
|
||||||
>
|
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
{renderingError && (
|
{renderingError && (
|
||||||
<Alert
|
<Alert
|
||||||
|
Reference in New Issue
Block a user