mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
Add table developer page, with api docs
This commit is contained in:
4238
package-lock.json
generated
4238
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,7 @@
|
||||
"formik": "2.2.9",
|
||||
"html-react-parser": "1.4.8",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"rapidoc": "9.3.4",
|
||||
"react": "17.0.2",
|
||||
"react-ace": "10.1.0",
|
||||
"react-chartjs-2": "3.0.4",
|
||||
|
14
src/App.tsx
14
src/App.tsx
@ -44,6 +44,7 @@ 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";
|
||||
import TableDeveloperView from "qqq/pages/records/developer/TableDeveloperView";
|
||||
import EntityEdit from "qqq/pages/records/edit/RecordEdit";
|
||||
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
||||
@ -279,6 +280,13 @@ export default function App()
|
||||
component: <EntityCreate table={table} />,
|
||||
});
|
||||
|
||||
routeList.push({
|
||||
name: `${app.label}`,
|
||||
key: `${app.name}.dev`,
|
||||
route: `${path}/dev`,
|
||||
component: <TableDeveloperView table={table} />,
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is the path to open a modal-form when viewing a record, to create a different (child) record //
|
||||
// it can also be done with a hash like: #/createChild=:childTableName //
|
||||
@ -332,8 +340,8 @@ export default function App()
|
||||
});
|
||||
});
|
||||
|
||||
const runRecordScriptProcess = metaData.processes.get("runRecordScript")
|
||||
if(runRecordScriptProcess)
|
||||
const runRecordScriptProcess = metaData.processes.get("runRecordScript");
|
||||
if (runRecordScriptProcess)
|
||||
{
|
||||
const process = runRecordScriptProcess;
|
||||
routeList.push({
|
||||
@ -403,7 +411,7 @@ export default function App()
|
||||
}
|
||||
if (metaData.branding.accentColor)
|
||||
{
|
||||
setAccentColor(metaData.branding.accentColor)
|
||||
setAccentColor(metaData.branding.accentColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
216
src/qqq/pages/records/developer/RapiDocReact.tsx
Normal file
216
src/qqq/pages/records/developer/RapiDocReact.tsx
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from "react";
|
||||
import "rapidoc";
|
||||
|
||||
interface RapiDocProps
|
||||
extends React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>
|
||||
{
|
||||
// General
|
||||
"spec-url": string;
|
||||
"update-route"?: boolean;
|
||||
"route-prefix"?: string;
|
||||
"sort-tags"?: boolean;
|
||||
"sort-endpoints-by"?: "path" | "method" | "summary" | "none";
|
||||
"heading-text"?: string;
|
||||
"goto-path"?: string;
|
||||
"fill-request-fields-with-example"?: boolean;
|
||||
"persist-auth"?: boolean;
|
||||
// UI Colors and Fonts
|
||||
theme?: "light" | "dark";
|
||||
"bg-color"?: string;
|
||||
"text-color"?: string;
|
||||
"header-color"?: string;
|
||||
"primary-color"?: string;
|
||||
"load-fonts"?: boolean;
|
||||
"regular-fonts"?: string;
|
||||
"mono-fonts"?: string;
|
||||
"font-size"?: "default" | "large" | "largest";
|
||||
// Navigation
|
||||
"use-path-in-nav-bar"?: boolean;
|
||||
"nav-bg-color"?: string;
|
||||
"nav-text-color"?: string;
|
||||
"nav-hover-bg-color"?: string;
|
||||
"nav-hover-text-color"?: string;
|
||||
"nav-accent-color"?: string;
|
||||
"nav-item-spacing"?: "default" | "compact" | "relaxed";
|
||||
// UI Layout & Placement
|
||||
layout?: "row" | "column";
|
||||
"render-style"?: "read" | "view" | "focused";
|
||||
"on-nav-tag-click"?: "expand-collapse" | "show-description";
|
||||
"schema-style"?: "tree" | "table";
|
||||
"schema-expand-level"?: number;
|
||||
"schema-description-expanded"?: boolean;
|
||||
"schema-hide-read-only"?: "always" | "never" | string;
|
||||
"default-schema-tab"?: "model" | "example";
|
||||
"response-area-height"?: string;
|
||||
// Hide/Show Sections
|
||||
"show-info"?: boolean;
|
||||
"info-description-headings-in-navbar"?: boolean;
|
||||
"show-components"?: boolean;
|
||||
"show-header"?: boolean;
|
||||
"allow-authentication"?: boolean;
|
||||
"allow-spec-url-load"?: boolean;
|
||||
"allow-spec-file-load"?: boolean;
|
||||
"allow-spec-file-download"?: boolean;
|
||||
"allow-search"?: boolean;
|
||||
"allow-advanced-search"?: boolean;
|
||||
"allow-try"?: boolean;
|
||||
"allow-server-selection"?: boolean;
|
||||
"allow-schema-description-expand-toggle"?: boolean;
|
||||
// API Server & calls
|
||||
"server-url"?: string;
|
||||
"default-api-server"?: string;
|
||||
"api-key-name"?: string;
|
||||
"api-key-location"?: "header" | "query";
|
||||
"api-key-value"?: string;
|
||||
"fetch-credentials"?: "omit" | "same-origin" | "include";
|
||||
// Events
|
||||
beforeRender?: (spec: any) => void;
|
||||
specLoaded?: (spec: any) => void;
|
||||
beforeTry?: (request: any) => any;
|
||||
afterTry?: (data: any) => any;
|
||||
apiServerChange?: (server: any) => any;
|
||||
}
|
||||
|
||||
declare global
|
||||
{
|
||||
interface HTMLElementTagNameMap
|
||||
{
|
||||
"rapi-doc": HTMLDivElement;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
namespace JSX
|
||||
{
|
||||
interface IntrinsicElements
|
||||
{
|
||||
"rapi-doc": RapiDocProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const RapiDocReact = React.forwardRef<HTMLDivElement, RapiDocProps>(
|
||||
(
|
||||
{
|
||||
beforeRender,
|
||||
specLoaded,
|
||||
beforeTry,
|
||||
afterTry,
|
||||
apiServerChange,
|
||||
children,
|
||||
...props
|
||||
}: RapiDocProps,
|
||||
ref
|
||||
) =>
|
||||
{
|
||||
const localRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() =>
|
||||
{
|
||||
const rapiDocRef =
|
||||
typeof ref === "object" && ref?.current
|
||||
? ref?.current
|
||||
: localRef.current;
|
||||
|
||||
const handleBeforeRender = (spec: any) =>
|
||||
{
|
||||
beforeRender && beforeRender(spec);
|
||||
};
|
||||
|
||||
const handleSpecLoaded = (spec: any) =>
|
||||
{
|
||||
specLoaded && specLoaded(spec);
|
||||
};
|
||||
|
||||
const handleBeforeTry = (request: any) =>
|
||||
{
|
||||
beforeTry && beforeTry(request);
|
||||
};
|
||||
|
||||
const handleAfterTry = (data: any) =>
|
||||
{
|
||||
afterTry && afterTry(data);
|
||||
};
|
||||
|
||||
const handleApiServerChange = (server: any) =>
|
||||
{
|
||||
apiServerChange && apiServerChange(server);
|
||||
};
|
||||
|
||||
console.log("rapiDocRef", rapiDocRef);
|
||||
if (rapiDocRef)
|
||||
{
|
||||
beforeRender &&
|
||||
rapiDocRef.addEventListener("before-render", handleBeforeRender);
|
||||
specLoaded &&
|
||||
rapiDocRef.addEventListener("spec-loaded", handleSpecLoaded);
|
||||
beforeTry && rapiDocRef.addEventListener("before-try", handleBeforeTry);
|
||||
afterTry && rapiDocRef.addEventListener("after-try", handleAfterTry);
|
||||
apiServerChange &&
|
||||
rapiDocRef.addEventListener(
|
||||
"api-server-change",
|
||||
handleApiServerChange
|
||||
);
|
||||
}
|
||||
return () =>
|
||||
{
|
||||
if (rapiDocRef)
|
||||
{
|
||||
beforeRender &&
|
||||
rapiDocRef.removeEventListener("before-render", handleBeforeRender);
|
||||
specLoaded &&
|
||||
rapiDocRef.removeEventListener("spec-loaded", handleSpecLoaded);
|
||||
beforeTry &&
|
||||
rapiDocRef.removeEventListener("before-try", handleBeforeTry);
|
||||
afterTry &&
|
||||
rapiDocRef.removeEventListener("after-try", handleAfterTry);
|
||||
apiServerChange &&
|
||||
rapiDocRef.removeEventListener(
|
||||
"api-server-change",
|
||||
handleApiServerChange
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
ref,
|
||||
localRef,
|
||||
specLoaded,
|
||||
beforeRender,
|
||||
beforeTry,
|
||||
afterTry,
|
||||
apiServerChange,
|
||||
]);
|
||||
|
||||
return (
|
||||
<rapi-doc {...props} ref={ref || localRef}>
|
||||
{children}
|
||||
</rapi-doc>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default RapiDocReact;
|
167
src/qqq/pages/records/developer/TableDeveloperView.tsx
Normal file
167
src/qqq/pages/records/developer/TableDeveloperView.tsx
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {useAuth0} from "@auth0/auth0-react";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {Select, SelectChangeEvent, Typography} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import React, {useContext, useState} from "react";
|
||||
import {useParams} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||
import {RapiDocReact} from "qqq/pages/records/developer/RapiDocReact";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
interface Props
|
||||
{
|
||||
table?: QTableMetaData;
|
||||
}
|
||||
|
||||
TableDeveloperView.defaultProps =
|
||||
{
|
||||
table: null,
|
||||
};
|
||||
|
||||
function TableDeveloperView({table}: Props): JSX.Element
|
||||
{
|
||||
const {id} = useParams();
|
||||
|
||||
const {getAccessTokenSilently} = useAuth0();
|
||||
const [accessToken, setAccessToken] = useState(null as string);
|
||||
|
||||
const tableName = table.name;
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [metaData, setMetaData] = useState(null as QInstance);
|
||||
const [supportedVersions, setSupportedVersions] = useState([] as string[]);
|
||||
const [currentVersion, setCurrentVersion] = useState(null as string);
|
||||
const [selectedVersion, setSelectedVersion] = useState(null as string);
|
||||
|
||||
const {setPageHeader} = useContext(QContext);
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const accessToken = await getAccessTokenSilently();
|
||||
setAccessToken(accessToken);
|
||||
})();
|
||||
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const versionsResponse = await fetch("/api/versions.json");
|
||||
const versionsJson = await versionsResponse.json();
|
||||
console.log(versionsJson);
|
||||
|
||||
setSupportedVersions(versionsJson.supportedVersions);
|
||||
if (versionsJson.currentVersion)
|
||||
{
|
||||
setCurrentVersion(versionsJson.currentVersion);
|
||||
setSelectedVersion(versionsJson.currentVersion);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// load the full table meta-data (the one we took in is a partial) //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
//////////////////////////////
|
||||
// load top-level meta-data //
|
||||
//////////////////////////////
|
||||
const metaData = await qController.loadMetaData();
|
||||
setMetaData(metaData);
|
||||
|
||||
setPageHeader(tableMetaData.label + " Developer Mode");
|
||||
|
||||
// forceUpdate();
|
||||
})();
|
||||
}
|
||||
|
||||
const beforeTry = (e: any) =>
|
||||
{
|
||||
e.detail.request.headers.append("Authorization", "Bearer " + accessToken);
|
||||
};
|
||||
|
||||
const selectVersion = (event: SelectChangeEvent) =>
|
||||
{
|
||||
setSelectedVersion(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseLayout>
|
||||
<Box>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Box mb={3}>
|
||||
{
|
||||
accessToken && metaData && selectedVersion &&
|
||||
<Card sx={{pb: 1}}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography variant="h6" p={2} pl={3} pb={1}>API Docs & Playground</Typography>
|
||||
<Box display="inline-block" pl={2}>
|
||||
<Typography fontSize="0.875rem" display="inline-block" pr={0.5} position="relative" top="2px">Version:</Typography>
|
||||
<Select
|
||||
native
|
||||
value={selectedVersion}
|
||||
onChange={selectVersion}
|
||||
size="small"
|
||||
inputProps={{
|
||||
id: "select-native",
|
||||
}}
|
||||
>
|
||||
{supportedVersions.map((v) => (<option key={v} value={v}>{v}</option>))}
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
<RapiDocReact
|
||||
spec-url={`/api/${selectedVersion}/${tableName}/openapi.json`}
|
||||
regular-font="Roboto,Helvetica,Arial,sans-serif"
|
||||
mono-font="Monaco, Menlo, Consolas, source-code-pro, monospace"
|
||||
primary-color={metaData.branding.accentColor || "blue"}
|
||||
font-size="large"
|
||||
render-style="view"
|
||||
show-header={false}
|
||||
allow-authentication={false}
|
||||
allow-server-selection={false}
|
||||
allow-spec-file-download={true}
|
||||
beforeTry={beforeTry}
|
||||
css-file={"/api/rapi-doc.css"}
|
||||
css-classes={"qqq-rapi-doc"}
|
||||
></RapiDocReact>
|
||||
</Card>
|
||||
}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</BaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableDeveloperView;
|
@ -1171,6 +1171,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
menuItems.push(<MenuItem key={process.name} onClick={() => processClicked(process)}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
|
||||
}
|
||||
|
||||
menuItems.push(<MenuItem onClick={() => navigate("dev")}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
|
||||
|
||||
if(tableProcesses && tableProcesses.length)
|
||||
{
|
||||
pushDividerIfNeeded(menuItems);
|
||||
|
@ -52,4 +52,5 @@ module.exports = function (app)
|
||||
app.use("/processes", getRequestHandler());
|
||||
app.use("/reports", getRequestHandler());
|
||||
app.use("/images", getRequestHandler());
|
||||
app.use("/api", getRequestHandler());
|
||||
};
|
||||
|
Reference in New Issue
Block a user