Compare commits

..

9 Commits

8 changed files with 172 additions and 54 deletions

View File

@ -33,6 +33,7 @@
"html-react-parser": "1.4.8", "html-react-parser": "1.4.8",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"http-proxy-middleware": "2.0.6", "http-proxy-middleware": "2.0.6",
"jwt-decode": "3.1.2",
"rapidoc": "9.3.4", "rapidoc": "9.3.4",
"react": "18.0.0", "react": "18.0.0",
"react-ace": "10.1.0", "react-ace": "10.1.0",

View File

@ -33,7 +33,8 @@ import CssBaseline from "@mui/material/CssBaseline";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {ThemeProvider} from "@mui/material/styles"; import {ThemeProvider} from "@mui/material/styles";
import {LicenseInfo} from "@mui/x-license-pro"; import {LicenseInfo} from "@mui/x-license-pro";
import React, {JSXElementConstructor, Key, ReactElement, useContext, useEffect, useState,} from "react"; import jwt_decode from "jwt-decode";
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
import {useCookies} from "react-cookie"; import {useCookies} from "react-cookie";
import {Navigate, Route, Routes, useLocation,} from "react-router-dom"; import {Navigate, Route, Routes, useLocation,} from "react-router-dom";
import {Md5} from "ts-md5/dist/md5"; import {Md5} from "ts-md5/dist/md5";
@ -57,11 +58,11 @@ import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
const qController = Client.getInstance(); const qController = Client.getInstance();
export const SESSION_ID_COOKIE_NAME = "sessionId"; export const SESSION_UUID_COOKIE_NAME = "sessionUUID";
export default function App() export default function App()
{ {
const [, setCookie, removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]); const [, setCookie, removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]);
const {user, getAccessTokenSilently, logout} = useAuth0(); const {user, getAccessTokenSilently, logout} = useAuth0();
const [loadingToken, setLoadingToken] = useState(false); const [loadingToken, setLoadingToken] = useState(false);
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false); const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
@ -69,8 +70,55 @@ export default function App()
const [branding, setBranding] = useState({} as QBrandingMetaData); const [branding, setBranding] = useState({} as QBrandingMetaData);
const [metaData, setMetaData] = useState({} as QInstance); const [metaData, setMetaData] = useState({} as QInstance);
const [needLicenseKey, setNeedLicenseKey] = useState(true); const [needLicenseKey, setNeedLicenseKey] = useState(true);
const [loggedInUser, setLoggedInUser] = useState({} as { name?: string, email?: string });
const [defaultRoute, setDefaultRoute] = useState("/no-apps"); const [defaultRoute, setDefaultRoute] = useState("/no-apps");
const shouldStoreNewToken = (newToken: string, oldToken: string): boolean =>
{
if (!oldToken)
{
return (true);
}
try
{
const oldJSON: any = jwt_decode(oldToken);
const newJSON: any = jwt_decode(newToken);
////////////////////////////////////////////////////////////////////////////////////
// if the old (local storage) token is expired, then we need to store the new one //
////////////////////////////////////////////////////////////////////////////////////
const oldExp = oldJSON["exp"];
if(oldExp * 1000 < (new Date().getTime()))
{
console.log("Access token in local storage was expired.");
return (true);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// remove the exp & iat values from what we compare - as they are always different from auth0 //
// note, this is only deleting them from what we compare, not from what we'd store. //
////////////////////////////////////////////////////////////////////////////////////////////////
delete newJSON["exp"]
delete newJSON["iat"]
delete oldJSON["exp"]
delete oldJSON["iat"]
const different = JSON.stringify(newJSON) !== JSON.stringify(oldJSON);
if(different)
{
console.log("Latest access token from auth0 has changed vs localStorage.");
}
return (different);
}
catch(e)
{
console.log("Caught in shouldStoreNewToken: " + e)
}
return (true);
};
useEffect(() => useEffect(() =>
{ {
if (loadingToken) if (loadingToken)
@ -92,20 +140,38 @@ export default function App()
{ {
console.log("Loading token from auth0..."); console.log("Loading token from auth0...");
const accessToken = await getAccessTokenSilently(); const accessToken = await getAccessTokenSilently();
qController.setAuthorizationHeaderValue("Bearer " + accessToken);
///////////////////////////////////////////////////////////////////////////////// const lsAccessToken = localStorage.getItem("accessToken");
// we've stopped using session id cook with auth0, so make sure it is not set. // if (shouldStoreNewToken(accessToken, lsAccessToken))
///////////////////////////////////////////////////////////////////////////////// {
removeCookie(SESSION_ID_COOKIE_NAME); console.log("Sending accessToken to backend, requesting a sessionUUID...");
const newSessionUuid = await qController.manageSession(accessToken, null);
setCookie(SESSION_UUID_COOKIE_NAME, newSessionUuid, {path: "/"});
localStorage.setItem("accessToken", accessToken);
}
/*
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// todo#authHeader - this is our quick rollback plan - if we feel the need to stop using the cookie approach. //
// we turn off the shouldStoreNewToken block above, and turn on these 2 lines. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
localStorage.removeItem("accessToken");
*/
setIsFullyAuthenticated(true); setIsFullyAuthenticated(true);
qController.setGotAuthentication();
qController.setAuthorizationHeaderValue("Bearer " + accessToken);
setLoggedInUser(user);
console.log("Token load complete."); console.log("Token load complete.");
} }
catch (e) catch (e)
{ {
console.log(`Error loading token: ${JSON.stringify(e)}`); console.log(`Error loading token: ${JSON.stringify(e)}`);
qController.clearAuthenticationMetaDataLocalStorage(); qController.clearAuthenticationMetaDataLocalStorage();
localStorage.removeItem("accessToken")
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
logout(); logout();
return; return;
} }
@ -116,9 +182,9 @@ export default function App()
// use a random token if anonymous or mock // // use a random token if anonymous or mock //
///////////////////////////////////////////// /////////////////////////////////////////////
console.log("Generating random token..."); console.log("Generating random token...");
qController.setAuthorizationHeaderValue(null); qController.setAuthorizationHeaderValue(Md5.hashStr(`${new Date()}`));
setIsFullyAuthenticated(true); setIsFullyAuthenticated(true);
setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"}); setCookie(SESSION_UUID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
console.log("Token generation complete."); console.log("Token generation complete.");
return; return;
} }
@ -149,7 +215,7 @@ export default function App()
const [needToLoadRoutes, setNeedToLoadRoutes] = useState(true); const [needToLoadRoutes, setNeedToLoadRoutes] = useState(true);
const [sideNavRoutes, setSideNavRoutes] = useState([]); const [sideNavRoutes, setSideNavRoutes] = useState([]);
const [appRoutes, setAppRoutes] = useState(null as any); const [appRoutes, setAppRoutes] = useState(null as any);
const [pathToLabelMap, setPathToLabelMap] = useState({} as {[path: string]: string}); const [pathToLabelMap, setPathToLabelMap] = useState({} as { [path: string]: string });
//////////////////////////////////////////// ////////////////////////////////////////////
// load qqq meta data to make more routes // // load qqq meta data to make more routes //
@ -267,14 +333,14 @@ export default function App()
name: `${app.label}`, name: `${app.label}`,
key: app.name, key: app.name,
route: path, route: path,
component: <RecordQuery table={table} key={table.name}/>, component: <RecordQuery table={table} key={table.name} />,
}); });
routeList.push({ routeList.push({
name: `${app.label}`, name: `${app.label}`,
key: app.name, key: app.name,
route: `${path}/savedFilter/:id`, route: `${path}/savedFilter/:id`,
component: <RecordQuery table={table} key={table.name}/>, component: <RecordQuery table={table} key={table.name} />,
}); });
routeList.push({ routeList.push({
@ -429,11 +495,11 @@ export default function App()
let profileRoutes = {}; let profileRoutes = {};
const gravatarBase = "https://www.gravatar.com/avatar/"; const gravatarBase = "https://www.gravatar.com/avatar/";
const hash = Md5.hashStr(user?.email || "user"); const hash = Md5.hashStr(loggedInUser?.email || "user");
const profilePicture = `${gravatarBase}${hash}`; const profilePicture = `${gravatarBase}${hash}`;
profileRoutes = { profileRoutes = {
type: "collapse", type: "collapse",
name: user?.name, name: loggedInUser?.name ?? "Anonymous",
key: "username", key: "username",
noCollapse: true, noCollapse: true,
icon: <Avatar src={profilePicture} alt="{user?.name}" />, icon: <Avatar src={profilePicture} alt="{user?.name}" />,
@ -469,7 +535,7 @@ export default function App()
} }
const pathToLabelMap: {[path: string]: string} = {} const pathToLabelMap: {[path: string]: string} = {}
for(let i =0; i<appRoutesList.length; i++) for (let i = 0; i < appRoutesList.length; i++)
{ {
const route = appRoutesList[i]; const route = appRoutesList[i];
pathToLabelMap[route.route] = route.name; pathToLabelMap[route.route] = route.name;
@ -495,7 +561,10 @@ export default function App()
{ {
if ((e as QException).status === "401") if ((e as QException).status === "401")
{ {
console.log("Exception is a QException with status = 401. Clearing some of localStorage & cookies");
qController.clearAuthenticationMetaDataLocalStorage(); qController.clearAuthenticationMetaDataLocalStorage();
localStorage.removeItem("accessToken")
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// todo - this is auth0 logout... make more generic // // todo - this is auth0 logout... make more generic //
@ -596,7 +665,7 @@ export default function App()
}}> }}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<CommandMenu metaData={metaData}/> <CommandMenu metaData={metaData} />
<Sidenav <Sidenav
color={sidenavColor} color={sidenavColor}
icon={branding.icon} icon={branding.icon}

View File

@ -22,7 +22,7 @@
import {useAuth0} from "@auth0/auth0-react"; import {useAuth0} from "@auth0/auth0-react";
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import {useCookies} from "react-cookie"; import {useCookies} from "react-cookie";
import {SESSION_ID_COOKIE_NAME} from "App"; import {SESSION_UUID_COOKIE_NAME} from "App";
interface Props interface Props
{ {
@ -33,13 +33,13 @@ interface Props
function HandleAuthorizationError({errorMessage}: Props) function HandleAuthorizationError({errorMessage}: Props)
{ {
const [, , removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]); const [, , removeCookie] = useCookies([SESSION_UUID_COOKIE_NAME]);
const {logout} = useAuth0(); const {logout} = useAuth0();
useEffect(() => useEffect(() =>
{ {
logout(); logout();
removeCookie(SESSION_ID_COOKIE_NAME, {path: "/"}); removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
}); });
return ( return (

View File

@ -22,6 +22,7 @@
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QTableVariant} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableVariant";
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
@ -45,8 +46,10 @@ interface Props
isOpen: boolean; isOpen: boolean;
metaData: QInstance; metaData: QInstance;
tableMetaData: QTableMetaData; tableMetaData: QTableMetaData;
tableVariant?: QTableVariant;
closeHandler: () => void; closeHandler: () => void;
mayClose: boolean; mayClose: boolean;
subHeader?: JSX.Element;
} }
GotoRecordDialog.defaultProps = { GotoRecordDialog.defaultProps = {
@ -155,15 +158,17 @@ function GotoRecordDialog(props: Props): JSX.Element
{ {
setError(""); setError("");
const filter = new QQueryFilter([new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [values[fieldName]])], null, "AND", null, 10); const filter = new QQueryFilter([new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [values[fieldName]])], null, "AND", null, 10);
const queryResult = await qController.query(props.tableMetaData.name, filter) try
if(queryResult.length == 0) {
const queryResult = await qController.query(props.tableMetaData.name, filter, null, props.tableVariant)
if (queryResult.length == 0)
{ {
setError("Record not found."); setError("Record not found.");
setTimeout(() => setError(""), 3000); setTimeout(() => setError(""), 3000);
} }
else if(queryResult.length == 1) else if (queryResult.length == 1)
{ {
navigate(`${props.metaData.getTablePathByName(props.tableMetaData.name)}/${queryResult[0].values.get(props.tableMetaData.primaryKeyField)}`); navigate(`${props.metaData.getTablePathByName(props.tableMetaData.name)}/${encodeURIComponent(queryResult[0].values.get(props.tableMetaData.primaryKeyField))}`);
close(); close();
} }
else else
@ -172,6 +177,13 @@ function GotoRecordDialog(props: Props): JSX.Element
setTimeout(() => setError(""), 3000); setTimeout(() => setError(""), 3000);
} }
} }
catch(e)
{
// @ts-ignore
setError(`Error: ${(e && e.message) ? e.message : e}`);
setTimeout(() => setError(""), 6000);
}
}
if(props.tableMetaData) if(props.tableMetaData)
{ {
@ -184,7 +196,9 @@ function GotoRecordDialog(props: Props): JSX.Element
return ( return (
<Dialog open={props.isOpen} onClose={() => closeRequested} onKeyPress={(e) => keyPressed(e)} fullWidth maxWidth={"sm"}> <Dialog open={props.isOpen} onClose={() => closeRequested} onKeyPress={(e) => keyPressed(e)} fullWidth maxWidth={"sm"}>
<DialogTitle>Go To...</DialogTitle> <DialogTitle>Go To...</DialogTitle>
<DialogContent> <DialogContent>
{props.subHeader}
{ {
fields.map((field, index) => fields.map((field, index) =>
( (
@ -237,9 +251,11 @@ interface GotoRecordButtonProps
{ {
metaData: QInstance; metaData: QInstance;
tableMetaData: QTableMetaData; tableMetaData: QTableMetaData;
tableVariant?: QTableVariant;
autoOpen?: boolean; autoOpen?: boolean;
buttonVisible?: boolean; buttonVisible?: boolean;
mayClose?: boolean; mayClose?: boolean;
subHeader?: JSX.Element;
} }
GotoRecordButton.defaultProps = { GotoRecordButton.defaultProps = {
@ -268,7 +284,7 @@ export function GotoRecordButton(props: GotoRecordButtonProps): JSX.Element
{ {
props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && <Button onClick={openGoto} >Go To...</Button> props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && <Button onClick={openGoto} >Go To...</Button>
} }
<GotoRecordDialog metaData={props.metaData} tableMetaData={props.tableMetaData} isOpen={gotoIsOpen} closeHandler={closeGoto} mayClose={props.mayClose} /> <GotoRecordDialog metaData={props.metaData} tableMetaData={props.tableMetaData} tableVariant={props.tableVariant} isOpen={gotoIsOpen} closeHandler={closeGoto} mayClose={props.mayClose} subHeader={props.subHeader} />
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -228,12 +228,19 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const download = (url: string, fileName: string) => const download = (url: string, fileName: string) =>
{ {
const qController = Client.getInstance(); /////////////////////////////////////////////////////////////////////////////////////////////
// todo - this could be simplified. //
// it was originally built like this when we had to submit full access token to backend... //
/////////////////////////////////////////////////////////////////////////////////////////////
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open("POST", url); xhr.open("POST", url);
xhr.responseType = "blob"; xhr.responseType = "blob";
let formData = new FormData(); let formData = new FormData();
////////////////////////////////////
// todo#authHeader - delete this. //
////////////////////////////////////
const qController = Client.getInstance();
formData.append("Authorization", qController.getAuthorizationHeaderValue()); formData.append("Authorization", qController.getAuthorizationHeaderValue());
// @ts-ignore // @ts-ignore

View File

@ -539,15 +539,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
<CustomWidthTooltip title={tooltipHTML}> <CustomWidthTooltip title={tooltipHTML}>
<IconButton sx={{p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton> <IconButton sx={{p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton>
</CustomWidthTooltip> </CustomWidthTooltip>
{ {tableVariant && getTableVariantHeader()}
tableVariant &&
<Typography variant="h6" color="text" fontWeight="light">
{tableMetaData.variantTableLabel}: {tableVariant.name}
<Tooltip title={`Change ${tableMetaData.variantTableLabel}`}>
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
</Tooltip>
</Typography>
}
</div>); </div>);
} }
else else
@ -555,19 +547,23 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
return ( return (
<div> <div>
{label} {label}
{ {tableVariant && getTableVariantHeader()}
tableVariant &&
<Typography variant="h6" color="text" fontWeight="light">
{tableMetaData.variantTableLabel}: {tableVariant.name}
<Tooltip title={`Change ${tableMetaData.variantTableLabel}`}>
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
</Tooltip>
</Typography>
}
</div>); </div>);
} }
}; };
const getTableVariantHeader = () =>
{
return (
<Typography variant="h6" color="text" fontWeight="light">
{tableMetaData?.variantTableLabel}: {tableVariant?.name}
<Tooltip title={`Change ${tableMetaData?.variantTableLabel}`}>
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
</Tooltip>
</Typography>
);
}
const updateTable = () => const updateTable = () =>
{ {
setLoading(true); setLoading(true);
@ -1140,6 +1136,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
<body> <body>
Generating file <u>${filename}</u>${totalRecords ? " with " + totalRecords.toLocaleString() + " record" + (totalRecords == 1 ? "" : "s") : ""}... Generating file <u>${filename}</u>${totalRecords ? " with " + totalRecords.toLocaleString() + " record" + (totalRecords == 1 ? "" : "s") : ""}...
<form id="exportForm" method="post" action="${url}" > <form id="exportForm" method="post" action="${url}" >
<!-- todo#authHeader - remove this. -->
<input type="hidden" name="Authorization" value="${qController.getAuthorizationHeaderValue()}"> <input type="hidden" name="Authorization" value="${qController.getAuthorizationHeaderValue()}">
<input type="hidden" name="fields" value="${visibleFields.join(",")}"> <input type="hidden" name="fields" value="${visibleFields.join(",")}">
<input type="hidden" name="filter" id="filter"> <input type="hidden" name="filter" id="filter">
@ -1887,10 +1884,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
// instead, try to just render a Goto Record button, in auto-open, and may-not-close modes // // instead, try to just render a Goto Record button, in auto-open, and may-not-close modes //
///////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////
if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_QUERY) && tableMetaData.capabilities.has(Capability.TABLE_GET)) if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_QUERY) && tableMetaData.capabilities.has(Capability.TABLE_GET))
{
if(tableMetaData?.usesVariants && (!tableVariant || tableVariantPromptOpen))
{ {
return ( return (
<BaseLayout> <BaseLayout>
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} autoOpen={true} buttonVisible={false} mayClose={false} /> <TableVariantDialog table={tableMetaData} isOpen={true} closeHandler={(value: QTableVariant) =>
{
setTableVariantPromptOpen(false);
setTableVariant(value);
}} />
</BaseLayout>
);
}
return (
<BaseLayout>
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} tableVariant={tableVariant} autoOpen={true} buttonVisible={false} mayClose={false} subHeader={
<Box mb={2}>
{getTableVariantHeader()}
</Box>
} />
</BaseLayout> </BaseLayout>
); );
} }

View File

@ -95,6 +95,11 @@ export default class HtmlUtils
form.setAttribute("target", "downloadIframe"); form.setAttribute("target", "downloadIframe");
iframe.appendChild(form); iframe.appendChild(form);
/////////////////////////////////////////////////////////////////////////////////////////////
// todo#authHeader - remove after comfortable with sessionUUID //
// todo - this could be simplified (i think?) //
// it was originally built like this when we had to submit full access token to backend... //
/////////////////////////////////////////////////////////////////////////////////////////////
const authorizationInput = document.createElement("input"); const authorizationInput = document.createElement("input");
authorizationInput.setAttribute("type", "hidden"); authorizationInput.setAttribute("type", "hidden");
authorizationInput.setAttribute("id", "authorizationInput"); authorizationInput.setAttribute("id", "authorizationInput");
@ -118,6 +123,11 @@ export default class HtmlUtils
{ {
if(url.startsWith("data:")) if(url.startsWith("data:"))
{ {
/////////////////////////////////////////////////////////////////////////////////////////////
// todo#authHeader - remove the Authorization input after comfortable with sessionUUID //
// todo - this could be simplified (i think?) //
// it was originally built like this when we had to submit full access token to backend... //
/////////////////////////////////////////////////////////////////////////////////////////////
const openInWindow = window.open("", "_blank"); const openInWindow = window.open("", "_blank");
openInWindow.document.write(`<html lang="en"> openInWindow.document.write(`<html lang="en">
<body style="margin: 0"> <body style="margin: 0">

View File

@ -49,6 +49,7 @@ module.exports = function (app)
app.use("/data/*", getRequestHandler()); app.use("/data/*", getRequestHandler());
app.use("/widget/*", getRequestHandler()); app.use("/widget/*", getRequestHandler());
app.use("/serverInfo", getRequestHandler()); app.use("/serverInfo", getRequestHandler());
app.use("/manageSession", getRequestHandler());
app.use("/processes", getRequestHandler()); app.use("/processes", getRequestHandler());
app.use("/reports", getRequestHandler()); app.use("/reports", getRequestHandler());
app.use("/images", getRequestHandler()); app.use("/images", getRequestHandler());