feat: ws settings

This commit is contained in:
Siddharth Gelera
2025-09-15 13:49:17 +05:30
parent 44cea985ac
commit 8425f9f37b
6 changed files with 275 additions and 9 deletions

View File

@@ -57,7 +57,8 @@ const ToggleSelector = ({
`}
/>
</button>
<div className="flex flex-col">
{(label || description)
&& <div className="flex flex-col">
<label className="text-xs font-medium text-gray-900 dark:text-gray-100">
{label}
</label>
@@ -66,7 +67,7 @@ const ToggleSelector = ({
{description}
</p>
)}
</div>
</div>}
</div>
);
};

View File

@@ -11,6 +11,7 @@ import { getPropertyFromDraftOrRequest } from 'utils/collections/index';
import WsBody from '../WsBody/index';
import StyledWrapper from './StyledWrapper';
import WSAuth from './WSAuth';
import WSSettingsPane from '../WSSettingsPane/index';
const WSRequestPane = ({ item, collection, handleRun }) => {
const dispatch = useDispatch();
@@ -43,7 +44,7 @@ const WSRequestPane = ({ item, collection, handleRun }) => {
return <RequestHeaders item={item} collection={collection} addHeaderText="Add Metadata" />;
}
case 'settings': {
return <div className="flex w-full mt-10 justify-center text-neutral-400">TBD</div>;
return <WSSettingsPane item={item} collection={collection} />;
}
case 'auth': {
return <WSAuth item={item} collection={collection} />;

View File

@@ -0,0 +1,25 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.single-line-editor-wrapper {
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
.tooltip-mod {
font-size: 11px !important;
width: 150px !important;
& ul {
padding-left: 4px;
}
& ul > li {
list-style: circle;
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,193 @@
import React from 'react';
import { Tooltip } from 'react-tooltip';
import { useDispatch } from 'react-redux';
import get from 'lodash/get';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateItemSettings } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
import ToggleSelector from '../Settings/ToggleSelector/index';
import StyledWrapper from './StyledWrapper';
import Accordion from 'components/Accordion';
import InfoTip from 'components/InfoTip/index';
/**
* @param {string} propertyKey
* @param {{draft?:Record<string,unknown>}} item
* @returns
*/
const getPropertyFromDraftOrRequest = (propertyKey, item) =>
item.draft ? get(item, `draft.${propertyKey}`, {}) : get(item, propertyKey, {});
const WSSettingsPane = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme, theme } = useTheme();
const { keepAlive, connectionTimeout, keepAliveInterval } = getPropertyFromDraftOrRequest('settings', item);
const onToggleKeepAlive = () => {
dispatch(
updateItemSettings({
collectionUid: collection.uid,
itemUid: item.uid,
settings: { keepAlive: !keepAlive }
})
);
};
const onChangeConnectionTimeout = (val) => {
dispatch(
updateItemSettings({
collectionUid: collection.uid,
itemUid: item.uid,
settings: { connectionTimeout: val }
})
);
};
const onChangeKeepAliveInterval = (val) => {
dispatch(
updateItemSettings({
collectionUid: collection.uid,
itemUid: item.uid,
settings: { keepAliveInterval: val }
})
);
};
return (
<StyledWrapper className="flex flex-col mt-4 gap-4 w-full">
<section className="grid gap-4 items-center grid-cols-2">
<div>
<label className="font-medium mb-2">Connection Timeout</label>
<InfoTip
infotipId="setting-connection-timeout"
className="tooltip-mod max-w-lg"
content={
<div>
<p>
<span>
Timeout in milliseconds
</span>
</p>
</div>
}
/>
</div>
<div>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={connectionTimeout}
theme={storedTheme}
onChange={(newValue) => onChangeConnectionTimeout(newValue)}
collection={collection}
/>
</div>
</div>
<div>
<label className="font-medium mb-2">Keep Alive Interval</label>
<InfoTip
infotipId="setting-keep-alive"
className="tooltip-mod max-w-lg"
content={
<div>
<p>
<span>
Keep the websocket alive by sending ping requests to the server at every interval (in millseconds)
</span>
</p>
<p className="mt-2">
0 (zero) = off
</p>
</div>
}
/>
</div>
<div>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={keepAliveInterval}
theme={storedTheme}
onChange={(newValue) => onChangeKeepAliveInterval(newValue)}
collection={collection}
/>
</div>
</div>
</section>
{/* Variation 2 */}
{/* <Accordion>
<Accordion.Item className="!border-0">
<Accordion.Header
style={{
paddingLeft: 0,
paddingRight: 0
}}
>
<div className="flex justify-between">
<label className="font-medium">Keep Alive</label>
<ToggleSelector
checked={keepAlive}
onChange={(e) => {
e.preventDefault();
e.stopPropagation();
onToggleKeepAlive();
}}
size="medium"
/>
</div>
</Accordion.Header>
<Accordion.Content>
<div>
<label className="flex font-medium mb-2">Keep Alive Interval (ms)</label>
<div className="flex-1 w-1/2 single-line-editor-wrapper">
<SingleLineEditor
value={keepAliveInterval}
theme={storedTheme}
onChange={(newValue) => onChangeKeepAliveInterval(newValue)}
collection={collection}
/>
</div>
</div>
</Accordion.Content>
</Accordion.Item>
</Accordion> */}
{/* Variation 3 */}
{/* <section>
<div className="flex justify-between">
<label className="font-medium">Keep Alive</label>
<ToggleSelector
checked={keepAlive}
onChange={(e) => {
onToggleKeepAlive();
}}
size="medium"
/>
</div>
{keepAlive ? (
<>
<div className="border-t border-neutral-200 my-2"></div>
<div className="p-2">
<div>
<label className="flex font-medium mb-2">Keep Alive Interval (ms)</label>
<div className="flex-1 w-1/2 single-line-editor-wrapper">
<SingleLineEditor
value={keepAliveInterval}
theme={storedTheme}
onChange={(newValue) => onChangeKeepAliveInterval(newValue)}
collection={collection}
/>
</div>
</div>
</div>
</>
) : null}
</section> */}
</StyledWrapper>
);
};
export default WSSettingsPane;

View File

@@ -279,6 +279,21 @@ const mapPairListToKeyValPair = (pairList = []) => {
return _.merge({}, ...pairList[0]);
};
/**
* @param {Record<unknown,unknown>} obj
* @returns {(key:string, opts?:{fallback: number })=>number|undefined}
*/
const createGetNumFromRecord =
(obj) =>
(key, { fallback } = {}) => {
if (!(key in obj)) return fallback;
const asNumber = typeof obj[key] === 'number' ? obj[key] : Number(obj.key);
if (isNaN(asNumber)) {
return fallback;
}
return asNumber;
};
const sem = grammar.createSemantics().addAttribute('ast', {
BruFile(tags) {
if (!tags || !tags.ast || !tags.ast.length) {
@@ -410,9 +425,20 @@ const sem = grammar.createSemantics().addAttribute('ast', {
settings(_1, dictionary) {
let settings = mapPairListToKeyValPair(dictionary.ast);
const getNumFromRecord = createGetNumFromRecord(settings);
const keepAliveInterval = getNumFromRecord('keepAliveInterval', {
fallback: 10000
});
const connectionTimeout = getNumFromRecord('connectionTimeout', {
fallback: 250
});
return {
settings: {
encodeUrl: typeof settings.encodeUrl === 'boolean' ? settings.encodeUrl : settings.encodeUrl === 'true'
encodeUrl: typeof settings.encodeUrl === 'boolean' ? settings.encodeUrl : settings.encodeUrl === 'true',
keepAlive: typeof settings.keepAlive === 'boolean' ? settings.keepAlive : settings.keepAlive === 'true',
keepAliveInterval,
connectionTimeout
}
};
},

View File

@@ -417,6 +417,22 @@ const wsRequestSchema = Yup.object({
.noUnknown(true)
.strict();
const wsSettingsSchema = Yup.object({
settings: Yup.object({
connectionTimeout: Yup.number()
.default(1000)
.min(250),
keepAlive: Yup.boolean().default(false),
keepAliveInterval: Yup.number()
.default(10000)
.min(500)
// max 2 minutes
.max(2 * 60 * 1000),
}).noUnknown(true)
.strict()
.nullable()
});
const folderRootSchema = Yup.object({
request: Yup.object({
headers: Yup.array().of(keyValueSchema).nullable(),
@@ -472,12 +488,16 @@ const itemSchema = Yup.object({
})
})
}),
settings: Yup.object({
encodeUrl: Yup.boolean().nullable()
})
.noUnknown(true)
settings: Yup.mixed()
.when('type', {
is: (type) => type === 'ws-request',
then: wsSettingsSchema,
otherwise: Yup.object({
encodeUrl: Yup.boolean().nullable()
}).noUnknown(true)
.strict()
.nullable(),
.nullable()
}),
fileContent: Yup.string().when('type', {
// If the type is 'js', the fileContent field is expected to be a string.
// This can include an empty string, indicating that the JS file may not have any content.