mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-21 23:43:15 +01:00
draft: global env ui and store
This commit is contained in:
parent
2e4051b022
commit
72de78025e
@ -36,6 +36,13 @@ const Wrapper = styled.div`
|
|||||||
padding: 0.35rem 0.6rem;
|
padding: 0.35rem 0.6rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
.icon {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: ${(props) => props.theme.dropdown.iconColor};
|
color: ${(props) => props.theme.dropdown.iconColor};
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||||||
import Tippy from '@tippyjs/react';
|
import Tippy from '@tippyjs/react';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const Dropdown = ({ icon, children, onCreate, placement }) => {
|
const Dropdown = ({ icon, children, onCreate, placement, transparent }) => {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="dropdown">
|
<StyledWrapper className="dropdown" transparent={transparent}>
|
||||||
<Tippy
|
<Tippy
|
||||||
content={children}
|
content={children}
|
||||||
placement={placement || 'bottom-end'}
|
placement={placement || 'bottom-end'}
|
||||||
|
@ -56,7 +56,7 @@ const EnvironmentSelector = ({ collection }) => {
|
|||||||
{environments && environments.length
|
{environments && environments.length
|
||||||
? environments.map((e) => (
|
? environments.map((e) => (
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className={`dropdown-item ${e?.uid === activeEnvironmentUid ? 'active' : ''}`}
|
||||||
key={e.uid}
|
key={e.uid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect(e);
|
onSelect(e);
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
.current-environment {
|
||||||
|
}
|
||||||
|
.environment-active {
|
||||||
|
padding: 0.3rem 0.4rem;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
border: solid 1px ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
}
|
||||||
|
.environment-selector {
|
||||||
|
.active: {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,93 @@
|
|||||||
|
import React, { useRef, forwardRef, useState } from 'react';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
import Dropdown from 'components/Dropdown';
|
||||||
|
import { IconSettings, IconWorld, IconDatabase, IconDatabaseOff, IconCheck } from '@tabler/icons';
|
||||||
|
import EnvironmentSettings from '../EnvironmentSettings';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
|
||||||
|
const EnvironmentSelector = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
|
||||||
|
const activeGlobalEnvironmentUid = useSelector((state) => state.globalEnvironments.activeGlobalEnvironmentUid);
|
||||||
|
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||||
|
const activeEnvironment = activeGlobalEnvironmentUid ? find(globalEnvironments, (e) => e.uid === activeGlobalEnvironmentUid) : null;
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
|
||||||
|
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
||||||
|
{
|
||||||
|
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSettingsIconClick = () => {
|
||||||
|
setOpenSettingsModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setOpenSettingsModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
|
|
||||||
|
const onSelect = (environment) => {
|
||||||
|
dispatch(selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null }))
|
||||||
|
.then(() => {
|
||||||
|
if (environment) {
|
||||||
|
toast.success(`Environment changed to ${environment.name}`);
|
||||||
|
} else {
|
||||||
|
toast.success(`No Environments are active now`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="flex items-center cursor-pointer environment-selector mr-3">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end" transparent={true}>
|
||||||
|
{globalEnvironments && globalEnvironments.length
|
||||||
|
? globalEnvironments.map((e) => (
|
||||||
|
<div
|
||||||
|
className={`dropdown-item ${e?.uid === activeGlobalEnvironmentUid ? 'active' : ''}`}
|
||||||
|
key={e.uid}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect(e);
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2 break-all">{e.name}</span>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onSelect(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||||
|
<span className="ml-2">No Environment</span>
|
||||||
|
</div>
|
||||||
|
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
|
||||||
|
<div className="pr-2 text-gray-600">
|
||||||
|
<IconSettings size={18} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<span>Configure</span>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
{openSettingsModal && <EnvironmentSettings globalEnvironments={globalEnvironments} activeGlobalEnvironmentUid={activeGlobalEnvironmentUid} onClose={handleModalClose} />}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentSelector;
|
@ -0,0 +1,78 @@
|
|||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import Portal from 'components/Portal/index';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { copyGlobalEnvironment } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const CopyEnvironment = ({ environment, onClose }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
name: environment.name + ' - Copy'
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.max(50, 'must be 50 characters or less')
|
||||||
|
.required('name is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
dispatch(copyGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Global environment created!');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error('An error occurred while created the environment');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal size="sm" title={'Copy Global Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||||
|
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block font-semibold">
|
||||||
|
New Environment Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="environment-name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
ref={inputRef}
|
||||||
|
className="block textbox mt-2 w-full"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name || ''}
|
||||||
|
/>
|
||||||
|
{formik.touched.name && formik.errors.name ? (
|
||||||
|
<div className="text-red-500">{formik.errors.name}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopyEnvironment;
|
@ -0,0 +1,83 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import Portal from 'components/Portal';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
|
||||||
|
const CreateEnvironment = ({ onClose }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.max(50, 'must be 50 characters or less')
|
||||||
|
.required('name is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
dispatch(addGlobalEnvironment({ name: values.name }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Global environment created!');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch(() => toast.error('An error occurred while creating the environment'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'Create Global Environment'}
|
||||||
|
confirmText="Create"
|
||||||
|
handleConfirm={onSubmit}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block font-semibold">
|
||||||
|
Environment Name
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center mt-2">
|
||||||
|
<input
|
||||||
|
id="environment-name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
ref={inputRef}
|
||||||
|
className="block textbox w-full"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name || ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{formik.touched.name && formik.errors.name ? (
|
||||||
|
<div className="text-red-500">{formik.errors.name}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateEnvironment;
|
@ -0,0 +1,15 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
button.submit {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--color-background-danger) !important;
|
||||||
|
border: inherit !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: inherit !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Portal from 'components/Portal/index';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { deleteGlobalEnvironment } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
|
||||||
|
const DeleteEnvironment = ({ onClose, environment }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const onConfirm = () => {
|
||||||
|
dispatch(deleteGlobalEnvironment({ environmentUid: environment.uid }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Environment deleted successfully');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch(() => toast.error('An error occurred while deleting the environment'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<StyledWrapper>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'Delete Global Environment'}
|
||||||
|
confirmText="Delete"
|
||||||
|
handleConfirm={onConfirm}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
|
||||||
|
</Modal>
|
||||||
|
</StyledWrapper>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteEnvironment;
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IconAlertTriangle } from '@tabler/icons';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
const ConfirmSwitchEnv = ({ onCancel }) => {
|
||||||
|
return createPortal(
|
||||||
|
<Modal
|
||||||
|
size="md"
|
||||||
|
title="Unsaved changes"
|
||||||
|
confirmText="Save and Close"
|
||||||
|
cancelText="Close without saving"
|
||||||
|
disableEscapeKey={true}
|
||||||
|
disableCloseOnOutsideClick={true}
|
||||||
|
closeModalFadeTimeout={150}
|
||||||
|
handleCancel={onCancel}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
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">You have unsaved changes in this environment.</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<div>
|
||||||
|
<button className="btn btn-sm btn-danger" onClick={onCancel}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</Modal>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmSwitchEnv;
|
@ -0,0 +1,61 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-weight: 600;
|
||||||
|
table-layout: fixed;
|
||||||
|
|
||||||
|
thead,
|
||||||
|
td {
|
||||||
|
border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
|
||||||
|
padding: 4px 10px;
|
||||||
|
|
||||||
|
&:nth-child(1),
|
||||||
|
&:nth-child(4) {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
&:nth-child(5) {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
color: ${(props) => props.theme.table.thead.color};
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
thead td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-param {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 100%;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
outline: none !important;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,195 @@
|
|||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import { IconTrash } from '@tabler/icons';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { uuid } from 'utils/common';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { variableNameRegex } from 'utils/common/regex';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
|
||||||
|
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
const addButtonRef = useRef(null);
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: environment.variables || [],
|
||||||
|
validationSchema: Yup.array().of(
|
||||||
|
Yup.object({
|
||||||
|
enabled: Yup.boolean(),
|
||||||
|
name: Yup.string()
|
||||||
|
.required('Name cannot be empty')
|
||||||
|
.matches(
|
||||||
|
variableNameRegex,
|
||||||
|
'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.'
|
||||||
|
)
|
||||||
|
.trim(),
|
||||||
|
secret: Yup.boolean(),
|
||||||
|
type: Yup.string(),
|
||||||
|
uid: Yup.string(),
|
||||||
|
value: Yup.string().trim().nullable()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (!formik.dirty) {
|
||||||
|
toast.error('Nothing to save');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(saveGlobalEnvironment({ environmentUid: environment.uid, variables: cloneDeep(values) }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Changes saved successfully');
|
||||||
|
formik.resetForm({ values });
|
||||||
|
setIsModified(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('An error occurred while saving the changes')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Effect to track modifications.
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsModified(formik.dirty);
|
||||||
|
}, [formik.dirty]);
|
||||||
|
|
||||||
|
const ErrorMessage = ({ name }) => {
|
||||||
|
const meta = formik.getFieldMeta(name);
|
||||||
|
if (!meta.error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label htmlFor={name} className="text-red-500">
|
||||||
|
{meta.error}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addVariable = () => {
|
||||||
|
const newVariable = {
|
||||||
|
uid: uuid(),
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
type: 'text',
|
||||||
|
secret: false,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
formik.setFieldValue(formik.values.length, newVariable, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveVar = (id) => {
|
||||||
|
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formik.dirty) {
|
||||||
|
// Smooth scrolling to the changed parameter is temporarily disabled
|
||||||
|
// due to UX issues when editing the first row in a long list of environment variables.
|
||||||
|
// addButtonRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, [formik.values, formik.dirty]);
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
formik.resetForm({ originalEnvironmentVariables });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full mt-6 mb-6">
|
||||||
|
<div className="h-[50vh] overflow-y-auto w-full">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td className="text-center">Enabled</td>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>Value</td>
|
||||||
|
<td className="text-center">Secret</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{formik.values.map((variable, index) => (
|
||||||
|
<tr key={variable.uid}>
|
||||||
|
<td className="text-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mousetrap"
|
||||||
|
name={`${index}.enabled`}
|
||||||
|
checked={variable.enabled}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
className="mousetrap"
|
||||||
|
id={`${index}.name`}
|
||||||
|
name={`${index}.name`}
|
||||||
|
value={variable.name}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
<ErrorMessage name={`${index}.name`} />
|
||||||
|
</td>
|
||||||
|
<td className="flex flex-row flex-nowrap">
|
||||||
|
<div className="overflow-hidden grow w-full relative">
|
||||||
|
<SingleLineEditor
|
||||||
|
theme={storedTheme}
|
||||||
|
name={`${index}.value`}
|
||||||
|
value={variable.value}
|
||||||
|
isSecret={variable.secret}
|
||||||
|
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mousetrap"
|
||||||
|
name={`${index}.secret`}
|
||||||
|
checked={variable.secret}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button onClick={() => handleRemoveVar(variable.uid)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
ref={addButtonRef}
|
||||||
|
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
|
||||||
|
onClick={addVariable}
|
||||||
|
>
|
||||||
|
+ Add Variable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default EnvironmentVariables;
|
@ -0,0 +1,46 @@
|
|||||||
|
import { IconCopy, IconDatabase, IconEdit, IconTrash } from '@tabler/icons';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import CopyEnvironment from '../../CopyEnvironment';
|
||||||
|
import DeleteEnvironment from '../../DeleteEnvironment';
|
||||||
|
import RenameEnvironment from '../../RenameEnvironment';
|
||||||
|
import EnvironmentVariables from './EnvironmentVariables';
|
||||||
|
|
||||||
|
const EnvironmentDetails = ({ environment, setIsModified }) => {
|
||||||
|
const [openEditModal, setOpenEditModal] = useState(false);
|
||||||
|
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||||
|
const [openCopyModal, setOpenCopyModal] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
|
||||||
|
{openEditModal && (
|
||||||
|
<RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} />
|
||||||
|
)}
|
||||||
|
{openDeleteModal && (
|
||||||
|
<DeleteEnvironment
|
||||||
|
onClose={() => setOpenDeleteModal(false)}
|
||||||
|
environment={environment}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{openCopyModal && (
|
||||||
|
<CopyEnvironment onClose={() => setOpenCopyModal(false)} environment={environment} />
|
||||||
|
)}
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex flex-grow items-center">
|
||||||
|
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||||
|
<span className="ml-1 font-semibold break-all">{environment.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-x-4 pl-4">
|
||||||
|
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)} />
|
||||||
|
<IconCopy className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenCopyModal(true)} />
|
||||||
|
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<EnvironmentVariables environment={environment} setIsModified={setIsModified} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentDetails;
|
@ -0,0 +1,58 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
margin-inline: -1rem;
|
||||||
|
margin-block: -1.5rem;
|
||||||
|
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.bg};
|
||||||
|
|
||||||
|
.environments-sidebar {
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
||||||
|
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
||||||
|
min-height: 400px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-item {
|
||||||
|
min-width: 150px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-left: solid 2px transparent;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
|
||||||
|
border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
|
||||||
|
&:hover {
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create-environment,
|
||||||
|
.btn-import-environment {
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: none;
|
||||||
|
color: ${(props) => props.theme.textLink};
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-import-environment {
|
||||||
|
color: ${(props) => props.theme.colors.text.muted};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,136 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import usePrevious from 'hooks/usePrevious';
|
||||||
|
import EnvironmentDetails from './EnvironmentDetails';
|
||||||
|
import CreateEnvironment from '../CreateEnvironment';
|
||||||
|
import { IconDownload, IconShieldLock } from '@tabler/icons';
|
||||||
|
import ManageSecrets from '../ManageSecrets';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
||||||
|
|
||||||
|
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
|
||||||
|
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||||
|
const [openImportModal, setOpenImportModal] = useState(false);
|
||||||
|
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
|
||||||
|
|
||||||
|
const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
|
||||||
|
const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
|
||||||
|
|
||||||
|
const envUids = environments ? environments.map((env) => env.uid) : [];
|
||||||
|
const prevEnvUids = usePrevious(envUids);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEnvironment) {
|
||||||
|
setOriginalEnvironmentVariables(selectedEnvironment.variables);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const environment = environments?.find(env => env?.uid === activeEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
setSelectedEnvironment(environment);
|
||||||
|
} else {
|
||||||
|
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||||
|
}
|
||||||
|
}, [environments, selectedEnvironment]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||||
|
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
|
||||||
|
if (newEnv) {
|
||||||
|
setSelectedEnvironment(newEnv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
|
||||||
|
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||||
|
}
|
||||||
|
}, [envUids, environments, prevEnvUids]);
|
||||||
|
|
||||||
|
const handleEnvironmentClick = (env) => {
|
||||||
|
if (!isModified) {
|
||||||
|
setSelectedEnvironment(env);
|
||||||
|
} else {
|
||||||
|
setSwitchEnvConfirmClose(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectedEnvironment) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateEnvClick = () => {
|
||||||
|
if (!isModified) {
|
||||||
|
setOpenCreateModal(true);
|
||||||
|
} else {
|
||||||
|
setSwitchEnvConfirmClose(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImportClick = () => {
|
||||||
|
if (!isModified) {
|
||||||
|
setOpenImportModal(true);
|
||||||
|
} else {
|
||||||
|
setSwitchEnvConfirmClose(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSecretsClick = () => {
|
||||||
|
setOpenManageSecretsModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSwitch = (saveChanges) => {
|
||||||
|
if (!saveChanges) {
|
||||||
|
setSwitchEnvConfirmClose(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
{openCreateModal && <CreateEnvironment onClose={() => setOpenCreateModal(false)} />}
|
||||||
|
{openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />}
|
||||||
|
|
||||||
|
<div className="flex">
|
||||||
|
<div>
|
||||||
|
{switchEnvConfirmClose && (
|
||||||
|
<div className="flex items-center justify-between tab-container px-1">
|
||||||
|
<ConfirmSwitchEnv onCancel={() => handleConfirmSwitch(false)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="environments-sidebar flex flex-col">
|
||||||
|
{environments &&
|
||||||
|
environments.length &&
|
||||||
|
environments.map((env) => (
|
||||||
|
<div
|
||||||
|
key={env.uid}
|
||||||
|
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||||
|
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||||
|
>
|
||||||
|
<span className="break-all">{env.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
||||||
|
+ <span>Create</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-auto btn-import-environment">
|
||||||
|
<div className="flex items-center" onClick={() => handleImportClick()}>
|
||||||
|
<IconDownload size={12} strokeWidth={2} />
|
||||||
|
<span className="label ml-1 text-xs">Import</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center mt-2" onClick={() => handleSecretsClick()}>
|
||||||
|
<IconShieldLock size={12} strokeWidth={2} />
|
||||||
|
<span className="label ml-1 text-xs">Managing Secrets</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<EnvironmentDetails
|
||||||
|
environment={selectedEnvironment}
|
||||||
|
setIsModified={setIsModified}
|
||||||
|
originalEnvironmentVariables={originalEnvironmentVariables}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentList;
|
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Portal from 'components/Portal';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
|
||||||
|
const ManageSecrets = ({ onClose }) => {
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal size="sm" title="Manage Secrets" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||||
|
<div>
|
||||||
|
<p>In any collection, there are secrets that need to be managed.</p>
|
||||||
|
<p className="mt-2">These secrets can be anything such as API keys, passwords, or tokens.</p>
|
||||||
|
<p className="mt-4">Bruno offers two approaches to manage secrets in collections.</p>
|
||||||
|
<p className="mt-2">
|
||||||
|
Read more about it in our{' '}
|
||||||
|
<a
|
||||||
|
href="https://docs.usebruno.com/secrets-management/overview"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-link hover:underline"
|
||||||
|
>
|
||||||
|
docs
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageSecrets;
|
@ -0,0 +1,88 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Portal from 'components/Portal/index';
|
||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
|
||||||
|
const RenameEnvironment = ({ onClose, environment }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
name: environment.name
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.max(50, 'must be 50 characters or less')
|
||||||
|
.required('name is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (values.name === environment.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(renameGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Environment renamed successfully');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error('An error occurred while renaming the environment');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'Rename Environment'}
|
||||||
|
confirmText="Rename"
|
||||||
|
handleConfirm={onSubmit}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block font-semibold">
|
||||||
|
Environment Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="environment-name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
ref={inputRef}
|
||||||
|
className="block textbox mt-2 w-full"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name || ''}
|
||||||
|
/>
|
||||||
|
{formik.touched.name && formik.errors.name ? (
|
||||||
|
<div className="text-red-500">{formik.errors.name}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenameEnvironment;
|
@ -0,0 +1,13 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
button.btn-create-environment {
|
||||||
|
&:hover {
|
||||||
|
span {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,69 @@
|
|||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import CreateEnvironment from './CreateEnvironment';
|
||||||
|
import EnvironmentList from './EnvironmentList';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { IconFileAlert } from '@tabler/icons';
|
||||||
|
|
||||||
|
export const SharedButton = ({ children, className, onClick }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className={`rounded bg-transparent px-2.5 py-2 w-fit text-xs font-semibold text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||||
|
${className}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DefaultTab = ({ setTab }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center items-center flex flex-col">
|
||||||
|
<IconFileAlert size={64} strokeWidth={1} />
|
||||||
|
<span className="font-semibold mt-2">No Global Environments found</span>
|
||||||
|
<div className="flex items-center justify-center mt-6">
|
||||||
|
<SharedButton onClick={() => setTab('create')}>
|
||||||
|
<span>Create Global Environment</span>
|
||||||
|
</SharedButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, onClose }) => {
|
||||||
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
const environments = globalEnvironments;
|
||||||
|
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||||
|
const [tab, setTab] = useState('default');
|
||||||
|
if (!environments || !environments.length) {
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<Modal size="md" title="Environments" handleCancel={onClose} hideCancel={true} hideFooter={true}>
|
||||||
|
{tab === 'create' ? (
|
||||||
|
<CreateEnvironment onClose={() => setTab('default')} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<DefaultTab setTab={setTab} />
|
||||||
|
</Modal>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}>
|
||||||
|
<EnvironmentList
|
||||||
|
environments={globalEnvironments}
|
||||||
|
activeEnvironmentUid={activeGlobalEnvironmentUid}
|
||||||
|
selectedEnvironment={selectedEnvironment}
|
||||||
|
setSelectedEnvironment={setSelectedEnvironment}
|
||||||
|
isModified={isModified}
|
||||||
|
setIsModified={setIsModified}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentSettings;
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||||
|
import GlobalEnvironmentSelector from 'components/GlobalEnvironments/EnvironmentSelector';
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import ToolHint from 'components/ToolHint';
|
import ToolHint from 'components/ToolHint';
|
||||||
@ -48,7 +49,7 @@ const CollectionToolBar = ({ collection }) => {
|
|||||||
<IconFiles size={18} strokeWidth={1.5} />
|
<IconFiles size={18} strokeWidth={1.5} />
|
||||||
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 items-center justify-end">
|
<div className="flex flex-3 items-center justify-end">
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
<JsSandboxMode collection={collection} />
|
<JsSandboxMode collection={collection} />
|
||||||
</span>
|
</span>
|
||||||
@ -67,6 +68,7 @@ const CollectionToolBar = ({ collection }) => {
|
|||||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||||
</ToolHint>
|
</ToolHint>
|
||||||
</span>
|
</span>
|
||||||
|
<GlobalEnvironmentSelector />
|
||||||
<EnvironmentSelector collection={collection} />
|
<EnvironmentSelector collection={collection} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,6 +23,7 @@ import { collectionAddEnvFileEvent, openCollectionEvent } from 'providers/ReduxS
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { isElectron } from 'utils/common/platform';
|
import { isElectron } from 'utils/common/platform';
|
||||||
|
import { updateGlobalEnvironments } from 'providers/ReduxStore/slices/globalEnvironments';
|
||||||
|
|
||||||
const useIpcEvents = () => {
|
const useIpcEvents = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -149,6 +150,10 @@ const useIpcEvents = () => {
|
|||||||
dispatch(updateCookies(val));
|
dispatch(updateCookies(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeGlobalEnvironmentsUpdatesListener = ipcRenderer.on('main:load-global-environments', (val) => {
|
||||||
|
dispatch(updateGlobalEnvironments(val));
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
removeCollectionTreeUpdateListener();
|
removeCollectionTreeUpdateListener();
|
||||||
removeOpenCollectionListener();
|
removeOpenCollectionListener();
|
||||||
@ -165,6 +170,7 @@ const useIpcEvents = () => {
|
|||||||
removePreferencesUpdatesListener();
|
removePreferencesUpdatesListener();
|
||||||
removeCookieUpdateListener();
|
removeCookieUpdateListener();
|
||||||
removeSystemProxyEnvUpdatesListener();
|
removeSystemProxyEnvUpdatesListener();
|
||||||
|
removeGlobalEnvironmentsUpdatesListener();
|
||||||
};
|
};
|
||||||
}, [isElectron]);
|
}, [isElectron]);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import appReducer from './slices/app';
|
|||||||
import collectionsReducer from './slices/collections';
|
import collectionsReducer from './slices/collections';
|
||||||
import tabsReducer from './slices/tabs';
|
import tabsReducer from './slices/tabs';
|
||||||
import notificationsReducer from './slices/notifications';
|
import notificationsReducer from './slices/notifications';
|
||||||
|
import globalEnvironmentsReducer from './slices/globalEnvironments';
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
const { publicRuntimeConfig } = getConfig();
|
||||||
const isDevEnv = () => {
|
const isDevEnv = () => {
|
||||||
@ -22,7 +23,8 @@ export const store = configureStore({
|
|||||||
app: appReducer,
|
app: appReducer,
|
||||||
collections: collectionsReducer,
|
collections: collectionsReducer,
|
||||||
tabs: tabsReducer,
|
tabs: tabsReducer,
|
||||||
notifications: notificationsReducer
|
notifications: notificationsReducer,
|
||||||
|
globalEnvironments: globalEnvironmentsReducer
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,193 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { generateUidBasedOnHash } from 'utils/common/index';
|
||||||
|
import { environmentSchema } from '@usebruno/schema';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
globalEnvironments: [],
|
||||||
|
activeGlobalEnvironmentUid: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export const globalEnvironmentsSlice = createSlice({
|
||||||
|
name: 'app',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
updateGlobalEnvironments: (state, action) => {
|
||||||
|
state.globalEnvironments = action.payload?.globalEnvironments;
|
||||||
|
state.activeGlobalEnvironmentUid = action.payload?.activeGlobalEnvironmentUid;
|
||||||
|
},
|
||||||
|
_addGlobalEnvironment: (state, action) => {
|
||||||
|
const { name, uid } = action.payload;
|
||||||
|
if (name?.length) {
|
||||||
|
state.globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_saveGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: globalEnvironmentUid, variables } = action.payload;
|
||||||
|
if (globalEnvironmentUid) {
|
||||||
|
const environment = state.globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.variables = variables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_renameGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: globalEnvironmentUid, name } = action.payload;
|
||||||
|
if (globalEnvironmentUid) {
|
||||||
|
const environment = state.globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_copyGlobalEnvironment: (state, action) => {
|
||||||
|
const { name, uid, variables } = action.payload;
|
||||||
|
if (name?.length && uid) {
|
||||||
|
state.globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_selectGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: globalEnvironmentUid } = action.payload;
|
||||||
|
if (globalEnvironmentUid) {
|
||||||
|
const environment = state.globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
state.activeGlobalEnvironmentUid = globalEnvironmentUid;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.activeGlobalEnvironmentUid = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_deleteGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: uid } = action.payload;
|
||||||
|
if (uid) {
|
||||||
|
state.globalEnvironments = state.globalEnvironments.filter(env => env?.uid !== uid);
|
||||||
|
if( uid === state.activeGlobalEnvironmentUid ) {
|
||||||
|
state.activeGlobalEnvironmentUid = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
updateGlobalEnvironments,
|
||||||
|
_addGlobalEnvironment,
|
||||||
|
_saveGlobalEnvironment,
|
||||||
|
_renameGlobalEnvironment,
|
||||||
|
_copyGlobalEnvironment,
|
||||||
|
_selectGlobalEnvironment,
|
||||||
|
_deleteGlobalEnvironment
|
||||||
|
} = globalEnvironmentsSlice.actions;
|
||||||
|
|
||||||
|
export const addGlobalEnvironment = ({ name }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const uid = generateUidBasedOnHash(name);
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:create-global-environment', { name, uid })
|
||||||
|
.then(
|
||||||
|
dispatch(_addGlobalEnvironment({ name, uid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyGlobalEnvironment = ({ name, environmentUid: baseEnvUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state.globalEnvironments.globalEnvironments;
|
||||||
|
const baseEnv = globalEnvironments?.find(env => env?.uid == baseEnvUid)
|
||||||
|
const uid = generateUidBasedOnHash(name);
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:create-global-environment', { name, variables: baseEnv.variables })
|
||||||
|
.then(() => {
|
||||||
|
dispatch(_copyGlobalEnvironment({ name, uid, variables: baseEnv.variables }))
|
||||||
|
})
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renameGlobalEnvironment = ({ name: newName, environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state.globalEnvironments.globalEnvironments;
|
||||||
|
const environment = globalEnvironments?.find(env => env?.uid == environmentUid)
|
||||||
|
if (!environment) {
|
||||||
|
return reject(new Error('Environment not found'));
|
||||||
|
}
|
||||||
|
environmentSchema
|
||||||
|
.validate(environment)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:rename-global-environment', { name: newName, environmentUid }))
|
||||||
|
.then(
|
||||||
|
dispatch(_renameGlobalEnvironment({ name: newName, environmentUid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveGlobalEnvironment = ({ variables, environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state.globalEnvironments.globalEnvironments;
|
||||||
|
const environment = globalEnvironments?.find(env => env?.uid == environmentUid);
|
||||||
|
|
||||||
|
if (!environment) {
|
||||||
|
return reject(new Error('Environment not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
environmentSchema
|
||||||
|
.validate(environment)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-global-environment', {
|
||||||
|
environmentUid,
|
||||||
|
variables
|
||||||
|
// variables: variables?.map(v => {
|
||||||
|
// let { uid, ...rest } = v;
|
||||||
|
// return rest;
|
||||||
|
// })
|
||||||
|
}))
|
||||||
|
.then(
|
||||||
|
dispatch(_saveGlobalEnvironment({ environmentUid, variables }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectGlobalEnvironment = ({ environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:select-global-environment', { environmentUid })
|
||||||
|
.then(
|
||||||
|
dispatch(_selectGlobalEnvironment({ environmentUid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteGlobalEnvironment = ({ environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:delete-global-environment', { environmentUid })
|
||||||
|
.then(
|
||||||
|
dispatch(_deleteGlobalEnvironment({ environmentUid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default globalEnvironmentsSlice.reducer;
|
@ -824,6 +824,7 @@ export const getTotalRequestCountInCollection = (collection) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getAllVariables = (collection, item) => {
|
export const getAllVariables = (collection, item) => {
|
||||||
|
if(!collection) return {};
|
||||||
const envVariables = getEnvironmentVariables(collection);
|
const envVariables = getEnvironmentVariables(collection);
|
||||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||||
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
|
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
|
||||||
|
@ -158,3 +158,9 @@ export const humanizeDate = (dateString) => {
|
|||||||
day: 'numeric'
|
day: 'numeric'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateUidBasedOnHash = (str) => {
|
||||||
|
const hash = simpleHash(str);
|
||||||
|
|
||||||
|
return `${hash}`.padEnd(21, '0');
|
||||||
|
};
|
||||||
|
@ -23,6 +23,7 @@ const registerPreferencesIpc = require('./ipc/preferences');
|
|||||||
const Watcher = require('./app/watcher');
|
const Watcher = require('./app/watcher');
|
||||||
const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window');
|
const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window');
|
||||||
const registerNotificationsIpc = require('./ipc/notifications');
|
const registerNotificationsIpc = require('./ipc/notifications');
|
||||||
|
const registerCommonIpc = require('./ipc/common');
|
||||||
|
|
||||||
const lastOpenedCollections = new LastOpenedCollections();
|
const lastOpenedCollections = new LastOpenedCollections();
|
||||||
|
|
||||||
@ -143,6 +144,7 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
// register all ipc handlers
|
// register all ipc handlers
|
||||||
registerNetworkIpc(mainWindow);
|
registerNetworkIpc(mainWindow);
|
||||||
|
registerCommonIpc(mainWindow);
|
||||||
registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections);
|
registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections);
|
||||||
registerPreferencesIpc(mainWindow, watcher, lastOpenedCollections);
|
registerPreferencesIpc(mainWindow, watcher, lastOpenedCollections);
|
||||||
registerNotificationsIpc(mainWindow, watcher);
|
registerNotificationsIpc(mainWindow, watcher);
|
||||||
|
50
packages/bruno-electron/src/ipc/common.js
Normal file
50
packages/bruno-electron/src/ipc/common.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
const { ipcMain } = require('electron');
|
||||||
|
const { globalEnvironmentsStore } = require('../store/global-environments');
|
||||||
|
|
||||||
|
const registerCommonIpc = (mainWindow) => {
|
||||||
|
|
||||||
|
// GLOBAL ENVIRONMENTS
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:create-global-environment', async (event, { uid, name }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.addGlobalEnvironment({ uid, name });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:save-global-environment', async (event, { environmentUid, variables }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.saveGlobalEnvironment({ environmentUid, variables })
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:rename-global-environment', async (event, { environmentUid, name }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.renameGlobalEnvironment({ environmentUid, name });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:delete-global-environment', async (event, { uid }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.deleteGlobalEnvironment({ uid });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:select-global-environment', async (event, { environmentUid }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.selectGlobalEnvironment({ environmentUid });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = registerCommonIpc;
|
@ -2,6 +2,7 @@ const { ipcMain } = require('electron');
|
|||||||
const { getPreferences, savePreferences, preferencesUtil } = require('../store/preferences');
|
const { getPreferences, savePreferences, preferencesUtil } = require('../store/preferences');
|
||||||
const { isDirectory } = require('../utils/filesystem');
|
const { isDirectory } = require('../utils/filesystem');
|
||||||
const { openCollection } = require('../app/collections');
|
const { openCollection } = require('../app/collections');
|
||||||
|
const { globalEnvironmentsStore } = require('../store/global-environments');
|
||||||
``;
|
``;
|
||||||
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
ipcMain.handle('renderer:ready', async (event) => {
|
ipcMain.handle('renderer:ready', async (event) => {
|
||||||
@ -9,10 +10,16 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
|||||||
const preferences = getPreferences();
|
const preferences = getPreferences();
|
||||||
mainWindow.webContents.send('main:load-preferences', preferences);
|
mainWindow.webContents.send('main:load-preferences', preferences);
|
||||||
|
|
||||||
|
// load system proxy vars
|
||||||
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
|
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
|
||||||
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
|
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
|
||||||
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });
|
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });
|
||||||
|
|
||||||
|
// load global environments
|
||||||
|
const globalEnvironments = globalEnvironmentsStore.getGlobalEnvironments();
|
||||||
|
const activeGlobalEnvironmentUid = globalEnvironmentsStore.getActiveGlobalEnvironmentUid();
|
||||||
|
mainWindow.webContents.send('main:load-global-environments', { globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
|
||||||
// reload last opened collections
|
// reload last opened collections
|
||||||
const lastOpened = lastOpenedCollections.getAll();
|
const lastOpened = lastOpenedCollections.getAll();
|
||||||
|
|
||||||
|
92
packages/bruno-electron/src/store/global-environments.js
Normal file
92
packages/bruno-electron/src/store/global-environments.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const Store = require('electron-store');
|
||||||
|
|
||||||
|
class GlobalEnvironmentsStore {
|
||||||
|
constructor() {
|
||||||
|
this.store = new Store({
|
||||||
|
name: 'global-environments',
|
||||||
|
clearInvalidConfig: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGlobalEnvironments() {
|
||||||
|
return this.store.get('environments', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveGlobalEnvironmentUid() {
|
||||||
|
return this.store.get('activeGlobalEnvironmentUid', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobalEnvironments(environments) {
|
||||||
|
return this.store.set('environments', environments);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveGlobalEnvironmentUid(uid) {
|
||||||
|
return this.store.set('activeGlobalEnvironmentUid', uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
addGlobalEnvironment({ uid, name }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables: []
|
||||||
|
});
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveGlobalEnvironment({ environmentUid: globalEnvironmentUid, variables }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const environment = globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
globalEnvironments = globalEnvironments.filter(env => env?.uid !== globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.variables = variables;
|
||||||
|
}
|
||||||
|
globalEnvironments.push(environment);
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renameGlobalEnvironment({ environmentUid: globalEnvironmentUid, name }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const environment = globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
globalEnvironments = globalEnvironments.filter(env => env?.uid !== globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.name = name;
|
||||||
|
}
|
||||||
|
globalEnvironments.push(environment);
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyGlobalEnvironment({ uid, name, variables }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectGlobalEnvironment({ environmentUid: globalEnvironmentUid }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const environment = globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
this.setActiveGlobalEnvironmentUid(globalEnvironmentUid);
|
||||||
|
} else {
|
||||||
|
this.setActiveGlobalEnvironmentUid(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGlobalEnvironment({ uid }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
globalEnvironments = globalEnvironments.filter(env => env?.uid !== uid);
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalEnvironmentsStore = new GlobalEnvironmentsStore();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
globalEnvironmentsStore
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user