mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 22:18:33 +00:00
333 lines
14 KiB
JavaScript
333 lines
14 KiB
JavaScript
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
import Modal from 'components/Modal';
|
|
import Accordion from 'components/Accordion/index';
|
|
import { IconTrash, IconEdit, IconCirclePlus, IconCookieOff, IconAlertTriangle, IconSearch } from '@tabler/icons';
|
|
import { deleteCookiesForDomain, deleteCookie } from 'providers/ReduxStore/slices/app';
|
|
import toast from 'react-hot-toast';
|
|
import ModifyCookieModal from 'components/Cookies/ModifyCookieModal/index';
|
|
import StyledWrapper from './StyledWrapper';
|
|
import moment from 'moment';
|
|
import { Tooltip } from 'react-tooltip';
|
|
|
|
const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
|
|
<Modal onClose={onClose} handleCancel={onClose} title="Clear Domain Cookies" hideFooter={true}>
|
|
<div className="flex items-center font-normal">
|
|
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
|
|
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
|
|
</div>
|
|
<div className="font-normal mt-4">
|
|
Are you sure you want to clear all cookies for the domain {domain}?
|
|
</div>
|
|
|
|
<div className="flex justify-between mt-6">
|
|
<div>
|
|
<button className="btn btn-sm btn-close" onClick={onClose}>
|
|
Close
|
|
</button>
|
|
</div>
|
|
<div>
|
|
<button className="btn btn-sm btn-danger" onClick={onClear}>
|
|
Clear All
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
|
|
const DeleteCookieModal = ({ onClose, cookieName, onDelete }) => (
|
|
<Modal onClose={onClose} handleCancel={onClose} title="Delete Cookie" hideFooter={true}>
|
|
<div className="flex items-center font-normal">
|
|
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
|
|
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
|
|
</div>
|
|
<div className="font-normal mt-4">
|
|
Are you sure you want to delete the cookie {cookieName}?
|
|
</div>
|
|
|
|
<div className="flex justify-between mt-6">
|
|
<div>
|
|
<button className="btn btn-sm btn-close" onClick={onClose}>
|
|
Close
|
|
</button>
|
|
</div>
|
|
<div>
|
|
<button className="btn btn-sm btn-danger" onClick={onDelete}>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
|
|
const CollectionProperties = ({ onClose }) => {
|
|
const dispatch = useDispatch();
|
|
const cookies = useSelector((state) => state.app.cookies) || [];
|
|
const [isModifyCookieModalOpen, setIsModifyCookieModalOpen] = useState(false);
|
|
const [currentDomain, setCurrentDomain] = useState(null);
|
|
const [cookieToEdit, setCookieToEdit] = useState(null);
|
|
|
|
const [domainToClear, setDomainToClear] = useState(null);
|
|
const [cookieToDelete, setCookieToDelete] = useState(null);
|
|
const [searchText, setSearchText] = useState(null);
|
|
|
|
const handleAddCookie = (domain) => {
|
|
if(domain) setCurrentDomain(domain);
|
|
setIsModifyCookieModalOpen(true);
|
|
};
|
|
|
|
const handleEditCookie = (domain, cookie) => {
|
|
setCurrentDomain(domain);
|
|
setCookieToEdit(cookie);
|
|
setIsModifyCookieModalOpen(true);
|
|
};
|
|
|
|
const handleClearDomainCookies = (domain) => {
|
|
setDomainToClear(domain);
|
|
};
|
|
|
|
const clearDomainCookiesAction = () => {
|
|
dispatch(deleteCookiesForDomain(domainToClear))
|
|
.then(() => {
|
|
toast.success('Domain cookies cleared successfully');
|
|
})
|
|
.catch((err) => console.log(err) && toast.error('Failed to clear domain cookies'));
|
|
setDomainToClear(null);
|
|
};
|
|
|
|
const handleDeleteCookie = (domain, path, key) => {
|
|
setCookieToDelete({ key, domain, path });
|
|
};
|
|
|
|
const deleteCookieAction = () => {
|
|
if (cookieToDelete) {
|
|
const { domain, path, key } = cookieToDelete;
|
|
dispatch(deleteCookie(domain, path, key))
|
|
.then(() => {
|
|
toast.success('Cookie deleted successfully');
|
|
})
|
|
.catch((err) => console.log(err) && toast.error('Failed to delete cookie'));
|
|
}
|
|
setCookieToDelete(null);
|
|
};
|
|
|
|
const filteredCookies = useMemo(() => {
|
|
if (!searchText) return cookies;
|
|
|
|
return cookies.filter((cookie) =>
|
|
cookie.domain.toLowerCase().includes(searchText.toLowerCase())
|
|
);
|
|
}, [cookies, searchText]);
|
|
|
|
const shouldShowHeader = cookies && cookies.length > 0;
|
|
|
|
return (
|
|
<>
|
|
<Modal
|
|
size="xl"
|
|
title="Cookies"
|
|
hideFooter={true}
|
|
handleCancel={onClose}
|
|
customHeader={shouldShowHeader ? (
|
|
<StyledWrapper className="header flex items-center justify-between w-full">
|
|
<h2 className="text-xs font-medium">Cookies</h2>
|
|
<input
|
|
type="search"
|
|
placeholder="Search by domain"
|
|
value={searchText || ''}
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
className="block textbox non-passphrase-input ml-auto font-normal"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="submit btn btn-sm btn-secondary flex items-center gap-1 mx-4 font-medium"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleAddCookie();
|
|
}}
|
|
>
|
|
<IconCirclePlus strokeWidth={1.5} size={16} />
|
|
<span>Add Cookie</span>
|
|
</button>
|
|
</StyledWrapper>
|
|
) : null}
|
|
>
|
|
<StyledWrapper>
|
|
{!cookies || !cookies.length ? (
|
|
// No cookies found
|
|
<div className="flex items-center justify-center flex-col">
|
|
<IconCookieOff size={48} strokeWidth={1.5} className="text-gray-500" />
|
|
<h2 className="text-lg font-semibold mt-4">No cookies found</h2>
|
|
<p className="text-gray-500 mt-2">Add cookies to get started</p>
|
|
<button
|
|
type="submit"
|
|
className="submit btn btn-sm btn-secondary flex items-center gap-1 mt-8"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleAddCookie();
|
|
}}
|
|
>
|
|
<IconCirclePlus strokeWidth={1.5} size={16} />
|
|
<span>Add Cookie</span>
|
|
</button>
|
|
</div>
|
|
) : cookies.length && !filteredCookies.length ? (
|
|
// No search results
|
|
<div className="flex items-center justify-center flex-col">
|
|
<IconSearch size={48} />
|
|
<h2 className="text-lg font-semibold mt-4">No search results</h2>
|
|
<p className="text-gray-500 mt-2">Try a different search term</p>
|
|
</div>
|
|
) : (
|
|
// Show cookies list
|
|
<div className="scroll-box">
|
|
<Accordion defaultIndex={0}>
|
|
{filteredCookies.map((domainWithCookies, i) => (
|
|
<Accordion.Item key={i} index={i}>
|
|
<Accordion.Header index={i} className="flex items-center">
|
|
<div className="flex items-center">
|
|
<span>{domainWithCookies.domain}</span>
|
|
<span className="ml-2 text-xs dark:text-gray-300 text-gray-500">
|
|
({domainWithCookies.cookies.length}{' '}
|
|
{domainWithCookies.cookies.length === 1 ? 'cookie' : 'cookies'})
|
|
</span>
|
|
<div className="ml-auto flex items-center gap-2">
|
|
<button
|
|
type="submit"
|
|
className="flex items-center gap-1 text-gray-500 hover:text-gray-950 dark:text-white dark:hover:text-gray-300"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleAddCookie(domainWithCookies.domain);
|
|
}}
|
|
>
|
|
<IconCirclePlus strokeWidth={1.5} size={16} />
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleClearDomainCookies(domainWithCookies.domain);
|
|
}}
|
|
className="text-gray-950 dark:text-white dark:hover:hover:text-red-600 hover:text-red-600 mr-2"
|
|
>
|
|
<IconTrash strokeWidth={1.5} size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Accordion.Header>
|
|
<Accordion.Content index={i}>
|
|
<div className="flex items-center justify-between">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="text-left border-b border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-gray-300">
|
|
<th className="py-2 px-4 font-semibold w-32">Name</th>
|
|
<th className="py-2 px-4 font-semibold w-52">Value</th>
|
|
<th className="py-2 px-4 font-semibold">Path</th>
|
|
<th className="py-2 px-4 font-semibold">Expires</th>
|
|
<th className="py-2 px-4 font-semibold text-center">Secure</th>
|
|
<th className="py-2 px-4 font-semibold text-center">HTTP Only</th>
|
|
<th className="py-2 px-4 font-semibold text-right w-24">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{domainWithCookies.cookies.map((cookie) => (
|
|
<tr key={cookie.key} className="border-b border-gray-200 dark:border-neutral-600 last:border-none">
|
|
<td className="py-2 px-4 truncate">
|
|
<span id={`cookie-key-${cookie.key}`}>{cookie.key}</span>
|
|
<Tooltip
|
|
anchorId={`cookie-key-${cookie.key}`}
|
|
className="tooltip-mod"
|
|
html={cookie.key}
|
|
/>
|
|
</td>
|
|
<td className="py-2 px-4 truncate">
|
|
<span id={`cookie-value-${cookie.key}`}>{cookie.value}</span>
|
|
<Tooltip
|
|
anchorId={`cookie-value-${cookie.key}`}
|
|
className="tooltip-mod"
|
|
html={cookie.value}
|
|
/>
|
|
</td>
|
|
<td className="py-2 px-4 truncate">{cookie.path || '/'}</td>
|
|
<td className="py-2 px-4 truncate">
|
|
<span id={`cookie-expires-${cookie.key}`}>
|
|
{cookie.expires && moment(cookie.expires).isValid()
|
|
? new Date(cookie.expires).toLocaleString()
|
|
: 'Session'}
|
|
</span>
|
|
{cookie.expires && moment(cookie.expires).isValid() && (
|
|
<Tooltip
|
|
anchorId={`cookie-expires-${cookie.key}`}
|
|
className="tooltip-mod"
|
|
html={new Date(cookie.expires).toLocaleString()}
|
|
/>
|
|
)}
|
|
</td>
|
|
<td className="py-2 px-4 text-center">{cookie.secure ? '✓' : ''}</td>
|
|
<td className="py-2 px-4 text-center">{cookie.httpOnly ? '✓' : ''}</td>
|
|
<td className="py-2 px-4">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleEditCookie(domainWithCookies.domain, cookie);
|
|
}}
|
|
className="text-gray-700 hover:text-gray-950
|
|
dark:text-white dark:hover:text-gray-300"
|
|
>
|
|
<IconEdit strokeWidth={1.5} size={16} />
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleDeleteCookie(domainWithCookies.domain, cookie.path, cookie.key);
|
|
}}
|
|
className="text-gray-950 dark:text-white dark:hover:hover:text-red-600 hover:text-red-600"
|
|
>
|
|
<IconTrash strokeWidth={1.5} size={16} />
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</Accordion.Content>
|
|
</Accordion.Item>
|
|
))}
|
|
</Accordion>
|
|
</div>
|
|
)}
|
|
</StyledWrapper>
|
|
</Modal>
|
|
{isModifyCookieModalOpen && (
|
|
<ModifyCookieModal
|
|
onClose={() => {
|
|
setCookieToEdit(null);
|
|
setCurrentDomain(null);
|
|
setIsModifyCookieModalOpen(false);
|
|
}}
|
|
domain={currentDomain}
|
|
cookie={cookieToEdit}
|
|
/>
|
|
)}
|
|
{domainToClear ? (
|
|
<ClearDomainCookiesModal
|
|
onClose={() => setDomainToClear(null)}
|
|
domain={domainToClear}
|
|
onClear={clearDomainCookiesAction}
|
|
/>
|
|
) : null}
|
|
{cookieToDelete ? (
|
|
<DeleteCookieModal
|
|
onClose={() => setCookieToDelete(null)}
|
|
cookieName={cookieToDelete.key}
|
|
onDelete={deleteCookieAction}
|
|
/>
|
|
) : null}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default CollectionProperties;
|