mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Checkpoint to get cypress working - auth-type from backend, less hard-coded auth0, improvements on query screen (less redundant fetches)
This commit is contained in:
66
src/App.tsx
66
src/App.tsx
@ -23,6 +23,7 @@ import {useAuth0} from "@auth0/auth0-react";
|
||||
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
||||
import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode";
|
||||
import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
@ -86,6 +87,7 @@ function getStaticRoutes()
|
||||
];
|
||||
}
|
||||
|
||||
const qController = QClient.getInstance();
|
||||
export const SESSION_ID_COOKIE_NAME = "sessionId";
|
||||
LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
|
||||
|
||||
@ -105,24 +107,51 @@ export default function App()
|
||||
return;
|
||||
}
|
||||
setLoadingToken(true);
|
||||
|
||||
(async () =>
|
||||
{
|
||||
try
|
||||
const authenticationMetaData: QAuthenticationMetaData = await qController.getAuthenticationMetaData();
|
||||
|
||||
if (authenticationMetaData.type === "AUTH_0")
|
||||
{
|
||||
console.log("Loading token...");
|
||||
await getAccessTokenSilently();
|
||||
const idToken = await getIdTokenClaims();
|
||||
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
|
||||
setIsFullyAuthenticated(true);
|
||||
console.log("Token load complete.");
|
||||
/////////////////////////////////////////
|
||||
// use auth0 if auth type is ... auth0 //
|
||||
/////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
console.log("Loading token...");
|
||||
await getAccessTokenSilently();
|
||||
const idToken = await getIdTokenClaims();
|
||||
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
|
||||
setIsFullyAuthenticated(true);
|
||||
console.log("Token load complete.");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(`Error loading token: ${JSON.stringify(e)}`);
|
||||
removeCookie(SESSION_ID_COOKIE_NAME);
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
else if (authenticationMetaData.type === "FULLY_ANONYMOUS" || authenticationMetaData.type === "MOCK")
|
||||
{
|
||||
console.log(`Error loading token: ${JSON.stringify(e)}`);
|
||||
removeCookie(SESSION_ID_COOKIE_NAME);
|
||||
logout();
|
||||
/////////////////////////////////////////////
|
||||
// use a random token if anonymous or mock //
|
||||
/////////////////////////////////////////////
|
||||
console.log("Generating random token...");
|
||||
setIsFullyAuthenticated(true);
|
||||
setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
|
||||
console.log("Token generation complete.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`Unrecognized authenticationMetaData.type: ${authenticationMetaData.type}`);
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
}
|
||||
|
||||
})();
|
||||
}, [loadingToken]);
|
||||
|
||||
@ -265,7 +294,7 @@ export default function App()
|
||||
|
||||
routeList.push({
|
||||
name: `${app.label}`,
|
||||
key: `${app.name}.dev`,
|
||||
key: `${app.name}.record.dev`,
|
||||
route: `${path}/:id/dev`,
|
||||
component: <EntityDeveloperView table={table} />,
|
||||
});
|
||||
@ -342,13 +371,13 @@ export default function App()
|
||||
|
||||
let profileRoutes = {};
|
||||
const gravatarBase = "https://www.gravatar.com/avatar/";
|
||||
const hash = Md5.hashStr(user.email);
|
||||
const hash = Md5.hashStr(user?.email || "user");
|
||||
const profilePicture = `${gravatarBase}${hash}`;
|
||||
profileRoutes = {
|
||||
type: "collapse",
|
||||
name: user.name,
|
||||
key: user.name,
|
||||
icon: <MDAvatar src={profilePicture} alt="{user.name}" size="sm" />,
|
||||
name: user?.name,
|
||||
key: "username",
|
||||
icon: <MDAvatar src={profilePicture} alt="{user?.name}" size="sm" />,
|
||||
collapse: [
|
||||
{
|
||||
name: "My Profile",
|
||||
@ -388,11 +417,16 @@ export default function App()
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
if (e instanceof QException)
|
||||
{
|
||||
if ((e as QException).message.indexOf("status code 401") !== -1)
|
||||
{
|
||||
removeCookie(SESSION_ID_COOKIE_NAME);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// todo - this is auth0 logout... make more generic //
|
||||
//////////////////////////////////////////////////////
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
|
@ -19,11 +19,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Auth0Provider, useAuth0} from "@auth0/auth0-react";
|
||||
import {useAuth0} from "@auth0/auth0-react";
|
||||
import React, {useEffect} from "react";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {SESSION_ID_COOKIE_NAME} from "App";
|
||||
import {AUTH0_CLIENT_ID, AUTH0_DOMAIN} from "index";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -44,11 +43,7 @@ function HandleAuthorizationError({errorMessage}: Props)
|
||||
});
|
||||
|
||||
return (
|
||||
<Auth0Provider domain={AUTH0_DOMAIN} clientId={AUTH0_CLIENT_ID}>
|
||||
<div>
|
||||
<div>{errorMessage}</div>
|
||||
</div>
|
||||
</Auth0Provider>
|
||||
<div>{errorMessage}</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
import {Auth0Provider} from "@auth0/auth0-react";
|
||||
import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
import React from "react";
|
||||
import {render} from "react-dom";
|
||||
import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom";
|
||||
@ -28,52 +29,73 @@ import "qqq/styles/qqq-override-styles.css";
|
||||
import {MaterialUIControllerProvider} from "context";
|
||||
import HandleAuthorizationError from "HandleAuthorizationError";
|
||||
import ProtectedRoute from "qqq/auth0/protected-route";
|
||||
import QClient from "qqq/utils/QClient";
|
||||
|
||||
export const AUTH0_DOMAIN = process.env.REACT_APP_AUTH0_DOMAIN;
|
||||
export const AUTH0_CLIENT_ID = process.env.REACT_APP_AUTH0_CLIENT_ID;
|
||||
const qController = QClient.getInstance();
|
||||
const authenticationMetaDataPromise: Promise<QAuthenticationMetaData> = qController.getAuthenticationMetaData()
|
||||
|
||||
// @ts-ignore
|
||||
function Auth0ProviderWithRedirectCallback({children, ...props})
|
||||
authenticationMetaDataPromise.then((authenticationMetaData) =>
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// @ts-ignore
|
||||
const onRedirectCallback = (appState) =>
|
||||
function Auth0ProviderWithRedirectCallback({children, ...props})
|
||||
{
|
||||
navigate((appState && appState.returnTo) || window.location.pathname);
|
||||
};
|
||||
if (searchParams.get("error"))
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// @ts-ignore
|
||||
const onRedirectCallback = (appState) =>
|
||||
{
|
||||
navigate((appState && appState.returnTo) || window.location.pathname);
|
||||
};
|
||||
if (searchParams.get("error"))
|
||||
{
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Auth0Provider {...props}>
|
||||
<HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
|
||||
</Auth0Provider>
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
|
||||
{children}
|
||||
</Auth0Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (authenticationMetaData.type === "AUTH_0")
|
||||
{
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Auth0Provider {...props}>
|
||||
<HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
|
||||
</Auth0Provider>
|
||||
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
|
||||
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
|
||||
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Auth0ProviderWithRedirectCallback
|
||||
domain={domain}
|
||||
clientId={clientId}
|
||||
redirectUri={`${window.location.origin}/dashboards/overview`}
|
||||
>
|
||||
<MaterialUIControllerProvider>
|
||||
<ProtectedRoute component={App} />
|
||||
</MaterialUIControllerProvider>
|
||||
</Auth0ProviderWithRedirectCallback>
|
||||
</BrowserRouter>,
|
||||
document.getElementById("root"),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
|
||||
{children}
|
||||
</Auth0Provider>
|
||||
);
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<MaterialUIControllerProvider>
|
||||
<App />
|
||||
</MaterialUIControllerProvider>
|
||||
</BrowserRouter>
|
||||
, document.getElementById("root"));
|
||||
}
|
||||
}
|
||||
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Auth0ProviderWithRedirectCallback
|
||||
domain={AUTH0_DOMAIN}
|
||||
clientId={AUTH0_CLIENT_ID}
|
||||
redirectUri={`${window.location.origin}/dashboards/overview`}
|
||||
>
|
||||
<MaterialUIControllerProvider>
|
||||
<ProtectedRoute component={App} />
|
||||
</MaterialUIControllerProvider>
|
||||
</Auth0ProviderWithRedirectCallback>
|
||||
</BrowserRouter>,
|
||||
document.getElementById("root"),
|
||||
);
|
||||
})
|
||||
|
@ -19,12 +19,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
||||
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||
@ -44,10 +41,9 @@ import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {DataGridPro, getGridDateOperators, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridLinkOperator, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
|
||||
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
|
||||
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridLinkOperator, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
|
||||
import React, {useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||
import {Link, useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import DashboardLayout from "qqq/components/DashboardLayout";
|
||||
import Footer from "qqq/components/Footer";
|
||||
@ -55,7 +51,6 @@ import Navbar from "qqq/components/Navbar";
|
||||
import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons";
|
||||
import MDAlert from "qqq/components/Temporary/MDAlert";
|
||||
import MDBox from "qqq/components/Temporary/MDBox";
|
||||
import {buildQGridPvsOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/entity-list/QGridFilterOperators";
|
||||
import ProcessRun from "qqq/pages/process-run";
|
||||
import DataGridUtils from "qqq/utils/DataGridUtils";
|
||||
import QClient from "qqq/utils/QClient";
|
||||
@ -237,7 +232,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
||||
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
|
||||
const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter)
|
||||
const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter);
|
||||
|
||||
const instance = useRef({timer: null});
|
||||
|
||||
@ -355,13 +350,26 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
||||
return qFilter;
|
||||
};
|
||||
|
||||
const getTableMetaData = async (): Promise<QTableMetaData> =>
|
||||
{
|
||||
if(tableMetaData !== null)
|
||||
{
|
||||
return(new Promise((resolve) =>
|
||||
{
|
||||
resolve(tableMetaData)
|
||||
}));
|
||||
}
|
||||
|
||||
return (qController.loadTableMetaData(tableName));
|
||||
}
|
||||
|
||||
const updateTable = () =>
|
||||
{
|
||||
setLoading(true);
|
||||
setRows([]);
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
const tableMetaData = await getTableMetaData();
|
||||
setPageHeader(tableMetaData.label);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -530,7 +538,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
const handlePinnedColumnsChange = (pinnedColumns: GridPinnedColumns) =>
|
||||
{
|
||||
setPinnedColumns(pinnedColumns)
|
||||
setPinnedColumns(pinnedColumns);
|
||||
localStorage.setItem(pinnedColumnsLocalStorageKey, JSON.stringify(pinnedColumns));
|
||||
};
|
||||
|
||||
@ -1080,7 +1088,14 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
updateTable();
|
||||
if(latestQueryId > 0)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// to avoid both this useEffect and the one below from both doing an "initial query", //
|
||||
// only run this one if at least 1 query has already been ran //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
updateTable();
|
||||
}
|
||||
}, [ pageNumber, rowsPerPage, columnSortModel ]);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -32,6 +32,7 @@ class QClient
|
||||
|
||||
private static handleException(exception: QException)
|
||||
{
|
||||
// todo - check for 401 and clear cookie et al & logout?
|
||||
console.log(`Caught Exception: ${JSON.stringify(exception)}`);
|
||||
throw (exception);
|
||||
}
|
||||
|
@ -32,30 +32,36 @@ class QProcessUtils
|
||||
public static getProcessesForTable(metaData: QInstance, tableName: string, includeHidden = false): QProcessMetaData[]
|
||||
{
|
||||
const matchingProcesses: QProcessMetaData[] = [];
|
||||
const processKeys = [...metaData.processes.keys()];
|
||||
processKeys.forEach((key) =>
|
||||
if (metaData.processes)
|
||||
{
|
||||
const process = metaData.processes.get(key);
|
||||
if (process.tableName === tableName && (includeHidden || !process.isHidden))
|
||||
const processKeys = [...metaData.processes.keys()];
|
||||
processKeys.forEach((key) =>
|
||||
{
|
||||
matchingProcesses.push(process);
|
||||
}
|
||||
});
|
||||
const process = metaData.processes.get(key);
|
||||
if (process.tableName === tableName && (includeHidden || !process.isHidden))
|
||||
{
|
||||
matchingProcesses.push(process);
|
||||
}
|
||||
});
|
||||
}
|
||||
return matchingProcesses;
|
||||
}
|
||||
|
||||
public static getReportsForTable(metaData: QInstance, tableName: string, includeHidden = false): QReportMetaData[]
|
||||
{
|
||||
const matchingReports: QReportMetaData[] = [];
|
||||
const reportKeys = [...metaData.reports.keys()];
|
||||
reportKeys.forEach((key) =>
|
||||
if (metaData.reports)
|
||||
{
|
||||
const process = metaData.reports.get(key);
|
||||
if (process.tableName === tableName)
|
||||
const reportKeys = [...metaData.reports.keys()];
|
||||
reportKeys.forEach((key) =>
|
||||
{
|
||||
matchingReports.push(process);
|
||||
}
|
||||
});
|
||||
const process = metaData.reports.get(key);
|
||||
if (process.tableName === tableName)
|
||||
{
|
||||
matchingReports.push(process);
|
||||
}
|
||||
});
|
||||
}
|
||||
return matchingReports;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user