mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
First pass at permissions; Updated auth0 to work with access token instead of id token
This commit is contained in:
@ -7,7 +7,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.40",
|
"@kingsrook/qqq-frontend-core": "1.0.41",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
|
77
src/App.tsx
77
src/App.tsx
@ -40,6 +40,7 @@ import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
|
|||||||
import theme from "qqq/components/legacy/Theme";
|
import theme from "qqq/components/legacy/Theme";
|
||||||
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "qqq/context";
|
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "qqq/context";
|
||||||
import AppHome from "qqq/pages/apps/Home";
|
import AppHome from "qqq/pages/apps/Home";
|
||||||
|
import NoApps from "qqq/pages/apps/NoApps";
|
||||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||||
import ReportRun from "qqq/pages/processes/ReportRun";
|
import ReportRun from "qqq/pages/processes/ReportRun";
|
||||||
import EntityCreate from "qqq/pages/records/create/RecordCreate";
|
import EntityCreate from "qqq/pages/records/create/RecordCreate";
|
||||||
@ -53,7 +54,6 @@ 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_ID_COOKIE_NAME = "sessionId";
|
||||||
LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
|
|
||||||
|
|
||||||
export default function App()
|
export default function App()
|
||||||
{
|
{
|
||||||
@ -63,6 +63,9 @@ export default function App()
|
|||||||
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
||||||
const [profileRoutes, setProfileRoutes] = useState({});
|
const [profileRoutes, setProfileRoutes] = useState({});
|
||||||
const [branding, setBranding] = useState({} as QBrandingMetaData);
|
const [branding, setBranding] = useState({} as QBrandingMetaData);
|
||||||
|
const [needLicenseKey, setNeedLicenseKey] = useState(true);
|
||||||
|
|
||||||
|
const [defaultRoute, setDefaultRoute] = useState("/no-apps");
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@ -83,17 +86,21 @@ export default function App()
|
|||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
console.log("Loading token...");
|
console.log("Loading token from auth0...");
|
||||||
await getAccessTokenSilently();
|
const accessToken = await getAccessTokenSilently();
|
||||||
const idToken = await getIdTokenClaims();
|
qController.setAuthorizationHeaderValue("Bearer " + accessToken);
|
||||||
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// we've stopped using session id cook with auth0, so make sure it is not set. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
removeCookie(SESSION_ID_COOKIE_NAME);
|
||||||
|
|
||||||
setIsFullyAuthenticated(true);
|
setIsFullyAuthenticated(true);
|
||||||
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)}`);
|
||||||
removeCookie(SESSION_ID_COOKIE_NAME);
|
|
||||||
qController.clearAuthenticationMetaDataLocalStorage();
|
qController.clearAuthenticationMetaDataLocalStorage();
|
||||||
logout();
|
logout();
|
||||||
return;
|
return;
|
||||||
@ -105,6 +112,7 @@ 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);
|
||||||
setIsFullyAuthenticated(true);
|
setIsFullyAuthenticated(true);
|
||||||
setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
|
setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
|
||||||
console.log("Token generation complete.");
|
console.log("Token generation complete.");
|
||||||
@ -119,6 +127,16 @@ export default function App()
|
|||||||
})();
|
})();
|
||||||
}, [loadingToken]);
|
}, [loadingToken]);
|
||||||
|
|
||||||
|
if(needLicenseKey)
|
||||||
|
{
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const metaData: QInstance = await qController.loadMetaData();
|
||||||
|
LicenseInfo.setLicenseKey(metaData.environmentValues.get("MATERIAL_UI_LICENSE_KEY") || process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
|
||||||
|
setNeedLicenseKey(false);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
const [controller, dispatch] = useMaterialUIController();
|
const [controller, dispatch] = useMaterialUIController();
|
||||||
const {miniSidenav, direction, layout, openConfigurator, sidenavColor} = controller;
|
const {miniSidenav, direction, layout, openConfigurator, sidenavColor} = controller;
|
||||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||||
@ -205,6 +223,8 @@ export default function App()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let foundFirstApp = false;
|
||||||
|
|
||||||
function addAppToAppRoutesList(metaData: QInstance, app: QAppTreeNode, routeList: any[], parentPath: string, depth: number)
|
function addAppToAppRoutesList(metaData: QInstance, app: QAppTreeNode, routeList: any[], parentPath: string, depth: number)
|
||||||
{
|
{
|
||||||
const path = `${parentPath}/${app.name}`;
|
const path = `${parentPath}/${app.name}`;
|
||||||
@ -224,6 +244,16 @@ export default function App()
|
|||||||
route: path,
|
route: path,
|
||||||
component: <AppHome app={metaData.apps.get(app.name)} />,
|
component: <AppHome app={metaData.apps.get(app.name)} />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(!foundFirstApp)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// keep track of what the top-most app the user has access to is. set that as their default route //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
foundFirstApp = true;
|
||||||
|
setDefaultRoute(path);
|
||||||
|
console.log("Set default route to: " + path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (app.type === QAppNodeType.TABLE)
|
else if (app.type === QAppNodeType.TABLE)
|
||||||
{
|
{
|
||||||
@ -363,14 +393,29 @@ export default function App()
|
|||||||
const sideNavAppList = [] as any[];
|
const sideNavAppList = [] as any[];
|
||||||
const appRoutesList = [] as any[];
|
const appRoutesList = [] as any[];
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
// iterate throught the list to find the 'main dashboard so we can put it first' //
|
// iterate through the list to find the 'main dashboard so we can put it first' //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
for (let i = 0; i < metaData.appTree.length; i++)
|
if(metaData.appTree && metaData.appTree.length)
|
||||||
{
|
{
|
||||||
const app = metaData.appTree[i];
|
for (let i = 0; i < metaData.appTree.length; i++)
|
||||||
addAppToSideNavList(app, sideNavAppList, "", 0);
|
{
|
||||||
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
|
const app = metaData.appTree[i];
|
||||||
|
addAppToSideNavList(app, sideNavAppList, "", 0);
|
||||||
|
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
// if the user doesn't have access to any apps, push this route. //
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
appRoutesList.push({
|
||||||
|
name: "No Apps",
|
||||||
|
key: "no-apps",
|
||||||
|
route: "/no-apps",
|
||||||
|
component: <NoApps />,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSideNavRoutes = [];
|
const newSideNavRoutes = [];
|
||||||
@ -390,10 +435,8 @@ export default function App()
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
if (e instanceof QException)
|
if (e instanceof QException)
|
||||||
{
|
{
|
||||||
if ((e as QException).message.indexOf("status code 401") !== -1)
|
if ((e as QException).status === "401")
|
||||||
{
|
{
|
||||||
removeCookie(SESSION_ID_COOKIE_NAME);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
// todo - this is auth0 logout... make more generic //
|
// todo - this is auth0 logout... make more generic //
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
@ -486,7 +529,7 @@ export default function App()
|
|||||||
onMouseLeave={handleOnMouseLeave}
|
onMouseLeave={handleOnMouseLeave}
|
||||||
/>
|
/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="*" element={<Navigate to="/dashboards/overview" />} />
|
<Route path="*" element={<Navigate to={defaultRoute} />} />
|
||||||
{appRoutes && getRoutes(appRoutes)}
|
{appRoutes && getRoutes(appRoutes)}
|
||||||
{profileRoutes && getRoutes([profileRoutes])}
|
{profileRoutes && getRoutes([profileRoutes])}
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -81,6 +81,9 @@ authenticationMetaDataPromise.then((authenticationMetaData) =>
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const clientId = authenticationMetaData.data.clientId;
|
const clientId = authenticationMetaData.data.clientId;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const audience = authenticationMetaData.data.audience;
|
||||||
|
|
||||||
if(!domain || !clientId)
|
if(!domain || !clientId)
|
||||||
{
|
{
|
||||||
render(
|
render(
|
||||||
@ -103,7 +106,8 @@ authenticationMetaDataPromise.then((authenticationMetaData) =>
|
|||||||
<Auth0ProviderWithRedirectCallback
|
<Auth0ProviderWithRedirectCallback
|
||||||
domain={domain}
|
domain={domain}
|
||||||
clientId={clientId}
|
clientId={clientId}
|
||||||
redirectUri={`${window.location.origin}/dashboards/overview`}
|
audience={audience}
|
||||||
|
redirectUri={`${window.location.origin}/`}
|
||||||
>
|
>
|
||||||
<MaterialUIControllerProvider>
|
<MaterialUIControllerProvider>
|
||||||
<ProtectedRoute component={App} />
|
<ProtectedRoute component={App} />
|
||||||
|
@ -88,7 +88,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
const [tableSections, setTableSections] = useState(null as QTableSection[]);
|
const [tableSections, setTableSections] = useState(null as QTableSection[]);
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
const [noCapabilityError, setNoCapabilityError] = useState(null as string);
|
const [notAllowedError, setNotAllowedError] = useState(null as string);
|
||||||
|
|
||||||
const {pageHeader, setPageHeader} = useContext(QContext);
|
const {pageHeader, setPageHeader} = useContext(QContext);
|
||||||
|
|
||||||
@ -189,7 +189,11 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
|
|
||||||
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||||
{
|
{
|
||||||
setNoCapabilityError("You may not edit records in this table");
|
setNotAllowedError("Records may not be edited in this table");
|
||||||
|
}
|
||||||
|
else if (!tableMetaData.editPermission)
|
||||||
|
{
|
||||||
|
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -206,7 +210,11 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
|
|
||||||
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
|
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
|
||||||
{
|
{
|
||||||
setNoCapabilityError("You may not create records in this table");
|
setNotAllowedError("Records may not be created in this table");
|
||||||
|
}
|
||||||
|
else if (!tableMetaData.insertPermission)
|
||||||
|
{
|
||||||
|
setNotAllowedError(`You do not have permission to create ${tableMetaData.label} records`);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -420,14 +428,19 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
const formId = props.id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
const formId = props.id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
if (noCapabilityError)
|
if (notAllowedError)
|
||||||
{
|
{
|
||||||
body = (
|
body = (
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Alert severity="error">{noCapabilityError}</Alert>
|
<Alert severity="error">{notAllowedError}</Alert>
|
||||||
|
{props.isModal &&
|
||||||
|
<Box mt={5}>
|
||||||
|
<QCancelButton onClickHandler={props.isModal ? props.closeModalHandler : handleCancelClicked} label="Close" disabled={false} />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -39,8 +39,8 @@ interface Props
|
|||||||
|
|
||||||
export function GoogleDriveFolderPicker({showDefaultFoldersView, showSharedDrivesView, qInstance}: Props): JSX.Element
|
export function GoogleDriveFolderPicker({showDefaultFoldersView, showSharedDrivesView, qInstance}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const clientId = "649816208522-m6oa971vqicrc1hlam7333pt4qck0tm8.apps.googleusercontent.com";
|
const clientId = qInstance.environmentValues.get("GOOGLE_APP_CLIENT_ID") || process.env.REACT_APP_GOOGLE_APP_CLIENT_ID;
|
||||||
const appApiKey = "AIzaSyBhXK34CF2fUfCgUS1VIHoKZbHxEBuHtDM";
|
const appApiKey = qInstance.environmentValues.get("GOOGLE_APP_API_KEY") || process.env.REACT_APP_GOOGLE_APP_API_KEY;
|
||||||
if(!clientId)
|
if(!clientId)
|
||||||
{
|
{
|
||||||
console.error("Missing environmentValue GOOGLE_APP_CLIENT_ID")
|
console.error("Missing environmentValue GOOGLE_APP_CLIENT_ID")
|
||||||
|
@ -24,6 +24,7 @@ import Card from "@mui/material/Card";
|
|||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import {ReactNode} from "react";
|
import {ReactNode} from "react";
|
||||||
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
@ -37,33 +38,34 @@ interface Props
|
|||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProcessLinkCard({
|
function ProcessLinkCard({
|
||||||
color, isReport, title, percentage, icon,
|
color, isReport, title, percentage, icon, isDisabled
|
||||||
}: Props): JSX.Element
|
}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Box display="flex" justifyContent="space-between" pt={3} px={2}>
|
<Box display="flex" justifyContent="space-between" pt={3} px={2} title={isDisabled ? `You do not have permission to access this ${isReport ? "report" : "process"}` : ""}>
|
||||||
<Box
|
<Box
|
||||||
color={color === "light" ? "dark" : "white"}
|
color={color === "light" ? "#000000" : "#FFFFFF"}
|
||||||
borderRadius="xl"
|
borderRadius="xl"
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
width="4rem"
|
width="4rem"
|
||||||
height="4rem"
|
height="4rem"
|
||||||
mt={-3}
|
mt={-1.5}
|
||||||
sx={{backgroundColor: color}}
|
sx={{borderRadius: "10px", backgroundColor: isDisabled ? colors.secondary.main : colors.info.main}}
|
||||||
>
|
>
|
||||||
<Icon fontSize="medium" color="inherit">
|
<Icon fontSize="medium" color="inherit">
|
||||||
{icon}
|
{icon}
|
||||||
</Icon>
|
</Icon>
|
||||||
</Box>
|
</Box>
|
||||||
<Box textAlign="right" lineHeight={1.25}>
|
<Box textAlign="right" lineHeight={1.25} mt={1}>
|
||||||
<MDTypography variant="button" fontWeight="bold" color="text">
|
<MDTypography variant="button" fontWeight="bold" color="text">
|
||||||
{title}
|
{title}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
@ -81,9 +83,15 @@ function ProcessLinkCard({
|
|||||||
{percentage.amount}
|
{percentage.amount}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
{
|
{
|
||||||
isReport
|
isDisabled ? (
|
||||||
? `Click here to access the ${title} report.`
|
isReport
|
||||||
: `Click here to run the process called ${title}.`
|
? `You do not have permission to access the ${title} report.`
|
||||||
|
: `You do not have permission to run the process called ${title}.`
|
||||||
|
) : (
|
||||||
|
isReport
|
||||||
|
? `Click here to access the ${title} report.`
|
||||||
|
: `Click here to run the process called ${title}.`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
{percentage.label}
|
{percentage.label}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
@ -100,6 +108,7 @@ ProcessLinkCard.defaultProps = {
|
|||||||
text: "",
|
text: "",
|
||||||
label: "",
|
label: "",
|
||||||
},
|
},
|
||||||
|
isDisabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProcessLinkCard;
|
export default ProcessLinkCard;
|
||||||
|
@ -61,7 +61,10 @@ function RecordGridWidget({title, data}: Props): JSX.Element
|
|||||||
const tableMetaData = new QTableMetaData(data.childTableMetaData);
|
const tableMetaData = new QTableMetaData(data.childTableMetaData);
|
||||||
const {rows, columnsToRender} = DataGridUtils.makeRows(records, tableMetaData);
|
const {rows, columnsToRender} = DataGridUtils.makeRows(records, tableMetaData);
|
||||||
|
|
||||||
const childTablePath = data.tablePath + (data.tablePath.endsWith("/") ? "" : "/")
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note - tablePath may be null, if the user doesn't have access to the table. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const childTablePath = data.tablePath ? data.tablePath + (data.tablePath.endsWith("/") ? "" : "/") : data.tablePath;
|
||||||
const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, childTablePath);
|
const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, childTablePath);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
@ -46,6 +46,7 @@ interface Props {
|
|||||||
component: ReactNode;
|
component: ReactNode;
|
||||||
};
|
};
|
||||||
direction?: "right" | "left";
|
direction?: "right" | "left";
|
||||||
|
isDisabled?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ function MiniStatisticsCard({
|
|||||||
percentage,
|
percentage,
|
||||||
icon,
|
icon,
|
||||||
direction,
|
direction,
|
||||||
|
isDisabled,
|
||||||
}: Props): JSX.Element
|
}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [controller] = useMaterialUIController();
|
const [controller] = useMaterialUIController();
|
||||||
@ -108,7 +110,7 @@ function MiniStatisticsCard({
|
|||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
color="#FFFFFF"
|
color="#FFFFFF"
|
||||||
sx={{borderRadius: "10px", backgroundColor: colors.info.main}}
|
sx={{borderRadius: "10px", backgroundColor: isDisabled ? colors.secondary.main : colors.info.main}}
|
||||||
>
|
>
|
||||||
<Icon fontSize="medium" color="inherit">
|
<Icon fontSize="medium" color="inherit">
|
||||||
{icon.component}
|
{icon.component}
|
||||||
@ -134,6 +136,7 @@ MiniStatisticsCard.defaultProps = {
|
|||||||
text: "",
|
text: "",
|
||||||
},
|
},
|
||||||
direction: "right",
|
direction: "right",
|
||||||
|
isDisabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MiniStatisticsCard;
|
export default MiniStatisticsCard;
|
||||||
|
@ -125,7 +125,7 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
const tableMetaData = await qController.loadTableMetaData(table.name);
|
const tableMetaData = await qController.loadTableMetaData(table.name);
|
||||||
let countResult = null;
|
let countResult = null;
|
||||||
if(tableMetaData.capabilities.has(Capability.TABLE_COUNT))
|
if(tableMetaData.capabilities.has(Capability.TABLE_COUNT) && tableMetaData.readPermission)
|
||||||
{
|
{
|
||||||
countResult = await qController.count(table.name);
|
countResult = await qController.count(table.name);
|
||||||
|
|
||||||
@ -183,6 +183,20 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
}, 1);
|
}, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasTablePermission = (tableName: string) =>
|
||||||
|
{
|
||||||
|
return tables.find(t => t.name === tableName && (t.readPermission || t.insertPermission || t.editPermission || t.deletePermission));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasProcessPermission = (processName: string) =>
|
||||||
|
{
|
||||||
|
return processes.find(p => p.name === processName && p.hasPermission);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasReportPermission = (reportName: string) =>
|
||||||
|
{
|
||||||
|
return reports.find(r => r.name === reportName && r.hasPermission);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
@ -210,7 +224,7 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
</Box>
|
</Box>
|
||||||
{
|
{
|
||||||
section.processes ? (
|
section.processes ? (
|
||||||
<Box p={3} pl={5} pt={0}>
|
<Box p={3} pl={5} pt={0} pb={1}>
|
||||||
<MDTypography variant="h6">Actions</MDTypography>
|
<MDTypography variant="h6">Actions</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
) : null
|
) : null
|
||||||
@ -224,12 +238,19 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
let process = app.childMap.get(processName);
|
let process = app.childMap.get(processName);
|
||||||
return (
|
return (
|
||||||
<Grid key={process.name} item xs={12} md={12} lg={tileSizeLg}>
|
<Grid key={process.name} item xs={12} md={12} lg={tileSizeLg}>
|
||||||
<Link to={process.name}>
|
{hasProcessPermission(processName) ?
|
||||||
|
<Link to={process.name}>
|
||||||
|
<ProcessLinkCard
|
||||||
|
icon={process.iconName || app.iconName}
|
||||||
|
title={process.label}
|
||||||
|
/>
|
||||||
|
</Link> :
|
||||||
<ProcessLinkCard
|
<ProcessLinkCard
|
||||||
icon={process.iconName || app.iconName}
|
icon={process.iconName || app.iconName}
|
||||||
title={process.label}
|
title={process.label}
|
||||||
|
isDisabled={true}
|
||||||
/>
|
/>
|
||||||
</Link>
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -244,7 +265,7 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
section.reports ? (
|
section.reports ? (
|
||||||
<Box p={3} pl={5} pt={0}>
|
<Box p={3} pl={5} pt={0} pb={1}>
|
||||||
<MDTypography variant="h6">Reports</MDTypography>
|
<MDTypography variant="h6">Reports</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
) : null
|
) : null
|
||||||
@ -258,13 +279,21 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
let report = app.childMap.get(reportName);
|
let report = app.childMap.get(reportName);
|
||||||
return (
|
return (
|
||||||
<Grid key={report.name} item xs={12} md={12} lg={tileSizeLg}>
|
<Grid key={report.name} item xs={12} md={12} lg={tileSizeLg}>
|
||||||
<Link to={report.name}>
|
{hasReportPermission(reportName) ?
|
||||||
|
<Link to={report.name}>
|
||||||
|
<ProcessLinkCard
|
||||||
|
icon={report.iconName || app.iconName}
|
||||||
|
title={report.label}
|
||||||
|
isReport={true}
|
||||||
|
/>
|
||||||
|
</Link> :
|
||||||
<ProcessLinkCard
|
<ProcessLinkCard
|
||||||
icon={report.iconName || app.iconName}
|
icon={report.iconName || app.iconName}
|
||||||
title={report.label}
|
title={report.label}
|
||||||
isReport={true}
|
isReport={true}
|
||||||
|
isDisabled={true}
|
||||||
/>
|
/>
|
||||||
</Link>
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -279,7 +308,7 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
section.tables ? (
|
section.tables ? (
|
||||||
<Box p={3} pl={5} pb={0} pt={0}>
|
<Box p={3} pl={5} pb={1} pt={0}>
|
||||||
<MDTypography variant="h6">Data</MDTypography>
|
<MDTypography variant="h6">Data</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
) : null
|
) : null
|
||||||
@ -293,16 +322,27 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
let table = app.childMap.get(tableName);
|
let table = app.childMap.get(tableName);
|
||||||
return (
|
return (
|
||||||
<Grid key={table.name} item xs={12} md={12} lg={tileSizeLg}>
|
<Grid key={table.name} item xs={12} md={12} lg={tileSizeLg}>
|
||||||
<Link to={table.name}>
|
{hasTablePermission(tableName) ?
|
||||||
<Box mb={3}>
|
<Link to={table.name}>
|
||||||
|
<Box mb={3}>
|
||||||
|
<MiniStatisticsCard
|
||||||
|
title={{fontWeight: "bold", text: table.label}}
|
||||||
|
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name))}
|
||||||
|
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name)))}}
|
||||||
|
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Link> :
|
||||||
|
<Box mb={3} title="You do not have permission to access this table">
|
||||||
<MiniStatisticsCard
|
<MiniStatisticsCard
|
||||||
title={{fontWeight: "bold", text: table.label}}
|
title={{fontWeight: "bold", text: table.label}}
|
||||||
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name))}
|
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name))}
|
||||||
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name)))}}
|
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name)))}}
|
||||||
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
|
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
|
||||||
|
isDisabled={true}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Link>
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
23
src/qqq/pages/apps/NoApps.tsx
Normal file
23
src/qqq/pages/apps/NoApps.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {Alert} from "@mui/material";
|
||||||
|
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||||
|
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
foo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoApps.defaultProps = {
|
||||||
|
foo: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function NoApps({foo}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<Alert color="error">You do not have permission to access any apps.</Alert>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NoApps;
|
@ -71,16 +71,18 @@ interface Props
|
|||||||
defaultProcessValues?: any;
|
defaultProcessValues?: any;
|
||||||
isModal?: boolean;
|
isModal?: boolean;
|
||||||
isWidget?: boolean;
|
isWidget?: boolean;
|
||||||
|
isReport?: boolean;
|
||||||
recordIds?: string | QQueryFilter;
|
recordIds?: string | QQueryFilter;
|
||||||
closeModalHandler?: (event: object, reason: string) => void;
|
closeModalHandler?: (event: object, reason: string) => void;
|
||||||
forceReInit?: number;
|
forceReInit?: number;
|
||||||
|
overrideLabel?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_RETRY_MILLIS = 1_500;
|
const INITIAL_RETRY_MILLIS = 1_500;
|
||||||
const RETRY_MAX_MILLIS = 12_000;
|
const RETRY_MAX_MILLIS = 12_000;
|
||||||
const BACKOFF_AMOUNT = 1.5;
|
const BACKOFF_AMOUNT = 1.5;
|
||||||
|
|
||||||
function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds, closeModalHandler, forceReInit}: Props): JSX.Element
|
function ProcessRun({process, defaultProcessValues, isModal, isWidget, isReport, recordIds, closeModalHandler, forceReInit, overrideLabel}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const processNameParam = useParams().processName;
|
const processNameParam = useParams().processName;
|
||||||
const processName = process === null ? processNameParam : process.name;
|
const processName = process === null ? processNameParam : process.name;
|
||||||
@ -236,9 +238,9 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
|||||||
Error
|
Error
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
<MDTypography color="body" variant="button">
|
<MDTypography color="body" variant="button">
|
||||||
An error occurred while running the process:
|
An error occurred while running the {isReport ? "report" : "process"}:
|
||||||
{" "}
|
{" "}
|
||||||
{process.label}
|
{overrideLabel ?? process.label}
|
||||||
{
|
{
|
||||||
isUserFacingError ? (
|
isUserFacingError ? (
|
||||||
<Box mt={1}>
|
<Box mt={1}>
|
||||||
@ -331,7 +333,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
!isWidget &&
|
!isWidget &&
|
||||||
<MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold">
|
<MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold">
|
||||||
{(isModal) ? `${process.label}: ` : ""}
|
{(isModal) ? `${overrideLabel ?? process.label}: ` : ""}
|
||||||
{step?.label}
|
{step?.label}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
}
|
}
|
||||||
@ -616,7 +618,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
|||||||
|
|
||||||
if(! isWidget)
|
if(! isWidget)
|
||||||
{
|
{
|
||||||
setPageHeader(processMetaData.label);
|
setPageHeader(overrideLabel ?? processMetaData.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
let newIndex = null;
|
let newIndex = null;
|
||||||
@ -935,6 +937,18 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
|||||||
}
|
}
|
||||||
}, [needToCheckJobStatus, retryMillis]);
|
}, [needToCheckJobStatus, retryMillis]);
|
||||||
|
|
||||||
|
|
||||||
|
const handlePermissionDenied = (e: any): boolean =>
|
||||||
|
{
|
||||||
|
if ((e as QException).status === "403")
|
||||||
|
{
|
||||||
|
setProcessError(`You do not have permission to run this ${isReport ? "report" : "process"}.`, true)
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// do the initial load of data for the process - that is, meta data, plus the init step //
|
// do the initial load of data for the process - that is, meta data, plus the init step //
|
||||||
// also - allow the component that contains this component to force a re-init, by //
|
// also - allow the component that contains this component to force a re-init, by //
|
||||||
@ -1011,7 +1025,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
|||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
setProcessError("Error loading process definition.");
|
handlePermissionDenied(e) || setProcessError("Error loading process definition.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1031,7 +1045,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
|||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
setProcessError("Error initializing process.");
|
handlePermissionDenied(e) || setProcessError("Error initializing process.");
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -1286,9 +1300,11 @@ ProcessRun.defaultProps = {
|
|||||||
defaultProcessValues: {},
|
defaultProcessValues: {},
|
||||||
isModal: false,
|
isModal: false,
|
||||||
isWidget: false,
|
isWidget: false,
|
||||||
|
isReport: false,
|
||||||
recordIds: null,
|
recordIds: null,
|
||||||
closeModalHandler: null,
|
closeModalHandler: null,
|
||||||
forceReInit: 0
|
forceReInit: 0,
|
||||||
|
overrideLabel: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProcessRun;
|
export default ProcessRun;
|
||||||
|
@ -53,7 +53,7 @@ function ReportRun({report}: Props): JSX.Element
|
|||||||
setPageHeader(report.label);
|
setPageHeader(report.label);
|
||||||
const process = metaData.processes.get(report.processName);
|
const process = metaData.processes.get(report.processName);
|
||||||
const defaultProcessValues = {reportName: report.name};
|
const defaultProcessValues = {reportName: report.name};
|
||||||
return (<ProcessRun process={process} defaultProcessValues={defaultProcessValues} />);
|
return (<ProcessRun process={process} overrideLabel={report.label} isReport={true} defaultProcessValues={defaultProcessValues} />);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1044,27 +1044,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
keepMounted
|
keepMounted
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_INSERT) &&
|
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
|
||||||
<MenuItem onClick={bulkLoadClicked}>
|
<MenuItem onClick={bulkLoadClicked}>
|
||||||
<ListItemIcon><Icon>library_add</Icon></ListItemIcon>
|
<ListItemIcon><Icon>library_add</Icon></ListItemIcon>
|
||||||
Bulk Load
|
Bulk Load
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_UPDATE) &&
|
table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission &&
|
||||||
<MenuItem onClick={bulkEditClicked}>
|
<MenuItem onClick={bulkEditClicked}>
|
||||||
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
||||||
Bulk Edit
|
Bulk Edit
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_DELETE) &&
|
table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission &&
|
||||||
<MenuItem onClick={bulkDeleteClicked}>
|
<MenuItem onClick={bulkDeleteClicked}>
|
||||||
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
||||||
Bulk Delete
|
Bulk Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
{(table.capabilities.has(Capability.TABLE_INSERT) || table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && tableProcesses.length > 0 && <Divider />}
|
{((table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission) || (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)) && tableProcesses.length > 0 && <Divider />}
|
||||||
{tableProcesses.map((process) => (
|
{tableProcesses.map((process) => (
|
||||||
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
||||||
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
||||||
@ -1072,7 +1072,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
{
|
{
|
||||||
tableProcesses.length == 0 && !table.capabilities.has(Capability.TABLE_INSERT) && !table.capabilities.has(Capability.TABLE_UPDATE) && !table.capabilities.has(Capability.TABLE_DELETE) &&
|
tableProcesses.length == 0 && !(table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission) && !(table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) && !(table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission) &&
|
||||||
<MenuItem disabled>
|
<MenuItem disabled>
|
||||||
<ListItemIcon><Icon>block</Icon></ListItemIcon>
|
<ListItemIcon><Icon>block</Icon></ListItemIcon>
|
||||||
<i>No actions available</i>
|
<i>No actions available</i>
|
||||||
@ -1112,6 +1112,17 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
document.scrollingElement.scrollTop = 0;
|
document.scrollingElement.scrollTop = 0;
|
||||||
}, [ pageNumber, rowsPerPage ]);
|
}, [ pageNumber, rowsPerPage ]);
|
||||||
|
|
||||||
|
if(tableMetaData && !tableMetaData.readPermission)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<DashboardLayout>
|
||||||
|
<NavBar />
|
||||||
|
<Alert severity="error">
|
||||||
|
You do not have permission to view {tableMetaData?.label} records
|
||||||
|
</Alert>
|
||||||
|
</DashboardLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
@ -1148,7 +1159,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_INSERT) &&
|
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
|
||||||
<QCreateNewButton />
|
<QCreateNewButton />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
|
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
|
||||||
const [actionsMenu, setActionsMenu] = useState(null);
|
const [actionsMenu, setActionsMenu] = useState(null);
|
||||||
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
||||||
|
const [successMessage, setSuccessMessage] = useState(null as string);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const {setPageHeader} = useContext(QContext);
|
const {setPageHeader} = useContext(QContext);
|
||||||
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
||||||
@ -108,6 +109,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
const reload = () =>
|
const reload = () =>
|
||||||
{
|
{
|
||||||
|
setSuccessMessage(null);
|
||||||
setNotFoundMessage(null);
|
setNotFoundMessage(null);
|
||||||
setAsyncLoadInited(false);
|
setAsyncLoadInited(false);
|
||||||
setTableMetaData(null);
|
setTableMetaData(null);
|
||||||
@ -267,21 +269,30 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
|
const historyPurge = (path: string) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HistoryUtils.ensurePathNotInHistory(location.pathname);
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
console.error("Error pushing history: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (e instanceof QException)
|
if (e instanceof QException)
|
||||||
{
|
{
|
||||||
if ((e as QException).status === "404")
|
if ((e as QException).status === "404")
|
||||||
{
|
{
|
||||||
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
|
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
|
||||||
|
historyPurge(location.pathname)
|
||||||
try
|
return;
|
||||||
{
|
}
|
||||||
HistoryUtils.ensurePathNotInHistory(location.pathname);
|
else if ((e as QException).status === "403")
|
||||||
}
|
{
|
||||||
catch(e)
|
setNotFoundMessage(`You do not have permission to view ${tableMetaData.label} records`);
|
||||||
{
|
historyPurge(location.pathname)
|
||||||
console.error("Error pushing history: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,7 +309,6 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
console.error("Error pushing history: " + e);
|
console.error("Error pushing history: " + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
// define the sections, e.g., for the left-bar //
|
// define the sections, e.g., for the left-bar //
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
@ -344,10 +354,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
section.fieldNames.map((fieldName: string) => (
|
section.fieldNames.map((fieldName: string) => (
|
||||||
<Box key={fieldName} flexDirection="row" pr={2}>
|
<Box key={fieldName} flexDirection="row" pr={2}>
|
||||||
<Typography variant="button" textTransform="none" fontWeight="bold" pr={1}>
|
<Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)">
|
||||||
{tableMetaData.fields.get(fieldName).label}:
|
{tableMetaData.fields.get(fieldName).label}:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="button" textTransform="none" fontWeight="regular" color="text">
|
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
||||||
{ValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")}
|
{ValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@ -396,6 +406,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
setSectionFieldElements(sectionFieldElements);
|
setSectionFieldElements(sectionFieldElements);
|
||||||
setNonT1TableSections(nonT1TableSections);
|
setNonT1TableSections(nonT1TableSections);
|
||||||
|
|
||||||
|
if (searchParams.get("createSuccess") || searchParams.get("updateSuccess"))
|
||||||
|
{
|
||||||
|
setSuccessMessage(`${tableMetaData.label} successfully ${searchParams.get("createSuccess") ? "created" : "updated"}`);
|
||||||
|
}
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -445,14 +460,14 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
keepMounted
|
keepMounted
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_UPDATE) &&
|
table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission &&
|
||||||
<MenuItem onClick={() => navigate("edit")}>
|
<MenuItem onClick={() => navigate("edit")}>
|
||||||
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
||||||
Edit
|
Edit
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_DELETE) &&
|
table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission &&
|
||||||
<MenuItem onClick={() =>
|
<MenuItem onClick={() =>
|
||||||
{
|
{
|
||||||
setActionsMenu(null);
|
setActionsMenu(null);
|
||||||
@ -463,7 +478,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
{tableProcesses.length > 0 && (table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && <Divider />}
|
{tableProcesses.length > 0 && ((table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)) && <Divider />}
|
||||||
{tableProcesses.map((process) => (
|
{tableProcesses.map((process) => (
|
||||||
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
||||||
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
||||||
@ -552,21 +567,18 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
notFoundMessage
|
notFoundMessage
|
||||||
?
|
?
|
||||||
<Box>{notFoundMessage}</Box>
|
<Alert color="error" sx={{mb: 3}}>{notFoundMessage}</Alert>
|
||||||
:
|
:
|
||||||
<Box pb={3}>
|
<Box pb={3}>
|
||||||
{
|
{
|
||||||
(searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? (
|
successMessage ?
|
||||||
<Alert color="success" onClose={() =>
|
<Alert color="success" sx={{mb: 3}} onClose={() =>
|
||||||
{}}>
|
{
|
||||||
{tableMetaData?.label}
|
setSuccessMessage(null)
|
||||||
{" "}
|
}}>
|
||||||
successfully
|
{successMessage}
|
||||||
{" "}
|
|
||||||
{searchParams.get("createSuccess") ? "created" : "updated"}
|
|
||||||
|
|
||||||
</Alert>
|
</Alert>
|
||||||
) : ("")
|
: ("")
|
||||||
}
|
}
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
@ -588,7 +600,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" justifyContent="space-between" width="100%" alignItems="center">
|
<Box display="flex" justifyContent="space-between" width="100%" alignItems="center">
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
|
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel || ""}` : ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||||
{renderActionsMenu}
|
{renderActionsMenu}
|
||||||
@ -610,10 +622,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
<Box component="form" p={3}>
|
<Box component="form" p={3}>
|
||||||
<Grid container justifyContent="flex-end" spacing={3}>
|
<Grid container justifyContent="flex-end" spacing={3}>
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_DELETE) && <QDeleteButton onClickHandler={handleClickDeleteButton} />
|
table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission && <QDeleteButton onClickHandler={handleClickDeleteButton} />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
table.capabilities.has(Capability.TABLE_UPDATE) && <QEditButton />
|
table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission && <QEditButton />
|
||||||
}
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -182,7 +182,7 @@ export default class DataGridUtils
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === tableMetaData.primaryKeyField)
|
if (key === tableMetaData.primaryKeyField && linkBase)
|
||||||
{
|
{
|
||||||
columns.splice(0, 0, column);
|
columns.splice(0, 0, column);
|
||||||
column.renderCell = (cellValues: any) => (
|
column.renderCell = (cellValues: any) => (
|
||||||
|
@ -95,8 +95,8 @@ class ValueUtils
|
|||||||
let tablePath = ValueUtils.getQInstance().getTablePathByName(toRecordFromTable);
|
let tablePath = ValueUtils.getQInstance().getTablePathByName(toRecordFromTable);
|
||||||
if (!tablePath)
|
if (!tablePath)
|
||||||
{
|
{
|
||||||
console.log("Couldn't find path for table: " + tablePath);
|
console.log("Couldn't find path for table: " + toRecordFromTable);
|
||||||
return ("");
|
return (displayValue ?? rawValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tablePath.endsWith("/"))
|
if (!tablePath.endsWith("/"))
|
||||||
|
Reference in New Issue
Block a user