diff --git a/package.json b/package.json
index 885fc5c..033068b 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1",
"@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/material": "5.11.1",
"@mui/styles": "5.11.1",
diff --git a/src/App.tsx b/src/App.tsx
index fae0804..655de15 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -40,6 +40,7 @@ import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
import theme from "qqq/components/legacy/Theme";
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "qqq/context";
import AppHome from "qqq/pages/apps/Home";
+import NoApps from "qqq/pages/apps/NoApps";
import ProcessRun from "qqq/pages/processes/ProcessRun";
import ReportRun from "qqq/pages/processes/ReportRun";
import EntityCreate from "qqq/pages/records/create/RecordCreate";
@@ -53,7 +54,6 @@ import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
const qController = Client.getInstance();
export const SESSION_ID_COOKIE_NAME = "sessionId";
-LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
export default function App()
{
@@ -63,6 +63,9 @@ export default function App()
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
const [profileRoutes, setProfileRoutes] = useState({});
const [branding, setBranding] = useState({} as QBrandingMetaData);
+ const [needLicenseKey, setNeedLicenseKey] = useState(true);
+
+ const [defaultRoute, setDefaultRoute] = useState("/no-apps");
useEffect(() =>
{
@@ -83,17 +86,21 @@ export default function App()
/////////////////////////////////////////
try
{
- console.log("Loading token...");
- await getAccessTokenSilently();
- const idToken = await getIdTokenClaims();
- setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
+ console.log("Loading token from auth0...");
+ const accessToken = await getAccessTokenSilently();
+ qController.setAuthorizationHeaderValue("Bearer " + accessToken);
+
+ /////////////////////////////////////////////////////////////////////////////////
+ // we've stopped using session id cook with auth0, so make sure it is not set. //
+ /////////////////////////////////////////////////////////////////////////////////
+ removeCookie(SESSION_ID_COOKIE_NAME);
+
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;
@@ -105,6 +112,7 @@ export default function App()
// use a random token if anonymous or mock //
/////////////////////////////////////////////
console.log("Generating random token...");
+ qController.setAuthorizationHeaderValue(null);
setIsFullyAuthenticated(true);
setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
console.log("Token generation complete.");
@@ -119,6 +127,16 @@ export default function App()
})();
}, [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 {miniSidenav, direction, layout, openConfigurator, sidenavColor} = controller;
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)
{
const path = `${parentPath}/${app.name}`;
@@ -224,6 +244,16 @@ export default function App()
route: path,
component: ,
});
+
+ 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)
{
@@ -363,14 +393,29 @@ export default function App()
const sideNavAppList = [] as any[];
const appRoutesList = [] as any[];
- ///////////////////////////////////////////////////////////////////////////////////
- // iterate throught the list to find the 'main dashboard so we can put it first' //
- ///////////////////////////////////////////////////////////////////////////////////
- for (let i = 0; i < metaData.appTree.length; i++)
+ //////////////////////////////////////////////////////////////////////////////////
+ // iterate through the list to find the 'main dashboard so we can put it first' //
+ //////////////////////////////////////////////////////////////////////////////////
+ if(metaData.appTree && metaData.appTree.length)
{
- const app = metaData.appTree[i];
- addAppToSideNavList(app, sideNavAppList, "", 0);
- addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
+ for (let i = 0; i < metaData.appTree.length; i++)
+ {
+ 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: ,
+ });
}
const newSideNavRoutes = [];
@@ -390,10 +435,8 @@ export default function App()
console.error(e);
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 //
//////////////////////////////////////////////////////
@@ -486,7 +529,7 @@ export default function App()
onMouseLeave={handleOnMouseLeave}
/>
- } />
+ } />
{appRoutes && getRoutes(appRoutes)}
{profileRoutes && getRoutes([profileRoutes])}
diff --git a/src/index.tsx b/src/index.tsx
index db91fa4..a5b5f71 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -81,6 +81,9 @@ authenticationMetaDataPromise.then((authenticationMetaData) =>
// @ts-ignore
const clientId = authenticationMetaData.data.clientId;
+ // @ts-ignore
+ const audience = authenticationMetaData.data.audience;
+
if(!domain || !clientId)
{
render(
@@ -103,7 +106,8 @@ authenticationMetaDataPromise.then((authenticationMetaData) =>
diff --git a/src/qqq/components/forms/EntityForm.tsx b/src/qqq/components/forms/EntityForm.tsx
index 78f68b0..287795f 100644
--- a/src/qqq/components/forms/EntityForm.tsx
+++ b/src/qqq/components/forms/EntityForm.tsx
@@ -88,7 +88,7 @@ function EntityForm(props: Props): JSX.Element
const [tableSections, setTableSections] = useState(null as QTableSection[]);
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);
@@ -189,7 +189,11 @@ function EntityForm(props: Props): JSX.Element
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
@@ -206,7 +210,11 @@ function EntityForm(props: Props): JSX.Element
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`;
let body;
- if (noCapabilityError)
+ if (notAllowedError)
{
body = (
- {noCapabilityError}
+ {notAllowedError}
+ {props.isModal &&
+
+
+
+ }
diff --git a/src/qqq/components/processes/GoogleDriveFolderPicker.tsx b/src/qqq/components/processes/GoogleDriveFolderPicker.tsx
index 22d5a78..3c2899d 100644
--- a/src/qqq/components/processes/GoogleDriveFolderPicker.tsx
+++ b/src/qqq/components/processes/GoogleDriveFolderPicker.tsx
@@ -39,8 +39,8 @@ interface Props
export function GoogleDriveFolderPicker({showDefaultFoldersView, showSharedDrivesView, qInstance}: Props): JSX.Element
{
- const clientId = "649816208522-m6oa971vqicrc1hlam7333pt4qck0tm8.apps.googleusercontent.com";
- const appApiKey = "AIzaSyBhXK34CF2fUfCgUS1VIHoKZbHxEBuHtDM";
+ const clientId = qInstance.environmentValues.get("GOOGLE_APP_CLIENT_ID") || process.env.REACT_APP_GOOGLE_APP_CLIENT_ID;
+ const appApiKey = qInstance.environmentValues.get("GOOGLE_APP_API_KEY") || process.env.REACT_APP_GOOGLE_APP_API_KEY;
if(!clientId)
{
console.error("Missing environmentValue GOOGLE_APP_CLIENT_ID")
diff --git a/src/qqq/components/processes/ProcessLinkCard.tsx b/src/qqq/components/processes/ProcessLinkCard.tsx
index 958b605..a86e786 100644
--- a/src/qqq/components/processes/ProcessLinkCard.tsx
+++ b/src/qqq/components/processes/ProcessLinkCard.tsx
@@ -24,6 +24,7 @@ import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider";
import Icon from "@mui/material/Icon";
import {ReactNode} from "react";
+import colors from "qqq/assets/theme/base/colors";
import MDTypography from "qqq/components/legacy/MDTypography";
interface Props
@@ -37,33 +38,34 @@ interface Props
label: string;
};
icon: ReactNode;
+ isDisabled?: boolean;
[key: string]: any;
}
function ProcessLinkCard({
- color, isReport, title, percentage, icon,
+ color, isReport, title, percentage, icon, isDisabled
}: Props): JSX.Element
{
return (
-
+
{icon}
-
+
{title}
@@ -81,9 +83,15 @@ function ProcessLinkCard({
{percentage.amount}
{
- isReport
- ? `Click here to access the ${title} report.`
- : `Click here to run the process called ${title}.`
+ isDisabled ? (
+ isReport
+ ? `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}
@@ -100,6 +108,7 @@ ProcessLinkCard.defaultProps = {
text: "",
label: "",
},
+ isDisabled: false,
};
export default ProcessLinkCard;
diff --git a/src/qqq/components/widgets/misc/RecordGridWidget.tsx b/src/qqq/components/widgets/misc/RecordGridWidget.tsx
index 954ef1f..c076491 100644
--- a/src/qqq/components/widgets/misc/RecordGridWidget.tsx
+++ b/src/qqq/components/widgets/misc/RecordGridWidget.tsx
@@ -61,7 +61,10 @@ function RecordGridWidget({title, data}: Props): JSX.Element
const tableMetaData = new QTableMetaData(data.childTableMetaData);
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);
////////////////////////////////////////////////////////////////
diff --git a/src/qqq/components/widgets/statistics/MiniStatisticsCard.tsx b/src/qqq/components/widgets/statistics/MiniStatisticsCard.tsx
index 5d933f1..e880bf1 100644
--- a/src/qqq/components/widgets/statistics/MiniStatisticsCard.tsx
+++ b/src/qqq/components/widgets/statistics/MiniStatisticsCard.tsx
@@ -46,6 +46,7 @@ interface Props {
component: ReactNode;
};
direction?: "right" | "left";
+ isDisabled?: boolean;
[key: string]: any;
}
@@ -56,6 +57,7 @@ function MiniStatisticsCard({
percentage,
icon,
direction,
+ isDisabled,
}: Props): JSX.Element
{
const [controller] = useMaterialUIController();
@@ -108,7 +110,7 @@ function MiniStatisticsCard({
justifyContent="center"
alignItems="center"
color="#FFFFFF"
- sx={{borderRadius: "10px", backgroundColor: colors.info.main}}
+ sx={{borderRadius: "10px", backgroundColor: isDisabled ? colors.secondary.main : colors.info.main}}
>
{icon.component}
@@ -134,6 +136,7 @@ MiniStatisticsCard.defaultProps = {
text: "",
},
direction: "right",
+ isDisabled: false,
};
export default MiniStatisticsCard;
diff --git a/src/qqq/pages/apps/Home.tsx b/src/qqq/pages/apps/Home.tsx
index 00aceab..223ad79 100644
--- a/src/qqq/pages/apps/Home.tsx
+++ b/src/qqq/pages/apps/Home.tsx
@@ -125,7 +125,7 @@ function AppHome({app}: Props): JSX.Element
{
const tableMetaData = await qController.loadTableMetaData(table.name);
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);
@@ -183,6 +183,20 @@ function AppHome({app}: Props): JSX.Element
}, 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 (
@@ -210,7 +224,7 @@ function AppHome({app}: Props): JSX.Element
{
section.processes ? (
-
+
Actions
) : null
@@ -224,12 +238,19 @@ function AppHome({app}: Props): JSX.Element
let process = app.childMap.get(processName);
return (
-
+ {hasProcessPermission(processName) ?
+
+
+ :
-
+ }
);
})
@@ -244,7 +265,7 @@ function AppHome({app}: Props): JSX.Element
}
{
section.reports ? (
-
+
Reports
) : null
@@ -258,13 +279,21 @@ function AppHome({app}: Props): JSX.Element
let report = app.childMap.get(reportName);
return (
-
+ {hasReportPermission(reportName) ?
+
+
+ :
-
+ }
);
})
@@ -279,7 +308,7 @@ function AppHome({app}: Props): JSX.Element
}
{
section.tables ? (
-
+
Data
) : null
@@ -293,16 +322,27 @@ function AppHome({app}: Props): JSX.Element
let table = app.childMap.get(tableName);
return (
-
-
+ {hasTablePermission(tableName) ?
+
+
+ {table.iconName || app.iconName}}}
+ />
+
+ :
+
{table.iconName || app.iconName}}}
+ isDisabled={true}
/>
-
+ }
);
})
diff --git a/src/qqq/pages/apps/NoApps.tsx b/src/qqq/pages/apps/NoApps.tsx
new file mode 100644
index 0000000..e5da2e7
--- /dev/null
+++ b/src/qqq/pages/apps/NoApps.tsx
@@ -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 (
+
+ You do not have permission to access any apps.
+
+ );
+}
+
+export default NoApps;
diff --git a/src/qqq/pages/processes/ProcessRun.tsx b/src/qqq/pages/processes/ProcessRun.tsx
index 1ef7e38..2ddd9f0 100644
--- a/src/qqq/pages/processes/ProcessRun.tsx
+++ b/src/qqq/pages/processes/ProcessRun.tsx
@@ -71,16 +71,18 @@ interface Props
defaultProcessValues?: any;
isModal?: boolean;
isWidget?: boolean;
+ isReport?: boolean;
recordIds?: string | QQueryFilter;
closeModalHandler?: (event: object, reason: string) => void;
forceReInit?: number;
+ overrideLabel?: string
}
const INITIAL_RETRY_MILLIS = 1_500;
const RETRY_MAX_MILLIS = 12_000;
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 processName = process === null ? processNameParam : process.name;
@@ -236,9 +238,9 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
Error
- An error occurred while running the process:
+ An error occurred while running the {isReport ? "report" : "process"}:
{" "}
- {process.label}
+ {overrideLabel ?? process.label}
{
isUserFacingError ? (
@@ -331,7 +333,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
!isWidget &&
- {(isModal) ? `${process.label}: ` : ""}
+ {(isModal) ? `${overrideLabel ?? process.label}: ` : ""}
{step?.label}
}
@@ -616,7 +618,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
if(! isWidget)
{
- setPageHeader(processMetaData.label);
+ setPageHeader(overrideLabel ?? processMetaData.label);
}
let newIndex = null;
@@ -935,6 +937,18 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
}
}, [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 //
// 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)
{
- setProcessError("Error loading process definition.");
+ handlePermissionDenied(e) || setProcessError("Error loading process definition.");
return;
}
@@ -1031,7 +1045,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
}
catch (e)
{
- setProcessError("Error initializing process.");
+ handlePermissionDenied(e) || setProcessError("Error initializing process.");
}
})();
}
@@ -1286,9 +1300,11 @@ ProcessRun.defaultProps = {
defaultProcessValues: {},
isModal: false,
isWidget: false,
+ isReport: false,
recordIds: null,
closeModalHandler: null,
- forceReInit: 0
+ forceReInit: 0,
+ overrideLabel: null,
};
export default ProcessRun;
diff --git a/src/qqq/pages/processes/ReportRun.tsx b/src/qqq/pages/processes/ReportRun.tsx
index 872d2cf..04186e6 100644
--- a/src/qqq/pages/processes/ReportRun.tsx
+++ b/src/qqq/pages/processes/ReportRun.tsx
@@ -53,7 +53,7 @@ function ReportRun({report}: Props): JSX.Element
setPageHeader(report.label);
const process = metaData.processes.get(report.processName);
const defaultProcessValues = {reportName: report.name};
- return ();
+ return ();
}
else
{
diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx
index 2262383..2538be2 100644
--- a/src/qqq/pages/records/query/RecordQuery.tsx
+++ b/src/qqq/pages/records/query/RecordQuery.tsx
@@ -1044,27 +1044,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
keepMounted
>
{
- table.capabilities.has(Capability.TABLE_INSERT) &&
+ table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
}
{
- table.capabilities.has(Capability.TABLE_UPDATE) &&
+ table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission &&
}
{
- table.capabilities.has(Capability.TABLE_DELETE) &&
+ table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission &&
}
- {(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)) && tableProcesses.length > 0 && }
{tableProcesses.map((process) => (
))}
{
- 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) &&
{
- table.capabilities.has(Capability.TABLE_INSERT) &&
+ table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
}
diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx
index cec6ada..b9a49c3 100644
--- a/src/qqq/pages/records/view/RecordView.tsx
+++ b/src/qqq/pages/records/view/RecordView.tsx
@@ -95,6 +95,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
const [actionsMenu, setActionsMenu] = useState(null);
const [notFoundMessage, setNotFoundMessage] = useState(null);
+ const [successMessage, setSuccessMessage] = useState(null as string);
const [searchParams] = useSearchParams();
const {setPageHeader} = useContext(QContext);
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
@@ -108,6 +109,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const reload = () =>
{
+ setSuccessMessage(null);
setNotFoundMessage(null);
setAsyncLoadInited(false);
setTableMetaData(null);
@@ -267,21 +269,30 @@ function RecordView({table, launchProcess}: Props): JSX.Element
}
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 as QException).status === "404")
{
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
-
- try
- {
- HistoryUtils.ensurePathNotInHistory(location.pathname);
- }
- catch(e)
- {
- console.error("Error pushing history: " + e);
- }
-
+ historyPurge(location.pathname)
+ return;
+ }
+ else if ((e as QException).status === "403")
+ {
+ setNotFoundMessage(`You do not have permission to view ${tableMetaData.label} records`);
+ historyPurge(location.pathname)
return;
}
}
@@ -298,7 +309,6 @@ function RecordView({table, launchProcess}: Props): JSX.Element
console.error("Error pushing history: " + e);
}
-
/////////////////////////////////////////////////
// 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) => (
-
+
{tableMetaData.fields.get(fieldName).label}:
-
+
{ValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")}
@@ -396,6 +406,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
setSectionFieldElements(sectionFieldElements);
setNonT1TableSections(nonT1TableSections);
+ if (searchParams.get("createSuccess") || searchParams.get("updateSuccess"))
+ {
+ setSuccessMessage(`${tableMetaData.label} successfully ${searchParams.get("createSuccess") ? "created" : "updated"}`);
+ }
+
forceUpdate();
})();
}
@@ -445,14 +460,14 @@ function RecordView({table, launchProcess}: Props): JSX.Element
keepMounted
>
{
- table.capabilities.has(Capability.TABLE_UPDATE) &&
+ table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission &&
}
{
- table.capabilities.has(Capability.TABLE_DELETE) &&
+ table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission &&
}
- {tableProcesses.length > 0 && (table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && }
+ {tableProcesses.length > 0 && ((table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)) && }
{tableProcesses.map((process) => (