mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
refactor (console): migrate api section to tailwind
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4820 GitOrigin-RevId: 541b15e64cc15f5db444304cbc02f75ff93577a4
This commit is contained in:
parent
584aa666bd
commit
387285b8f1
@ -27,16 +27,14 @@ export default class AnalyzeButton extends React.Component {
|
||||
|
||||
let options = null;
|
||||
if (hasOptions && optionsOpen) {
|
||||
const highlight = this.state.highlight;
|
||||
const validOperations = operations.filter(isValidGraphQLOperation);
|
||||
if (validOperations.length) {
|
||||
options = (
|
||||
<ul className="execute-options">
|
||||
<ul>
|
||||
{validOperations.map(operation => {
|
||||
return (
|
||||
<li
|
||||
key={operation.name ? operation.name.value : '*'}
|
||||
className={operation === highlight ? 'selected' : null}
|
||||
onMouseOver={() => this.setState({ highlight: operation })}
|
||||
onFocus={() => this.setState({ highlight: operation })}
|
||||
onMouseOut={() => this.setState({ highlight: null })}
|
||||
@ -57,7 +55,7 @@ export default class AnalyzeButton extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="analyse-button-wrap">
|
||||
<span className="relative">
|
||||
<GraphiQL.Button
|
||||
onClick={this.handleAnalyseClick.bind(this)}
|
||||
onMouseDown={onMouseDown}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { ImCopy } from 'react-icons/im';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from 'react-modal';
|
||||
import sqlFormatter from 'sql-formatter';
|
||||
import hljs from 'highlight.js';
|
||||
import RootFields from './RootFields';
|
||||
import { ImCopy } from 'react-icons/im';
|
||||
|
||||
export default class QueryAnalyser extends React.Component {
|
||||
constructor() {
|
||||
@ -13,9 +13,32 @@ export default class QueryAnalyser extends React.Component {
|
||||
this.state = {
|
||||
analyseData: [],
|
||||
activeNode: 0,
|
||||
previouslyCopied: null,
|
||||
};
|
||||
}
|
||||
|
||||
copyToClip(type, text) {
|
||||
try {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.setState(
|
||||
{
|
||||
previouslyCopied: type,
|
||||
},
|
||||
() =>
|
||||
// This variable decides if a "Copied!" message should be shown.
|
||||
// Awaiting 2 seconds to highlight this for the user
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
previouslyCopied: null,
|
||||
});
|
||||
}, 2000)
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
alert(`failed to copy`);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatch, analyseQuery } = this.props;
|
||||
this.props
|
||||
@ -34,26 +57,29 @@ export default class QueryAnalyser extends React.Component {
|
||||
this.props.clearAnalyse();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { show, clearAnalyse } = this.props;
|
||||
return (
|
||||
<Modal
|
||||
className="modalWrapper"
|
||||
overlayClassName="myOverlayClass"
|
||||
className="flex flex-col p-10 rounded-xl w-full z-[101]"
|
||||
overlayClassName="fixed top-0 left-0 right-0 bottom-6 bg-white bg-opacity-75 z-[101]"
|
||||
isOpen={show && this.state.analyseData.length > 0}
|
||||
>
|
||||
<div className="modalHeader">
|
||||
<div className="modalTitle">Query Analysis</div>
|
||||
<div className="modalClose">
|
||||
<button onClick={clearAnalyse} className="form-control">
|
||||
<div className="bg-[#43495a] border-b border-gray-200 py-sm px-md flex justify-between">
|
||||
<div className="font-xl font-bold text-[#ffc627]">Query Analysis</div>
|
||||
<div className="text-[#ccc] font-bold">
|
||||
<button onClick={clearAnalyse} className="">
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modalBody">
|
||||
<div className="wd25">
|
||||
<div className="topLevelNodesWrapper">
|
||||
<div className="title">Top level nodes</div>
|
||||
<div className="flex min-h-full bg-white border border-gray-500">
|
||||
<div className="w-1/4 border border-gray-500">
|
||||
<div className="h-8/12">
|
||||
<div className="p-md pr-0 font-bold text-lg bg-[#fff3d5] text-[#767e93] mb-md">
|
||||
Top level nodes
|
||||
</div>
|
||||
<RootFields
|
||||
data={this.state.analyseData}
|
||||
activeNode={this.state.activeNode}
|
||||
@ -61,23 +87,29 @@ export default class QueryAnalyser extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="wd75">
|
||||
<div className="analysisWrapper">
|
||||
<div className="plansWrapper">
|
||||
<div className="plansTitle">Generated SQL</div>
|
||||
<div className="codeBlock">
|
||||
<div className="copyGenerated">
|
||||
<div className="copyTooltip">
|
||||
<span className="tooltiptext" id="copySql">
|
||||
Copy
|
||||
</span>
|
||||
<ImCopy
|
||||
onClick={this.copyToClip.bind(this, 'sql', 'copySql')}
|
||||
onMouseLeave={this.resetCopy.bind(this, 'copySql')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<pre>
|
||||
<div className="w-3/4 z-100 bg-white">
|
||||
<div className="w-full">
|
||||
<div className="p-md pt-0">
|
||||
<div className="text-[#767e93] font-bold py-sm">
|
||||
Generated SQL
|
||||
</div>
|
||||
<div className="w-full overflow-y-scroll h-[calc(30vh)] mb-sm">
|
||||
<button
|
||||
onClick={() =>
|
||||
this.copyToClip(
|
||||
'sql',
|
||||
sqlFormatter.format(
|
||||
this.state.analyseData[this.state.activeNode].sql,
|
||||
{ language: 'sql' }
|
||||
)
|
||||
)
|
||||
}
|
||||
className="cursor-pointer rounded-sm bg-gray-50 p-1 absolute right-24 mt-2"
|
||||
>
|
||||
<ImCopy className="mr-sm" />
|
||||
{this.state.previouslyCopied === 'sql' ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
<pre className="bg-[#fdf9ed]">
|
||||
<code
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
@ -95,30 +127,39 @@ export default class QueryAnalyser extends React.Component {
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="plansWrapper">
|
||||
<div className="plansTitle">Execution Plan</div>
|
||||
<div className="codeBlock">
|
||||
<div className="copyGenerated">
|
||||
<div className="copyTooltip">
|
||||
<span className="tooltiptext" id="copyPlan">
|
||||
Copy
|
||||
</span>
|
||||
<ImCopy
|
||||
onClick={this.copyToClip.bind(this, 'plan', 'copyPlan')}
|
||||
onMouseLeave={this.resetCopy.bind(this, 'copyPlan')}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-md pt-0">
|
||||
<div>
|
||||
<div className="text-[#767e93] font-bold py-md pt-0">
|
||||
Execution Plan
|
||||
</div>
|
||||
<div className="w-full h-[calc(30vh)] overflow-y-scroll mb-sm">
|
||||
<pre className="bg-[#fdf9ed]">
|
||||
<button
|
||||
onClick={() =>
|
||||
this.copyToClip(
|
||||
'plan',
|
||||
this.state.analyseData[
|
||||
this.state.activeNode
|
||||
].plan.join('\n')
|
||||
)
|
||||
}
|
||||
className="cursor-pointer rounded-sm bg-gray-50 p-1 absolute right-24"
|
||||
>
|
||||
<ImCopy className="mr-sm" />
|
||||
{this.state.previouslyCopied === 'plan'
|
||||
? 'Copied!'
|
||||
: 'Copy'}
|
||||
</button>
|
||||
<code>
|
||||
{this.state.activeNode >= 0 &&
|
||||
this.state.analyseData.length > 0
|
||||
? this.state.analyseData[
|
||||
this.state.activeNode
|
||||
].plan.join('\n')
|
||||
: ''}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<pre>
|
||||
<code>
|
||||
{this.state.activeNode >= 0 &&
|
||||
this.state.analyseData.length > 0
|
||||
? this.state.analyseData[
|
||||
this.state.activeNode
|
||||
].plan.join('\n')
|
||||
: ''}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -133,40 +174,6 @@ export default class QueryAnalyser extends React.Component {
|
||||
this.setState({ activeNode: parseInt(nodeKey, 10) });
|
||||
}
|
||||
};
|
||||
copyToClip(type, id) {
|
||||
let text = '';
|
||||
if (this.state.analyseData.length > 0) {
|
||||
if (type === 'sql') {
|
||||
text = sqlFormatter.format(
|
||||
this.state.analyseData[this.state.activeNode].sql,
|
||||
{ language: 'sql' }
|
||||
);
|
||||
} else {
|
||||
text = this.state.analyseData[this.state.activeNode].plan.join('\n');
|
||||
}
|
||||
}
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
const tooltip = document.getElementById(id);
|
||||
tooltip.innerHTML = 'Copied';
|
||||
if (!successful) {
|
||||
throw new Error('Copy was unsuccessful');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Oops, unable to copy - ' + err);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
resetCopy(id) {
|
||||
const tooltip = document.getElementById(id);
|
||||
tooltip.innerHTML = 'Copy';
|
||||
}
|
||||
}
|
||||
|
||||
QueryAnalyser.propTypes = {
|
||||
|
@ -24,12 +24,15 @@ const RootFields: React.FC<RootFieldsProps> = ({
|
||||
analysis.field && (
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||
<li
|
||||
className={i === activeNode ? 'active' : ''}
|
||||
className={i === activeNode ? 'text-[#fd9540] cursor-pointer' : ''}
|
||||
key={i}
|
||||
data-key={i}
|
||||
onClick={onClick}
|
||||
>
|
||||
<FaTable className="text-sm mr-2" aria-hidden="true" />
|
||||
<FaTable
|
||||
className="text-sm ml-md mr-2 hover:text-[#fd9540]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{analysis.field}
|
||||
</li>
|
||||
)
|
||||
|
@ -1,399 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import React, { Component } from 'react';
|
||||
import { FaChevronDown, FaChevronUp, FaTrash } from 'react-icons/fa';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import Globals from 'Globals';
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
|
||||
class ApiCollectionPanel extends Component {
|
||||
onClearHistoryClicked = () => {
|
||||
const isOk = getConfirmation();
|
||||
if (isOk) {
|
||||
this.props.clearHistoryCallback();
|
||||
}
|
||||
};
|
||||
|
||||
onTabSelectionChanged = e => {
|
||||
this.props.tabSelectionCallback(e);
|
||||
};
|
||||
|
||||
getTabs(tabs, currentTab, styles) {
|
||||
return tabs.map((tab, i) => {
|
||||
const style =
|
||||
styles.apiCollectionTabList +
|
||||
(currentTab === i ? ' ' + styles.activeApiCollectionTab : '');
|
||||
return (
|
||||
<Tab key={tab.title + i} className={style}>
|
||||
{tab.title}
|
||||
</Tab>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getTabPanelApiList(
|
||||
apiList,
|
||||
selectedApiKey,
|
||||
styles,
|
||||
isCategorised,
|
||||
authApiExpanded
|
||||
) {
|
||||
return apiList.map((apiDetails, outerIndex) => {
|
||||
const rightArrowImage = require('./chevron.svg');
|
||||
if (isCategorised) {
|
||||
// form accordion
|
||||
const finalCategorisedArray = [];
|
||||
const currentContentLength = apiDetails.content.length;
|
||||
let categorisedArray = [];
|
||||
let prevTitle = '';
|
||||
// form accordion items
|
||||
const categories = apiDetails.content.map(
|
||||
(categorisedApiDetails, index) => {
|
||||
const key = categorisedApiDetails.id;
|
||||
const method = categorisedApiDetails.request.method;
|
||||
const methodStyle =
|
||||
method === 'GET'
|
||||
? styles.apiCollectionGet
|
||||
: styles.apiCollectionPost;
|
||||
const apiDetailsStyle =
|
||||
method === 'GET'
|
||||
? styles.apiCollectionGetDetails
|
||||
: styles.apiCollectionPostDetails;
|
||||
const wrapperStyle =
|
||||
method === 'GET'
|
||||
? styles.apiCollectionGetWrapper
|
||||
: styles.apiCollectionPostWrapper;
|
||||
const style =
|
||||
wrapperStyle +
|
||||
(selectedApiKey === key
|
||||
? ' ' + styles.activeApiCollectionGetWrapper
|
||||
: '') +
|
||||
' ' +
|
||||
styles.wd100;
|
||||
const rightArrow =
|
||||
selectedApiKey === key ? (
|
||||
<img
|
||||
className={
|
||||
'img-responsive ' + styles.activeApiCollectionGetWrapperIcon
|
||||
}
|
||||
src={rightArrowImage}
|
||||
alt={'Right arrow'}
|
||||
/>
|
||||
) : null;
|
||||
categorisedArray.push(
|
||||
<div
|
||||
onClick={() => {
|
||||
// eslint-disable-line no-loop-func
|
||||
this.props.apiSelectionCallback(
|
||||
categorisedApiDetails,
|
||||
authApiExpanded
|
||||
);
|
||||
}}
|
||||
key={key}
|
||||
className={style}
|
||||
>
|
||||
<div className={'col-xs-3 ' + styles.padd_remove}>
|
||||
<div className={methodStyle}>{method}</div>
|
||||
</div>
|
||||
<div className={'col-xs-9 ' + styles.padd_remove}>
|
||||
<div className={styles.apiCollectionGetDetailsWrapper}>
|
||||
<div
|
||||
className={
|
||||
apiDetailsStyle + ' col-xs-11 ' + styles.padd_remove
|
||||
}
|
||||
>
|
||||
{categorisedApiDetails.details.title}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
styles.apiRightArrowWrapper +
|
||||
' col-xs-1 ' +
|
||||
styles.padd_remove
|
||||
}
|
||||
>
|
||||
{rightArrow}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (index + 1 === currentContentLength) {
|
||||
if (apiDetails.title !== authApiExpanded) {
|
||||
finalCategorisedArray.push(
|
||||
<div
|
||||
key={index + 1 + apiDetails.title}
|
||||
className={
|
||||
styles.collectionButtonWrapper +
|
||||
' ' +
|
||||
styles.border_bottom +
|
||||
' ' +
|
||||
styles.wd100
|
||||
}
|
||||
>
|
||||
<button
|
||||
className={'btn ' + styles.collectionButtonLess}
|
||||
onClick={() => {
|
||||
this.props.authApiExpandCallback(apiDetails.title);
|
||||
}}
|
||||
>
|
||||
<span>{apiDetails.title}</span>
|
||||
<FaChevronDown aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
finalCategorisedArray.push(
|
||||
<div
|
||||
key={index + 1 + apiDetails.title}
|
||||
className={
|
||||
styles.collectionButtonWrapper +
|
||||
' ' +
|
||||
styles.border_bottom +
|
||||
' ' +
|
||||
styles.wd100
|
||||
}
|
||||
>
|
||||
<button
|
||||
className={'btn ' + styles.collectionButtonLess}
|
||||
onClick={() => {
|
||||
this.props.authApiExpandCallback('None');
|
||||
}}
|
||||
>
|
||||
<span className={styles.padd_bottom}>
|
||||
{apiDetails.title}
|
||||
</span>
|
||||
<FaChevronUp aria-hidden="true" />
|
||||
</button>
|
||||
{categorisedArray}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
categorisedArray = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
return <div>{finalCategorisedArray}</div>;
|
||||
} else {
|
||||
const key = apiDetails.id;
|
||||
const method = apiDetails.request.method;
|
||||
const methodStyle =
|
||||
method === 'GET' ? styles.apiCollectionGet : styles.apiCollectionPost;
|
||||
const apiDetailsStyle =
|
||||
method === 'GET'
|
||||
? styles.apiCollectionGetDetails
|
||||
: styles.apiCollectionPostDetails;
|
||||
const wrapperStyle =
|
||||
method === 'GET'
|
||||
? styles.apiCollectionGetWrapper
|
||||
: styles.apiCollectionPostWrapper;
|
||||
const style =
|
||||
wrapperStyle +
|
||||
(selectedApiKey === key
|
||||
? ' ' + styles.activeApiCollectionGetWrapper
|
||||
: '') +
|
||||
' ' +
|
||||
styles.wd100;
|
||||
const rightArrow =
|
||||
selectedApiKey === key ? (
|
||||
<img
|
||||
className={
|
||||
'img-responsive ' + styles.activeApiCollectionGetWrapperIcon
|
||||
}
|
||||
src={rightArrowImage}
|
||||
alt={'Right arrow'}
|
||||
/>
|
||||
) : null;
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
this.props.apiSelectionCallback(apiDetails, authApiExpanded);
|
||||
}}
|
||||
key={key}
|
||||
className={style}
|
||||
>
|
||||
<div className={'col-xs-3 ' + styles.padd_remove}>
|
||||
<div className={methodStyle}>{method}</div>
|
||||
</div>
|
||||
<div className={'col-xs-9 ' + styles.padd_remove}>
|
||||
<div className={styles.apiCollectionGetDetailsWrapper}>
|
||||
<div
|
||||
className={
|
||||
apiDetailsStyle + ' col-xs-11 ' + styles.padd_remove
|
||||
}
|
||||
>
|
||||
{apiDetails.details.title}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
styles.apiRightArrowWrapper +
|
||||
' col-xs-1 ' +
|
||||
styles.padd_remove
|
||||
}
|
||||
>
|
||||
{rightArrow}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTabPanelContent(
|
||||
tabContentList,
|
||||
emptyText,
|
||||
selectedApi,
|
||||
styles,
|
||||
authApiExpanded,
|
||||
index
|
||||
) {
|
||||
if (tabContentList.length === 0) {
|
||||
return (
|
||||
<div
|
||||
key={'noTabContentList' + index}
|
||||
className={
|
||||
styles.apiCollectionTabListDetails +
|
||||
' ' +
|
||||
styles.wd100 +
|
||||
' ' +
|
||||
styles.apiPaddTop
|
||||
}
|
||||
>
|
||||
<div className={styles.apiCollectionTabListHead}>
|
||||
<span className={styles.serviceBaseDomain}>{emptyText}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return tabContentList.map((tabContent, index) => {
|
||||
let paddingClassname = '';
|
||||
if (index === 0) {
|
||||
paddingClassname = ' ' + styles.apiPaddTop;
|
||||
}
|
||||
if (tabContent.title === 'Clear History') {
|
||||
return (
|
||||
<div
|
||||
key={tabContent.title + index}
|
||||
className={
|
||||
styles.apiCollectionClearHistory +
|
||||
' ' +
|
||||
styles.wd100 +
|
||||
paddingClassname
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={this.onClearHistoryClicked}
|
||||
className={styles.apiCollectionClearHistoryButton}
|
||||
>
|
||||
<FaTrash aria-hidden="true" />
|
||||
Clear History
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const tabPanelList = this.getTabPanelApiList(
|
||||
tabContent.content,
|
||||
selectedApi,
|
||||
styles,
|
||||
tabContent.isCategorised,
|
||||
authApiExpanded
|
||||
);
|
||||
let html = tabPanelList;
|
||||
return (
|
||||
<div
|
||||
key={tabContent.title + index}
|
||||
className={
|
||||
styles.apiCollectionTabListDetails +
|
||||
' ' +
|
||||
styles.wd100 +
|
||||
paddingClassname
|
||||
}
|
||||
>
|
||||
{tabContent.title ? (
|
||||
<div
|
||||
className={
|
||||
styles.apiCollectionTabListHead + ' ' + styles.add_ellipsis
|
||||
}
|
||||
>
|
||||
{tabContent.title}
|
||||
<span className={styles.serviceBaseDomain + ' hide'}>
|
||||
{Globals.projectDomain}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.apiCollectionGetPost + ' ' + styles.wd100}>
|
||||
{html}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getTabPanels(tabs, selectedApi, styles, authApiExpanded) {
|
||||
const selectedApiKey = selectedApi.id;
|
||||
return tabs.map((tab, index) => {
|
||||
return (
|
||||
<TabPanel key={tab.title}>
|
||||
{this.getTabPanelContent(
|
||||
tab.content,
|
||||
tab.emptyText,
|
||||
selectedApiKey,
|
||||
styles,
|
||||
authApiExpanded,
|
||||
index
|
||||
)}
|
||||
</TabPanel>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const styles = require('../ApiExplorer.scss');
|
||||
const {
|
||||
selectedApi,
|
||||
currentTab,
|
||||
tabs,
|
||||
panelStyles,
|
||||
authApiExpanded,
|
||||
} = this.props;
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
styles.padd_remove +
|
||||
' ' +
|
||||
styles.apiCollectionWrapper +
|
||||
' ' +
|
||||
styles.wd20 +
|
||||
' ' +
|
||||
panelStyles
|
||||
}
|
||||
>
|
||||
<Tabs
|
||||
className={styles.apiCollectionTabWrapper + ' ' + styles.wd100}
|
||||
selectedIndex={currentTab}
|
||||
onSelect={this.onTabSelectionChanged}
|
||||
>
|
||||
<TabList className={styles.apiCollectionTab}>
|
||||
{this.getTabs(tabs, currentTab, styles)}
|
||||
</TabList>
|
||||
{this.getTabPanels(tabs, selectedApi, styles, authApiExpanded)}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ApiCollectionPanel.propTypes = {
|
||||
selectedApi: PropTypes.object.isRequired,
|
||||
currentTab: PropTypes.number.isRequired,
|
||||
tabs: PropTypes.array.isRequired,
|
||||
tabSelectionCallback: PropTypes.func.isRequired,
|
||||
apiSelectionCallback: PropTypes.func.isRequired,
|
||||
clearHistoryCallback: PropTypes.func.isRequired,
|
||||
panelStyles: PropTypes.string,
|
||||
authApiExpandCallback: PropTypes.func.isRequired,
|
||||
authApiExpanded: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ApiCollectionPanel;
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="14" viewBox="0 0 7 14"><path fill="#788095" d="M.535.5L6.465 7l-5.93 6.5z"/></svg>
|
Before Width: | Height: | Size: 140 B |
@ -24,7 +24,6 @@ class ApiExplorer extends Component {
|
||||
loading,
|
||||
} = this.props;
|
||||
|
||||
const styles = require('./ApiExplorer.scss');
|
||||
const consoleUrl =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
@ -32,9 +31,9 @@ class ApiExplorer extends Component {
|
||||
globals.urlPrefix;
|
||||
|
||||
return (
|
||||
<div className={'container-fluid ' + styles.padd_remove}>
|
||||
<div className="p-0">
|
||||
<Helmet title="API Explorer | Hasura" />
|
||||
<div className={styles.apiExplorerWrapper}>
|
||||
<div>
|
||||
<ApiRequestWrapper
|
||||
dispatch={this.props.dispatch}
|
||||
credentials={credentials}
|
||||
|
@ -1,854 +0,0 @@
|
||||
@import '../../Common/Common.scss';
|
||||
|
||||
.display_inl {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.analyzerBearerModal {
|
||||
width: 768px;
|
||||
}
|
||||
.analyzerLabel {
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #000;
|
||||
zoom: 1;
|
||||
border-bottom: 1px solid rgba(155, 155, 155, 0.5);
|
||||
line-height: 2.5;
|
||||
padding-bottom: 2px;
|
||||
.token_validity {
|
||||
font-size: 14px;
|
||||
.invalid_jwt_icon {
|
||||
// cursor: pointer;
|
||||
color: #dc3545;
|
||||
}
|
||||
.valid_jwt_token {
|
||||
// cursor: pointer;
|
||||
color: #28a745;
|
||||
}
|
||||
}
|
||||
span {
|
||||
color: #979797;
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.jwt_verification_fail_message {
|
||||
background-color: #e53935;
|
||||
padding: 10px 10px;
|
||||
color: #ffffff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.width_80 {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.responseHeader {
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.admin_token_align {
|
||||
vertical-align: middle;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.marginBottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.apiExplorerMini {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.panelGreyed {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.requestGreyed {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.apiRequestBody {
|
||||
min-height: calc(100vh - 400px);
|
||||
height: 100%;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.apiExplorerWrapper {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
height: calc(100vh - 126px);
|
||||
|
||||
.ApiRequestWrapperVH {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.apiCollectionWrapper {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
// Changed it from overfllow-y: scroll
|
||||
|
||||
.apiCollectionTabWrapper {
|
||||
.apiCollectionTab {
|
||||
-webkit-padding-start: 0px;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
-moz-padding-start: 0px;
|
||||
-moz-margin-before: 0;
|
||||
-moz-margin-after: 0;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
min-width: 240px;
|
||||
background-color: #fff;
|
||||
z-index: 10;
|
||||
|
||||
.apiCollectionTabList {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
padding: 10px 11px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #d8d8d8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.activeApiCollectionTab {
|
||||
border-bottom: 3px solid #ffca27;
|
||||
color: #6b6b6b;
|
||||
}
|
||||
|
||||
.apiCollectionTabList:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.apiCollectionClearHistory {
|
||||
padding: 0 15px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
/*
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform: translateX(-88%);
|
||||
*/
|
||||
|
||||
.apiCollectionClearHistoryButton {
|
||||
/*
|
||||
float: right;
|
||||
margin: 10px 0;
|
||||
cursor: pointer;
|
||||
*/
|
||||
margin: 10px 0;
|
||||
cursor: pointer;
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
text-align: center;
|
||||
width: 20%;
|
||||
margin-left: -15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
i {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apiPaddTop {
|
||||
padding-top: 46px !important;
|
||||
}
|
||||
|
||||
.apiCollectionTabListDetails {
|
||||
padding: 0 15px;
|
||||
margin: 10px 0;
|
||||
// padding-top: 46px;
|
||||
|
||||
.apiCollectionTabListHead {
|
||||
padding-left: 15px;
|
||||
padding-bottom: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
|
||||
.serviceBaseDomain {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.add_ellipsis {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.apiCollectionGetPost {
|
||||
.apiCollectionGetWrapper {
|
||||
padding: 5px 0;
|
||||
cursor: pointer;
|
||||
|
||||
.apiCollectionGet {
|
||||
text-align: left;
|
||||
color: #70cd00;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.apiCollectionGetDetailsWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.apiCollectionGetDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.apiCollectionPostDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.apiRightArrowWrapper {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapper {
|
||||
background-color: #fff3d5;
|
||||
border-radius: 4px;
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.apiCollectionPostWrapper {
|
||||
padding: 5px 0;
|
||||
cursor: pointer;
|
||||
|
||||
.apiCollectionPost {
|
||||
text-align: left;
|
||||
color: #fd9540;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.apiCollectionGetDetailsWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.apiCollectionGetDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.apiCollectionPostDetails {
|
||||
word-wrap: break-word;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.apiRightArrowWrapper {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.activeApiCollectionGetWrapper {
|
||||
background-color: #fff3d5;
|
||||
border-radius: 4px;
|
||||
|
||||
.activeApiCollectionGetWrapperIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apiContentPadd {
|
||||
padding-top: 20px;
|
||||
// padding-bottom: 10px;
|
||||
// margin-bottom: -15px;
|
||||
}
|
||||
|
||||
.closeHeader {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
float: right;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.showAdminSecret,
|
||||
.showInspector {
|
||||
cursor: pointer;
|
||||
padding-right: 8px;
|
||||
font-size: 16px;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.showInspectorLoading {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.apiRequestWrapper {
|
||||
.headerWrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.file_upload_wrapper {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
margin-bottom: 15px;
|
||||
|
||||
input[type='file'] {
|
||||
display: inline-block;
|
||||
width: 162px;
|
||||
}
|
||||
}
|
||||
|
||||
.apiRequestheader {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
padding-bottom: 10px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.apiRequestContent {
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: #fec53d;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #fec53d;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
border: 1px solid #767e93;
|
||||
padding: 1px 4px !important;
|
||||
color: #767e93;
|
||||
}
|
||||
}
|
||||
|
||||
.apiPostRequestWrapper {
|
||||
// padding: 20px 0;
|
||||
// padding-top: 20px;
|
||||
padding-top: 5px;
|
||||
background-color: #f8fafb;
|
||||
|
||||
.inputGroupWrapper {
|
||||
.inputGroupBtn {
|
||||
button {
|
||||
width: 100px;
|
||||
border: 0;
|
||||
padding: 10px 12px;
|
||||
background-color: #f9f9f9;
|
||||
color: #fd9540;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
|
||||
.caret {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputGroupInput {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding: 10px 12px;
|
||||
height: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.sendBtn {
|
||||
button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
height: 39px;
|
||||
color: #606060;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
background-color: #fec53d;
|
||||
border: 1px solid #fec53d;
|
||||
/*
|
||||
background-color: #FFCA27;
|
||||
border: 1px solid #FFCA27;
|
||||
*/
|
||||
&:hover {
|
||||
background-color: #f2b130;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
button:hover {
|
||||
border: 1px solid #F2B130;
|
||||
background-color: #F2B130;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
.generateBtn {
|
||||
button {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
height: 39px;
|
||||
border: 1px solid #606060;
|
||||
color: #606060;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border: 1px solid #606060;
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.responseWrapper {
|
||||
clear: both;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.responseHeader {
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
|
||||
.viewDetails {
|
||||
padding-left: 10px;
|
||||
font-weight: normal;
|
||||
color: #ffca27;
|
||||
}
|
||||
|
||||
.addAdminToken {
|
||||
text-align: right;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.addAdminToken {
|
||||
text-align: right;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apiResponseWrapper {
|
||||
.apiResponseheaderWrapper {
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.apiResponseheader {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
padding-bottom: 10px;
|
||||
// color: #000;
|
||||
color: #788095;
|
||||
}
|
||||
|
||||
.statusDetails {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
padding-left: 20px;
|
||||
|
||||
.statusView {
|
||||
padding-left: 5px;
|
||||
font-weight: normal;
|
||||
color: #ffca27;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.helpTextWrapper {
|
||||
padding: 15px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
clear: both;
|
||||
margin-bottom: 20px;
|
||||
|
||||
i {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 10px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.copyBtn {
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestionTextColor {
|
||||
color: #111;
|
||||
background-color: #ffd760;
|
||||
border-color: #ffd760;
|
||||
}
|
||||
|
||||
.noResponseWrapper {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
background-color: #fff;
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
clear: both;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.noResponseContainer {
|
||||
width: 325px;
|
||||
|
||||
.noResponseHeader {
|
||||
font-size: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.barWrapper {
|
||||
padding-top: 15px;
|
||||
text-align: center;
|
||||
|
||||
.bigBar {
|
||||
width: 56%;
|
||||
margin-right: 7px;
|
||||
height: 20px;
|
||||
background-color: #eeeeee;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mediumBar {
|
||||
width: 23%;
|
||||
margin-right: 7px;
|
||||
height: 20px;
|
||||
background-color: #ffca27;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.smallBar {
|
||||
width: 13%;
|
||||
margin-right: 7px;
|
||||
height: 20px;
|
||||
background-color: #eeeeee;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.responseHeader {
|
||||
padding-top: 15px;
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
clear: both;
|
||||
|
||||
.viewDetails {
|
||||
padding-left: 10px;
|
||||
font-weight: normal;
|
||||
color: #ffca27;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common
|
||||
.responseTable {
|
||||
padding-top: 15px;
|
||||
|
||||
.tableBorder {
|
||||
background-color: #fff;
|
||||
border: 1px solid #e3e5e5;
|
||||
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
td {
|
||||
border-top: 0;
|
||||
// padding: 5px;
|
||||
padding: 0px 5px;
|
||||
min-width: 50px;
|
||||
vertical-align: middle;
|
||||
.responseTableInput {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
// padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.headerPadd {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.borderTop {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.tableTdLeft {
|
||||
padding-left: 5%;
|
||||
}
|
||||
|
||||
.tableEnterKey {
|
||||
// padding: 10px 0;
|
||||
padding: 0px 5px;
|
||||
padding-left: 4.5%;
|
||||
}
|
||||
|
||||
.tableLastTd {
|
||||
padding-left: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerHeading {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.queryBuilderWrapper {
|
||||
padding-bottom: 20px;
|
||||
|
||||
.queryBuilderTab {
|
||||
ul {
|
||||
border: 1px solid #e7e7e7;
|
||||
-webkit-padding-start: 0px;
|
||||
-moz-padding-start: 0px;
|
||||
display: inline-block;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
padding: 12px 20px;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
color: #788094;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
li:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.activeQueryBuilderTab {
|
||||
background-color: #fff050;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AceEditorWrapper {
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
/*
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
*/
|
||||
}
|
||||
|
||||
.queryBuilderLayout {
|
||||
background-color: #fff;
|
||||
// padding: 15px;
|
||||
}
|
||||
|
||||
.queryBuilderLayoutSub {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.qbTabHeading {
|
||||
font-weight: bold;
|
||||
padding-top: 15px;
|
||||
padding-left: 20px;
|
||||
padding-bottom: 15px;
|
||||
|
||||
i {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.common_checkbox {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
.common_checkbox_label {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.common_checkbox_label {
|
||||
margin-bottom: 0px !important;
|
||||
padding-top: 0px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.common_checkbox + .common_checkbox_label:before {
|
||||
content: '';
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding-top: 2px;
|
||||
margin-right: 2px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.common_checkbox:checked + .common_checkbox_label:before {
|
||||
content: url('./tick.png');
|
||||
background: #ffca27;
|
||||
color: #fff;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.authPanelSubHeadings {
|
||||
font-size: 16px;
|
||||
font-weight: italic;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.apiResponseTab {
|
||||
padding-top: 20px;
|
||||
|
||||
.apiResponseTabUl {
|
||||
display: inline-block;
|
||||
-webkit-padding-start: 0px;
|
||||
-webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.apiResponseTabList {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
list-style-type: none;
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
color: #6b6b6b;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.apiResponseTabList:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.activeApiResponseTab {
|
||||
background-color: #fff3d5;
|
||||
}
|
||||
}
|
||||
|
||||
.apiResponseTabPanel {
|
||||
.AceEditorWrapper {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.graphiqlModeToggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.headerInfoIcon {
|
||||
cursor: pointer;
|
||||
padding-right: 8px;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.topNavContainer {
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
background: #f8fafb;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.sectionLink {
|
||||
display: inline-block;
|
||||
color: #273848;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background: transparent;
|
||||
padding: 14px 12px 9px;
|
||||
margin-right: 8px;
|
||||
text-decoration: none;
|
||||
&.sectionLinkActive {
|
||||
padding-top: 14px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 12px;
|
||||
border-bottom: 4px solid #ccc;
|
||||
}
|
||||
}
|
@ -46,7 +46,6 @@ import {
|
||||
} from './utils';
|
||||
import { getGraphQLEndpoint } from '../utils';
|
||||
|
||||
import styles from '../ApiExplorer.scss';
|
||||
import {
|
||||
getLSItem,
|
||||
removeLSItem,
|
||||
@ -65,7 +64,7 @@ import {
|
||||
const ActionIcon = ({ message, dataHeaderID }) => (
|
||||
<Tooltip placement="left" message={message}>
|
||||
<FaQuestionCircle
|
||||
className={styles.headerInfoIcon}
|
||||
className="cursor-pointer text-md p-sm inline-block"
|
||||
data-header-id={dataHeaderID}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
@ -263,16 +262,7 @@ class ApiRequest extends Component {
|
||||
toggleHandler={toggleHandler}
|
||||
useDefaultTitleStyle
|
||||
>
|
||||
<div
|
||||
id="stickyHeader"
|
||||
className={
|
||||
styles.apiPostRequestWrapper +
|
||||
' ' +
|
||||
styles.wd100 +
|
||||
' ' +
|
||||
styles.stickyHeader
|
||||
}
|
||||
>
|
||||
<div className="bg-[#f8fafb] pt-sm w-full top-0 z-20">
|
||||
<div className={'flex mb-md'}>
|
||||
<div className="flex items-center w-full">
|
||||
<button
|
||||
@ -295,7 +285,7 @@ class ApiRequest extends Component {
|
||||
>
|
||||
<Toggle
|
||||
checked={mode === 'relay'}
|
||||
className={`${styles.display_flex} ${styles.add_mar_right_mid}`}
|
||||
className="flex mr-md"
|
||||
readOnly
|
||||
disabled={loading}
|
||||
icons={false}
|
||||
@ -369,8 +359,7 @@ class ApiRequest extends Component {
|
||||
<input
|
||||
type="checkbox"
|
||||
name="sponsored"
|
||||
style={{ marginTop: '4px' }}
|
||||
className="legacy-input-fix"
|
||||
className="legacy-input-fix mt-xs"
|
||||
id={i + 1}
|
||||
checked={header.isActive}
|
||||
data-header-id={i}
|
||||
@ -585,7 +574,7 @@ class ApiRequest extends Component {
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th
|
||||
className={`w-16 px-sm py-xs max-w-xs text-left text-sm font-semibold bg-gray-50 text-gray-600 uppercase tracking-wider ${styles.wd4}`}
|
||||
className={`w-16 px-sm py-xs max-w-xs text-left text-sm font-semibold bg-gray-50 text-gray-600 uppercase tracking-wider align-center`}
|
||||
>
|
||||
Enable
|
||||
</th>
|
||||
@ -609,7 +598,7 @@ class ApiRequest extends Component {
|
||||
switch (this.props.bodyType) {
|
||||
case 'graphql':
|
||||
return (
|
||||
<div className={styles.apiRequestBody}>
|
||||
<div className={'h-[calc(100vh-400px)] pb-[50px]'}>
|
||||
<GraphiQLWrapper
|
||||
mode={mode}
|
||||
data={this.props}
|
||||
@ -659,14 +648,14 @@ class ApiRequest extends Component {
|
||||
id="tooltip-jwt-validity-status"
|
||||
message="Valid JWT token"
|
||||
>
|
||||
<span className={styles.valid_jwt_token}>
|
||||
<span className="text-[#28a745]">
|
||||
<FaCheck />
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
case !tokenVerified && JWTError.length > 0:
|
||||
return (
|
||||
<span className={styles.invalid_jwt_icon}>
|
||||
<span className="text-[#dc3545]">
|
||||
<FaTimes />
|
||||
</span>
|
||||
);
|
||||
@ -678,7 +667,7 @@ class ApiRequest extends Component {
|
||||
const getJWTFailMessage = () => {
|
||||
if (!tokenVerified && JWTError.length > 0) {
|
||||
return (
|
||||
<div className={styles.jwt_verification_fail_message}>
|
||||
<div className={'bg-[#e53935] py-sm px-sm text-white rounded-md'}>
|
||||
{JWTError}
|
||||
</div>
|
||||
);
|
||||
@ -724,7 +713,12 @@ class ApiRequest extends Component {
|
||||
|
||||
return [
|
||||
<br key="hasura_claim_element_break" />,
|
||||
<span key="hasura_claim_label" className={styles.analyzerLabel}>
|
||||
<span
|
||||
key="hasura_claim_label"
|
||||
className={
|
||||
'uppercase m-0 text-black pb-sm border-b border-[#9b9b9b80]'
|
||||
}
|
||||
>
|
||||
Hasura Claims:
|
||||
<span>hasura headers</span>
|
||||
</span>,
|
||||
@ -746,15 +740,23 @@ class ApiRequest extends Component {
|
||||
} else {
|
||||
analyzeBearerBody = (
|
||||
<div>
|
||||
<span className={styles.analyzerLabel}>
|
||||
<span
|
||||
className={
|
||||
'uppercase m-0 text-black pb-sm border-b border-[#9b9b9b80]'
|
||||
}
|
||||
>
|
||||
Token Validity:
|
||||
<span className={styles.token_validity}>
|
||||
<span className="text-md">
|
||||
{generateJWTVerificationStatus()}
|
||||
</span>
|
||||
</span>
|
||||
{getJWTFailMessage() || <br />}
|
||||
{getHasuraClaims() || <br />}
|
||||
<span className={styles.analyzerLabel}>
|
||||
<span
|
||||
className={
|
||||
'uppercase m-0 text-black pb-sm border-b border-[#9b9b9b80]'
|
||||
}
|
||||
>
|
||||
Header:
|
||||
<span>Algorithm & Token Type</span>
|
||||
</span>
|
||||
@ -765,7 +767,11 @@ class ApiRequest extends Component {
|
||||
containerId="headerCopyBlock"
|
||||
/>
|
||||
<br />
|
||||
<span className={styles.analyzerLabel}>
|
||||
<span
|
||||
className={
|
||||
'uppercase m-0 text-black pb-sm border-b border-[#9b9b9b80]'
|
||||
}
|
||||
>
|
||||
Full Payload:
|
||||
<span>Data</span>
|
||||
</span>
|
||||
@ -790,7 +796,7 @@ class ApiRequest extends Component {
|
||||
Object.keys(tokenAnalyzeResp).length > 0
|
||||
}
|
||||
onClose={this.onAnalyzeBearerClose}
|
||||
customClass={styles.analyzerBearerModal}
|
||||
customClass={'w-[768]'}
|
||||
title={tokenAnalyzeError ? 'Error decoding JWT' : 'Decoded JWT'}
|
||||
>
|
||||
{getAnalyzeBearerBody()}
|
||||
@ -799,13 +805,11 @@ class ApiRequest extends Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.apiRequestWrapper} ${styles.height100} ${styles.flexColumn}`}
|
||||
>
|
||||
<div className={`h-full flex flex-col pt-md`}>
|
||||
{getGraphQLEndpointBar()}
|
||||
{getHeaderTable()}
|
||||
{getRequestBody()}
|
||||
{isJWTSet && getAnalyzeTokenModal()}
|
||||
{getAnalyzeTokenModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ import PropTypes from 'prop-types';
|
||||
|
||||
class ApiRequestDetails extends Component {
|
||||
render() {
|
||||
const styles = require('../ApiExplorer.scss');
|
||||
return (
|
||||
<div className={styles.apiRequestWrapper + ' ' + styles.apiContentPadd}>
|
||||
<div className={styles.apiRequestheader}>{this.props.title}</div>
|
||||
<div className={styles.apiRequestContent}>{this.props.description}</div>
|
||||
<div className="pt-md">
|
||||
<div>{this.props.title}</div>
|
||||
<div>{this.props.description}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,48 +1,15 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ApiRequest from './ApiRequest/ApiRequest';
|
||||
import ApiResponse from './ApiResponse/ApiResponse';
|
||||
|
||||
class ApiRequestWrapper extends Component {
|
||||
render() {
|
||||
const styles = require('./ApiExplorer.scss');
|
||||
|
||||
const getAPIRequestDetailsSection = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const getAPIResponseSection = () => {
|
||||
let apiResponseSection = null;
|
||||
|
||||
if (this.props.request.bodyType !== 'graphql') {
|
||||
apiResponseSection = (
|
||||
<ApiResponse
|
||||
{...this.props.explorerData}
|
||||
categoryType={this.props.details.category}
|
||||
showHelpBulb={
|
||||
this.props.request.showHelpBulb
|
||||
? this.props.request.showHelpBulb
|
||||
: false
|
||||
}
|
||||
url={this.props.request.url}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return apiResponseSection;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
id="apiRequestBlock"
|
||||
className={
|
||||
styles.padd_left +
|
||||
' ' +
|
||||
styles.padd_right +
|
||||
' ' +
|
||||
styles.ApiRequestWrapperVH
|
||||
}
|
||||
>
|
||||
<div id="apiRequestBlock" className="px-md h-full w-full">
|
||||
{getAPIRequestDetailsSection()}
|
||||
|
||||
<ApiRequest
|
||||
@ -67,8 +34,6 @@ class ApiRequestWrapper extends Component {
|
||||
consoleUrl={this.props.consoleUrl}
|
||||
serverConfig={this.props.serverConfig}
|
||||
/>
|
||||
|
||||
{getAPIResponseSection()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,239 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FaLightbulb } from 'react-icons/fa';
|
||||
import PropTypes from 'prop-types';
|
||||
import AceEditor from 'react-ace';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
|
||||
import generateSuggestionBox from './generateSuggestionBox';
|
||||
|
||||
import suggestionFunctions from './suggestionFunctions';
|
||||
|
||||
class ApiResponse extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
helpCopied: false,
|
||||
tabIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
categoryType,
|
||||
showHelpBulb,
|
||||
enableResponseSection,
|
||||
response,
|
||||
url,
|
||||
} = this.props;
|
||||
|
||||
const styles = require('../ApiExplorer.scss');
|
||||
|
||||
const suggestionFunction = suggestionFunctions[categoryType];
|
||||
const isResponseError =
|
||||
'statusCode' in response ? response.statusCode !== 200 : false;
|
||||
|
||||
const responseHtml =
|
||||
isResponseError && suggestionFunction
|
||||
? generateSuggestionBox(response, suggestionFunction)
|
||||
: '';
|
||||
|
||||
const imgHTMLTag = `<img src='${url}' />`;
|
||||
|
||||
let formattedResponse = JSON.stringify(response.response, null, 4);
|
||||
let responseMode = 'json';
|
||||
let showGutter = true;
|
||||
if (response.statusCode === 500) {
|
||||
responseMode = 'text';
|
||||
formattedResponse = 'Could not get any response';
|
||||
formattedResponse += '\n\nThere was an error connecting to ' + url;
|
||||
formattedResponse +=
|
||||
'\n\nCheck if the URL is valid or the server is timing out';
|
||||
showGutter = false;
|
||||
}
|
||||
|
||||
const getHeaders = responseHeader => {
|
||||
const currHeaders = [];
|
||||
|
||||
if (responseHeader.responseHeaders) {
|
||||
responseHeader.responseHeaders.forEach((value, name) => {
|
||||
currHeaders.push(
|
||||
<tr key={name}>
|
||||
<td
|
||||
className={
|
||||
styles.headerPadd +
|
||||
' ' +
|
||||
styles.wd48 +
|
||||
' ' +
|
||||
styles.border_right
|
||||
}
|
||||
>
|
||||
{name}{' '}
|
||||
</td>
|
||||
<td className={styles.headerPadd + ' ' + styles.wd48}>{value}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return currHeaders.length > 0 ? currHeaders : '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.apiResponseWrapper}>
|
||||
<div
|
||||
id="apiResponseBlock"
|
||||
className={styles.fixed_header_internal_link}
|
||||
/>
|
||||
<div className={styles.apiResponseheaderWrapper + ' ' + styles.wd100}>
|
||||
<div
|
||||
className={
|
||||
styles.apiResponseheader + ' col-xs-6 ' + styles.padd_remove
|
||||
}
|
||||
>
|
||||
Response
|
||||
</div>
|
||||
{enableResponseSection ? (
|
||||
<div className={'col-xs-6 ' + styles.padd_remove}>
|
||||
<div className={styles.statusDetails}>
|
||||
Time:{' '}
|
||||
<span className={styles.statusView}>
|
||||
{response.timeElapsed} ms
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statusDetails}>
|
||||
Status:{' '}
|
||||
<span className={styles.statusView}>{response.statusCode}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
|
||||
{responseHtml}
|
||||
|
||||
{showHelpBulb ? (
|
||||
<div className={styles.helpTextWrapper}>
|
||||
<FaLightbulb aria-hidden="true" />
|
||||
Embed in your HTML as follows
|
||||
<div className="input-group">
|
||||
<pre>{imgHTMLTag}</pre>
|
||||
<span className="input-group-btn">
|
||||
<CopyToClipboard
|
||||
text={imgHTMLTag}
|
||||
onCopy={() => {
|
||||
this.setState({ helpCopied: true });
|
||||
const timer = setInterval(() => {
|
||||
this.setState({ helpCopied: false });
|
||||
clearInterval(timer);
|
||||
}, 3000);
|
||||
}}
|
||||
>
|
||||
<button className={styles.copyBtn + ' btn'} type="button">
|
||||
{this.state.helpCopied ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{enableResponseSection ? (
|
||||
<Tabs
|
||||
className={styles.apiResponseTab}
|
||||
selectedIndex={this.state.tabIndex}
|
||||
onSelect={tabIndex => this.setState({ tabIndex })}
|
||||
>
|
||||
<TabList className={styles.apiResponseTabUl}>
|
||||
<Tab
|
||||
className={
|
||||
this.state.tabIndex === 0
|
||||
? ' ' +
|
||||
styles.activeApiResponseTab +
|
||||
' ' +
|
||||
styles.apiResponseTabList
|
||||
: styles.apiResponseTabList
|
||||
}
|
||||
>
|
||||
Body
|
||||
</Tab>
|
||||
<Tab
|
||||
className={
|
||||
this.state.tabIndex === 1
|
||||
? ' ' +
|
||||
styles.activeApiResponseTab +
|
||||
' ' +
|
||||
styles.apiResponseTabList
|
||||
: styles.apiResponseTabList
|
||||
}
|
||||
>
|
||||
Headers
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className={styles.apiResponseTabPanel}>
|
||||
<div className={styles.AceEditorWrapper}>
|
||||
{response.isImage ? (
|
||||
<img
|
||||
src={response.response}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
) : (
|
||||
<AceEditor
|
||||
readOnly
|
||||
showPrintMargin={false}
|
||||
mode={responseMode}
|
||||
showGutter={showGutter}
|
||||
theme="github"
|
||||
name="api-explorer-request"
|
||||
value={formattedResponse}
|
||||
minLines={10}
|
||||
maxLines={50}
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel className={styles.apiResponseTabPanel}>
|
||||
<div className={styles.responseHeader + ' hide'}>
|
||||
Header
|
||||
<span className={styles.viewDetails + ' hide'}>
|
||||
View Details
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.responseTable}>
|
||||
<table className={'table ' + styles.tableBorder}>
|
||||
<tbody>{getHeaders(response)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
) : (
|
||||
<div className={styles.noResponseWrapper}>
|
||||
<div className={styles.noResponseContainer}>
|
||||
<div className={styles.noResponseHeader}>
|
||||
Hit the Send button to get a response
|
||||
</div>
|
||||
<div className={styles.barWrapper}>
|
||||
<div className={styles.bigBar} />
|
||||
<div className={styles.mediumBar} />
|
||||
<div className={styles.smallBar} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ApiResponse.propTypes = {
|
||||
enableResponseSection: PropTypes.bool.isRequired,
|
||||
response: PropTypes.object.isRequired,
|
||||
showHelpBulb: PropTypes.bool,
|
||||
url: PropTypes.string,
|
||||
categoryType: PropTypes.string,
|
||||
};
|
||||
export default ApiResponse;
|
@ -1,33 +0,0 @@
|
||||
const dataErrorMapping = {
|
||||
'postgres-error':
|
||||
'When inserting into table, all columns which are not nullable needs to be given a value.',
|
||||
'permission-denied':
|
||||
'Looks like current role doesnt have permissions to perform this operation on this table. Add permission to execute this query',
|
||||
'not-exists':
|
||||
'Looks like either the table or column name doesnt exist. Modify the query and try again',
|
||||
'already-tracked': 'The view/table has already been tracked.',
|
||||
'access-denied':
|
||||
'The action you are trying to perform is admin only. Login as an admin and try again',
|
||||
'not-supported': 'Table renames are not supported',
|
||||
'already-exists':
|
||||
'The column name already exists as a relationship name. Try with a different name.',
|
||||
'invalid-json': 'Request body is not a valid json',
|
||||
'invalid-headers':
|
||||
'Expected headers are missing or not in the right format in the request. Modify them before making the query.',
|
||||
'dependency-error':
|
||||
'There is a dependency issue. Look at the response for the dependent objects',
|
||||
'parse-failed': 'Parsing table failed',
|
||||
'already-initialised':
|
||||
'The state seems to be initialised already. You may need to migrate from the given catalog version',
|
||||
'constraint-error':
|
||||
'There may be no foreign key constraint / multiple foreign key constraints on the given column',
|
||||
'permission-error': 'Permission for the given role exists/does not exist',
|
||||
'unexpected-payload': 'Check the response for more details.',
|
||||
'invalid-params':
|
||||
'Parameter is missing. Check the response for missing parameter',
|
||||
unexpected: 'Internal Server Error. Check the response for more details',
|
||||
'not-found':
|
||||
'No such endpoint exists. Check the URL/Method for which the query is being made to.',
|
||||
};
|
||||
|
||||
export default dataErrorMapping;
|
@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FaInfoCircle } from 'react-icons/fa';
|
||||
|
||||
const styles = require('../ApiExplorer.scss');
|
||||
|
||||
const generateSuggestionBox = (response, parseFunc) => {
|
||||
const suggestionText = parseFunc(response);
|
||||
return suggestionText ? (
|
||||
<div
|
||||
style={{ marginBottom: '0px', display: 'flex' }}
|
||||
className={
|
||||
styles.clear_fix + ' ' + styles.alertDanger + ' alert alert-danger'
|
||||
}
|
||||
>
|
||||
<FaInfoCircle className={styles.padd_right} aria-hidden="true" />
|
||||
{suggestionText}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
export default generateSuggestionBox;
|
@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import dataErrorMapping from './dataErrorMapping';
|
||||
|
||||
const styles = require('../ApiExplorer.scss');
|
||||
|
||||
const dataApiErrorMap = {
|
||||
'postgres-error': dataErrorMapping['postgres-error'],
|
||||
'permission-denied': dataErrorMapping['permission-denied'],
|
||||
'not-exists': dataErrorMapping['not-exists'],
|
||||
'already-tracked': dataErrorMapping['already-tracked'],
|
||||
'access-denied': dataErrorMapping['access-denied'],
|
||||
'not-supported': dataErrorMapping['not-supported'],
|
||||
'already-exists': dataErrorMapping['already-exists'],
|
||||
'invalid-json': dataErrorMapping['invalid-json'],
|
||||
'invalid-headers': dataErrorMapping['invalid-headers'],
|
||||
'dependency-error': dataErrorMapping['dependency-error'],
|
||||
'parse-failed': dataErrorMapping['parse-failed'],
|
||||
'already-initialised': dataErrorMapping['already-initialised'],
|
||||
'constraint-error': dataErrorMapping['constraint-error'],
|
||||
'permission-error': dataErrorMapping['permission-error'],
|
||||
'unexpected-payload': dataErrorMapping['unexpected-payload'],
|
||||
'invalid-params': dataErrorMapping['invalid-params'],
|
||||
unexpected: dataErrorMapping['unexpected'], //eslint-disable-line
|
||||
'not-found': dataErrorMapping['not-found'],
|
||||
};
|
||||
|
||||
const dataApiSuggest = response => {
|
||||
try {
|
||||
const respFunc = dataApiErrorMap[response.response.code];
|
||||
if (respFunc) {
|
||||
return (
|
||||
<div
|
||||
className={styles.display_inl}
|
||||
dangerouslySetInnerHTML={{ __html: respFunc }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/* */
|
||||
|
||||
export { dataApiErrorMap };
|
||||
|
||||
export default {
|
||||
data: dataApiSuggest,
|
||||
};
|
@ -1709,6 +1709,7 @@ span.CodeMirror-selectedtext {
|
||||
font-size: 14px;
|
||||
margin: -15px -15px 12px 0;
|
||||
position: relative;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.graphiql-container .search-box:before {
|
||||
@ -1726,6 +1727,10 @@ span.CodeMirror-selectedtext {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* .graphiql-container .search-box .search-box-icon {
|
||||
font-weight: 100 !important;
|
||||
} */
|
||||
|
||||
.graphiql-container .search-box .search-box-clear {
|
||||
background-color: #d0d0d0;
|
||||
border-radius: 12px;
|
||||
@ -2184,3 +2189,7 @@ li.CodeMirror-hint-active {
|
||||
.color_green {
|
||||
color: #047857 !important;
|
||||
}
|
||||
|
||||
.graphiql-explorer-node > span > svg {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ import snippets from './snippets';
|
||||
import globals from '../../../../Globals';
|
||||
|
||||
import 'graphiql/graphiql.css';
|
||||
import './GraphiQL.css';
|
||||
import 'graphiql-code-exporter/CodeExporter.css';
|
||||
import _push from '../../Data/push';
|
||||
import { isQueryValid } from '../Rest/utils';
|
||||
@ -190,26 +189,32 @@ class GraphiQLWrapper extends Component {
|
||||
const renderGraphiqlFooter = responseTime &&
|
||||
responseTrackingId === requestTrackingId && (
|
||||
<GraphiQL.Footer>
|
||||
<div className="graphiql_footer">
|
||||
<span className="graphiql_footer_label">Response Time</span>
|
||||
<span className="graphiql_footer_value">{responseTime} ms</span>
|
||||
<div className="flex items-center sticky bottom-0 w-full z-[100] p-sm bg-[#eeeeee]">
|
||||
<span className="text-xs text-[#777777] font-semibold mr-sm uppercase">
|
||||
Response Time
|
||||
</span>
|
||||
<span className="text-sm text-black mr-md">{responseTime} ms</span>
|
||||
{responseSize && (
|
||||
<>
|
||||
<span className="graphiql_footer_label">Response Size</span>
|
||||
<span className="graphiql_footer_value">
|
||||
<span className="text-xs text-[#777777] mr-sm uppercase font-semibold">
|
||||
Response Size
|
||||
</span>
|
||||
<span className="text-sm text-black mr-md">
|
||||
{responseSize} bytes
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{isResponseCached && (
|
||||
<>
|
||||
<span className="graphiql_footer_label">Cached</span>
|
||||
<span className="text-xs text-[#777777] font-semibold mr-sm uppercase">
|
||||
Cached
|
||||
</span>
|
||||
<ToolTip
|
||||
message="This query response was cached using the @cached directive"
|
||||
placement="top"
|
||||
tooltipStyle="graphiql_footer_icon"
|
||||
tooltipStyle="text-[#777] ml-sm mr-xs"
|
||||
/>
|
||||
<FaCheckCircle className="color_green" />
|
||||
<FaCheckCircle className="text-[#008000]" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -308,9 +313,7 @@ class GraphiQLWrapper extends Component {
|
||||
|
||||
return (
|
||||
<GraphiQLErrorBoundary>
|
||||
<div
|
||||
className={`react-container-graphql w-full h-full border mt-md overflow-hidden rounded border-gray-300`}
|
||||
>
|
||||
<div className="w-full h-full border mt-md overflow-hidden rounded border-gray-300">
|
||||
<OneGraphExplorer
|
||||
renderGraphiql={renderGraphiql}
|
||||
endpoint={getGraphQLEndpoint(mode)}
|
||||
|
@ -14,10 +14,8 @@ import {
|
||||
import { getGraphiQLQueryFromLocalStorage } from '../GraphiQLWrapper/utils';
|
||||
import { getRemoteQueries } from '../Actions';
|
||||
import { getHeadersAsJSON } from '../utils';
|
||||
|
||||
import '../GraphiQLWrapper/GraphiQL.css';
|
||||
import './OneGraphExplorer.css';
|
||||
import styles from '../ApiExplorer.scss';
|
||||
import Spinner from '../../../Common/Spinner/Spinner';
|
||||
import {
|
||||
showErrorNotification,
|
||||
@ -139,7 +137,7 @@ class OneGraphExplorer extends React.Component {
|
||||
`We are not able to render GraphiQL Explorer and Docs.
|
||||
You should still be able to try out your API from the GraphiQL Editor.`,
|
||||
null,
|
||||
<p style={{ paddingTop: '10px', margin: '0' }}>
|
||||
<p className="pt-sm m-0">
|
||||
Please report an issue on our{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
@ -260,10 +258,7 @@ class OneGraphExplorer extends React.Component {
|
||||
<div className="gqlexplorer">
|
||||
{this.props.loading ? (
|
||||
<div
|
||||
className={`${styles.height100} ${styles.display_flex}`}
|
||||
style={{
|
||||
width: explorerWidth,
|
||||
}}
|
||||
className={`h-full flex w-[${String(explorerWidth) || '300'}px]`}
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
@ -8,12 +8,10 @@ import Button from '../../../Common/Button/Button';
|
||||
import { Badge } from '../../../UIKit/atoms';
|
||||
import { Dispatch, ReduxState } from '../../../../types';
|
||||
import _push from '../../Data/push';
|
||||
import { badgeSort, getCurrentPageHost } from './utils';
|
||||
import { badgeSort, getCurrentPageHost, inputStyles } from './utils';
|
||||
import { LS_KEYS, setLSItem } from '../../../../utils/localStorage';
|
||||
import LivePreview from './LivePreview';
|
||||
|
||||
import styles from './RESTStyles.scss';
|
||||
|
||||
interface DetailsComponentProps extends InjectedProps {
|
||||
location: RouteComponentProps<unknown, unknown>['location'];
|
||||
}
|
||||
@ -50,37 +48,31 @@ const DetailsComponent: React.FC<DetailsComponentProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`container-fluid ${styles.rest_add_padding_left} ${styles.padd_top}`}
|
||||
>
|
||||
<div className={styles.subHeader}>
|
||||
<div className="px-md pt-md">
|
||||
<div className="">
|
||||
<BreadCrumb breadCrumbs={crumbs} />
|
||||
</div>
|
||||
<div className={styles.display_flex}>
|
||||
<h2 className={`${styles.headerText} ${styles.display_inline}`}>
|
||||
<div className="flex">
|
||||
<h2 className="text-xl font-bold inline-block">
|
||||
{endpointState.name}
|
||||
</h2>
|
||||
<Button
|
||||
color="yellow"
|
||||
size="xs"
|
||||
className={styles.add_mar_left}
|
||||
className="ml-md"
|
||||
onClick={onClickEdit}
|
||||
>
|
||||
Edit Endpoint
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>{endpointState.comment}</div>
|
||||
<div className="mt-md">{endpointState.comment}</div>
|
||||
<hr className="my-md" />
|
||||
<div className={styles.rest_details_layout}>
|
||||
<div className={styles.rest_details_left_content}>
|
||||
<div className="flex w-full justify-between">
|
||||
<div className="w-7/12">
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${styles.display_inline}`}
|
||||
>
|
||||
REST Endpoint
|
||||
</h4>
|
||||
<h4 className="text-base font-bold mb-md">REST Endpoint</h4>
|
||||
<input
|
||||
className="form-control"
|
||||
className={`${inputStyles} w-full disabled:bg-gray-100`}
|
||||
type="text"
|
||||
placeholder="Rest endpoint URL"
|
||||
value={endpointLocation && endpointLocation.current}
|
||||
@ -89,28 +81,19 @@ const DetailsComponent: React.FC<DetailsComponentProps> = ({
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${styles.display_inline} ${styles.padd_top}`}
|
||||
>
|
||||
<h4 className="text-base font-bold pt-md mb-md">
|
||||
Methods Available
|
||||
</h4>
|
||||
<div>
|
||||
{badgeSort(endpointState.methods).map(method => (
|
||||
<span
|
||||
className={`${styles.headerBadge} ${styles.padd_right}`}
|
||||
key={`badge-details-${method}`}
|
||||
>
|
||||
<span className="pr-md" key={`badge-details-${method}`}>
|
||||
<Badge type={`rest-${method}`} />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<hr className="my-md" />
|
||||
<div className={styles.gql_header_details}>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${styles.display_inline}`}
|
||||
>
|
||||
GraphQL Request
|
||||
</h4>
|
||||
<hr className="mb-lg mt-md" />
|
||||
<div className="flex align-center justify-between mb-md">
|
||||
<h4 className="text-base font-bold pt-xs">GraphQL Request</h4>
|
||||
<Button
|
||||
size="sm"
|
||||
color="white"
|
||||
@ -129,7 +112,7 @@ const DetailsComponent: React.FC<DetailsComponentProps> = ({
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.rest_details_right_content}>
|
||||
<div className="h-full w-1/2 pl-xl">
|
||||
<LivePreview
|
||||
endpointState={endpointState}
|
||||
pageHost={endpointLocation && endpointLocation.current}
|
||||
|
@ -4,28 +4,20 @@ import { Link } from 'react-router';
|
||||
import TopicDescription from '../../Common/Landing/TopicDescription';
|
||||
import LandingImage from './LandingImage';
|
||||
|
||||
import styles from './RESTStyles.scss';
|
||||
|
||||
const landingDescription = `REST endpoints allow for the creation of a REST interface to your saved GraphQL queries and mutations.
|
||||
Endpoints are accessible from /api/rest/* and inherit the authorization and permission structure from your associated GraphQL nodes.
|
||||
To create a new endpoint simply test your query in GraphiQL then click the REST button on GraphiQL to configure a URL.`;
|
||||
|
||||
const Landing = () => (
|
||||
<div
|
||||
className={`container-fluid ${styles.rest_add_padding_left} ${styles.padd_top}`}
|
||||
>
|
||||
<div className={`${styles.display_flex} ${styles.marginBottom}`}>
|
||||
<h2
|
||||
className={`${styles.headerText} ${styles.display_inline} ${styles.margin_bottom}`}
|
||||
>
|
||||
REST Endpoints
|
||||
</h2>
|
||||
<div className="pl-md pt-md">
|
||||
<div className="flex">
|
||||
<h2 className="text-xl font-bold">REST Endpoints</h2>
|
||||
</div>
|
||||
<div className={`${styles.subHeader} ${styles.padd_top}`}>
|
||||
<div className="pt-md">
|
||||
Create endpoints from GraphQL queries using{' '}
|
||||
<Link to="/api/api-explorer">GraphiQL</Link>.
|
||||
</div>
|
||||
<hr className="my-md" />
|
||||
<hr className="mb-md" />
|
||||
<TopicDescription
|
||||
title="What are REST endpoints?"
|
||||
imgElement={<LandingImage />}
|
||||
@ -33,7 +25,7 @@ const Landing = () => (
|
||||
description={landingDescription}
|
||||
knowMoreHref="https://hasura.io/docs/latest/graphql/core/api-reference/restified.html"
|
||||
/>
|
||||
<hr className={`${styles.clear_fix} my-lg`} />
|
||||
<hr className="clear-both my-lg" />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -15,8 +15,6 @@ import Landing from './Landing';
|
||||
import { badgeSort } from './utils';
|
||||
import CollapsibleToggle from '../../../Common/CollapsibleToggle/CollapsibleToggle';
|
||||
|
||||
import styles from './RESTStyles.scss';
|
||||
|
||||
const ListComponent: React.FC<Props> = ({
|
||||
restEndpoints,
|
||||
queryCollections,
|
||||
@ -43,15 +41,11 @@ const ListComponent: React.FC<Props> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`container-fluid ${styles.rest_add_padding_left} ${styles.padd_top}`}
|
||||
>
|
||||
<div className={`${styles.display_flex} ${styles.marginBottom}`}>
|
||||
<h2 className={`${styles.headerText} ${styles.display_inline}`}>
|
||||
REST Endpoints
|
||||
</h2>
|
||||
<div className="pl-md pt-md pr-md">
|
||||
<div className="flex">
|
||||
<h2 className="text-xl font-bold">REST Endpoints</h2>
|
||||
</div>
|
||||
<div className={`${styles.subHeader} ${styles.padd_top}`}>
|
||||
<div className="pt-md">
|
||||
Create endpoints from GraphQL queries using{' '}
|
||||
<Link to="/api/api-explorer">GraphiQL</Link>.
|
||||
<div className="w-8/12 mt-sm">
|
||||
@ -82,7 +76,7 @@ const ListComponent: React.FC<Props> = ({
|
||||
<th className="px-sm py-xs text-left text-sm bg-gray-50 font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Methods
|
||||
</th>
|
||||
<th className="px-sm py-xs text-right text-sm bg-gray-50 font-semibold text-gray-600 uppercase tracking-wider">
|
||||
<th className="px-sm py-xs float-right text-sm bg-gray-50 font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Modify
|
||||
</th>
|
||||
</thead>
|
||||
@ -110,7 +104,7 @@ const ListComponent: React.FC<Props> = ({
|
||||
|
||||
{/* Endpoint */}
|
||||
<td className="px-sm py-xs align-top">
|
||||
<div className={styles.rest_list_left_content}>
|
||||
<div className="flex flex-col w-3/4">
|
||||
<URLPreview urlInput={endpoint.url} />
|
||||
<CollapsibleToggle
|
||||
title="GraphQL Request"
|
||||
@ -139,7 +133,7 @@ const ListComponent: React.FC<Props> = ({
|
||||
</td>
|
||||
|
||||
{/* Modify Column */}
|
||||
<td className="px-sm py-xs align-top text-right">
|
||||
<td className="px-sm py-xs align-top float-right">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={onClickDelete(
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FaChevronRight } from 'react-icons/fa';
|
||||
|
||||
import styles from '../RESTStyles.scss';
|
||||
|
||||
type CollapsibleToggleProps = {
|
||||
state: Record<string, any>[];
|
||||
properties: string[];
|
||||
@ -20,43 +18,36 @@ const CollapsibleToggle: React.FC<CollapsibleToggleProps> = ({
|
||||
const toggleHandler = () => setIsOpen(prev => !prev);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.collapsibleWrapper} ${
|
||||
!isOpen ? styles.collapsedWrapper : ''
|
||||
}`}
|
||||
>
|
||||
<div className={`rounded-sm p-md ${!isOpen ? ' bg-gray-100' : ''}`}>
|
||||
<div
|
||||
className={styles.collapsibleToggle}
|
||||
className="cursor-pointer flex items-center"
|
||||
onClick={toggleHandler}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<span className={styles.collapsibleIndicatorWrapper}>
|
||||
<span className="text-base pr-sm">
|
||||
<FaChevronRight
|
||||
className={`${styles.collapsibleIndicator} ${
|
||||
isOpen && styles.collapsibleIndicatorOpen
|
||||
className={`transition duration-150 ease-in-out ${
|
||||
isOpen && ' rotate-90'
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span className={styles.titleWrapper}>
|
||||
<div className={styles.defaultCollapsibleTitle}>{title}</div>
|
||||
<span className="flex items-center">
|
||||
<div className="font-base font-bold text-gray-500">{title}</div>
|
||||
</span>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{isOpen ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<div className={styles.collapsible_info_container}>
|
||||
<div className="flex flex-wrap break-words">
|
||||
{state?.map((stateVar, index) => (
|
||||
<div className={styles.collapsed_text_container}>
|
||||
<div className={styles.collapsed_text}>
|
||||
<div className="flex">
|
||||
<div className="overflow-hidden text-ellipsis max-w-[300] text-base">
|
||||
{`${stateVar[properties[0]]} : ${stateVar[properties[1]]}`}
|
||||
</div>
|
||||
{index !== state?.length - 1 ? (
|
||||
<p className={styles.collapsed_separator}>|</p>
|
||||
) : null}
|
||||
{index !== state?.length - 1 ? <p>|</p> : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -5,8 +5,6 @@ import { HeaderState } from './state';
|
||||
import Input from './Input';
|
||||
import PreviewTable from './PreviewTable';
|
||||
|
||||
import styles from '../RESTStyles.scss';
|
||||
|
||||
type UpdateHeaderValues = (
|
||||
index: number
|
||||
) => (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
@ -22,10 +20,10 @@ type HeaderComponentProps = {
|
||||
const requestHeadersHeadings = [
|
||||
{
|
||||
content: '',
|
||||
className: styles.rest_preview_table_sm_width,
|
||||
className: 'w-2/12',
|
||||
},
|
||||
{ content: 'Key' },
|
||||
{ content: 'Value' },
|
||||
{ content: 'Key', className: 'p-md' },
|
||||
{ content: 'Value', className: 'p-md' },
|
||||
{ content: '' },
|
||||
];
|
||||
|
||||
@ -38,7 +36,7 @@ const Headers: React.FC<HeaderComponentProps> = ({
|
||||
}) => {
|
||||
if (!headerState || !headerState.length) {
|
||||
return (
|
||||
<div className={styles.rest_empty_container}>
|
||||
<div className="w-full pt-md flex justify-center items-center font-lg font-bold">
|
||||
Click on the 'Add Header' to add some Request Headers
|
||||
</div>
|
||||
);
|
||||
@ -48,10 +46,10 @@ const Headers: React.FC<HeaderComponentProps> = ({
|
||||
<PreviewTable headings={requestHeadersHeadings}>
|
||||
{headerState.map(header => (
|
||||
<tr
|
||||
className={styles.rest_preview_table_row}
|
||||
className="p-md border-b border-gray-300 mb-md"
|
||||
key={`rest-header-${header.index}`}
|
||||
>
|
||||
<td className={styles.text_center}>
|
||||
<td className="p-md">
|
||||
<input
|
||||
type="checkbox"
|
||||
value={header.key}
|
||||
@ -86,7 +84,7 @@ const Headers: React.FC<HeaderComponentProps> = ({
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
className={styles.rest_preview_clear_btn}
|
||||
className="ml-xs cursor-pointer"
|
||||
onClick={onClickRemove(header.index)}
|
||||
>
|
||||
<FaTimesCircle />
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from '../RESTStyles.scss';
|
||||
|
||||
const Input: React.FC<React.ComponentProps<'input'>> = props => (
|
||||
<input className={styles.rest_preview_input} {...props} />
|
||||
<input className="text-base bg-transparent border-0 w-full" {...props} />
|
||||
);
|
||||
|
||||
export default Input;
|
||||
|
@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from '../RESTStyles.scss';
|
||||
|
||||
type PreviewTableProps = {
|
||||
headings: {
|
||||
content: string;
|
||||
@ -10,8 +8,8 @@ type PreviewTableProps = {
|
||||
};
|
||||
|
||||
const PreviewTable: React.FC<PreviewTableProps> = ({ headings, children }) => (
|
||||
<table className={styles.rest_preview_table}>
|
||||
<tr className={styles.rest_preview_table_row}>
|
||||
<table className="w-full border-collapse mb-md">
|
||||
<tr className="border-b border-gray-300">
|
||||
{headings.map(heading => (
|
||||
<th className={heading?.className}>{heading.content}</th>
|
||||
))}
|
||||
|
@ -5,8 +5,6 @@ import Input from './Input';
|
||||
import PreviewTable from './PreviewTable';
|
||||
import ToolTip from '../../../../Common/Tooltip/Tooltip';
|
||||
|
||||
import styles from '../RESTStyles.scss';
|
||||
|
||||
type VariableComponentProps = {
|
||||
updateVariableValue: (
|
||||
name: string
|
||||
@ -15,8 +13,8 @@ type VariableComponentProps = {
|
||||
};
|
||||
|
||||
const requestVariablesHeadings = [
|
||||
{ content: 'Name', className: styles.rest_preview_w20 },
|
||||
{ content: 'Type' },
|
||||
{ content: 'Name', className: 'w-1/5 p-md text-center' },
|
||||
{ content: 'Type', className: 'p-md text-center' },
|
||||
{ content: 'Value' },
|
||||
];
|
||||
|
||||
@ -26,7 +24,7 @@ const Variables: React.FC<VariableComponentProps> = ({
|
||||
}) => {
|
||||
if (!variablesState || !variablesState.length) {
|
||||
return (
|
||||
<div className={styles.rest_empty_container}>
|
||||
<div className="w-full pt-sm flex justify-center items-center text-base font-bold text-center">
|
||||
This query doesn't require any request variables
|
||||
</div>
|
||||
);
|
||||
@ -35,11 +33,8 @@ const Variables: React.FC<VariableComponentProps> = ({
|
||||
return (
|
||||
<PreviewTable headings={requestVariablesHeadings}>
|
||||
{variablesState.map(v => (
|
||||
<tr
|
||||
className={styles.rest_preview_table_row}
|
||||
key={`rest-var-${v.name}`}
|
||||
>
|
||||
<td className={styles.text_center}>
|
||||
<tr className="border-b border-gray-300" key={`rest-var-${v.name}`}>
|
||||
<td className="">
|
||||
<b>{v.name}</b>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -28,8 +28,6 @@ import {
|
||||
import Spinner from '../../../../Common/Spinner/Spinner';
|
||||
import CollapsibleToggle from './CollapsibleToggle';
|
||||
|
||||
import styles from '../RESTStyles.scss';
|
||||
|
||||
interface EndpointState extends RestEndpointEntry {
|
||||
currentQuery: string;
|
||||
}
|
||||
@ -229,9 +227,9 @@ const LivePreview: React.FC<LivePreviewProps> = ({
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.rest_live_layout}>
|
||||
<h3 className={styles.rest_live_header}>Preview Request</h3>
|
||||
<div className={styles.rest_preview_req_header_layout}>
|
||||
<div className="flex flex-col pb-md text-center">
|
||||
<h3 className="text-lg font-bold">Preview Request</h3>
|
||||
<div className="my-md w-full">
|
||||
<CollapsibleToggle
|
||||
title="Request Headers"
|
||||
state={headerState}
|
||||
@ -244,17 +242,14 @@ const LivePreview: React.FC<LivePreviewProps> = ({
|
||||
updateValueText={updateHeaderValueText}
|
||||
toggleActiveState={updateActiveStateForHeader}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={onClickAddHeader}
|
||||
className={styles.float_right}
|
||||
>
|
||||
<FaPlusCircle className={styles.icon_margin} />
|
||||
<Button size="sm" onClick={onClickAddHeader} className="float-right">
|
||||
<FaPlusCircle className="mr-xs" />
|
||||
Add Header
|
||||
</Button>
|
||||
</CollapsibleToggle>
|
||||
</div>
|
||||
<div className={styles.rest_preview_req_var_layout}>
|
||||
<p>here</p>
|
||||
<div className="w-full">
|
||||
<CollapsibleToggle
|
||||
title="Request Variables"
|
||||
state={variableState}
|
||||
@ -266,27 +261,27 @@ const LivePreview: React.FC<LivePreviewProps> = ({
|
||||
/>
|
||||
</CollapsibleToggle>
|
||||
</div>
|
||||
<div className={styles.rest_preview_req_var_layout}>
|
||||
<div className="mb-sm w-full">
|
||||
<hr className="my-md" />
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={runQuery}
|
||||
color="yellow"
|
||||
className={styles.float_right}
|
||||
className="float-right"
|
||||
>
|
||||
<FaPlay className={styles.icon_margin} />
|
||||
<FaPlay className="mr-xs" />
|
||||
Run Request
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.rest_preview_show_response}>
|
||||
<div className="w-full">
|
||||
{progressState.isLoading ? <Spinner /> : null}
|
||||
{progressState?.data && (
|
||||
<pre className={styles.live_preview_pre}>
|
||||
<pre className="h-[200] overflow-scroll text-left">
|
||||
{JSON.stringify(progressState.data, null, 4)}
|
||||
</pre>
|
||||
)}
|
||||
{progressState?.error && (
|
||||
<div className={styles.rest_preview_error_display}>
|
||||
<div className="w-full border border-gray-500 rounded-sm p-sm pt-md">
|
||||
<b>Error Message: </b> {progressState?.error?.message}
|
||||
<div>
|
||||
<b>Error Status code: </b>
|
||||
|
@ -1,412 +0,0 @@
|
||||
@import '../../../Main/Main.scss';
|
||||
|
||||
.rest_create_layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rest_heading_layout {
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rest_form_layout {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rest_action_btns {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.name_input_layout {
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.rest_create_header {
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form_input_label {
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.margin_input {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.url_preview_layout {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.url_preview {
|
||||
font-family: monospace;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.input_note {
|
||||
color: #6b7280 !important;
|
||||
font-size: 12px;
|
||||
width: 30%;
|
||||
margin-top: 24px;
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
.create_input_layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input_width_50 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.methods_layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.method_checkboxes {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.method_checkbox_layout {
|
||||
display: inline-flex;
|
||||
width: 6%;
|
||||
input[type='checkbox'] {
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #d1d5db;
|
||||
transition: none;
|
||||
}
|
||||
label {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
margin-top: 1px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.request_viewer_layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.request_viewer_heading {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.create_btn_styles {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.rest_list_header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.rest_list_action_btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rest_add_padding_left {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.list_gql_header {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gql_header_details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.location_input_margin {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.rest_list_item_style {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rest_list_left_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.rest_list_right_content {
|
||||
display: flex;
|
||||
width: 30%;
|
||||
height: 40px;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.rest_list_header_know_more {
|
||||
margin-top: 6px;
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.rest_details_layout {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.rest_details_left_content {
|
||||
width: 62%;
|
||||
}
|
||||
|
||||
.rest_details_right_content {
|
||||
width: 35%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rest_live_layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.rest_live_header {
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rest_preview_table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.rest_preview_table_header {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 8px;
|
||||
text-align: left;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.rest_preview_table_row {
|
||||
border-bottom: 1px solid #ccc;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
& th {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
& td {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
& b {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
& input[type='checkbox'] {
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #d1d5db;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rest_preview_table_sm_width {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.rest_preview_req_header_layout {
|
||||
width: 100%;
|
||||
margin-bottom: 18px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.rest_preview_req_var_layout {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.rest_preview_input {
|
||||
font-size: 15px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.rest_preview_clear_btn {
|
||||
margin-left: 8px;
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.rest_preview_show_response {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rest_preview_w20 {
|
||||
width: 20%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rest_empty_container {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
.rest_preview_error_display {
|
||||
width: 100%;
|
||||
border: 1px solid #6b7280;
|
||||
border-radius: 4px;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.collapsibleWrapper {
|
||||
margin-bottom: 5px;
|
||||
padding: 6px 12px !important;
|
||||
border-radius: 8px;
|
||||
|
||||
.collapsibleToggle {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
outline: none;
|
||||
|
||||
.collapsibleIndicatorWrapper {
|
||||
padding-right: 10px;
|
||||
font-size: 12px;
|
||||
|
||||
.collapsibleIndicator {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.collapsibleIndicatorOpen {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.titleWrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.defaultCollapsibleTitle {
|
||||
color: #788095;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsibleContent {
|
||||
padding: 3px;
|
||||
display: block;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
|
||||
.inputBox {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
.inputBox:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsibleHeader {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsedWrapper {
|
||||
background-color: #e0e7f1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.collapsedWrapperChildren {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.collapsed_text {
|
||||
line-height: 1.5;
|
||||
font-size: 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.collapsed_text_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.collapsed_separator {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.collapsible_info_container {
|
||||
display: flex;
|
||||
word-break: break-word;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.live_preview_pre {
|
||||
max-height: 200px;
|
||||
overflow: scroll;
|
||||
}
|
@ -316,3 +316,9 @@ export const getRequestBody = ({
|
||||
? JSON.stringify(requestBody)
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export const inputStyles =
|
||||
'block h-10 shadow-sm rounded border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400';
|
||||
|
||||
export const focusYellowRing =
|
||||
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-400';
|
||||
|
@ -1093,10 +1093,10 @@ const ViewRows = props => {
|
||||
return (
|
||||
<div className={isVisible ? '' : 'hide '}>
|
||||
{getFilterQuery()}
|
||||
<div className="ml-0 mt-md">
|
||||
<div className="w-fit ml-0 mt-md">
|
||||
{getSelectedRowsSection()}
|
||||
<div className="w-full">
|
||||
<div className="">{renderTableBody()}</div>
|
||||
<div>
|
||||
<div>{renderTableBody()}</div>
|
||||
<br />
|
||||
<br />
|
||||
<div>{getChildComponent()}</div>
|
||||
|
@ -90,7 +90,7 @@ export const Checkbox: React.FC<CheckboxProps> = ({
|
||||
<label
|
||||
htmlFor={componentId}
|
||||
className={clsx(
|
||||
'cursor-pointer m-0 ml-xs',
|
||||
'cursor-pointer m-0 ml-xs font-normal',
|
||||
disabled || optionDisabled
|
||||
? 'text-gray-500 cursor-not-allowed'
|
||||
: ''
|
||||
|
@ -73,7 +73,7 @@ export const FieldWrapper = (props: FieldWrapperProps) => {
|
||||
{tooltip ? <ToolTip message={tooltip} /> : null}
|
||||
</span>
|
||||
{description ? (
|
||||
<span className="text-gray-600 mb-xs">{description}</span>
|
||||
<span className="text-gray-600 mb-xs font-normal">{description}</span>
|
||||
) : null}
|
||||
</label>
|
||||
<div>{children}</div>
|
||||
|
Loading…
Reference in New Issue
Block a user