feat: query string dynamic app config (#3391)
* feat: query string dynamic app config * fix: bug fix and couple of very small improvements
This commit is contained in:
parent
f4be73b5cf
commit
af8bb27575
@ -46,13 +46,27 @@ function ViewerLayout({
|
|||||||
const onClickReturnButton = () => {
|
const onClickReturnButton = () => {
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
const dataSourceIdx = pathname.indexOf('/', 1);
|
const dataSourceIdx = pathname.indexOf('/', 1);
|
||||||
const search =
|
// const search =
|
||||||
dataSourceIdx === -1
|
// dataSourceIdx === -1
|
||||||
? undefined
|
// ? undefined
|
||||||
: `datasources=${pathname.substring(dataSourceIdx + 1)}`;
|
// : `datasources=${pathname.substring(dataSourceIdx + 1)}`;
|
||||||
|
|
||||||
|
// Todo: Handle parameters in a better way.
|
||||||
|
const query = new URLSearchParams(window.location.search);
|
||||||
|
const configUrl = query.get('configUrl');
|
||||||
|
|
||||||
|
const searchQuery = new URLSearchParams();
|
||||||
|
if (dataSourceIdx !== -1) {
|
||||||
|
searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configUrl) {
|
||||||
|
searchQuery.append('configUrl', configUrl);
|
||||||
|
}
|
||||||
|
|
||||||
navigate({
|
navigate({
|
||||||
pathname: '/',
|
pathname: '/',
|
||||||
search,
|
search: decodeURIComponent(searchQuery.toString()),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -116,7 +116,24 @@ Here are a list of some options available:
|
|||||||
if auth headers are used, a preflight request is required.
|
if auth headers are used, a preflight request is required.
|
||||||
- `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type.
|
- `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type.
|
||||||
- `showLoadingIndicator`: (default to true), if set to false, the loading indicator will not be shown when navigating between studies.
|
- `showLoadingIndicator`: (default to true), if set to false, the loading indicator will not be shown when navigating between studies.
|
||||||
|
- `dangerouslyUseDynamicConfig`: Dynamic config allows user to pass `configUrl` query string. This allows to load config without recompiling application. If the `configUrl` query string is passed, the worklist and modes will load from the referenced json rather than the default .env config. If there is no `configUrl` path provided, the default behaviour is used and there should not be any deviation from current user experience.<br/>
|
||||||
|
Points to consider while using `dangerouslyUseDynamicConfig`:<br/>
|
||||||
|
- User have to enable this feature by setting `dangerouslyUseDynamicConfig.enabled:true`. By default it is `false`.
|
||||||
|
- Regex helps to avoid easy exploit. Dafault is `/.*/`. Setup your own regex to choose a specific source of configuration only.
|
||||||
|
- System administrators can return `cross-origin: same-origin` with OHIF files to disallow any loading from other origin. It will block read access to resources loaded from a different origin to avoid potential attack vector.
|
||||||
|
- Example config:
|
||||||
|
```js
|
||||||
|
dangerouslyUseDynamicConfig: {
|
||||||
|
enabled: false,
|
||||||
|
regex: /.*/
|
||||||
|
}
|
||||||
|
```
|
||||||
|
> Example 1, to allow numbers and letters in an absolute or sub-path only.<br/>
|
||||||
|
`regex: /(0-9A-Za-z.]+)(\/[0-9A-Za-z.]+)*/`<br/>
|
||||||
|
Example 2, to restricts to either hosptial.com or othersite.com.<br/>
|
||||||
|
`regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/` <br/>
|
||||||
|
Example usage:<br/>
|
||||||
|
`http://localhost:3000/?configUrl=http://localhost:3000/config/example.json`<br/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,16 @@ window.config = {
|
|||||||
},
|
},
|
||||||
// filterQueryParam: false,
|
// filterQueryParam: false,
|
||||||
defaultDataSourceName: 'dicomweb',
|
defaultDataSourceName: 'dicomweb',
|
||||||
|
/* Dynamic config allows user to pass "configUrl" query string this allows to load config without recompiling application. The regex will ensure valid configuration source */
|
||||||
|
// dangerouslyUseDynamicConfig: {
|
||||||
|
// enabled: true,
|
||||||
|
// // regex will ensure valid configuration source and default is /.*/ which matches any character. To use this, setup your own regex to choose a specific source of configuration only.
|
||||||
|
// // Example 1, to allow numbers and letters in an absolute or sub-path only.
|
||||||
|
// // regex: /(0-9A-Za-z.]+)(\/[0-9A-Za-z.]+)*/
|
||||||
|
// // Example 2, to restricts to either hosptial.com or othersite.com.
|
||||||
|
// // regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/
|
||||||
|
// regex: /.*/,
|
||||||
|
// },
|
||||||
dataSources: [
|
dataSources: [
|
||||||
{
|
{
|
||||||
friendlyName: 'dcmjs DICOMWeb Server',
|
friendlyName: 'dcmjs DICOMWeb Server',
|
||||||
|
|||||||
@ -20,20 +20,28 @@ import {
|
|||||||
modes as defaultModes,
|
modes as defaultModes,
|
||||||
extensions as defaultExtensions,
|
extensions as defaultExtensions,
|
||||||
} from './pluginImports';
|
} from './pluginImports';
|
||||||
|
import loadDynamicConfig from './loadDynamicConfig';
|
||||||
|
|
||||||
/**
|
loadDynamicConfig(window.config).then(config_json => {
|
||||||
* Combine our appConfiguration with installed extensions and modes.
|
// Reset Dynamic config if defined
|
||||||
* In the future appConfiguration may contain modes added at runtime.
|
if (config_json !== null) {
|
||||||
* */
|
window.config = config_json;
|
||||||
const appProps = {
|
}
|
||||||
config: window ? window.config : {},
|
|
||||||
defaultExtensions,
|
|
||||||
defaultModes,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Create App */
|
/**
|
||||||
const app = React.createElement(App, appProps, null);
|
* Combine our appConfiguration with installed extensions and modes.
|
||||||
/** Render */
|
* In the future appConfiguration may contain modes added at runtime.
|
||||||
ReactDOM.render(app, document.getElementById('root'));
|
* */
|
||||||
|
const appProps = {
|
||||||
|
config: window ? window.config : {},
|
||||||
|
defaultExtensions,
|
||||||
|
defaultModes,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Create App */
|
||||||
|
const app = React.createElement(App, appProps, null);
|
||||||
|
/** Render */
|
||||||
|
ReactDOM.render(app, document.getElementById('root'));
|
||||||
|
});
|
||||||
|
|
||||||
export { history };
|
export { history };
|
||||||
|
|||||||
23
platform/viewer/src/loadDynamicConfig.js
Normal file
23
platform/viewer/src/loadDynamicConfig.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default async config => {
|
||||||
|
const useDynamicConfig = config.dangerouslyUseDynamicConfig;
|
||||||
|
|
||||||
|
// Check if dangerouslyUseDynamicConfig enabled
|
||||||
|
if (useDynamicConfig?.enabled) {
|
||||||
|
// If enabled then get configUrl query-string
|
||||||
|
let query = new URLSearchParams(window.location.search);
|
||||||
|
let configUrl = query.get('configUrl');
|
||||||
|
|
||||||
|
if (configUrl) {
|
||||||
|
// validate regex
|
||||||
|
const regex = useDynamicConfig.regex;
|
||||||
|
|
||||||
|
if (configUrl.match(regex)) {
|
||||||
|
const response = await fetch(configUrl);
|
||||||
|
return response.json();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -170,6 +170,7 @@ function _getQueryFilterValues(query, queryLimit) {
|
|||||||
// Offset...
|
// Offset...
|
||||||
offset:
|
offset:
|
||||||
Math.floor((pageNumber * resultsPerPage) / queryLimit) * (queryLimit - 1),
|
Math.floor((pageNumber * resultsPerPage) / queryLimit) * (queryLimit - 1),
|
||||||
|
config: query.get('configUrl'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// patientName: good
|
// patientName: good
|
||||||
|
|||||||
@ -345,12 +345,17 @@ function WorkList({
|
|||||||
// mode.routeName
|
// mode.routeName
|
||||||
// mode.routes[x].path
|
// mode.routes[x].path
|
||||||
// Don't specify default data source, and it should just be picked up... (this may not currently be the case)
|
// Don't specify default data source, and it should just be picked up... (this may not currently be the case)
|
||||||
// How do we know which params to pass? Today, it's just StudyInstanceUIDs
|
// How do we know which params to pass? Today, it's just StudyInstanceUIDs and configUrl if exists
|
||||||
|
const query = new URLSearchParams();
|
||||||
|
if (filterValues.configUrl) {
|
||||||
|
query.append('configUrl', filterValues.configUrl);
|
||||||
|
}
|
||||||
|
query.append('StudyInstanceUIDs', studyInstanceUid);
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={i}
|
key={i}
|
||||||
to={`${dataPath ? '../../' : ''}${mode.routeName}${dataPath ||
|
to={`${dataPath ? '../../' : ''}${mode.routeName}${dataPath ||
|
||||||
''}?StudyInstanceUIDs=${studyInstanceUid}`}
|
''}?${query.toString()}`}
|
||||||
// to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`}
|
// to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@ -531,6 +536,7 @@ const defaultFilterValues = {
|
|||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
resultsPerPage: 25,
|
resultsPerPage: 25,
|
||||||
datasources: '',
|
datasources: '',
|
||||||
|
configUrl: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function _tryParseInt(str, defaultValue) {
|
function _tryParseInt(str, defaultValue) {
|
||||||
@ -561,6 +567,7 @@ function _getQueryFilterValues(params) {
|
|||||||
pageNumber: _tryParseInt(params.get('pagenumber'), undefined),
|
pageNumber: _tryParseInt(params.get('pagenumber'), undefined),
|
||||||
resultsPerPage: _tryParseInt(params.get('resultsperpage'), undefined),
|
resultsPerPage: _tryParseInt(params.get('resultsperpage'), undefined),
|
||||||
datasources: params.get('datasources'),
|
datasources: params.get('datasources'),
|
||||||
|
configUrl: params.get('configurl'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete null/undefined keys
|
// Delete null/undefined keys
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user