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:
Erik Magnusson 2022-07-06 16:46:53 +02:00 committed by hasura-bot
parent 584aa666bd
commit 387285b8f1
31 changed files with 245 additions and 2325 deletions

View File

@ -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}

View File

@ -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 = {

View File

@ -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>
)

View File

@ -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;

View File

@ -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

View File

@ -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}

View File

@ -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;
}
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};

View File

@ -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;
}

View File

@ -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)}

View File

@ -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>

View File

@ -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}

View File

@ -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>
);

View File

@ -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(

View File

@ -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>

View File

@ -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 &apos;Add Header&apos; 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 />

View File

@ -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;

View File

@ -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>
))}

View File

@ -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&apos;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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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';

View File

@ -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>

View File

@ -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'
: ''

View File

@ -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>