Compare commits
25 Commits
2023.12.3
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faab1d20fd | ||
|
|
bd406616ec | ||
|
|
6827e97ec5 | ||
|
|
10d2048975 | ||
|
|
291f18591e | ||
|
|
342532c9b1 | ||
|
|
4bd54b12cd | ||
|
|
ed6e9b6954 | ||
|
|
dfdd44b4ed | ||
|
|
fc34871dae | ||
|
|
45b532747e | ||
|
|
de4635df23 | ||
|
|
41bad1f3dc | ||
|
|
ecca3d2032 | ||
|
|
47226be6d0 | ||
|
|
6a0e73fdec | ||
|
|
672ee69b2c | ||
|
|
c0fae79678 | ||
|
|
5bcc38e36b | ||
|
|
00862eb192 | ||
|
|
16803acb26 | ||
|
|
3911c9cd1f | ||
|
|
0028f6e878 | ||
|
|
0ba33ec187 | ||
|
|
d7cdeb796a |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.12.3",
|
"version": "2023.12.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class MailerService {
|
|||||||
): string {
|
): string {
|
||||||
switch (mailDesc.template) {
|
switch (mailDesc.template) {
|
||||||
case 'team-invitation':
|
case 'team-invitation':
|
||||||
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
|
return `A user has invited you to join a team workspace in Hoppscotch`;
|
||||||
|
|
||||||
case 'user-invitation':
|
case 'user-invitation':
|
||||||
return 'Sign in to Hoppscotch';
|
return 'Sign in to Hoppscotch';
|
||||||
|
|||||||
@@ -27,6 +27,12 @@
|
|||||||
color: #3869D4;
|
color: #3869D4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.nohighlight {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
a img {
|
a img {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -458,7 +464,7 @@
|
|||||||
<td class="content-cell">
|
<td class="content-cell">
|
||||||
<div class="f-fallback">
|
<div class="f-fallback">
|
||||||
<h1>Hi there,</h1>
|
<h1>Hi there,</h1>
|
||||||
<p>{{invitee}} with {{invite_team_name}} has invited you to use Hoppscotch to collaborate with them. Click the button below to set up your account and get started:</p>
|
<p><a class="nohighlight" name="invitee" href="#">{{invitee}}</a> with <a class="nohighlight" name="invite_team_name" href="#">{{invite_team_name}}</a> has invited you to use Hoppscotch to collaborate with them. Click the button below to set up your account and get started:</p>
|
||||||
<!-- Action -->
|
<!-- Action -->
|
||||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -484,7 +490,7 @@
|
|||||||
Welcome aboard, <br />
|
Welcome aboard, <br />
|
||||||
Your friends at Hoppscotch
|
Your friends at Hoppscotch
|
||||||
</p>
|
</p>
|
||||||
<p><strong>P.S.</strong> If you don't associate with {{invitee}} or {{invite_team_name}}, just ignore this email.</p>
|
<p><strong>P.S.</strong> If you don't associate with <a class="nohighlight" name="invitee" href="#">{{invitee}}</a> or <a class="nohighlight" name="invite_team_name" href="#">{{invite_team_name}}</a>, just ignore this email.</p>
|
||||||
<!-- Sub copy -->
|
<!-- Sub copy -->
|
||||||
<table class="body-sub">
|
<table class="body-sub">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<style type="text/css" rel="stylesheet" media="all">
|
<style type="text/css" rel="stylesheet" media="all">
|
||||||
/* Base ------------------------------ */
|
/* Base ------------------------------ */
|
||||||
|
|
||||||
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
||||||
body {
|
body {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -22,19 +22,25 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
-webkit-text-size-adjust: none;
|
-webkit-text-size-adjust: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #3869D4;
|
color: #3869D4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.nohighlight {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
a img {
|
a img {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preheader {
|
.preheader {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -47,13 +53,13 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
/* Type ------------------------------ */
|
/* Type ------------------------------ */
|
||||||
|
|
||||||
body,
|
body,
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
@@ -61,7 +67,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
@@ -69,7 +75,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
@@ -77,12 +83,12 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
ul,
|
ul,
|
||||||
ol,
|
ol,
|
||||||
@@ -91,25 +97,25 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.625;
|
line-height: 1.625;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.sub {
|
p.sub {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
/* Utilities ------------------------------ */
|
/* Utilities ------------------------------ */
|
||||||
|
|
||||||
.align-right {
|
.align-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-left {
|
.align-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
/* Buttons ------------------------------ */
|
/* Buttons ------------------------------ */
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background-color: #3869D4;
|
background-color: #3869D4;
|
||||||
border-top: 10px solid #3869D4;
|
border-top: 10px solid #3869D4;
|
||||||
@@ -124,7 +130,7 @@
|
|||||||
-webkit-text-size-adjust: none;
|
-webkit-text-size-adjust: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--green {
|
.button--green {
|
||||||
background-color: #22BC66;
|
background-color: #22BC66;
|
||||||
border-top: 10px solid #22BC66;
|
border-top: 10px solid #22BC66;
|
||||||
@@ -132,7 +138,7 @@
|
|||||||
border-bottom: 10px solid #22BC66;
|
border-bottom: 10px solid #22BC66;
|
||||||
border-left: 18px solid #22BC66;
|
border-left: 18px solid #22BC66;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--red {
|
.button--red {
|
||||||
background-color: #FF6136;
|
background-color: #FF6136;
|
||||||
border-top: 10px solid #FF6136;
|
border-top: 10px solid #FF6136;
|
||||||
@@ -140,7 +146,7 @@
|
|||||||
border-bottom: 10px solid #FF6136;
|
border-bottom: 10px solid #FF6136;
|
||||||
border-left: 18px solid #FF6136;
|
border-left: 18px solid #FF6136;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
@media only screen and (max-width: 500px) {
|
||||||
.button {
|
.button {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -148,21 +154,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Attribute list ------------------------------ */
|
/* Attribute list ------------------------------ */
|
||||||
|
|
||||||
.attributes {
|
.attributes {
|
||||||
margin: 0 0 21px;
|
margin: 0 0 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes_content {
|
.attributes_content {
|
||||||
background-color: #F4F4F7;
|
background-color: #F4F4F7;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes_item {
|
.attributes_item {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
/* Related Items ------------------------------ */
|
/* Related Items ------------------------------ */
|
||||||
|
|
||||||
.related {
|
.related {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -171,31 +177,31 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_item {
|
.related_item {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
color: #CBCCCF;
|
color: #CBCCCF;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_item-title {
|
.related_item-title {
|
||||||
display: block;
|
display: block;
|
||||||
margin: .5em 0 0;
|
margin: .5em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_item-thumb {
|
.related_item-thumb {
|
||||||
display: block;
|
display: block;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related_heading {
|
.related_heading {
|
||||||
border-top: 1px solid #CBCCCF;
|
border-top: 1px solid #CBCCCF;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 25px 0 10px;
|
padding: 25px 0 10px;
|
||||||
}
|
}
|
||||||
/* Discount Code ------------------------------ */
|
/* Discount Code ------------------------------ */
|
||||||
|
|
||||||
.discount {
|
.discount {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -206,33 +212,33 @@
|
|||||||
background-color: #F4F4F7;
|
background-color: #F4F4F7;
|
||||||
border: 2px dashed #CBCCCF;
|
border: 2px dashed #CBCCCF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discount_heading {
|
.discount_heading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discount_body {
|
.discount_body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
/* Social Icons ------------------------------ */
|
/* Social Icons ------------------------------ */
|
||||||
|
|
||||||
.social {
|
.social {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social td {
|
.social td {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social_icon {
|
.social_icon {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin: 0 8px 10px 8px;
|
margin: 0 8px 10px 8px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
/* Data table ------------------------------ */
|
/* Data table ------------------------------ */
|
||||||
|
|
||||||
.purchase {
|
.purchase {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -241,7 +247,7 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_content {
|
.purchase_content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -250,50 +256,50 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_item {
|
.purchase_item {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
color: #51545E;
|
color: #51545E;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_heading {
|
.purchase_heading {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
border-bottom: 1px solid #EAEAEC;
|
border-bottom: 1px solid #EAEAEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_heading p {
|
.purchase_heading p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #85878E;
|
color: #85878E;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_footer {
|
.purchase_footer {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
border-top: 1px solid #EAEAEC;
|
border-top: 1px solid #EAEAEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_total {
|
.purchase_total {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase_total--label {
|
.purchase_total--label {
|
||||||
padding: 0 15px 0 0;
|
padding: 0 15px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #F2F4F6;
|
background-color: #F2F4F6;
|
||||||
color: #51545E;
|
color: #51545E;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: #51545E;
|
color: #51545E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-wrapper {
|
.email-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -303,7 +309,7 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
background-color: #F2F4F6;
|
background-color: #F2F4F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-content {
|
.email-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -313,16 +319,16 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
/* Masthead ----------------------- */
|
/* Masthead ----------------------- */
|
||||||
|
|
||||||
.email-masthead {
|
.email-masthead {
|
||||||
padding: 25px 0;
|
padding: 25px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-masthead_logo {
|
.email-masthead_logo {
|
||||||
width: 94px;
|
width: 94px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-masthead_name {
|
.email-masthead_name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -331,7 +337,7 @@
|
|||||||
text-shadow: 0 1px 0 white;
|
text-shadow: 0 1px 0 white;
|
||||||
}
|
}
|
||||||
/* Body ------------------------------ */
|
/* Body ------------------------------ */
|
||||||
|
|
||||||
.email-body {
|
.email-body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -340,7 +346,7 @@
|
|||||||
-premailer-cellpadding: 0;
|
-premailer-cellpadding: 0;
|
||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-body_inner {
|
.email-body_inner {
|
||||||
width: 570px;
|
width: 570px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -350,7 +356,7 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-footer {
|
.email-footer {
|
||||||
width: 570px;
|
width: 570px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -360,11 +366,11 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-footer p {
|
.email-footer p {
|
||||||
color: #A8AAAF;
|
color: #A8AAAF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-action {
|
.body-action {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
@@ -374,25 +380,25 @@
|
|||||||
-premailer-cellspacing: 0;
|
-premailer-cellspacing: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-sub {
|
.body-sub {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
padding-top: 25px;
|
padding-top: 25px;
|
||||||
border-top: 1px solid #EAEAEC;
|
border-top: 1px solid #EAEAEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-cell {
|
.content-cell {
|
||||||
padding: 45px;
|
padding: 45px;
|
||||||
}
|
}
|
||||||
/*Media Queries ------------------------------ */
|
/*Media Queries ------------------------------ */
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.email-body_inner,
|
.email-body_inner,
|
||||||
.email-footer {
|
.email-footer {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body,
|
body,
|
||||||
.email-body,
|
.email-body,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/cli",
|
"name": "@hoppscotch/cli",
|
||||||
"version": "0.5.2",
|
"version": "0.6.0",
|
||||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||||
"homepage": "https://hoppscotch.io",
|
"homepage": "https://hoppscotch.io",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"tsup": "^7.2.0",
|
"tsup": "^7.2.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
"verzod": "^0.2.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,138 +3,247 @@ import { ExecException } from "child_process";
|
|||||||
import { HoppErrorCode } from "../../types/errors";
|
import { HoppErrorCode } from "../../types/errors";
|
||||||
import { runCLI, getErrorCode, getTestJsonFilePath } from "../utils";
|
import { runCLI, getErrorCode, getTestJsonFilePath } from "../utils";
|
||||||
|
|
||||||
describe("Test 'hopp test <file>' command:", () => {
|
describe("Test `hopp test <file>` command:", () => {
|
||||||
test("No collection file path provided.", async () => {
|
describe("Argument parsing", () => {
|
||||||
const args = "test";
|
test("Errors with the code `INVALID_ARGUMENT` for not supplying enough arguments", async () => {
|
||||||
const { stderr } = await runCLI(args);
|
const args = "test";
|
||||||
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection file not found.", async () => {
|
test("Errors with the code `INVALID_ARGUMENT` for an invalid command", async () => {
|
||||||
const args = "test notfound.json";
|
const args = "invalid-arg";
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
|
||||||
test("Collection file is invalid JSON.", async () => {
|
describe("Supplied collection export file validations", () => {
|
||||||
const args = `test ${getTestJsonFilePath(
|
test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => {
|
||||||
"malformed-collection.json"
|
const args = "test notfound.json";
|
||||||
)}`;
|
const { stderr } = await runCLI(args);
|
||||||
const { stderr } = await runCLI(args);
|
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
|
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Malformed collection file.", async () => {
|
test("Errors with the code UNKNOWN_ERROR if the supplied collection export file content isn't valid JSON", async () => {
|
||||||
const args = `test ${getTestJsonFilePath(
|
const args = `test ${getTestJsonFilePath("malformed-coll.json", "collection")}`;
|
||||||
"malformed-collection2.json"
|
const { stderr } = await runCLI(args);
|
||||||
)}`;
|
|
||||||
const { stderr } = await runCLI(args);
|
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION");
|
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Invalid arguement.", async () => {
|
test("Errors with the code `MALFORMED_COLLECTION` if the supplied collection export file content is malformed", async () => {
|
||||||
const args = "invalid-arg";
|
const args = `test ${getTestJsonFilePath("malformed-coll-2.json", "collection")}`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection file not JSON type.", async () => {
|
test("Errors with the code `INVALID_FILE_TYPE` if the supplied collection export file doesn't end with the `.json` extension", async () => {
|
||||||
const args = `test ${getTestJsonFilePath("notjson.txt")}`;
|
const args = `test ${getTestJsonFilePath("notjson-coll.txt", "collection")}`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Some errors occured (exit code 1).", async () => {
|
test("Fails if the collection file includes scripts with incorrect API usage and failed assertions", async () => {
|
||||||
const args = `test ${getTestJsonFilePath("fails.json")}`;
|
const args = `test ${getTestJsonFilePath("fails-coll.json", "collection")}`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
expect(error).not.toBeNull();
|
expect(error).not.toBeNull();
|
||||||
expect(error).toMatchObject(<ExecException>{
|
expect(error).toMatchObject(<ExecException>{
|
||||||
code: 1,
|
code: 1,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("No errors occured (exit code 0).", async () => {
|
test("Successfully processes a supplied collection export file of the expected format", async () => {
|
||||||
const args = `test ${getTestJsonFilePath("passes.json")}`;
|
const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Supports inheriting headers and authorization set at the root collection", async () => {
|
test("Successfully inherits headers and authorization set at the root collection", async () => {
|
||||||
const args = `test ${getTestJsonFilePath("collection-level-headers-auth.json")}`;
|
const args = `test ${getTestJsonFilePath(
|
||||||
|
"collection-level-headers-auth-coll.json", "collection"
|
||||||
|
)}`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
})
|
});
|
||||||
|
|
||||||
|
test("Persists environment variables set in the pre-request script for consumption in the test script", async () => {
|
||||||
|
const args = `test ${getTestJsonFilePath(
|
||||||
|
"pre-req-script-env-var-persistence-coll.json", "collection"
|
||||||
|
)}`;
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test 'hopp test <file> --env <file>' command:", () => {
|
describe("Test `hopp test <file> --env <file>` command:", () => {
|
||||||
const VALID_TEST_ARGS = `test ${getTestJsonFilePath(
|
describe("Supplied environment export file validations", () => {
|
||||||
"passes.json"
|
const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
|
||||||
)}`;
|
|
||||||
|
|
||||||
test("No env file path provided.", async () => {
|
test("Errors with the code `INVALID_ARGUMENT` if no file is supplied", async () => {
|
||||||
const args = `${VALID_TEST_ARGS} --env`;
|
const args = `${VALID_TEST_ARGS} --env`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => {
|
||||||
|
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath(
|
||||||
|
"notjson-coll.txt", "collection"
|
||||||
|
)}`;
|
||||||
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
|
const out = getErrorCode(stderr);
|
||||||
|
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Errors with the code `FILE_NOT_FOUND` if the supplied environment export file doesn't exist", async () => {
|
||||||
|
const args = `${VALID_TEST_ARGS} --env notfound.json`;
|
||||||
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
|
const out = getErrorCode(stderr);
|
||||||
|
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => {
|
||||||
|
const ENV_PATH = getTestJsonFilePath("malformed-envs.json", "environment");
|
||||||
|
const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
|
||||||
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
|
const out = getErrorCode(stderr);
|
||||||
|
expect(out).toBe<HoppErrorCode>("MALFORMED_ENV_FILE");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Errors with the code `BULK_ENV_FILE` on supplying an environment export file based on the bulk environment export format", async () => {
|
||||||
|
const ENV_PATH = getTestJsonFilePath("bulk-envs.json", "environment");
|
||||||
|
const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
|
||||||
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
|
const out = getErrorCode(stderr);
|
||||||
|
expect(out).toBe<HoppErrorCode>("BULK_ENV_FILE");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("ENV file not JSON type.", async () => {
|
test("Successfully resolves values from the supplied environment export file", async () => {
|
||||||
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath("notjson.txt")}`;
|
const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
|
||||||
const { stderr } = await runCLI(args);
|
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("ENV file not found.", async () => {
|
|
||||||
const args = `${VALID_TEST_ARGS} --env notfound.json`;
|
|
||||||
const { stderr } = await runCLI(args);
|
|
||||||
|
|
||||||
const out = getErrorCode(stderr);
|
|
||||||
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("No errors occured (exit code 0).", async () => {
|
|
||||||
const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
|
|
||||||
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
|
|
||||||
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
|
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
|
||||||
|
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Correctly resolves environment variables referenced in the request body", async () => {
|
test("Successfully resolves environment variables referenced in the request body", async () => {
|
||||||
const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json");
|
const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json", "collection");
|
||||||
const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json");
|
const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json", "environment");
|
||||||
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Works with shorth `-e` flag", async () => {
|
||||||
|
const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
|
||||||
|
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
|
||||||
|
const args = `test ${TESTS_PATH} -e ${ENV_PATH}`;
|
||||||
|
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Secret environment variables", () => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
|
||||||
|
// Reads secret environment values from system environment
|
||||||
|
test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => {
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
secretBearerToken: "test-token",
|
||||||
|
secretBasicAuthUsername: "test-user",
|
||||||
|
secretBasicAuthPassword: "test-pass",
|
||||||
|
secretQueryParamValue: "secret-query-param-value",
|
||||||
|
secretBodyValue: "secret-body-value",
|
||||||
|
secretHeaderValue: "secret-header-value",
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
|
||||||
|
const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment");
|
||||||
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
|
const { error, stdout } = await runCLI(args, { env });
|
||||||
|
|
||||||
|
expect(stdout).toContain(
|
||||||
|
"https://httpbin.org/basic-auth/*********/*********"
|
||||||
|
);
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prefers values specified in the environment export file over values set in the system environment
|
||||||
|
test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => {
|
||||||
|
const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
|
||||||
|
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
|
||||||
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
|
const { error, stdout } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(stdout).toContain(
|
||||||
|
"https://httpbin.org/basic-auth/*********/*********"
|
||||||
|
);
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Values set from the scripting context takes the highest precedence
|
||||||
|
test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => {
|
||||||
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
|
"secret-envs-persistence-coll.json", "collection"
|
||||||
|
);
|
||||||
|
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
|
||||||
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
|
const { error, stdout } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(stdout).toContain(
|
||||||
|
"https://httpbin.org/basic-auth/*********/*********"
|
||||||
|
);
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => {
|
||||||
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
|
"secret-envs-persistence-scripting-coll.json", "collection"
|
||||||
|
);
|
||||||
|
const ENVS_PATH = getTestJsonFilePath(
|
||||||
|
"secret-envs-persistence-scripting-envs.json", "environment"
|
||||||
|
);
|
||||||
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
|
describe("Test `hopp test <file> --delay <delay_in_ms>` command:", () => {
|
||||||
const VALID_TEST_ARGS = `test ${getTestJsonFilePath(
|
const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
|
||||||
"passes.json"
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
test("No value passed to delay flag.", async () => {
|
test("Errors with the code `INVALID_ARGUMENT` on not supplying a delay value", async () => {
|
||||||
const args = `${VALID_TEST_ARGS} --delay`;
|
const args = `${VALID_TEST_ARGS} --delay`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
@@ -142,7 +251,7 @@ describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
|
|||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Invalid value passed to delay flag.", async () => {
|
test("Errors with the code `INVALID_ARGUMENT` on supplying an invalid delay value", async () => {
|
||||||
const args = `${VALID_TEST_ARGS} --delay 'NaN'`;
|
const args = `${VALID_TEST_ARGS} --delay 'NaN'`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
@@ -150,10 +259,17 @@ describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
|
|||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Valid value passed to delay flag.", async () => {
|
test("Successfully performs delayed request execution for a valid delay value", async () => {
|
||||||
const args = `${VALID_TEST_ARGS} --delay 1`;
|
const args = `${VALID_TEST_ARGS} --delay 1`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Works with the short `-d` flag", async () => {
|
||||||
|
const args = `${VALID_TEST_ARGS} -d 1`;
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"v": 2,
|
||||||
|
"name": "pre-req-script-env-var-persistence-coll",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"body": { "body": null, "contentType": null },
|
||||||
|
"name": "sample-req",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
|
"testScript": "pw.expect(pw.env.get(\"variable\")).toBe(\"value\")",
|
||||||
|
"preRequestScript": "pw.env.set(\"variable\", \"value\");"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": { "authType": "inherit", "authActive": true },
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"v": 2,
|
||||||
|
"name": "secret-envs-coll",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"body": { "body": null, "contentType": null },
|
||||||
|
"name": "test-secret-headers",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Secret-Header-Key",
|
||||||
|
"value": "<<secretHeaderValue>>",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpoint": "<<baseURL>>/headers",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||||
|
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"body": {
|
||||||
|
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
|
||||||
|
"contentType": "application/json"
|
||||||
|
},
|
||||||
|
"name": "test-secret-body",
|
||||||
|
"method": "POST",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/post",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||||
|
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"body": { "body": null, "contentType": null },
|
||||||
|
"name": "test-secret-query-params",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"key": "secretQueryParamKey",
|
||||||
|
"value": "<<secretQueryParamValue>>",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/get",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
|
||||||
|
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"authType": "basic",
|
||||||
|
"password": "<<secretBasicAuthPassword>>",
|
||||||
|
"username": "<<secretBasicAuthUsername>>",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": { "body": null, "contentType": null },
|
||||||
|
"name": "test-secret-basic-auth",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||||
|
"preRequestScript": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"token": "<<secretBearerToken>>",
|
||||||
|
"authType": "bearer",
|
||||||
|
"password": "testpassword",
|
||||||
|
"username": "testuser",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": { "body": null, "contentType": null },
|
||||||
|
"name": "test-secret-bearer-auth",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/bearer",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||||
|
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"body": { "body": null, "contentType": null },
|
||||||
|
"name": "test-secret-fallback",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>",
|
||||||
|
"testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})",
|
||||||
|
"preRequestScript": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": { "authType": "inherit", "authActive": false },
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
{
|
||||||
|
"v": 2,
|
||||||
|
"name": "secret-envs-setters-coll",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"body": null,
|
||||||
|
"contentType": null
|
||||||
|
},
|
||||||
|
"name": "test-secret-headers",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Secret-Header-Key",
|
||||||
|
"value": "<<secretHeaderValue>>",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpoint": "<<baseURL>>/headers",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||||
|
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"body": null,
|
||||||
|
"contentType": null
|
||||||
|
},
|
||||||
|
"name": "test-secret-headers-overrides",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Secret-Header-Key",
|
||||||
|
"value": "<<secretHeaderValue>>",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpoint": "<<baseURL>>/headers",
|
||||||
|
"testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})",
|
||||||
|
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
|
||||||
|
"contentType": "application/json"
|
||||||
|
},
|
||||||
|
"name": "test-secret-body",
|
||||||
|
"method": "POST",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/post",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||||
|
"preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"body": null,
|
||||||
|
"contentType": null
|
||||||
|
},
|
||||||
|
"name": "test-secret-query-params",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"key": "secretQueryParamKey",
|
||||||
|
"value": "<<secretQueryParamValue>>",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/get",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
|
||||||
|
"preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"authType": "basic",
|
||||||
|
"password": "<<secretBasicAuthPassword>>",
|
||||||
|
"username": "<<secretBasicAuthUsername>>",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"body": null,
|
||||||
|
"contentType": null
|
||||||
|
},
|
||||||
|
"name": "test-secret-basic-auth",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||||
|
"preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"auth": {
|
||||||
|
"token": "<<secretBearerToken>>",
|
||||||
|
"authType": "bearer",
|
||||||
|
"password": "testpassword",
|
||||||
|
"username": "testuser",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"body": null,
|
||||||
|
"contentType": null
|
||||||
|
},
|
||||||
|
"name": "test-secret-bearer-auth",
|
||||||
|
"method": "GET",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"endpoint": "<<baseURL>>/bearer",
|
||||||
|
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||||
|
"preRequestScript": "let secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"authType": "inherit",
|
||||||
|
"authActive": false
|
||||||
|
},
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"v": 2,
|
||||||
|
"name": "secret-envs-persistence-scripting-req",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"endpoint": "https://httpbin.org/post",
|
||||||
|
"name": "req",
|
||||||
|
"params": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"key": "Custom-Header",
|
||||||
|
"value": "<<customHeaderValueFromSecretVar>>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"method": "POST",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")",
|
||||||
|
"testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.json.key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})",
|
||||||
|
"body": {
|
||||||
|
"contentType": "application/json",
|
||||||
|
"body": "{\n \"key\": \"<<customBodyValue>>\"\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": { "authType": "inherit", "authActive": false },
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"v": 0,
|
||||||
|
"name": "Env-I",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "firstName",
|
||||||
|
"value": "John"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "lastName",
|
||||||
|
"value": "Doe"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"id": "2",
|
||||||
|
"name": "Env-II",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "baseUrl",
|
||||||
|
"value": "https://echo.hoppscotch.io",
|
||||||
|
"secret": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretVar",
|
||||||
|
"secret": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"v": "1",
|
||||||
|
"name": "secret-envs",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"key": "secretVar",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "regularVar",
|
||||||
|
"secret": false,
|
||||||
|
"value": "regular-variable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"v": 0,
|
||||||
"name": "Response body sample",
|
"name": "Response body sample",
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
@@ -34,4 +35,4 @@
|
|||||||
"value": "<<salutation>> <<fullName>>"
|
"value": "<<salutation>> <<fullName>>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"id": "2",
|
||||||
|
"name": "secret-envs-persistence-scripting-envs",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "preReqVarOne",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "preReqVarTwo",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "postReqVarOne",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "preReqVarTwo",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "customHeaderValueFromSecretVar",
|
||||||
|
"secret": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"v": 1,
|
||||||
|
"name": "secret-envs",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "secretBearerToken",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretBasicAuthUsername",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretBasicAuthPassword",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretQueryParamValue",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretBodyValue",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretHeaderValue",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "nonExistentValueInSystemEnv",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "baseURL",
|
||||||
|
"value": "https://httpbin.org",
|
||||||
|
"secret": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"id": "2",
|
||||||
|
"name": "secret-values-envs",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "secretBearerToken",
|
||||||
|
"value": "test-token",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretBasicAuthUsername",
|
||||||
|
"value": "test-user",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretBasicAuthPassword",
|
||||||
|
"value": "test-pass",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretQueryParamValue",
|
||||||
|
"value": "secret-query-param-value",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretBodyValue",
|
||||||
|
"value": "secret-body-value",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretHeaderValue",
|
||||||
|
"value": "secret-header-value",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "nonExistentValueInSystemEnv",
|
||||||
|
"secret": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "baseURL",
|
||||||
|
"value": "https://httpbin.org",
|
||||||
|
"secret": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -3,13 +3,13 @@ import { resolve } from "path";
|
|||||||
|
|
||||||
import { ExecResponse } from "./types";
|
import { ExecResponse } from "./types";
|
||||||
|
|
||||||
export const runCLI = (args: string): Promise<ExecResponse> =>
|
export const runCLI = (args: string, options = {}): Promise<ExecResponse> =>
|
||||||
{
|
{
|
||||||
const CLI_PATH = resolve(__dirname, "../../bin/hopp");
|
const CLI_PATH = resolve(__dirname, "../../bin/hopp");
|
||||||
const command = `node ${CLI_PATH} ${args}`
|
const command = `node ${CLI_PATH} ${args}`
|
||||||
|
|
||||||
return new Promise((resolve) =>
|
return new Promise((resolve) =>
|
||||||
exec(command, (error, stdout, stderr) => resolve({ error, stdout, stderr }))
|
exec(command, options, (error, stdout, stderr) => resolve({ error, stdout, stderr }))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,12 @@ export const getErrorCode = (out: string) => {
|
|||||||
return ansiTrimmedStr.split(" ")[0];
|
return ansiTrimmedStr.split(" ")[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTestJsonFilePath = (file: string) => {
|
export const getTestJsonFilePath = (file: string, kind: "collection" | "environment") => {
|
||||||
const filePath = resolve(__dirname, `../../src/__tests__/samples/${file}`);
|
const kindDir = {
|
||||||
|
collection: "collections",
|
||||||
|
environment: "environments",
|
||||||
|
}[kind];
|
||||||
|
|
||||||
|
const filePath = resolve(__dirname, `../../src/__tests__/samples/${kindDir}/${file}`);
|
||||||
return filePath;
|
return filePath;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface RequestStack {
|
|||||||
*/
|
*/
|
||||||
export interface RequestConfig extends AxiosRequestConfig {
|
export interface RequestConfig extends AxiosRequestConfig {
|
||||||
supported: boolean;
|
supported: boolean;
|
||||||
|
displayUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||||
@@ -30,6 +31,7 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
* This contains path, params and environment variables all applied to it
|
* This contains path, params and environment variables all applied to it
|
||||||
*/
|
*/
|
||||||
effectiveFinalURL: string;
|
effectiveFinalURL: string;
|
||||||
|
effectiveFinalDisplayURL?: string;
|
||||||
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
||||||
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
||||||
effectiveFinalBody: FormData | string | null;
|
effectiveFinalBody: FormData | string | null;
|
||||||
|
|||||||
@@ -1,34 +1,42 @@
|
|||||||
|
import { Environment } from "@hoppscotch/data";
|
||||||
|
import { entityReference } from "verzod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { error } from "../../types/errors";
|
import { error } from "../../types/errors";
|
||||||
import {
|
import {
|
||||||
HoppEnvs,
|
|
||||||
HoppEnvPair,
|
|
||||||
HoppEnvKeyPairObject,
|
HoppEnvKeyPairObject,
|
||||||
HoppEnvExportObject,
|
HoppEnvPair,
|
||||||
HoppBulkEnvExportObject,
|
HoppEnvs
|
||||||
} from "../../types/request";
|
} from "../../types/request";
|
||||||
import { readJsonFile } from "../../utils/mutators";
|
import { readJsonFile } from "../../utils/mutators";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses env json file for given path and validates the parsed env json object.
|
* Parses env json file for given path and validates the parsed env json object
|
||||||
* @param path Path of env.json file to be parsed.
|
* @param path Path of env.json file to be parsed
|
||||||
* @returns For successful parsing we get HoppEnvs object.
|
* @returns For successful parsing we get HoppEnvs object
|
||||||
*/
|
*/
|
||||||
export async function parseEnvsData(path: string) {
|
export async function parseEnvsData(path: string) {
|
||||||
const contents = await readJsonFile(path);
|
const contents = await readJsonFile(path);
|
||||||
const envPairs: Array<HoppEnvPair> = [];
|
const envPairs: Array<Environment["variables"][number] | HoppEnvPair> = [];
|
||||||
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
|
||||||
const HoppEnvExportObjectResult = HoppEnvExportObject.safeParse(contents);
|
|
||||||
const HoppBulkEnvExportObjectResult =
|
|
||||||
HoppBulkEnvExportObject.safeParse(contents);
|
|
||||||
|
|
||||||
// CLI doesnt support bulk environments export.
|
// The legacy key-value pair format that is still supported
|
||||||
// Hence we check for this case and throw an error if it matches the format.
|
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
||||||
|
|
||||||
|
// Shape of the single environment export object that is exported from the app
|
||||||
|
const HoppEnvExportObjectResult = Environment.safeParse(contents);
|
||||||
|
|
||||||
|
// Shape of the bulk environment export object that is exported from the app
|
||||||
|
const HoppBulkEnvExportObjectResult = z.array(entityReference(Environment)).safeParse(contents)
|
||||||
|
|
||||||
|
// CLI doesnt support bulk environments export
|
||||||
|
// Hence we check for this case and throw an error if it matches the format
|
||||||
if (HoppBulkEnvExportObjectResult.success) {
|
if (HoppBulkEnvExportObjectResult.success) {
|
||||||
throw error({ code: "BULK_ENV_FILE", path, data: error });
|
throw error({ code: "BULK_ENV_FILE", path, data: error });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the environment file is of the correct format.
|
// Checks if the environment file is of the correct format
|
||||||
// If it doesnt match either of them, we throw an error.
|
// If it doesnt match either of them, we throw an error
|
||||||
if (!(HoppEnvKeyPairResult.success || HoppEnvExportObjectResult.success)) {
|
if (!HoppEnvKeyPairResult.success && HoppEnvExportObjectResult.type === "err") {
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +44,8 @@ export async function parseEnvsData(path: string) {
|
|||||||
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
||||||
envPairs.push({ key, value });
|
envPairs.push({ key, value });
|
||||||
}
|
}
|
||||||
} else if (HoppEnvExportObjectResult.success) {
|
} else if (HoppEnvExportObjectResult.type === "ok") {
|
||||||
envPairs.push(...HoppEnvExportObjectResult.data.variables);
|
envPairs.push(...HoppEnvExportObjectResult.value.variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <HoppEnvs>{ global: [], selected: envPairs };
|
return <HoppEnvs>{ global: [], selected: envPairs };
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
import { Environment, HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TestReport } from "../interfaces/response";
|
import { TestReport } from "../interfaces/response";
|
||||||
import { HoppCLIError } from "./errors";
|
import { HoppCLIError } from "./errors";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export type FormDataEntry = {
|
export type FormDataEntry = {
|
||||||
key: string;
|
key: string;
|
||||||
value: string | Blob;
|
value: string | Blob;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HoppEnvPair = { key: string; value: string };
|
export type HoppEnvPair = Environment["variables"][number];
|
||||||
|
|
||||||
export const HoppEnvKeyPairObject = z.record(z.string(), z.string());
|
export const HoppEnvKeyPairObject = z.record(z.string(), z.string());
|
||||||
|
|
||||||
// Shape of the single environment export object that is exported from the app.
|
|
||||||
export const HoppEnvExportObject = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
variables: z.array(
|
|
||||||
z.object({
|
|
||||||
key: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Shape of the bulk environment export object that is exported from the app.
|
|
||||||
export const HoppBulkEnvExportObject = z.array(HoppEnvExportObject);
|
|
||||||
|
|
||||||
export type HoppEnvs = {
|
export type HoppEnvs = {
|
||||||
global: HoppEnvPair[];
|
global: HoppEnvPair[];
|
||||||
selected: HoppEnvPair[];
|
selected: HoppEnvPair[];
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export const printRequestRunner = {
|
|||||||
*/
|
*/
|
||||||
start: (requestConfig: RequestConfig) => {
|
start: (requestConfig: RequestConfig) => {
|
||||||
const METHOD = BG_INFO(` ${requestConfig.method} `);
|
const METHOD = BG_INFO(` ${requestConfig.method} `);
|
||||||
const ENDPOINT = requestConfig.url;
|
const ENDPOINT = requestConfig.displayUrl || requestConfig.url;
|
||||||
|
|
||||||
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ import { toFormData } from "./mutators";
|
|||||||
export const preRequestScriptRunner = (
|
export const preRequestScriptRunner = (
|
||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
envs: HoppEnvs
|
envs: HoppEnvs
|
||||||
): TE.TaskEither<HoppCLIError, EffectiveHoppRESTRequest> =>
|
): TE.TaskEither<
|
||||||
|
HoppCLIError,
|
||||||
|
{ effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs }
|
||||||
|
> =>
|
||||||
pipe(
|
pipe(
|
||||||
TE.of(request),
|
TE.of(request),
|
||||||
TE.chain(({ preRequestScript }) =>
|
TE.chain(({ preRequestScript }) =>
|
||||||
@@ -68,7 +71,10 @@ export const preRequestScriptRunner = (
|
|||||||
export function getEffectiveRESTRequest(
|
export function getEffectiveRESTRequest(
|
||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
environment: Environment
|
environment: Environment
|
||||||
): E.Either<HoppCLIError, EffectiveHoppRESTRequest> {
|
): E.Either<
|
||||||
|
HoppCLIError,
|
||||||
|
{ effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs }
|
||||||
|
> {
|
||||||
const envVariables = environment.variables;
|
const envVariables = environment.variables;
|
||||||
|
|
||||||
// Parsing final headers with applied ENVs.
|
// Parsing final headers with applied ENVs.
|
||||||
@@ -162,12 +168,30 @@ export function getEffectiveRESTRequest(
|
|||||||
}
|
}
|
||||||
const effectiveFinalURL = _effectiveFinalURL.right;
|
const effectiveFinalURL = _effectiveFinalURL.right;
|
||||||
|
|
||||||
|
// Secret environment variables referenced in the request endpoint should be masked
|
||||||
|
let effectiveFinalDisplayURL;
|
||||||
|
if (envVariables.some(({ secret }) => secret)) {
|
||||||
|
const _effectiveFinalDisplayURL = parseTemplateStringE(
|
||||||
|
request.endpoint,
|
||||||
|
envVariables,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isRight(_effectiveFinalDisplayURL)) {
|
||||||
|
effectiveFinalDisplayURL = _effectiveFinalDisplayURL.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return E.right({
|
return E.right({
|
||||||
...request,
|
effectiveRequest: {
|
||||||
effectiveFinalURL,
|
...request,
|
||||||
effectiveFinalHeaders,
|
effectiveFinalURL,
|
||||||
effectiveFinalParams,
|
effectiveFinalDisplayURL,
|
||||||
effectiveFinalBody,
|
effectiveFinalHeaders,
|
||||||
|
effectiveFinalParams,
|
||||||
|
effectiveFinalBody,
|
||||||
|
},
|
||||||
|
updatedEnvs: { global: [], selected: envVariables },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
import { Environment, HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
import axios, { Method } from "axios";
|
import axios, { Method } from "axios";
|
||||||
import * as A from "fp-ts/Array";
|
import * as A from "fp-ts/Array";
|
||||||
import * as E from "fp-ts/Either";
|
import * as E from "fp-ts/Either";
|
||||||
@@ -29,6 +29,38 @@ import { getTestScriptParams, hasFailedTestCases, testRunner } from "./test";
|
|||||||
|
|
||||||
// !NOTE: The `config.supported` checks are temporary until OAuth2 and Multipart Forms are supported
|
// !NOTE: The `config.supported` checks are temporary until OAuth2 and Multipart Forms are supported
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes given variable, which includes checking for secret variables
|
||||||
|
* and getting value from system environment
|
||||||
|
* @param variable Variable to be processed
|
||||||
|
* @returns Updated variable with value from system environment
|
||||||
|
*/
|
||||||
|
const processVariables = (variable: Environment["variables"][number]) => {
|
||||||
|
if (variable.secret) {
|
||||||
|
return {
|
||||||
|
...variable,
|
||||||
|
value:
|
||||||
|
"value" in variable ? variable.value : process.env[variable.key] || "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes given envs, which includes processing each variable in global
|
||||||
|
* and selected envs
|
||||||
|
* @param envs Global + selected envs used by requests with in collection
|
||||||
|
* @returns Processed envs with each variable processed
|
||||||
|
*/
|
||||||
|
const processEnvs = (envs: HoppEnvs) => {
|
||||||
|
const processedEnvs = {
|
||||||
|
global: envs.global.map(processVariables),
|
||||||
|
selected: envs.selected.map(processVariables),
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedEnvs
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms given request data to request-config used by request-runner to
|
* Transforms given request data to request-config used by request-runner to
|
||||||
* perform HTTP request.
|
* perform HTTP request.
|
||||||
@@ -38,6 +70,7 @@ import { getTestScriptParams, hasFailedTestCases, testRunner } from "./test";
|
|||||||
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
|
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
|
||||||
const config: RequestConfig = {
|
const config: RequestConfig = {
|
||||||
supported: true,
|
supported: true,
|
||||||
|
displayUrl: req.effectiveFinalDisplayURL
|
||||||
};
|
};
|
||||||
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
|
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
|
||||||
const reqParams = finalParams(req);
|
const reqParams = finalParams(req);
|
||||||
@@ -221,9 +254,13 @@ export const processRequest =
|
|||||||
effectiveFinalParams: [],
|
effectiveFinalParams: [],
|
||||||
effectiveFinalURL: "",
|
effectiveFinalURL: "",
|
||||||
};
|
};
|
||||||
|
let updatedEnvs = <HoppEnvs>{};
|
||||||
|
|
||||||
|
// Fetch values for secret environment variables from system environment
|
||||||
|
const processedEnvs = processEnvs(envs)
|
||||||
|
|
||||||
// Executing pre-request-script
|
// Executing pre-request-script
|
||||||
const preRequestRes = await preRequestScriptRunner(request, envs)();
|
const preRequestRes = await preRequestScriptRunner(request, processedEnvs)();
|
||||||
if (E.isLeft(preRequestRes)) {
|
if (E.isLeft(preRequestRes)) {
|
||||||
printPreRequestRunner.fail();
|
printPreRequestRunner.fail();
|
||||||
|
|
||||||
@@ -231,8 +268,8 @@ export const processRequest =
|
|||||||
report.errors.push(preRequestRes.left);
|
report.errors.push(preRequestRes.left);
|
||||||
report.result = report.result && false;
|
report.result = report.result && false;
|
||||||
} else {
|
} else {
|
||||||
// Updating effective-request
|
// Updating effective-request and consuming updated envs after pre-request script execution
|
||||||
effectiveRequest = preRequestRes.right;
|
({ effectiveRequest, updatedEnvs } = preRequestRes.right);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating request-config for request-runner.
|
// Creating request-config for request-runner.
|
||||||
@@ -270,7 +307,7 @@ export const processRequest =
|
|||||||
const testScriptParams = getTestScriptParams(
|
const testScriptParams = getTestScriptParams(
|
||||||
_requestRunnerRes,
|
_requestRunnerRes,
|
||||||
request,
|
request,
|
||||||
envs
|
updatedEnvs
|
||||||
);
|
);
|
||||||
|
|
||||||
// Executing test-runner.
|
// Executing test-runner.
|
||||||
|
|||||||
@@ -429,6 +429,11 @@ pre.ace_editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.splitpanes__pane {
|
||||||
|
@apply will-change-auto;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
.smart-splitter .splitpanes__splitter {
|
.smart-splitter .splitpanes__splitter {
|
||||||
@apply relative;
|
@apply relative;
|
||||||
@apply before:absolute;
|
@apply before:absolute;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"go_back": "Go back",
|
"go_back": "Go back",
|
||||||
"go_forward": "Go forward",
|
"go_forward": "Go forward",
|
||||||
"group_by": "Group by",
|
"group_by": "Group by",
|
||||||
|
"hide_secret": "Hide secret",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"learn_more": "Learn more",
|
"learn_more": "Learn more",
|
||||||
"less": "Less",
|
"less": "Less",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"search": "Search",
|
"search": "Search",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
|
"show_secret": "Show secret",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"starting": "Starting",
|
"starting": "Starting",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
@@ -238,6 +240,7 @@
|
|||||||
"profile": "Login to view your profile",
|
"profile": "Login to view your profile",
|
||||||
"protocols": "Protocols are empty",
|
"protocols": "Protocols are empty",
|
||||||
"schema": "Connect to a GraphQL endpoint to view schema",
|
"schema": "Connect to a GraphQL endpoint to view schema",
|
||||||
|
"secret_environments": "Secrets are not synced to Hoppscotch",
|
||||||
"shared_requests": "Shared requests are empty",
|
"shared_requests": "Shared requests are empty",
|
||||||
"shared_requests_logout": "Login to view your shared requests or create a new one",
|
"shared_requests_logout": "Login to view your shared requests or create a new one",
|
||||||
"subscription": "Subscriptions are empty",
|
"subscription": "Subscriptions are empty",
|
||||||
@@ -269,6 +272,8 @@
|
|||||||
"quick_peek": "Environment Quick Peek",
|
"quick_peek": "Environment Quick Peek",
|
||||||
"replace_with_variable": "Replace with variable",
|
"replace_with_variable": "Replace with variable",
|
||||||
"scope": "Scope",
|
"scope": "Scope",
|
||||||
|
"secrets": "Secrets",
|
||||||
|
"secret_value": "Secret value",
|
||||||
"select": "Select environment",
|
"select": "Select environment",
|
||||||
"set": "Set environment",
|
"set": "Set environment",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "Set as environment",
|
||||||
@@ -277,6 +282,7 @@
|
|||||||
"updated": "Environment updated",
|
"updated": "Environment updated",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
|
"variables":"Variables",
|
||||||
"variable_list": "Variable List"
|
"variable_list": "Variable List"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -413,6 +419,8 @@
|
|||||||
"description": "Inspect possible errors",
|
"description": "Inspect possible errors",
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_environment": "Add to Environment",
|
"add_environment": "Add to Environment",
|
||||||
|
"add_environment_value": "Add value",
|
||||||
|
"empty_value": "Environment value is empty for the variable '{variable}' ",
|
||||||
"not_found": "Environment variable “{environment}” not found."
|
"not_found": "Environment variable “{environment}” not found."
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@@ -889,6 +897,7 @@
|
|||||||
"query": "Query",
|
"query": "Query",
|
||||||
"schema": "Schema",
|
"schema": "Schema",
|
||||||
"shared_requests": "Shared Requests",
|
"shared_requests": "Shared Requests",
|
||||||
|
"share_tab_request": "Share tab request",
|
||||||
"socketio": "Socket.IO",
|
"socketio": "Socket.IO",
|
||||||
"sse": "SSE",
|
"sse": "SSE",
|
||||||
"tests": "Tests",
|
"tests": "Tests",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.12.3",
|
"version": "2023.12.6",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="col-span-1 flex items-center justify-between space-x-2">
|
<div class="col-span-1 flex items-center justify-between space-x-2">
|
||||||
<button
|
<button
|
||||||
class="flex h-full flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
class="flex h-full flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
||||||
@click="invokeAction('modals.search.toggle')"
|
@click="invokeAction('modals.search.toggle', undefined, 'mouseclick')"
|
||||||
>
|
>
|
||||||
<span class="inline-flex flex-1 items-center">
|
<span class="inline-flex flex-1 items-center">
|
||||||
<icon-lucide-search class="svg-icons mr-2" />
|
<icon-lucide-search class="svg-icons mr-2" />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-if="show"
|
v-if="show"
|
||||||
styles="sm:max-w-lg"
|
styles="sm:max-w-lg"
|
||||||
full-width
|
full-width
|
||||||
@close="emit('hide-modal')"
|
@close="closeSpotlightModal"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col border-b border-divider transition">
|
<div class="flex flex-col border-b border-divider transition">
|
||||||
@@ -86,35 +86,36 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from "vue"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { isEqual } from "lodash-es"
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { HoppSpotlightSessionEventData } from "~/platform/analytics"
|
||||||
import {
|
import {
|
||||||
SpotlightService,
|
|
||||||
SpotlightSearchState,
|
SpotlightSearchState,
|
||||||
SpotlightSearcherResult,
|
SpotlightSearcherResult,
|
||||||
|
SpotlightService,
|
||||||
} from "~/services/spotlight"
|
} from "~/services/spotlight"
|
||||||
import { isEqual } from "lodash-es"
|
|
||||||
import { HistorySpotlightSearcherService } from "~/services/spotlight/searchers/history.searcher"
|
|
||||||
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
|
||||||
import { NavigationSpotlightSearcherService } from "~/services/spotlight/searchers/navigation.searcher"
|
|
||||||
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
|
||||||
import { CollectionsSpotlightSearcherService } from "~/services/spotlight/searchers/collections.searcher"
|
import { CollectionsSpotlightSearcherService } from "~/services/spotlight/searchers/collections.searcher"
|
||||||
import { MiscellaneousSpotlightSearcherService } from "~/services/spotlight/searchers/miscellaneous.searcher"
|
|
||||||
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
|
||||||
import { GeneralSpotlightSearcherService } from "~/services/spotlight/searchers/general.searcher"
|
|
||||||
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
|
||||||
import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/request.searcher"
|
|
||||||
import {
|
import {
|
||||||
EnvironmentsSpotlightSearcherService,
|
EnvironmentsSpotlightSearcherService,
|
||||||
SwitchEnvSpotlightSearcherService,
|
SwitchEnvSpotlightSearcherService,
|
||||||
} from "~/services/spotlight/searchers/environment.searcher"
|
} from "~/services/spotlight/searchers/environment.searcher"
|
||||||
|
import { GeneralSpotlightSearcherService } from "~/services/spotlight/searchers/general.searcher"
|
||||||
|
import { HistorySpotlightSearcherService } from "~/services/spotlight/searchers/history.searcher"
|
||||||
|
import { InterceptorSpotlightSearcherService } from "~/services/spotlight/searchers/interceptor.searcher"
|
||||||
|
import { MiscellaneousSpotlightSearcherService } from "~/services/spotlight/searchers/miscellaneous.searcher"
|
||||||
|
import { NavigationSpotlightSearcherService } from "~/services/spotlight/searchers/navigation.searcher"
|
||||||
|
import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/request.searcher"
|
||||||
|
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
||||||
|
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
||||||
|
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
||||||
|
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
||||||
import {
|
import {
|
||||||
SwitchWorkspaceSpotlightSearcherService,
|
SwitchWorkspaceSpotlightSearcherService,
|
||||||
WorkspaceSpotlightSearcherService,
|
WorkspaceSpotlightSearcherService,
|
||||||
} from "~/services/spotlight/searchers/workspace.searcher"
|
} from "~/services/spotlight/searchers/workspace.searcher"
|
||||||
import { InterceptorSpotlightSearcherService } from "~/services/spotlight/searchers/interceptor.searcher"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -290,4 +291,17 @@ function newUseArrowKeysForNavigation() {
|
|||||||
|
|
||||||
return { selectedEntry }
|
return { selectedEntry }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeSpotlightModal() {
|
||||||
|
const analyticsData: HoppSpotlightSessionEventData = {
|
||||||
|
action: "close",
|
||||||
|
searcherID: null,
|
||||||
|
rank: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the action indicating `close` and rank as `null` in the state for analytics event logging
|
||||||
|
spotlightService.setAnalyticsData(analyticsData)
|
||||||
|
|
||||||
|
emit("hide-modal")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -614,8 +614,8 @@ const addNewRootCollection = (name: string) => {
|
|||||||
requests: [],
|
requests: [],
|
||||||
headers: [],
|
headers: [],
|
||||||
auth: {
|
auth: {
|
||||||
authType: "inherit",
|
authType: "none",
|
||||||
authActive: false,
|
authActive: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'cookie')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
@@ -102,6 +102,8 @@ import {
|
|||||||
useCopyResponse,
|
useCopyResponse,
|
||||||
useDownloadResponse,
|
useDownloadResponse,
|
||||||
} from "~/composables/lens-actions"
|
} from "~/composables/lens-actions"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
// TODO: Build Managed Mode!
|
// TODO: Build Managed Mode!
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ const toast = useToast()
|
|||||||
|
|
||||||
const cookieEditor = ref<HTMLElement>()
|
const cookieEditor = ref<HTMLElement>()
|
||||||
const rawCookieString = ref("")
|
const rawCookieString = ref("")
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "cookie")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
cookieEditor,
|
cookieEditor,
|
||||||
@@ -131,7 +133,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/plain",
|
mode: "text/plain",
|
||||||
placeholder: `${t("cookies.modal.enter_cookie_string")}`,
|
placeholder: `${t("cookies.modal.enter_cookie_string")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<label for="value" class="min-w-[2.5rem] font-semibold">{{
|
<label for="value" class="min-w-[2.5rem] font-semibold">{{
|
||||||
t("environment.value")
|
t("environment.value")
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<SmartEnvInput
|
||||||
v-model="editingValue"
|
v-model="editingValue"
|
||||||
type="text"
|
type="text"
|
||||||
class="input"
|
class="input"
|
||||||
@@ -154,12 +154,14 @@ const addEnvironment = async () => {
|
|||||||
addGlobalEnvVariable({
|
addGlobalEnvVariable({
|
||||||
key: editingName.value,
|
key: editingName.value,
|
||||||
value: editingValue.value,
|
value: editingValue.value,
|
||||||
|
secret: false,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.updated")}`)
|
toast.success(`${t("environment.updated")}`)
|
||||||
} else if (scope.value.type === "my-environment") {
|
} else if (scope.value.type === "my-environment") {
|
||||||
addEnvironmentVariable(scope.value.index, {
|
addEnvironmentVariable(scope.value.index, {
|
||||||
key: editingName.value,
|
key: editingName.value,
|
||||||
value: editingValue.value,
|
value: editingValue.value,
|
||||||
|
secret: false,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.updated")}`)
|
toast.success(`${t("environment.updated")}`)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment, NonSecretEnvironment } from "@hoppscotch/data"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
@@ -340,13 +340,13 @@ const showImportFailedError = () => {
|
|||||||
|
|
||||||
const handleImportToStore = async (
|
const handleImportToStore = async (
|
||||||
environments: Environment[],
|
environments: Environment[],
|
||||||
globalEnv?: Environment
|
globalEnv?: NonSecretEnvironment
|
||||||
) => {
|
) => {
|
||||||
// if there's a global env, add them to the store
|
// if there's a global env, add them to the store
|
||||||
if (globalEnv) {
|
if (globalEnv) {
|
||||||
globalEnv.variables.forEach(({ key, value }) => {
|
globalEnv.variables.forEach(({ key, value, secret }) =>
|
||||||
addGlobalEnvVariable({ key, value })
|
addGlobalEnvVariable({ key, value, secret })
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.environmentType === "MY_ENV") {
|
if (props.environmentType === "MY_ENV") {
|
||||||
|
|||||||
@@ -210,7 +210,10 @@
|
|||||||
{{ variable.key }}
|
{{ variable.key }}
|
||||||
</span>
|
</span>
|
||||||
<span class="min-w-[9rem] w-full truncate text-secondaryLight">
|
<span class="min-w-[9rem] w-full truncate text-secondaryLight">
|
||||||
{{ variable.value }}
|
<template v-if="variable.secret"> ******** </template>
|
||||||
|
<template v-else>
|
||||||
|
{{ variable.value }}
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="globalEnvs.length === 0" class="text-secondaryLight">
|
<div v-if="globalEnvs.length === 0" class="text-secondaryLight">
|
||||||
@@ -265,7 +268,10 @@
|
|||||||
{{ variable.key }}
|
{{ variable.key }}
|
||||||
</span>
|
</span>
|
||||||
<span class="min-w-[9rem] w-full truncate text-secondaryLight">
|
<span class="min-w-[9rem] w-full truncate text-secondaryLight">
|
||||||
{{ variable.value }}
|
<template v-if="variable.secret"> ******** </template>
|
||||||
|
<template v-else>
|
||||||
|
{{ variable.value }}
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -479,15 +485,20 @@ const selectedEnv = computed(() => {
|
|||||||
type: "MY_ENV",
|
type: "MY_ENV",
|
||||||
index: props.modelValue.index,
|
index: props.modelValue.index,
|
||||||
name: props.modelValue.environment?.name,
|
name: props.modelValue.environment?.name,
|
||||||
|
variables: props.modelValue.environment?.variables,
|
||||||
}
|
}
|
||||||
} else if (props.modelValue?.type === "team-environment") {
|
} else if (props.modelValue?.type === "team-environment") {
|
||||||
return {
|
return {
|
||||||
type: "TEAM_ENV",
|
type: "TEAM_ENV",
|
||||||
name: props.modelValue.environment.environment.name,
|
name: props.modelValue.environment.environment.name,
|
||||||
teamEnvID: props.modelValue.environment.id,
|
teamEnvID: props.modelValue.environment.id,
|
||||||
|
variables: props.modelValue.environment.environment.variables,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { type: "global", name: "Global" }
|
return {
|
||||||
|
type: "global",
|
||||||
|
name: "Global",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
const environment =
|
const environment =
|
||||||
@@ -582,9 +593,7 @@ const environmentVariables = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const editGlobalEnv = () => {
|
const editGlobalEnv = () => {
|
||||||
invokeAction("modals.my.environment.edit", {
|
invokeAction("modals.global.environment.update", {})
|
||||||
envName: "Global",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const editEnv = () => {
|
const editEnv = () => {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
:action="action"
|
:action="action"
|
||||||
:editing-environment-index="editingEnvironmentIndex"
|
:editing-environment-index="editingEnvironmentIndex"
|
||||||
:editing-variable-name="editingVariableName"
|
:editing-variable-name="editingVariableName"
|
||||||
|
:env-vars="envVars"
|
||||||
|
:is-secret-option-selected="secretOptionSelected"
|
||||||
@hide-modal="displayModalEdit(false)"
|
@hide-modal="displayModalEdit(false)"
|
||||||
/>
|
/>
|
||||||
<EnvironmentsAdd
|
<EnvironmentsAdd
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
|
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="showConfirmRemoveEnvModal"
|
:show="showConfirmRemoveEnvModal"
|
||||||
:title="t('confirm.remove_team')"
|
:title="`${t('confirm.remove_environment')}`"
|
||||||
@hide-modal="showConfirmRemoveEnvModal = false"
|
@hide-modal="showConfirmRemoveEnvModal = false"
|
||||||
@resolve="removeSelectedEnvironment()"
|
@resolve="removeSelectedEnvironment()"
|
||||||
/>
|
/>
|
||||||
@@ -67,6 +69,7 @@ import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironme
|
|||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -88,6 +91,8 @@ const environmentType = ref<EnvironmentsChooseType>({
|
|||||||
const globalEnv = useReadonlyStream(globalEnv$, [])
|
const globalEnv = useReadonlyStream(globalEnv$, [])
|
||||||
|
|
||||||
const globalEnvironment = computed(() => ({
|
const globalEnvironment = computed(() => ({
|
||||||
|
v: 1 as const,
|
||||||
|
id: "Global",
|
||||||
name: "Global",
|
name: "Global",
|
||||||
variables: globalEnv.value,
|
variables: globalEnv.value,
|
||||||
}))
|
}))
|
||||||
@@ -186,6 +191,7 @@ const action = ref<"new" | "edit">("edit")
|
|||||||
const editingEnvironmentIndex = ref<"Global" | null>(null)
|
const editingEnvironmentIndex = ref<"Global" | null>(null)
|
||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
const editingVariableValue = ref("")
|
const editingVariableValue = ref("")
|
||||||
|
const secretOptionSelected = ref(false)
|
||||||
|
|
||||||
const position = ref({ top: 0, left: 0 })
|
const position = ref({ top: 0, left: 0 })
|
||||||
|
|
||||||
@@ -203,6 +209,7 @@ const displayModalEdit = (shouldDisplay: boolean) => {
|
|||||||
const editEnvironment = (environmentIndex: "Global") => {
|
const editEnvironment = (environmentIndex: "Global") => {
|
||||||
editingEnvironmentIndex.value = environmentIndex
|
editingEnvironmentIndex.value = environmentIndex
|
||||||
action.value = "edit"
|
action.value = "edit"
|
||||||
|
editingVariableName.value = ""
|
||||||
displayModalEdit(true)
|
displayModalEdit(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +239,9 @@ const removeSelectedEnvironment = () => {
|
|||||||
|
|
||||||
const resetSelectedData = () => {
|
const resetSelectedData = () => {
|
||||||
editingEnvironmentIndex.value = null
|
editingEnvironmentIndex.value = null
|
||||||
|
editingVariableName.value = ""
|
||||||
|
editingVariableValue.value = ""
|
||||||
|
secretOptionSelected.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
defineActionHandler("modals.environment.new", () => {
|
defineActionHandler("modals.environment.new", () => {
|
||||||
@@ -243,11 +253,19 @@ defineActionHandler("modals.environment.delete-selected", () => {
|
|||||||
showConfirmRemoveEnvModal.value = true
|
showConfirmRemoveEnvModal.value = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const additionalVars = ref<Environment["variables"]>([])
|
||||||
|
|
||||||
|
const envVars = () => [...globalEnv.value, ...additionalVars.value]
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.my.environment.edit",
|
"modals.global.environment.update",
|
||||||
({ envName, variableName }) => {
|
({ variables, isSecret }) => {
|
||||||
if (variableName) editingVariableName.value = variableName
|
if (variables) {
|
||||||
envName === "Global" && editEnvironment("Global")
|
additionalVars.value = variables
|
||||||
|
}
|
||||||
|
secretOptionSelected.value = isSecret ?? false
|
||||||
|
editEnvironment("Global")
|
||||||
|
editingVariableName.value = "Global"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,76 +16,103 @@
|
|||||||
@submit="saveEnvironment"
|
@submit="saveEnvironment"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-1 items-center justify-between">
|
<div class="my-4 flex flex-col border border-divider rounded">
|
||||||
<label for="variableList" class="p-4">
|
|
||||||
{{ t("environment.variable_list") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.clear_all')"
|
|
||||||
:icon="clearIcon"
|
|
||||||
@click="clearContent()"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconPlus"
|
|
||||||
:title="t('add.new')"
|
|
||||||
@click="addEnvironmentVariable"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="evnExpandError"
|
|
||||||
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
|
||||||
>
|
|
||||||
{{ t("environment.nested_overflow") }}
|
|
||||||
</div>
|
|
||||||
<div class="divide-y divide-dividerLight rounded border border-divider">
|
|
||||||
<div
|
<div
|
||||||
v-for="({ id, env }, index) in vars"
|
v-if="evnExpandError"
|
||||||
:key="`variable-${id}-${index}`"
|
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||||
class="flex divide-x divide-dividerLight"
|
|
||||||
>
|
>
|
||||||
<input
|
{{ t("environment.nested_overflow") }}
|
||||||
v-model="env.key"
|
|
||||||
v-focus
|
|
||||||
class="flex flex-1 bg-transparent px-4 py-2"
|
|
||||||
:placeholder="`${t('count.variable', { count: index + 1 })}`"
|
|
||||||
:name="'param' + index"
|
|
||||||
/>
|
|
||||||
<SmartEnvInput
|
|
||||||
v-model="env.value"
|
|
||||||
:select-text-on-mount="env.key === editingVariableName"
|
|
||||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
|
||||||
:envs="liveEnvs"
|
|
||||||
:name="'value' + index"
|
|
||||||
/>
|
|
||||||
<div class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
id="variable"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.remove')"
|
|
||||||
:icon="IconTrash"
|
|
||||||
color="red"
|
|
||||||
@click="removeEnvironmentVariable(index)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartTabs v-model="selectedEnvOption" render-inactive-tabs>
|
||||||
v-if="vars.length === 0"
|
<template #actions>
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
<div class="flex flex-1 items-center justify-between">
|
||||||
:alt="`${t('empty.environments')}`"
|
<HoppButtonSecondary
|
||||||
:text="t('empty.environments')"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
>
|
to="https://docs.hoppscotch.io/documentation/features/environments"
|
||||||
<template #body>
|
blank
|
||||||
<HoppButtonSecondary
|
:title="t('app.wiki')"
|
||||||
:label="`${t('add.new')}`"
|
:icon="IconHelpCircle"
|
||||||
filled
|
/>
|
||||||
@click="addEnvironmentVariable"
|
<HoppButtonSecondary
|
||||||
/>
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.clear_all')"
|
||||||
|
:icon="clearIcon"
|
||||||
|
@click="clearContent()"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconPlus"
|
||||||
|
:title="t('add.new')"
|
||||||
|
@click="addEnvironmentVariable"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
|
<HoppSmartTab
|
||||||
|
v-for="tab in tabsData"
|
||||||
|
:id="tab.id"
|
||||||
|
:key="tab.id"
|
||||||
|
:label="tab.label"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="divide-y divide-dividerLight rounded border border-divider"
|
||||||
|
>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-if="tab.variables.length === 0"
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
:alt="tab.emptyStateLabel"
|
||||||
|
:text="tab.emptyStateLabel"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="`${t('add.new')}`"
|
||||||
|
filled
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="addEnvironmentVariable"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="({ id, env }, index) in tab.variables"
|
||||||
|
:key="`variable-${id}-${index}`"
|
||||||
|
class="flex divide-x divide-dividerLight"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="env.key"
|
||||||
|
v-focus
|
||||||
|
class="flex flex-1 bg-transparent px-4 py-2"
|
||||||
|
:placeholder="`${t('count.variable', {
|
||||||
|
count: index + 1,
|
||||||
|
})}`"
|
||||||
|
:name="'param' + index"
|
||||||
|
/>
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="env.value"
|
||||||
|
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||||
|
:envs="liveEnvs"
|
||||||
|
:name="'value' + index"
|
||||||
|
:secret="tab.isSecret"
|
||||||
|
:select-text-on-mount="
|
||||||
|
env.key ? env.key === editingVariableName : false
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
id="variable"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.remove')"
|
||||||
|
:icon="IconTrash"
|
||||||
|
color="red"
|
||||||
|
@click="removeEnvironmentVariable(id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
</HoppSmartTabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -112,8 +139,8 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
|||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
import { clone } from "lodash-es"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import { computed, ref, watch } from "vue"
|
import { ComputedRef, computed, ref, watch } from "vue"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
@@ -136,12 +163,16 @@ import { useReadonlyStream } from "@composables/stream"
|
|||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { environmentsStore } from "~/newstore/environments"
|
import { environmentsStore } from "~/newstore/environments"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
import { uniqueId } from "lodash-es"
|
||||||
|
|
||||||
type EnvironmentVariable = {
|
type EnvironmentVariable = {
|
||||||
id: number
|
id: number
|
||||||
env: {
|
env: {
|
||||||
key: string
|
|
||||||
value: string
|
value: string
|
||||||
|
key: string
|
||||||
|
secret: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +186,7 @@ const props = withDefaults(
|
|||||||
action: "edit" | "new"
|
action: "edit" | "new"
|
||||||
editingEnvironmentIndex?: number | "Global" | null
|
editingEnvironmentIndex?: number | "Global" | null
|
||||||
editingVariableName?: string | null
|
editingVariableName?: string | null
|
||||||
|
isSecretOptionSelected?: boolean
|
||||||
envVars?: () => Environment["variables"]
|
envVars?: () => Environment["variables"]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -162,6 +194,7 @@ const props = withDefaults(
|
|||||||
action: "edit",
|
action: "edit",
|
||||||
editingEnvironmentIndex: null,
|
editingEnvironmentIndex: null,
|
||||||
editingVariableName: null,
|
editingVariableName: null,
|
||||||
|
isSecretOptionSelected: false,
|
||||||
envVars: () => [],
|
envVars: () => [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -172,11 +205,55 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
|
const tabsData: ComputedRef<
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
emptyStateLabel: string
|
||||||
|
isSecret: boolean
|
||||||
|
variables: EnvironmentVariable[]
|
||||||
|
}[]
|
||||||
|
> = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "variables",
|
||||||
|
label: t("environment.variables"),
|
||||||
|
emptyStateLabel: t("empty.environments"),
|
||||||
|
isSecret: false,
|
||||||
|
variables: nonSecretVars.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "secret",
|
||||||
|
label: t("environment.secrets"),
|
||||||
|
emptyStateLabel: t("empty.secret_environments"),
|
||||||
|
isSecret: true,
|
||||||
|
variables: secretVars.value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const editingName = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
|
const editingID = ref<string>("")
|
||||||
const vars = ref<EnvironmentVariable[]>([
|
const vars = ref<EnvironmentVariable[]>([
|
||||||
{ id: idTicker.value++, env: { key: "", value: "" } },
|
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const secretEnvironmentService = useService(SecretEnvironmentService)
|
||||||
|
|
||||||
|
const secretVars = computed(() =>
|
||||||
|
pipe(
|
||||||
|
vars.value,
|
||||||
|
A.filter((e) => e.env.secret)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const nonSecretVars = computed(() =>
|
||||||
|
pipe(
|
||||||
|
vars.value,
|
||||||
|
A.filter((e) => !e.env.secret)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||||
IconTrash2,
|
IconTrash2,
|
||||||
1000
|
1000
|
||||||
@@ -184,14 +261,23 @@ const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
|||||||
|
|
||||||
const globalVars = useReadonlyStream(globalEnv$, [])
|
const globalVars = useReadonlyStream(globalEnv$, [])
|
||||||
|
|
||||||
|
type SelectedEnv = "variables" | "secret"
|
||||||
|
|
||||||
|
const selectedEnvOption = ref<SelectedEnv>("variables")
|
||||||
|
|
||||||
const workingEnv = computed(() => {
|
const workingEnv = computed(() => {
|
||||||
if (props.editingEnvironmentIndex === "Global") {
|
if (props.editingEnvironmentIndex === "Global") {
|
||||||
|
const vars =
|
||||||
|
props.editingVariableName === "Global"
|
||||||
|
? props.envVars()
|
||||||
|
: getGlobalVariables()
|
||||||
return {
|
return {
|
||||||
name: "Global",
|
name: "Global",
|
||||||
variables: getGlobalVariables(),
|
variables: vars,
|
||||||
} as Environment
|
} as Environment
|
||||||
} else if (props.action === "new") {
|
} else if (props.action === "new") {
|
||||||
return {
|
return {
|
||||||
|
id: uniqueId(),
|
||||||
name: "",
|
name: "",
|
||||||
variables: props.envVars(),
|
variables: props.envVars(),
|
||||||
}
|
}
|
||||||
@@ -214,6 +300,7 @@ const evnExpandError = computed(() => {
|
|||||||
|
|
||||||
return pipe(
|
return pipe(
|
||||||
variables,
|
variables,
|
||||||
|
A.filter(({ secret }) => !secret),
|
||||||
A.exists(({ value }) => E.isLeft(parseTemplateStringE(value, variables)))
|
A.exists(({ value }) => E.isLeft(parseTemplateStringE(value, variables)))
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -239,11 +326,29 @@ watch(
|
|||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
editingName.value = workingEnv.value?.name ?? null
|
editingName.value = workingEnv.value?.name ?? null
|
||||||
|
selectedEnvOption.value = props.isSecretOptionSelected
|
||||||
|
? "secret"
|
||||||
|
: "variables"
|
||||||
|
|
||||||
|
if (props.editingEnvironmentIndex !== "Global") {
|
||||||
|
editingID.value = workingEnv.value?.id ?? uniqueId()
|
||||||
|
}
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
workingEnv.value?.variables ?? [],
|
workingEnv.value?.variables ?? [],
|
||||||
A.map((e) => ({
|
A.mapWithIndex((index, e) => ({
|
||||||
id: idTicker.value++,
|
id: idTicker.value++,
|
||||||
env: clone(e),
|
env: {
|
||||||
|
key: e.key,
|
||||||
|
value: e.secret
|
||||||
|
? secretEnvironmentService.getSecretEnvironmentVariable(
|
||||||
|
props.editingEnvironmentIndex === "Global"
|
||||||
|
? "Global"
|
||||||
|
: workingEnv.value?.id,
|
||||||
|
index
|
||||||
|
)?.value ?? ""
|
||||||
|
: e.value,
|
||||||
|
secret: e.secret,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -251,7 +356,10 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
vars.value = []
|
vars.value = vars.value.filter((e) =>
|
||||||
|
selectedEnvOption.value === "secret" ? !e.env.secret : e.env.secret
|
||||||
|
)
|
||||||
|
|
||||||
clearIcon.value = IconDone
|
clearIcon.value = IconDone
|
||||||
toast.success(`${t("state.cleared")}`)
|
toast.success(`${t("state.cleared")}`)
|
||||||
}
|
}
|
||||||
@@ -262,12 +370,16 @@ const addEnvironmentVariable = () => {
|
|||||||
env: {
|
env: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
|
secret: selectedEnvOption.value === "secret",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeEnvironmentVariable = (index: number) => {
|
const removeEnvironmentVariable = (id: number) => {
|
||||||
vars.value.splice(index, 1)
|
const index = vars.value.findIndex((e) => e.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
vars.value.splice(index, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveEnvironment = () => {
|
const saveEnvironment = () => {
|
||||||
@@ -276,7 +388,7 @@ const saveEnvironment = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterdVariables = pipe(
|
const filteredVariables = pipe(
|
||||||
vars.value,
|
vars.value,
|
||||||
A.filterMap(
|
A.filterMap(
|
||||||
flow(
|
flow(
|
||||||
@@ -286,14 +398,43 @@ const saveEnvironment = () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const secretVariables = pipe(
|
||||||
|
filteredVariables,
|
||||||
|
A.filterMapWithIndex((i, e) =>
|
||||||
|
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (editingID.value) {
|
||||||
|
secretEnvironmentService.addSecretEnvironment(
|
||||||
|
editingID.value,
|
||||||
|
secretVariables
|
||||||
|
)
|
||||||
|
} else if (props.editingEnvironmentIndex === "Global") {
|
||||||
|
secretEnvironmentService.addSecretEnvironment("Global", secretVariables)
|
||||||
|
}
|
||||||
|
|
||||||
|
const variables = pipe(
|
||||||
|
filteredVariables,
|
||||||
|
A.map((e) =>
|
||||||
|
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const environmentUpdated: Environment = {
|
const environmentUpdated: Environment = {
|
||||||
|
v: 1,
|
||||||
|
id: uniqueId(),
|
||||||
name: editingName.value,
|
name: editingName.value,
|
||||||
variables: filterdVariables,
|
variables,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.action === "new") {
|
if (props.action === "new") {
|
||||||
// Creating a new environment
|
// Creating a new environment
|
||||||
createEnvironment(editingName.value, environmentUpdated.variables)
|
createEnvironment(
|
||||||
|
editingName.value,
|
||||||
|
environmentUpdated.variables,
|
||||||
|
editingID.value
|
||||||
|
)
|
||||||
setSelectedEnvironmentIndex({
|
setSelectedEnvironmentIndex({
|
||||||
type: "MY_ENV",
|
type: "MY_ENV",
|
||||||
index: envList.value.length - 1,
|
index: envList.value.length - 1,
|
||||||
@@ -332,6 +473,7 @@ const saveEnvironment = () => {
|
|||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
editingName.value = null
|
editingName.value = null
|
||||||
|
selectedEnvOption.value = "variables"
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ import { useToast } from "@composables/toast"
|
|||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -150,6 +152,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
|
const secretEnvironmentService = useService(SecretEnvironmentService)
|
||||||
|
|
||||||
const exportEnvironmentAsJSON = () => {
|
const exportEnvironmentAsJSON = () => {
|
||||||
const { environment, environmentIndex } = props
|
const { environment, environmentIndex } = props
|
||||||
exportAsJSON(environment, environmentIndex)
|
exportAsJSON(environment, environmentIndex)
|
||||||
@@ -168,6 +172,7 @@ const removeEnvironment = () => {
|
|||||||
if (props.environmentIndex === null) return
|
if (props.environmentIndex === null) return
|
||||||
if (props.environmentIndex !== "Global") {
|
if (props.environmentIndex !== "Global") {
|
||||||
deleteEnvironment(props.environmentIndex, props.environment.id)
|
deleteEnvironment(props.environmentIndex, props.environment.id)
|
||||||
|
secretEnvironmentService.deleteSecretEnvironment(props.environment.id)
|
||||||
}
|
}
|
||||||
toast.success(`${t("state.deleted")}`)
|
toast.success(`${t("state.deleted")}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
:action="action"
|
:action="action"
|
||||||
:editing-environment-index="editingEnvironmentIndex"
|
:editing-environment-index="editingEnvironmentIndex"
|
||||||
:editing-variable-name="editingVariableName"
|
:editing-variable-name="editingVariableName"
|
||||||
|
:is-secret-option-selected="secretOptionSelected"
|
||||||
@hide-modal="displayModalEdit(false)"
|
@hide-modal="displayModalEdit(false)"
|
||||||
/>
|
/>
|
||||||
<EnvironmentsImportExport
|
<EnvironmentsImportExport
|
||||||
@@ -99,6 +100,7 @@ const showModalDetails = ref(false)
|
|||||||
const action = ref<"new" | "edit">("edit")
|
const action = ref<"new" | "edit">("edit")
|
||||||
const editingEnvironmentIndex = ref<number | null>(null)
|
const editingEnvironmentIndex = ref<number | null>(null)
|
||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
|
const secretOptionSelected = ref(false)
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
action.value = "new"
|
action.value = "new"
|
||||||
@@ -120,18 +122,23 @@ const editEnvironment = (environmentIndex: number) => {
|
|||||||
}
|
}
|
||||||
const resetSelectedData = () => {
|
const resetSelectedData = () => {
|
||||||
editingEnvironmentIndex.value = null
|
editingEnvironmentIndex.value = null
|
||||||
|
editingVariableName.value = ""
|
||||||
|
secretOptionSelected.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.my.environment.edit",
|
"modals.my.environment.edit",
|
||||||
({ envName, variableName }) => {
|
({ envName, variableName, isSecret }) => {
|
||||||
if (variableName) editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
const envIndex: number = environments.value.findIndex(
|
const envIndex: number = environments.value.findIndex(
|
||||||
(environment: Environment) => {
|
(environment: Environment) => {
|
||||||
return environment.name === envName
|
return environment.name === envName
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (envName !== "Global") editEnvironment(envIndex)
|
if (envName !== "Global") {
|
||||||
|
editEnvironment(envIndex)
|
||||||
|
secretOptionSelected.value = isSecret ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -16,90 +16,112 @@
|
|||||||
@submit="saveEnvironment"
|
@submit="saveEnvironment"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-1 items-center justify-between">
|
<div class="my-4 flex flex-col border border-divider rounded">
|
||||||
<label for="variableList" class="p-4">
|
|
||||||
{{ t("environment.variable_list") }}
|
|
||||||
</label>
|
|
||||||
<div v-if="!isViewer" class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.clear_all')"
|
|
||||||
:icon="clearIcon"
|
|
||||||
@click="clearContent()"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconPlus"
|
|
||||||
:title="t('add.new')"
|
|
||||||
@click="addEnvironmentVariable"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="evnExpandError"
|
|
||||||
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
|
||||||
>
|
|
||||||
{{ t("environment.nested_overflow") }}
|
|
||||||
</div>
|
|
||||||
<div class="divide-y divide-dividerLight rounded border border-divider">
|
|
||||||
<div
|
<div
|
||||||
v-for="({ id, env }, index) in vars"
|
v-if="evnExpandError"
|
||||||
:key="`variable-${id}-${index}`"
|
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||||
class="flex divide-x divide-dividerLight"
|
|
||||||
>
|
>
|
||||||
<input
|
{{ t("environment.nested_overflow") }}
|
||||||
v-model="env.key"
|
|
||||||
v-focus
|
|
||||||
class="flex flex-1 bg-transparent px-4 py-2"
|
|
||||||
:class="isViewer && 'opacity-25'"
|
|
||||||
:placeholder="`${t('count.variable', { count: index + 1 })}`"
|
|
||||||
:name="'param' + index"
|
|
||||||
:disabled="isViewer"
|
|
||||||
/>
|
|
||||||
<SmartEnvInput
|
|
||||||
v-model="env.value"
|
|
||||||
:select-text-on-mount="env.key === editingVariableName"
|
|
||||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
|
||||||
:envs="liveEnvs"
|
|
||||||
:name="'value' + index"
|
|
||||||
:readonly="isViewer"
|
|
||||||
/>
|
|
||||||
<div v-if="!isViewer" class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
id="variable"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.remove')"
|
|
||||||
:icon="IconTrash"
|
|
||||||
color="red"
|
|
||||||
@click="removeEnvironmentVariable(index)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartTabs v-model="selectedEnvOption" render-inactive-tabs>
|
||||||
v-if="vars.length === 0"
|
<template #actions>
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
<div class="flex flex-1 items-center justify-between">
|
||||||
:alt="`${t('empty.environments')}`"
|
<HoppButtonSecondary
|
||||||
:text="t('empty.environments')"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
>
|
to="https://docs.hoppscotch.io/documentation/features/environments"
|
||||||
<template #body>
|
blank
|
||||||
<HoppButtonSecondary
|
:title="t('app.wiki')"
|
||||||
v-if="isViewer"
|
:icon="IconHelpCircle"
|
||||||
disabled
|
/>
|
||||||
:label="`${t('add.new')}`"
|
<HoppButtonSecondary
|
||||||
filled
|
v-if="!isViewer"
|
||||||
/>
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
<HoppButtonSecondary
|
:title="t('action.clear_all')"
|
||||||
v-else
|
:icon="clearIcon"
|
||||||
:label="`${t('add.new')}`"
|
@click="clearContent()"
|
||||||
filled
|
/>
|
||||||
@click="addEnvironmentVariable"
|
<HoppButtonSecondary
|
||||||
/>
|
v-if="!isViewer"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconPlus"
|
||||||
|
:title="t('add.new')"
|
||||||
|
@click="addEnvironmentVariable"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
|
<HoppSmartTab
|
||||||
|
v-for="tab in tabsData"
|
||||||
|
:id="tab.id"
|
||||||
|
:key="tab.id"
|
||||||
|
:label="tab.label"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="divide-y divide-dividerLight rounded border border-divider"
|
||||||
|
>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-if="tab.variables.length === 0"
|
||||||
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
|
:alt="tab.emptyStateLabel"
|
||||||
|
:text="tab.emptyStateLabel"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="!isViewer"
|
||||||
|
:label="`${t('add.new')}`"
|
||||||
|
filled
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="addEnvironmentVariable"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="({ id, env }, index) in tab.variables"
|
||||||
|
:key="`variable-${id}-${index}`"
|
||||||
|
class="flex divide-x divide-dividerLight"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="env.key"
|
||||||
|
v-focus
|
||||||
|
class="flex flex-1 bg-transparent px-4 py-2"
|
||||||
|
:placeholder="`${t('count.variable', {
|
||||||
|
count: index + 1,
|
||||||
|
})}`"
|
||||||
|
:name="'param' + index"
|
||||||
|
:disabled="isViewer"
|
||||||
|
/>
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="env.value"
|
||||||
|
:select-text-on-mount="
|
||||||
|
env.key ? env.key === editingVariableName : false
|
||||||
|
"
|
||||||
|
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||||
|
:envs="liveEnvs"
|
||||||
|
:name="'value' + index"
|
||||||
|
:secret="tab.isSecret"
|
||||||
|
:readonly="isViewer && !tab.isSecret"
|
||||||
|
/>
|
||||||
|
<div v-if="!isViewer" class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
id="variable"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.remove')"
|
||||||
|
:icon="IconTrash"
|
||||||
|
color="red"
|
||||||
|
@click="removeEnvironmentVariable(id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
</HoppSmartTabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!isViewer" #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:label="`${t('action.save')}`"
|
:label="`${t('action.save')}`"
|
||||||
@@ -119,7 +141,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue"
|
import { ComputedRef, computed, ref, watch } from "vue"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
@@ -141,13 +163,17 @@ import IconTrash from "~icons/lucide/trash"
|
|||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
|
||||||
type EnvironmentVariable = {
|
type EnvironmentVariable = {
|
||||||
id: number
|
id: number
|
||||||
env: {
|
env: {
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
|
secret: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +189,7 @@ const props = withDefaults(
|
|||||||
editingTeamId: string | undefined
|
editingTeamId: string | undefined
|
||||||
editingVariableName?: string | null
|
editingVariableName?: string | null
|
||||||
isViewer?: boolean
|
isViewer?: boolean
|
||||||
|
isSecretOptionSelected?: boolean
|
||||||
envVars?: () => Environment["variables"]
|
envVars?: () => Environment["variables"]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -172,6 +199,7 @@ const props = withDefaults(
|
|||||||
editingTeamId: "",
|
editingTeamId: "",
|
||||||
editingVariableName: null,
|
editingVariableName: null,
|
||||||
isViewer: false,
|
isViewer: false,
|
||||||
|
isSecretOptionSelected: false,
|
||||||
envVars: () => [],
|
envVars: () => [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -182,11 +210,59 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
|
const tabsData: ComputedRef<
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
emptyStateLabel: string
|
||||||
|
isSecret: boolean
|
||||||
|
variables: EnvironmentVariable[]
|
||||||
|
}[]
|
||||||
|
> = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "variables",
|
||||||
|
label: t("environment.variables"),
|
||||||
|
emptyStateLabel: t("empty.environments"),
|
||||||
|
isSecret: false,
|
||||||
|
variables: nonSecretVars.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "secret",
|
||||||
|
label: t("environment.secrets"),
|
||||||
|
emptyStateLabel: t("empty.secret_environments"),
|
||||||
|
isSecret: true,
|
||||||
|
variables: secretVars.value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const editingName = ref<string | null>(null)
|
const editingName = ref<string | null>(null)
|
||||||
|
const editingID = ref<string | null>(null)
|
||||||
const vars = ref<EnvironmentVariable[]>([
|
const vars = ref<EnvironmentVariable[]>([
|
||||||
{ id: idTicker.value++, env: { key: "", value: "" } },
|
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const secretEnvironmentService = useService(SecretEnvironmentService)
|
||||||
|
|
||||||
|
const secretVars = computed(() =>
|
||||||
|
pipe(
|
||||||
|
vars.value,
|
||||||
|
A.filter((e) => e.env.secret)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const nonSecretVars = computed(() =>
|
||||||
|
pipe(
|
||||||
|
vars.value,
|
||||||
|
A.filter((e) => !e.env.secret)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelectedEnv = "variables" | "secret"
|
||||||
|
|
||||||
|
const selectedEnvOption = ref<SelectedEnv>("variables")
|
||||||
|
|
||||||
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
|
||||||
IconTrash2,
|
IconTrash2,
|
||||||
1000
|
1000
|
||||||
@@ -215,22 +291,34 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
|
editingName.value = props.editingEnvironment?.environment.name ?? null
|
||||||
|
selectedEnvOption.value = props.isSecretOptionSelected
|
||||||
|
? "secret"
|
||||||
|
: "variables"
|
||||||
if (props.action === "new") {
|
if (props.action === "new") {
|
||||||
editingName.value = null
|
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.envVars() ?? [],
|
props.envVars() ?? [],
|
||||||
A.map((e: { key: string; value: string }) => ({
|
A.map((e) => ({
|
||||||
id: idTicker.value++,
|
id: idTicker.value++,
|
||||||
env: clone(e),
|
env: clone(e),
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
} else if (props.editingEnvironment !== null) {
|
} else if (props.editingEnvironment !== null) {
|
||||||
editingName.value = props.editingEnvironment.environment.name ?? null
|
editingID.value = props.editingEnvironment.id
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
props.editingEnvironment.environment.variables ?? [],
|
props.editingEnvironment.environment.variables ?? [],
|
||||||
A.map((e: { key: string; value: string }) => ({
|
A.mapWithIndex((index, e) => ({
|
||||||
id: idTicker.value++,
|
id: idTicker.value++,
|
||||||
env: clone(e),
|
env: {
|
||||||
|
key: e.key,
|
||||||
|
value: e.secret
|
||||||
|
? secretEnvironmentService.getSecretEnvironmentVariable(
|
||||||
|
editingID.value ?? "",
|
||||||
|
index
|
||||||
|
)?.value ?? ""
|
||||||
|
: e.value,
|
||||||
|
secret: e.secret,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -250,12 +338,16 @@ const addEnvironmentVariable = () => {
|
|||||||
env: {
|
env: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
|
secret: selectedEnvOption.value === "secret",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeEnvironmentVariable = (index: number) => {
|
const removeEnvironmentVariable = (id: number) => {
|
||||||
vars.value.splice(index, 1)
|
const index = vars.value.findIndex((e) => e.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
vars.value.splice(index, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
@@ -278,52 +370,102 @@ const saveEnvironment = async () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const secretVariables = pipe(
|
||||||
|
filterdVariables,
|
||||||
|
A.filterMapWithIndex((i, e) =>
|
||||||
|
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const variables = pipe(
|
||||||
|
filterdVariables,
|
||||||
|
A.map((e) =>
|
||||||
|
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const environmentUpdated: Environment = {
|
||||||
|
v: 1,
|
||||||
|
id: editingID.value ?? "",
|
||||||
|
name: editingName.value,
|
||||||
|
variables,
|
||||||
|
}
|
||||||
|
|
||||||
if (props.action === "new") {
|
if (props.action === "new") {
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_CREATE_ENVIRONMENT",
|
type: "HOPP_CREATE_ENVIRONMENT",
|
||||||
workspaceType: "team",
|
workspaceType: "team",
|
||||||
})
|
})
|
||||||
|
|
||||||
await pipe(
|
if (!props.isViewer) {
|
||||||
createTeamEnvironment(
|
await pipe(
|
||||||
JSON.stringify(filterdVariables),
|
createTeamEnvironment(
|
||||||
props.editingTeamId,
|
JSON.stringify(environmentUpdated.variables),
|
||||||
editingName.value
|
props.editingTeamId,
|
||||||
),
|
environmentUpdated.name
|
||||||
TE.match(
|
),
|
||||||
(err: GQLError<string>) => {
|
TE.match(
|
||||||
console.error(err)
|
(err: GQLError<string>) => {
|
||||||
toast.error(`${getErrorMessage(err)}`)
|
console.error(err)
|
||||||
},
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
() => {
|
isLoading.value = false
|
||||||
hideModal()
|
},
|
||||||
toast.success(`${t("environment.created")}`)
|
(res) => {
|
||||||
}
|
const envID = res.createTeamEnvironment.id
|
||||||
)
|
if (envID) {
|
||||||
)()
|
secretEnvironmentService.addSecretEnvironment(
|
||||||
|
envID,
|
||||||
|
secretVariables
|
||||||
|
)
|
||||||
|
}
|
||||||
|
hideModal()
|
||||||
|
toast.success(`${t("environment.created")}`)
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!props.editingEnvironment) {
|
if (!props.editingEnvironment) {
|
||||||
console.error("No Environment Found")
|
console.error("No Environment Found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await pipe(
|
if (editingID.value) {
|
||||||
updateTeamEnvironment(
|
secretEnvironmentService.addSecretEnvironment(
|
||||||
JSON.stringify(filterdVariables),
|
editingID.value,
|
||||||
props.editingEnvironment.id,
|
secretVariables
|
||||||
editingName.value
|
|
||||||
),
|
|
||||||
TE.match(
|
|
||||||
(err: GQLError<string>) => {
|
|
||||||
console.error(err)
|
|
||||||
toast.error(`${getErrorMessage(err)}`)
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
hideModal()
|
|
||||||
toast.success(`${t("environment.updated")}`)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)()
|
|
||||||
|
// If the user is a viewer, we don't need to update the environment in BE
|
||||||
|
// just update the secret environment in the local storage
|
||||||
|
if (props.isViewer) {
|
||||||
|
hideModal()
|
||||||
|
toast.success(`${t("environment.updated")}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.isViewer) {
|
||||||
|
await pipe(
|
||||||
|
updateTeamEnvironment(
|
||||||
|
JSON.stringify(environmentUpdated.variables),
|
||||||
|
props.editingEnvironment.id,
|
||||||
|
environmentUpdated.name
|
||||||
|
),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
console.error(err)
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
isLoading.value = false
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
hideModal()
|
||||||
|
toast.success(`${t("environment.updated")}`)
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@@ -331,6 +473,7 @@ const saveEnvironment = async () => {
|
|||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
editingName.value = null
|
editingName.value = null
|
||||||
|
selectedEnvOption.value = "variables"
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<tippy
|
<tippy
|
||||||
v-if="!isViewer"
|
|
||||||
ref="options"
|
ref="options"
|
||||||
interactive
|
interactive
|
||||||
trigger="click"
|
trigger="click"
|
||||||
@@ -57,6 +56,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
|
v-if="!isViewer"
|
||||||
ref="duplicate"
|
ref="duplicate"
|
||||||
:icon="IconCopy"
|
:icon="IconCopy"
|
||||||
:label="`${t('action.duplicate')}`"
|
:label="`${t('action.duplicate')}`"
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
|
v-if="!isViewer"
|
||||||
ref="exportAsJsonEl"
|
ref="exportAsJsonEl"
|
||||||
:icon="IconEdit"
|
:icon="IconEdit"
|
||||||
:label="`${t('export.as_json')}`"
|
:label="`${t('export.as_json')}`"
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
|
v-if="!isViewer"
|
||||||
ref="deleteAction"
|
ref="deleteAction"
|
||||||
:icon="IconTrash2"
|
:icon="IconTrash2"
|
||||||
:label="`${t('action.delete')}`"
|
:label="`${t('action.delete')}`"
|
||||||
@@ -124,6 +126,8 @@ import IconMoreVertical from "~icons/lucide/more-vertical"
|
|||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -137,6 +141,8 @@ const emit = defineEmits<{
|
|||||||
(e: "edit-environment"): void
|
(e: "edit-environment"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const secretEnvironmentService = useService(SecretEnvironmentService)
|
||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
const exportEnvironmentAsJSON = () =>
|
const exportEnvironmentAsJSON = () =>
|
||||||
@@ -161,6 +167,7 @@ const removeEnvironment = () => {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
toast.success(`${t("team_environment.deleted")}`)
|
toast.success(`${t("team_environment.deleted")}`)
|
||||||
|
secretEnvironmentService.deleteSecretEnvironment(props.environment.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)()
|
)()
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
:editing-environment="editingEnvironment"
|
:editing-environment="editingEnvironment"
|
||||||
:editing-team-id="team?.id"
|
:editing-team-id="team?.id"
|
||||||
:editing-variable-name="editingVariableName"
|
:editing-variable-name="editingVariableName"
|
||||||
|
:is-secret-option-selected="secretOptionSelected"
|
||||||
:is-viewer="team?.myRole === 'VIEWER'"
|
:is-viewer="team?.myRole === 'VIEWER'"
|
||||||
@hide-modal="displayModalEdit(false)"
|
@hide-modal="displayModalEdit(false)"
|
||||||
/>
|
/>
|
||||||
@@ -148,6 +149,7 @@ const showModalDetails = ref(false)
|
|||||||
const action = ref<"new" | "edit">("edit")
|
const action = ref<"new" | "edit">("edit")
|
||||||
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
const editingEnvironment = ref<TeamEnvironment | null>(null)
|
||||||
const editingVariableName = ref("")
|
const editingVariableName = ref("")
|
||||||
|
const secretOptionSelected = ref(false)
|
||||||
|
|
||||||
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
const isTeamViewer = computed(() => props.team?.myRole === "VIEWER")
|
||||||
|
|
||||||
@@ -171,6 +173,8 @@ const editEnvironment = (environment: TeamEnvironment | null) => {
|
|||||||
}
|
}
|
||||||
const resetSelectedData = () => {
|
const resetSelectedData = () => {
|
||||||
editingEnvironment.value = null
|
editingEnvironment.value = null
|
||||||
|
editingVariableName.value = ""
|
||||||
|
secretOptionSelected.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
@@ -187,12 +191,15 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
"modals.team.environment.edit",
|
"modals.team.environment.edit",
|
||||||
({ envName, variableName }) => {
|
({ envName, variableName, isSecret }) => {
|
||||||
if (variableName) editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
const teamEnvToEdit = props.teamEnvironments.find(
|
const teamEnvToEdit = props.teamEnvironments.find(
|
||||||
(environment) => environment.environment.name === envName
|
(environment) => environment.environment.name === envName
|
||||||
)
|
)
|
||||||
if (teamEnvToEdit) editEnvironment(teamEnvToEdit)
|
if (teamEnvToEdit) {
|
||||||
|
editEnvironment(teamEnvToEdit)
|
||||||
|
secretOptionSelected.value = isSecret ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -31,17 +31,6 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
|
||||||
label="None"
|
|
||||||
:icon="authName === 'None' ? IconCircleDot : IconCircle"
|
|
||||||
:active="authName === 'None'"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
auth.authType = 'none'
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-if="!isRootCollection"
|
v-if="!isRootCollection"
|
||||||
label="Inherit"
|
label="Inherit"
|
||||||
@@ -54,6 +43,17 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
label="None"
|
||||||
|
:icon="authName === 'None' ? IconCircleDot : IconCircle"
|
||||||
|
:active="authName === 'None'"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
auth.authType = 'none'
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
label="Basic Auth"
|
label="Basic Auth"
|
||||||
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
||||||
@@ -284,7 +284,7 @@ const authActive = pluckRef(auth, "authActive")
|
|||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
auth.value = {
|
auth.value = {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'graphqlHeaders')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -315,6 +315,8 @@ import { commonHeaders } from "~/helpers/headers"
|
|||||||
import { useCodemirror } from "@composables/codemirror"
|
import { useCodemirror } from "@composables/codemirror"
|
||||||
import { objRemoveKey } from "~/helpers/functional/object"
|
import { objRemoveKey } from "~/helpers/functional/object"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
import { HoppGQLHeader } from "~/helpers/graphql"
|
import { HoppGQLHeader } from "~/helpers/graphql"
|
||||||
import { throwError } from "~/helpers/functional/error"
|
import { throwError } from "~/helpers/functional/error"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
@@ -338,7 +340,7 @@ const request = useVModel(props, "modelValue", emit)
|
|||||||
|
|
||||||
const idTicker = ref(0)
|
const idTicker = ref(0)
|
||||||
|
|
||||||
const linewrapEnabled = ref(false)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "graphqlHeaders")
|
||||||
const bulkMode = ref(false)
|
const bulkMode = ref(false)
|
||||||
const bulkHeaders = ref("")
|
const bulkHeaders = ref("")
|
||||||
|
|
||||||
@@ -353,7 +355,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/x-yaml",
|
mode: "text/x-yaml",
|
||||||
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -61,9 +61,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'graphqlQuery')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -112,6 +112,8 @@ import {
|
|||||||
socketDisconnect,
|
socketDisconnect,
|
||||||
subscriptionState,
|
subscriptionState,
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const queryEditor = ref<any | null>(null)
|
const queryEditor = ref<any | null>(null)
|
||||||
@@ -137,7 +139,7 @@ const prettifyQueryIcon = refAutoReset<
|
|||||||
typeof IconWand | typeof IconCheck | typeof IconInfo
|
typeof IconWand | typeof IconCheck | typeof IconInfo
|
||||||
>(IconWand, 1000)
|
>(IconWand, 1000)
|
||||||
|
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "graphqlQuery")
|
||||||
|
|
||||||
const selectedOperation = ref<gql.OperationDefinitionNode | null>(null)
|
const selectedOperation = ref<gql.OperationDefinitionNode | null>(null)
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "graphql",
|
mode: "graphql",
|
||||||
placeholder: `${t("request.query")}`,
|
placeholder: `${t("request.query")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: createGQLQueryLinter(schema),
|
linter: createGQLQueryLinter(schema),
|
||||||
completer: queryCompleter(schema),
|
completer: queryCompleter(schema),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h-full flex-1 flex-col">
|
<div class="h-full">
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedOptionTab"
|
v-model="selectedOptionTab"
|
||||||
styles="sticky top-0 bg-primary z-10 border-b-0"
|
styles="sticky top-0 bg-primary z-10 border-b-0"
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="
|
||||||
|
toggleNestedSetting('WRAP_LINES', 'graphqlResponseBody')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
@@ -70,7 +72,9 @@
|
|||||||
</tippy>
|
</tippy>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="schemaEditor" class="flex flex-1 flex-col"></div>
|
<div class="h-full">
|
||||||
|
<div ref="schemaEditor"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<component
|
<component
|
||||||
:is="response[0].error.component"
|
:is="response[0].error.component"
|
||||||
@@ -99,6 +103,8 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
import interfaceLanguages from "~/helpers/utils/interfaceLanguages"
|
import interfaceLanguages from "~/helpers/utils/interfaceLanguages"
|
||||||
import {
|
import {
|
||||||
useCopyInterface,
|
useCopyInterface,
|
||||||
@@ -133,8 +139,8 @@ const responseString = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const schemaEditor = ref<any | null>(null)
|
const schemaEditor = ref<any | null>(null)
|
||||||
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "graphqlResponseBody")
|
||||||
const copyInterfaceTippyActions = ref<any | null>(null)
|
const copyInterfaceTippyActions = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
schemaEditor,
|
schemaEditor,
|
||||||
@@ -143,7 +149,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/ld+json",
|
mode: "application/ld+json",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -127,9 +127,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'graphqlSchema')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -145,11 +145,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="schemaString" class="h-full relative w-full">
|
||||||
v-if="schemaString"
|
<div ref="schemaEditor" class="absolute inset-0"></div>
|
||||||
ref="schemaEditor"
|
</div>
|
||||||
class="flex flex-1 flex-col"
|
|
||||||
></div>
|
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else
|
v-else
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
@@ -202,6 +200,8 @@ import {
|
|||||||
subscriptionFields,
|
subscriptionFields,
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
||||||
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
||||||
@@ -349,7 +349,7 @@ const handleJumpToType = async (type: GraphQLType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const schemaEditor = ref<any | null>(null)
|
const schemaEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "graphqlSchema")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
schemaEditor,
|
schemaEditor,
|
||||||
@@ -358,7 +358,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "graphql",
|
mode: "graphql",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -49,9 +49,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'graphqlVariables')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -67,7 +67,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="variableEditor" class="flex flex-1 flex-col"></div>
|
<div class="h-full relative">
|
||||||
|
<div ref="variableEditor" class="flex flex-1 flex-col"></div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -93,6 +95,8 @@ import {
|
|||||||
socketDisconnect,
|
socketDisconnect,
|
||||||
subscriptionState,
|
subscriptionState,
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -114,7 +118,7 @@ const variableString = useVModel(props, "modelValue", emit)
|
|||||||
|
|
||||||
const variableEditor = ref<any | null>(null)
|
const variableEditor = ref<any | null>(null)
|
||||||
|
|
||||||
const linewrapEnabled = ref(false)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "graphqlVariables")
|
||||||
|
|
||||||
const copyVariablesIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
const copyVariablesIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
IconCopy,
|
IconCopy,
|
||||||
@@ -131,7 +135,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/ld+json",
|
mode: "application/ld+json",
|
||||||
placeholder: `${t("request.variables")}`,
|
placeholder: `${t("request.variables")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: computed(() =>
|
linter: computed(() =>
|
||||||
variableString.value.length > 0 ? jsonLinter : null
|
variableString.value.length > 0 ? jsonLinter : null
|
||||||
|
|||||||
@@ -331,7 +331,8 @@ const deleteHistory = (entry: HistoryEntry) => {
|
|||||||
const addToCollection = (entry: HistoryEntry) => {
|
const addToCollection = (entry: HistoryEntry) => {
|
||||||
if (props.page === "rest") {
|
if (props.page === "rest") {
|
||||||
invokeAction("request.save-as", {
|
invokeAction("request.save-as", {
|
||||||
request: entry.request,
|
requestType: "rest",
|
||||||
|
request: entry.request as HoppRESTRequest,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,17 +31,6 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
|
||||||
label="None"
|
|
||||||
:icon="authName === 'None' ? IconCircleDot : IconCircle"
|
|
||||||
:active="authName === 'None'"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
auth.authType = 'none'
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-if="!isRootCollection"
|
v-if="!isRootCollection"
|
||||||
label="Inherit"
|
label="Inherit"
|
||||||
@@ -54,6 +43,17 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
label="None"
|
||||||
|
:icon="authName === 'None' ? IconCircleDot : IconCircle"
|
||||||
|
:active="authName === 'None'"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
auth.authType = 'none'
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
label="Basic Auth"
|
label="Basic Auth"
|
||||||
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
||||||
@@ -265,7 +265,7 @@ const authActive = pluckRef(auth, "authActive")
|
|||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
auth.value = {
|
auth.value = {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,9 +86,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'codeGen')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
@@ -161,6 +161,8 @@ import cloneDeep from "lodash-es/cloneDeep"
|
|||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -187,6 +189,8 @@ const copyCodeIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
|||||||
const requestCode = computed(() => {
|
const requestCode = computed(() => {
|
||||||
const aggregateEnvs = getAggregateEnvs()
|
const aggregateEnvs = getAggregateEnvs()
|
||||||
const env: Environment = {
|
const env: Environment = {
|
||||||
|
v: 1,
|
||||||
|
id: "env",
|
||||||
name: "Env",
|
name: "Env",
|
||||||
variables: aggregateEnvs,
|
variables: aggregateEnvs,
|
||||||
}
|
}
|
||||||
@@ -222,7 +226,7 @@ const requestCode = computed(() => {
|
|||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
const generatedCode = ref<any | null>(null)
|
const generatedCode = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "codeGen")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
generatedCode,
|
generatedCode,
|
||||||
@@ -231,7 +235,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/plain",
|
mode: "text/plain",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
v-if="bulkMode"
|
v-if="bulkMode"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpHeaders')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -49,7 +49,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-1 flex-col"></div>
|
<div v-if="bulkMode" class="h-full relative w-full">
|
||||||
|
<div ref="bulkEditor" class="absolute inset-0"></div>
|
||||||
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<draggable
|
<draggable
|
||||||
v-model="workingHeaders"
|
v-model="workingHeaders"
|
||||||
@@ -332,6 +334,8 @@ import { useVModel } from "@vueuse/core"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -346,7 +350,7 @@ const idTicker = ref(0)
|
|||||||
const bulkMode = ref(false)
|
const bulkMode = ref(false)
|
||||||
const bulkHeaders = ref("")
|
const bulkHeaders = ref("")
|
||||||
const bulkEditor = ref<any | null>(null)
|
const bulkEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpHeaders")
|
||||||
|
|
||||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||||
|
|
||||||
@@ -371,7 +375,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/x-yaml",
|
mode: "text/x-yaml",
|
||||||
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter,
|
linter,
|
||||||
completer: null,
|
completer: null,
|
||||||
@@ -553,7 +557,7 @@ const clearContent = () => {
|
|||||||
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, getAggregateEnvs())
|
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, getAggregateEnvs())
|
||||||
|
|
||||||
const computedHeaders = computed(() =>
|
const computedHeaders = computed(() =>
|
||||||
getComputedHeaders(request.value, aggregateEnvs.value).map(
|
getComputedHeaders(request.value, aggregateEnvs.value, false).map(
|
||||||
(header, index) => ({
|
(header, index) => ({
|
||||||
id: `header-${index}`,
|
id: `header-${index}`,
|
||||||
...header,
|
...header,
|
||||||
@@ -606,7 +610,8 @@ const inheritedProperties = computed(() => {
|
|||||||
const computedAuthHeader = getComputedAuthHeaders(
|
const computedAuthHeader = getComputedAuthHeaders(
|
||||||
aggregateEnvs.value,
|
aggregateEnvs.value,
|
||||||
request.value,
|
request.value,
|
||||||
props.inheritedProperties.auth.inheritedAuth
|
props.inheritedProperties.auth.inheritedAuth,
|
||||||
|
false
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'importCurl')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
@@ -96,6 +96,8 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
|||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ const tabs = useService(RESTTabService)
|
|||||||
const curl = ref("")
|
const curl = ref("")
|
||||||
|
|
||||||
const curlEditor = ref<any | null>(null)
|
const curlEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "importCurl")
|
||||||
|
|
||||||
const props = defineProps<{ show: boolean; text: string }>()
|
const props = defineProps<{ show: boolean; text: string }>()
|
||||||
|
|
||||||
@@ -117,7 +119,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/x-sh",
|
mode: "application/x-sh",
|
||||||
placeholder: `${t("request.enter_curl")}`,
|
placeholder: `${t("request.enter_curl")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -3,14 +3,25 @@
|
|||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="oidcDiscoveryURL"
|
v-model="oidcDiscoveryURL"
|
||||||
|
:styles="
|
||||||
|
hasAccessTokenOrAuthURL ? 'pointer-events-none opacity-70' : ''
|
||||||
|
"
|
||||||
placeholder="OpenID Connect Discovery URL"
|
placeholder="OpenID Connect Discovery URL"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput v-model="authURL" placeholder="Authorization URL" />
|
<SmartEnvInput
|
||||||
|
v-model="authURL"
|
||||||
|
placeholder="Authorization URL"
|
||||||
|
:styles="hasOIDCURL ? 'pointer-events-none opacity-70' : ''"
|
||||||
|
></SmartEnvInput>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput v-model="accessTokenURL" placeholder="Access Token URL" />
|
<SmartEnvInput
|
||||||
|
v-model="accessTokenURL"
|
||||||
|
placeholder="Access Token URL"
|
||||||
|
:styles="hasOIDCURL ? 'pointer-events-none opacity-70' : ''"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput v-model="clientID" placeholder="Client ID" />
|
<SmartEnvInput v-model="clientID" placeholder="Client ID" />
|
||||||
@@ -44,6 +55,7 @@ import { useToast } from "@composables/toast"
|
|||||||
import { tokenRequest } from "~/helpers/oauth"
|
import { tokenRequest } from "~/helpers/oauth"
|
||||||
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -66,10 +78,16 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const oidcDiscoveryURL = pluckRef(auth, "oidcDiscoveryURL")
|
const oidcDiscoveryURL = pluckRef(auth, "oidcDiscoveryURL")
|
||||||
|
const hasOIDCURL = computed(() => {
|
||||||
|
return oidcDiscoveryURL.value
|
||||||
|
})
|
||||||
|
|
||||||
const authURL = pluckRef(auth, "authURL")
|
const authURL = pluckRef(auth, "authURL")
|
||||||
|
|
||||||
const accessTokenURL = pluckRef(auth, "accessTokenURL")
|
const accessTokenURL = pluckRef(auth, "accessTokenURL")
|
||||||
|
const hasAccessTokenOrAuthURL = computed(() => {
|
||||||
|
return accessTokenURL.value || authURL.value
|
||||||
|
})
|
||||||
|
|
||||||
const clientID = pluckRef(auth, "clientID")
|
const clientID = pluckRef(auth, "clientID")
|
||||||
|
|
||||||
@@ -88,13 +106,11 @@ function translateTokenRequestError(error: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAccessTokenRequest = async () => {
|
const handleAccessTokenRequest = async () => {
|
||||||
if (
|
if (!oidcDiscoveryURL.value && !(authURL.value || accessTokenURL.value)) {
|
||||||
oidcDiscoveryURL.value === "" &&
|
|
||||||
(authURL.value === "" || accessTokenURL.value === "")
|
|
||||||
) {
|
|
||||||
toast.error(`${t("error.incomplete_config_urls")}`)
|
toast.error(`${t("error.incomplete_config_urls")}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const envs = getCombinedEnvVariables()
|
const envs = getCombinedEnvVariables()
|
||||||
const envVars = [...envs.selected, ...envs.global]
|
const envVars = [...envs.selected, ...envs.global]
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
v-if="bulkMode"
|
v-if="bulkMode"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpParams')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -44,7 +44,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-1 flex-col"></div>
|
<div v-if="bulkMode" class="h-full relative">
|
||||||
|
<div ref="bulkEditor" class="absolute inset-0"></div>
|
||||||
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<draggable
|
<draggable
|
||||||
v-model="workingParams"
|
v-model="workingParams"
|
||||||
@@ -205,6 +207,8 @@ import { useVModel } from "@vueuse/core"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
@@ -217,7 +221,7 @@ const idTicker = ref(0)
|
|||||||
const bulkMode = ref(false)
|
const bulkMode = ref(false)
|
||||||
const bulkParams = ref("")
|
const bulkParams = ref("")
|
||||||
const bulkEditor = ref<any | null>(null)
|
const bulkEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpParams")
|
||||||
|
|
||||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||||
|
|
||||||
@@ -228,7 +232,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/x-yaml",
|
mode: "text/x-yaml",
|
||||||
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter,
|
linter,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -23,15 +23,15 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpPreRequest')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<div class="w-2/3 border-r border-dividerLight">
|
<div class="w-2/3 border-r border-dividerLight h-full relative">
|
||||||
<div ref="preRequestEditor" class="h-full"></div>
|
<div ref="preRequestEditor" class="h-full absolute inset-0"></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
|
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
|
||||||
@@ -72,6 +72,8 @@ import linter from "~/helpers/editor/linting/preRequest"
|
|||||||
import completer from "~/helpers/editor/completion/preRequest"
|
import completer from "~/helpers/editor/completion/preRequest"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ const emit = defineEmits<{
|
|||||||
const preRequestScript = useVModel(props, "modelValue", emit)
|
const preRequestScript = useVModel(props, "modelValue", emit)
|
||||||
|
|
||||||
const preRequestEditor = ref<any | null>(null)
|
const preRequestEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpPreRequest")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
preRequestEditor,
|
preRequestEditor,
|
||||||
@@ -93,7 +95,7 @@ useCodemirror(
|
|||||||
reactive({
|
reactive({
|
||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/javascript",
|
mode: "application/javascript",
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
placeholder: `${t("preRequest.javascript_code")}`,
|
placeholder: `${t("preRequest.javascript_code")}`,
|
||||||
},
|
},
|
||||||
linter,
|
linter,
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpRequestBody')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="
|
v-if="
|
||||||
@@ -59,7 +59,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="rawBodyParameters" class="flex flex-1 flex-col"></div>
|
<div class="h-full relative">
|
||||||
|
<div ref="rawBodyParameters" class="absolute inset-0"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -85,6 +87,8 @@ import { isJSONContentType } from "~/helpers/utils/contenttypes"
|
|||||||
import jsonLinter from "~/helpers/editor/linting/json"
|
import jsonLinter from "~/helpers/editor/linting/json"
|
||||||
import { readFileAsText } from "~/helpers/functional/files"
|
import { readFileAsText } from "~/helpers/functional/files"
|
||||||
import xmlFormat from "xml-formatter"
|
import xmlFormat from "xml-formatter"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
type PossibleContentTypes = Exclude<
|
type PossibleContentTypes = Exclude<
|
||||||
ValidContentTypes,
|
ValidContentTypes,
|
||||||
@@ -122,7 +126,7 @@ const langLinter = computed(() =>
|
|||||||
isJSONContentType(body.value.contentType) ? jsonLinter : null
|
isJSONContentType(body.value.contentType) ? jsonLinter : null
|
||||||
)
|
)
|
||||||
|
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpRequestBody")
|
||||||
const rawBodyParameters = ref<any | null>(null)
|
const rawBodyParameters = ref<any | null>(null)
|
||||||
|
|
||||||
const codemirrorValue: Ref<string | undefined> =
|
const codemirrorValue: Ref<string | undefined> =
|
||||||
@@ -148,7 +152,7 @@ useCodemirror(
|
|||||||
codemirrorValue,
|
codemirrorValue,
|
||||||
reactive({
|
reactive({
|
||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
mode: rawInputEditorLang,
|
mode: rawInputEditorLang,
|
||||||
placeholder: t("request.raw_body").toString(),
|
placeholder: t("request.raw_body").toString(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ import IconShare2 from "~icons/lucide/share-2"
|
|||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
@@ -263,6 +263,7 @@ import { HoppTab } from "~/services/tab"
|
|||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
@@ -326,6 +327,8 @@ const inspectionService = useService(InspectionService)
|
|||||||
|
|
||||||
const tabs = useService(RESTTabService)
|
const tabs = useService(RESTTabService)
|
||||||
|
|
||||||
|
const workspaceService = useService(WorkspaceService)
|
||||||
|
|
||||||
const newSendRequest = async () => {
|
const newSendRequest = async () => {
|
||||||
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
|
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
|
||||||
toast.error(`${t("empty.endpoint")}`)
|
toast.error(`${t("empty.endpoint")}`)
|
||||||
@@ -341,6 +344,7 @@ const newSendRequest = async () => {
|
|||||||
type: "HOPP_REQUEST_RUN",
|
type: "HOPP_REQUEST_RUN",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
strategy: interceptorService.currentInterceptorID.value!,
|
strategy: interceptorService.currentInterceptorID.value!,
|
||||||
|
workspaceType: workspaceService.currentWorkspace.value.type,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [cancel, streamPromise] = runRESTRequest$(tab)
|
const [cancel, streamPromise] = runRESTRequest$(tab)
|
||||||
@@ -395,17 +399,14 @@ const newSendRequest = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ensureMethodInEndpoint = () => {
|
const ensureMethodInEndpoint = () => {
|
||||||
if (
|
const endpoint = newEndpoint.value.trim()
|
||||||
!/^http[s]?:\/\//.test(newEndpoint.value) &&
|
tab.value.document.request.endpoint = endpoint
|
||||||
!newEndpoint.value.startsWith("<<")
|
if (!/^http[s]?:\/\//.test(endpoint) && !endpoint.startsWith("<<")) {
|
||||||
) {
|
const domain = endpoint.split(/[/:#?]+/)[0]
|
||||||
const domain = newEndpoint.value.split(/[/:#?]+/)[0]
|
|
||||||
if (domain === "localhost" || /([0-9]+\.)*[0-9]/.test(domain)) {
|
if (domain === "localhost" || /([0-9]+\.)*[0-9]/.test(domain)) {
|
||||||
tab.value.document.request.endpoint =
|
tab.value.document.request.endpoint = "http://" + endpoint
|
||||||
"http://" + tab.value.document.request.endpoint
|
|
||||||
} else {
|
} else {
|
||||||
tab.value.document.request.endpoint =
|
tab.value.document.request.endpoint = "https://" + endpoint
|
||||||
"https://" + tab.value.document.request.endpoint
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,25 +578,12 @@ defineActionHandler("request.share-request", shareRequest)
|
|||||||
defineActionHandler("request.method.next", cycleDownMethod)
|
defineActionHandler("request.method.next", cycleDownMethod)
|
||||||
defineActionHandler("request.method.prev", cycleUpMethod)
|
defineActionHandler("request.method.prev", cycleUpMethod)
|
||||||
defineActionHandler("request.save", saveRequest)
|
defineActionHandler("request.save", saveRequest)
|
||||||
defineActionHandler(
|
defineActionHandler("request.save-as", (req) => {
|
||||||
"request.save-as",
|
showSaveRequestModal.value = true
|
||||||
(
|
if (req?.requestType === "rest") {
|
||||||
req:
|
request.value = req.request
|
||||||
| {
|
|
||||||
requestType: "rest"
|
|
||||||
request: HoppRESTRequest
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
requestType: "gql"
|
|
||||||
request: HoppGQLRequest
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
showSaveRequestModal.value = true
|
|
||||||
if (req && req.requestType === "rest") {
|
|
||||||
request.value = req.request
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
defineActionHandler("request.method.get", () => updateMethod("GET"))
|
defineActionHandler("request.method.get", () => updateMethod("GET"))
|
||||||
defineActionHandler("request.method.post", () => updateMethod("POST"))
|
defineActionHandler("request.method.post", () => updateMethod("POST"))
|
||||||
defineActionHandler("request.method.put", () => updateMethod("PUT"))
|
defineActionHandler("request.method.put", () => updateMethod("PUT"))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #secondary>
|
<template #secondary>
|
||||||
<HttpResponse v-model:document="tab.document" />
|
<HttpResponse v-model:document="tab.document" :is-embed="false" />
|
||||||
</template>
|
</template>
|
||||||
</AppPaneLayout>
|
</AppPaneLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
class="flex flex-col focus:outline-none"
|
class="flex flex-col focus:outline-none"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.r="renameAction?.$el.click()"
|
@keyup.r="renameAction?.$el.click()"
|
||||||
|
@keyup.s="shareRequestAction?.$el.click()"
|
||||||
@keyup.d="duplicateAction?.$el.click()"
|
@keyup.d="duplicateAction?.$el.click()"
|
||||||
@keyup.w="closeAction?.$el.click()"
|
@keyup.w="closeAction?.$el.click()"
|
||||||
@keyup.x="closeOthersAction?.$el.click()"
|
@keyup.x="closeOthersAction?.$el.click()"
|
||||||
@@ -58,6 +59,18 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="shareRequestAction"
|
||||||
|
:icon="IconShare2"
|
||||||
|
:label="t('tab.share_tab_request')"
|
||||||
|
:shortcut="['S']"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
emit('share-tab-request')
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-if="isRemovable"
|
v-if="isRemovable"
|
||||||
ref="closeAction"
|
ref="closeAction"
|
||||||
@@ -99,6 +112,7 @@ import IconXCircle from "~icons/lucide/x-circle"
|
|||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
import IconCopy from "~icons/lucide/copy"
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
import { HoppTab } from "~/services/tab"
|
import { HoppTab } from "~/services/tab"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
@@ -114,6 +128,7 @@ const emit = defineEmits<{
|
|||||||
(event: "close-tab"): void
|
(event: "close-tab"): void
|
||||||
(event: "close-other-tabs"): void
|
(event: "close-other-tabs"): void
|
||||||
(event: "duplicate-tab"): void
|
(event: "duplicate-tab"): void
|
||||||
|
(event: "share-tab-request"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
@@ -123,4 +138,5 @@ const renameAction = ref<HTMLButtonElement | null>(null)
|
|||||||
const closeAction = ref<HTMLButtonElement | null>(null)
|
const closeAction = ref<HTMLButtonElement | null>(null)
|
||||||
const closeOthersAction = ref<HTMLButtonElement | null>(null)
|
const closeOthersAction = ref<HTMLButtonElement | null>(null)
|
||||||
const duplicateAction = ref<HTMLButtonElement | null>(null)
|
const duplicateAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const shareRequestAction = ref<HTMLButtonElement | null>(null)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -211,7 +211,6 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import {
|
import {
|
||||||
globalEnv$,
|
globalEnv$,
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setGlobalEnvVariables,
|
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
|
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
|
||||||
@@ -225,6 +224,7 @@ import { useColorMode } from "~/composables/theming"
|
|||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: HoppTestResult | null | undefined
|
modelValue: HoppTestResult | null | undefined
|
||||||
@@ -304,9 +304,10 @@ const globalHasAdditions = computed(() => {
|
|||||||
|
|
||||||
const addEnvToGlobal = () => {
|
const addEnvToGlobal = () => {
|
||||||
if (!testResults.value?.envDiff.selected.additions) return
|
if (!testResults.value?.envDiff.selected.additions) return
|
||||||
setGlobalEnvVariables([
|
|
||||||
...globalEnvVars.value,
|
invokeAction("modals.global.environment.update", {
|
||||||
...testResults.value.envDiff.selected.additions,
|
variables: testResults.value.envDiff.selected.additions,
|
||||||
])
|
isSecret: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -23,15 +23,15 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpTest')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<div class="w-2/3 border-r border-dividerLight">
|
<div class="w-2/3 border-r border-dividerLight h-full relative">
|
||||||
<div ref="testScriptEditor" class="h-full"></div>
|
<div ref="testScriptEditor" class="h-full absolute inset-0"></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
|
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
|
||||||
@@ -72,6 +72,8 @@ import linter from "~/helpers/editor/linting/testScript"
|
|||||||
import completer from "~/helpers/editor/completion/testScript"
|
import completer from "~/helpers/editor/completion/testScript"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ const props = defineProps<{
|
|||||||
const emit = defineEmits(["update:modelValue"])
|
const emit = defineEmits(["update:modelValue"])
|
||||||
const testScript = useVModel(props, "modelValue", emit)
|
const testScript = useVModel(props, "modelValue", emit)
|
||||||
const testScriptEditor = ref<any | null>(null)
|
const testScriptEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpTest")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
testScriptEditor,
|
testScriptEditor,
|
||||||
@@ -89,7 +91,7 @@ useCodemirror(
|
|||||||
reactive({
|
reactive({
|
||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/javascript",
|
mode: "application/javascript",
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
placeholder: `${t("test.javascript_code")}`,
|
placeholder: `${t("test.javascript_code")}`,
|
||||||
},
|
},
|
||||||
linter,
|
linter,
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
v-if="bulkMode"
|
v-if="bulkMode"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpUrlEncoded')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -44,7 +44,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-1 flex-col"></div>
|
<div v-if="bulkMode" class="h-full relative">
|
||||||
|
<div ref="bulkEditor" class="absolute inset-0"></div>
|
||||||
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<draggable
|
<draggable
|
||||||
v-model="workingUrlEncodedParams"
|
v-model="workingUrlEncodedParams"
|
||||||
@@ -196,6 +198,8 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { objRemoveKey } from "~/helpers/functional/object"
|
import { objRemoveKey } from "~/helpers/functional/object"
|
||||||
import { throwError } from "~/helpers/functional/error"
|
import { throwError } from "~/helpers/functional/error"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
type Body = HoppRESTReqBody & {
|
type Body = HoppRESTReqBody & {
|
||||||
contentType: "application/x-www-form-urlencoded"
|
contentType: "application/x-www-form-urlencoded"
|
||||||
@@ -220,7 +224,7 @@ const idTicker = ref(0)
|
|||||||
const bulkMode = ref(false)
|
const bulkMode = ref(false)
|
||||||
const bulkUrlEncodedParams = ref("")
|
const bulkUrlEncodedParams = ref("")
|
||||||
const bulkEditor = ref<any | null>(null)
|
const bulkEditor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpUrlEncoded")
|
||||||
|
|
||||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||||
|
|
||||||
@@ -231,7 +235,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/x-yaml",
|
mode: "text/x-yaml",
|
||||||
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter,
|
linter,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
@@ -44,11 +44,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-show="!previewEnabled" class="h-full">
|
||||||
v-show="!previewEnabled"
|
<div ref="htmlResponse" class="flex flex-1 flex-col"></div>
|
||||||
ref="htmlResponse"
|
</div>
|
||||||
class="flex flex-1 flex-col"
|
|
||||||
></div>
|
|
||||||
<iframe
|
<iframe
|
||||||
v-show="previewEnabled"
|
v-show="previewEnabled"
|
||||||
ref="previewFrame"
|
ref="previewFrame"
|
||||||
@@ -76,6 +74,8 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import type { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import type { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const htmlResponse = ref<any | null>(null)
|
const htmlResponse = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||||
|
|
||||||
const { responseBodyText } = useResponseBody(props.response)
|
const { responseBodyText } = useResponseBody(props.response)
|
||||||
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
||||||
@@ -104,7 +104,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "htmlmixed",
|
mode: "htmlmixed",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
@@ -119,11 +119,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="h-full">
|
||||||
ref="jsonResponse"
|
<div
|
||||||
class="flex h-auto h-full flex-1 flex-col"
|
ref="jsonResponse"
|
||||||
:class="toggleFilter ? 'responseToggleOn' : 'responseToggleOff'"
|
:class="toggleFilter ? 'responseToggleOn' : 'responseToggleOff'"
|
||||||
></div>
|
></div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="outlinePath"
|
v-if="outlinePath"
|
||||||
class="sticky bottom-0 z-10 flex flex-shrink-0 flex-nowrap overflow-auto overflow-x-auto border-t border-dividerLight bg-primaryLight px-2"
|
class="sticky bottom-0 z-10 flex flex-shrink-0 flex-nowrap overflow-auto overflow-x-auto border-t border-dividerLight bg-primaryLight px-2"
|
||||||
@@ -260,6 +261,8 @@ import {
|
|||||||
} from "@composables/lens-actions"
|
} from "@composables/lens-actions"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
import interfaceLanguages from "~/helpers/utils/interfaceLanguages"
|
import interfaceLanguages from "~/helpers/utils/interfaceLanguages"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -371,8 +374,8 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
const jsonResponse = ref<any | null>(null)
|
const jsonResponse = ref<any | null>(null)
|
||||||
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||||
const copyInterfaceTippyActions = ref<any | null>(null)
|
const copyInterfaceTippyActions = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
|
||||||
|
|
||||||
const { cursor } = useCodemirror(
|
const { cursor } = useCodemirror(
|
||||||
jsonResponse,
|
jsonResponse,
|
||||||
@@ -381,7 +384,7 @@ const { cursor } = useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/ld+json",
|
mode: "application/ld+json",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
@@ -35,7 +35,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="rawResponse" class="flex flex-1 flex-col"></div>
|
<div class="h-full">
|
||||||
|
<div ref="rawResponse" class="flex flex-1 flex-col"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -58,6 +60,8 @@ import {
|
|||||||
import { objFieldMatches } from "~/helpers/functional/object"
|
import { objFieldMatches } from "~/helpers/functional/object"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -97,7 +101,7 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||||||
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
||||||
|
|
||||||
const rawResponse = ref<any | null>(null)
|
const rawResponse = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
rawResponse,
|
rawResponse,
|
||||||
@@ -106,7 +110,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/plain",
|
mode: "text/plain",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('state.linewrap')"
|
:title="t('state.linewrap')"
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
:class="{ '!text-accent': WRAP_LINES }"
|
||||||
:icon="IconWrapText"
|
:icon="IconWrapText"
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
@@ -35,7 +35,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="xmlResponse" class="flex flex-1 flex-col"></div>
|
<div class="h-full">
|
||||||
|
<div ref="xmlResponse" class="flex flex-1 flex-col"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -58,6 +60,8 @@ import {
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { objFieldMatches } from "~/helpers/functional/object"
|
import { objFieldMatches } from "~/helpers/functional/object"
|
||||||
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -91,7 +95,7 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||||||
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
||||||
|
|
||||||
const xmlResponse = ref<any | null>(null)
|
const xmlResponse = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||||
|
|
||||||
useCodemirror(
|
useCodemirror(
|
||||||
xmlResponse,
|
xmlResponse,
|
||||||
@@ -100,7 +104,7 @@ useCodemirror(
|
|||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "application/xml",
|
mode: "application/xml",
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineWrapping: linewrapEnabled,
|
lineWrapping: WRAP_LINES,
|
||||||
},
|
},
|
||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
|
|||||||
@@ -130,7 +130,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="wsCommunicationBody" class="flex flex-1 flex-col"></div>
|
<div class="h-full">
|
||||||
|
<div ref="wsCommunicationBody" class="flex flex-1 flex-col"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||||
class="flex items-center justify-center flex-1 min-w-0 py-2 cursor-pointer pointer-events-auto"
|
class="flex items-center justify-center flex-1 min-w-0 py-2 cursor-pointer pointer-events-auto"
|
||||||
:title="`${timeStamp}`"
|
:title="`${timeStamp}`"
|
||||||
@click="openInNewTab"
|
@click="customizeSharedRequest()"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
|
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
:shortcut="['T']"
|
:shortcut="['T']"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
openInNewTab()
|
emit('open-shared-request', parseRequest)
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -128,7 +128,7 @@ const emit = defineEmits<{
|
|||||||
embedProperties?: string | null
|
embedProperties?: string | null
|
||||||
): void
|
): void
|
||||||
(e: "delete-shared-request", codeID: string): void
|
(e: "delete-shared-request", codeID: string): void
|
||||||
(e: "open-new-tab", request: HoppRESTRequest): void
|
(e: "open-shared-request", request: HoppRESTRequest): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
@@ -145,10 +145,6 @@ const requestLabelColor = computed(() =>
|
|||||||
getMethodLabelColorClassOf(parseRequest.value)
|
getMethodLabelColorClassOf(parseRequest.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
const openInNewTab = () => {
|
|
||||||
emit("open-new-tab", parseRequest.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const customizeSharedRequest = () => {
|
const customizeSharedRequest = () => {
|
||||||
const embedProperties = props.request.properties
|
const embedProperties = props.request.properties
|
||||||
emit(
|
emit(
|
||||||
|
|||||||
@@ -9,8 +9,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-end overflow-x-auto border-b border-dividerLight bg-primary"
|
class="sticky top-sidebarPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary"
|
||||||
>
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('action.new')"
|
||||||
|
:icon="IconPlus"
|
||||||
|
class="!rounded-none"
|
||||||
|
@click="shareRequest()"
|
||||||
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
to="https://docs.hoppscotch.io/documentation/features/widgets"
|
to="https://docs.hoppscotch.io/documentation/features/widgets"
|
||||||
@@ -47,7 +53,7 @@
|
|||||||
:request="request"
|
:request="request"
|
||||||
@customize-shared-request="customizeSharedRequest"
|
@customize-shared-request="customizeSharedRequest"
|
||||||
@delete-shared-request="deleteSharedRequest"
|
@delete-shared-request="deleteSharedRequest"
|
||||||
@open-new-tab="openInNewTab"
|
@open-shared-request="openRequestInNewTab"
|
||||||
/>
|
/>
|
||||||
<HoppSmartIntersection
|
<HoppSmartIntersection
|
||||||
v-if="hasMoreSharedRequests"
|
v-if="hasMoreSharedRequests"
|
||||||
@@ -70,7 +76,15 @@
|
|||||||
:alt="`${t('empty.shared_requests')}`"
|
:alt="`${t('empty.shared_requests')}`"
|
||||||
:text="t('empty.shared_requests')"
|
:text="t('empty.shared_requests')"
|
||||||
@drop.stop
|
@drop.stop
|
||||||
/>
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:label="t('add.new')"
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="shareRequest()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
@@ -95,6 +109,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import ShortcodeListAdapter from "~/helpers/shortcode/ShortcodeListAdapter"
|
import ShortcodeListAdapter from "~/helpers/shortcode/ShortcodeListAdapter"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
@@ -270,6 +285,17 @@ onAuthEvent((ev) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const shareRequest = () => {
|
||||||
|
if (currentUser.value) {
|
||||||
|
const tab = restTab.currentActiveTab
|
||||||
|
invokeAction("share.request", {
|
||||||
|
request: tab.value.document.request,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
invokeAction("modals.login.toggle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const deleteSharedRequest = (codeID: string) => {
|
const deleteSharedRequest = (codeID: string) => {
|
||||||
if (currentUser.value) {
|
if (currentUser.value) {
|
||||||
sharedRequestID.value = codeID
|
sharedRequestID.value = codeID
|
||||||
@@ -434,13 +460,6 @@ const copySharedRequest = (payload: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openInNewTab = (request: HoppRESTRequest) => {
|
|
||||||
restTab.createNewTab({
|
|
||||||
isDirty: false,
|
|
||||||
request,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveConfirmModal = (title: string | null) => {
|
const resolveConfirmModal = (title: string | null) => {
|
||||||
if (title === `${t("confirm.remove_shared_request")}`) onDeleteSharedRequest()
|
if (title === `${t("confirm.remove_shared_request")}`) onDeleteSharedRequest()
|
||||||
else {
|
else {
|
||||||
@@ -465,6 +484,13 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openRequestInNewTab = (request: HoppRESTRequest) => {
|
||||||
|
restTab.createNewTab({
|
||||||
|
isDirty: false,
|
||||||
|
request,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
defineActionHandler("share.request", ({ request }) => {
|
defineActionHandler("share.request", ({ request }) => {
|
||||||
requestToShare.value = request
|
requestToShare.value = request
|
||||||
displayShareRequestModal(true)
|
displayShareRequestModal(true)
|
||||||
|
|||||||
@@ -3,7 +3,18 @@
|
|||||||
<div
|
<div
|
||||||
class="no-scrollbar absolute inset-0 flex flex-1 divide-x divide-dividerLight overflow-x-auto"
|
class="no-scrollbar absolute inset-0 flex flex-1 divide-x divide-dividerLight overflow-x-auto"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
v-if="isSecret"
|
||||||
|
id="secret"
|
||||||
|
v-model="secretText"
|
||||||
|
name="secret"
|
||||||
|
:placeholder="t('environment.secret_value')"
|
||||||
|
class="flex flex-1 bg-transparent px-4"
|
||||||
|
:class="styles"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
|
v-else
|
||||||
ref="editor"
|
ref="editor"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
class="flex flex-1"
|
class="flex flex-1"
|
||||||
@@ -11,7 +22,14 @@
|
|||||||
@click="emit('click', $event)"
|
@click="emit('click', $event)"
|
||||||
@keydown="handleKeystroke"
|
@keydown="handleKeystroke"
|
||||||
@focusin="showSuggestionPopover = true"
|
@focusin="showSuggestionPopover = true"
|
||||||
></div>
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="secret"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="isSecret ? t('action.show_secret') : t('action.hide_secret')"
|
||||||
|
:icon="isSecret ? IconEyeoff : IconEye"
|
||||||
|
@click="toggleSecret"
|
||||||
|
/>
|
||||||
<AppInspection
|
<AppInspection
|
||||||
:inspection-results="inspectionResults"
|
:inspection-results="inspectionResults"
|
||||||
class="sticky inset-y-0 right-0 rounded-r bg-primary"
|
class="sticky inset-y-0 right-0 rounded-r bg-primary"
|
||||||
@@ -61,18 +79,29 @@ import { history, historyKeymap } from "@codemirror/commands"
|
|||||||
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
||||||
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
|
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
|
import {
|
||||||
|
AggregateEnvironment,
|
||||||
|
aggregateEnvsWithSecrets$,
|
||||||
|
} from "~/newstore/environments"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { onClickOutside, useDebounceFn } from "@vueuse/core"
|
import { onClickOutside, useDebounceFn } from "@vueuse/core"
|
||||||
import { InspectorResult } from "~/services/inspection"
|
import { InspectorResult } from "~/services/inspection"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import IconEye from "~icons/lucide/eye"
|
||||||
|
import IconEyeoff from "~icons/lucide/eye-off"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
type Env = Environment["variables"][number] & { source: string }
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue?: string
|
modelValue?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
styles?: string
|
styles?: string
|
||||||
envs?: { key: string; value: string; source: string }[] | null
|
envs?: Env[] | null
|
||||||
focus?: boolean
|
focus?: boolean
|
||||||
selectTextOnMount?: boolean
|
selectTextOnMount?: boolean
|
||||||
environmentHighlights?: boolean
|
environmentHighlights?: boolean
|
||||||
@@ -80,6 +109,7 @@ const props = withDefaults(
|
|||||||
autoCompleteSource?: string[]
|
autoCompleteSource?: string[]
|
||||||
inspectionResults?: InspectorResult[] | undefined
|
inspectionResults?: InspectorResult[] | undefined
|
||||||
contextMenuEnabled?: boolean
|
contextMenuEnabled?: boolean
|
||||||
|
secret?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: "",
|
modelValue: "",
|
||||||
@@ -93,6 +123,7 @@ const props = withDefaults(
|
|||||||
inspectionResult: undefined,
|
inspectionResult: undefined,
|
||||||
inspectionResults: undefined,
|
inspectionResults: undefined,
|
||||||
contextMenuEnabled: true,
|
contextMenuEnabled: true,
|
||||||
|
secret: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,10 +149,27 @@ const showSuggestionPopover = ref(false)
|
|||||||
const suggestionsMenu = ref<any | null>(null)
|
const suggestionsMenu = ref<any | null>(null)
|
||||||
const autoCompleteWrapper = ref<any | null>(null)
|
const autoCompleteWrapper = ref<any | null>(null)
|
||||||
|
|
||||||
|
const isSecret = ref(props.secret)
|
||||||
|
|
||||||
|
const secretText = ref(props.modelValue)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => secretText.value,
|
||||||
|
(newVal) => {
|
||||||
|
if (isSecret.value) {
|
||||||
|
updateModelValue(newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
onClickOutside(autoCompleteWrapper, () => {
|
onClickOutside(autoCompleteWrapper, () => {
|
||||||
showSuggestionPopover.value = false
|
showSuggestionPopover.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const toggleSecret = () => {
|
||||||
|
isSecret.value = !isSecret.value
|
||||||
|
}
|
||||||
|
|
||||||
//filter autocompleteSource with unique values
|
//filter autocompleteSource with unique values
|
||||||
const uniqueAutoCompleteSource = computed(() => {
|
const uniqueAutoCompleteSource = computed(() => {
|
||||||
if (props.autoCompleteSource) {
|
if (props.autoCompleteSource) {
|
||||||
@@ -169,8 +217,6 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleKeystroke = (ev: KeyboardEvent) => {
|
const handleKeystroke = (ev: KeyboardEvent) => {
|
||||||
if (!props.autoCompleteSource) return
|
|
||||||
|
|
||||||
if (["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(ev.key)) {
|
if (["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(ev.key)) {
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
}
|
}
|
||||||
@@ -307,19 +353,28 @@ watch(
|
|||||||
let clipboardEv: ClipboardEvent | null = null
|
let clipboardEv: ClipboardEvent | null = null
|
||||||
let pastedValue: string | null = null
|
let pastedValue: string | null = null
|
||||||
|
|
||||||
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, []) as Ref<
|
const aggregateEnvs = useReadonlyStream(aggregateEnvsWithSecrets$, []) as Ref<
|
||||||
AggregateEnvironment[]
|
AggregateEnvironment[]
|
||||||
>
|
>
|
||||||
|
|
||||||
const envVars = computed(() =>
|
const envVars = computed(() => {
|
||||||
props.envs
|
return props.envs
|
||||||
? props.envs.map((x) => ({
|
? props.envs.map((x) => {
|
||||||
key: x.key,
|
if (x.secret) {
|
||||||
value: x.value,
|
return {
|
||||||
sourceEnv: x.source,
|
key: x.key,
|
||||||
}))
|
sourceEnv: "source" in x ? x.source : null,
|
||||||
|
value: "********",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: x.key,
|
||||||
|
value: x.value,
|
||||||
|
sourceEnv: "source" in x ? x.source : null,
|
||||||
|
}
|
||||||
|
})
|
||||||
: aggregateEnvs.value
|
: aggregateEnvs.value
|
||||||
)
|
})
|
||||||
|
|
||||||
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
|
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
|
||||||
|
|
||||||
@@ -363,17 +418,28 @@ const initView = (el: any) => {
|
|||||||
el.addEventListener("keyup", debounceFn)
|
el.addEventListener("keyup", debounceFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extensions: Extension = getExtensions(props.readonly || isSecret.value)
|
||||||
|
view.value = new EditorView({
|
||||||
|
parent: el,
|
||||||
|
state: EditorState.create({
|
||||||
|
doc: props.modelValue,
|
||||||
|
extensions,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getExtensions = (readonly: boolean): Extension => {
|
||||||
const extensions: Extension = [
|
const extensions: Extension = [
|
||||||
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
||||||
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (props.readonly) {
|
if (readonly) {
|
||||||
update.view.contentDOM.inputMode = "none"
|
update.view.contentDOM.inputMode = "none"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
EditorState.changeFilter.of(() => !props.readonly),
|
EditorState.changeFilter.of(() => !readonly),
|
||||||
inputTheme,
|
inputTheme,
|
||||||
props.readonly
|
readonly
|
||||||
? EditorView.theme({
|
? EditorView.theme({
|
||||||
".cm-content": {
|
".cm-content": {
|
||||||
caretColor: "var(--secondary-dark-color)",
|
caretColor: "var(--secondary-dark-color)",
|
||||||
@@ -384,6 +450,7 @@ const initView = (el: any) => {
|
|||||||
})
|
})
|
||||||
: EditorView.theme({}),
|
: EditorView.theme({}),
|
||||||
tooltips({
|
tooltips({
|
||||||
|
parent: document.body,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
}),
|
}),
|
||||||
props.environmentHighlights ? envTooltipPlugin : [],
|
props.environmentHighlights ? envTooltipPlugin : [],
|
||||||
@@ -405,7 +472,8 @@ const initView = (el: any) => {
|
|||||||
ViewPlugin.fromClass(
|
ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
if (props.readonly) return
|
if (readonly) return
|
||||||
|
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
const prevValue = clone(cachedValue.value)
|
const prevValue = clone(cachedValue.value)
|
||||||
|
|
||||||
@@ -454,14 +522,7 @@ const initView = (el: any) => {
|
|||||||
history(),
|
history(),
|
||||||
keymap.of([...historyKeymap]),
|
keymap.of([...historyKeymap]),
|
||||||
]
|
]
|
||||||
|
return extensions
|
||||||
view.value = new EditorView({
|
|
||||||
parent: el,
|
|
||||||
state: EditorState.create({
|
|
||||||
doc: props.modelValue,
|
|
||||||
extensions,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerTextSelection = () => {
|
const triggerTextSelection = () => {
|
||||||
@@ -474,11 +535,11 @@ const triggerTextSelection = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (editor.value) {
|
if (editor.value) {
|
||||||
if (!view.value) initView(editor.value)
|
if (!view.value) initView(editor.value)
|
||||||
if (props.selectTextOnMount) triggerTextSelection()
|
if (props.selectTextOnMount) triggerTextSelection()
|
||||||
|
if (props.focus) view.value?.focus()
|
||||||
platform.ui?.onCodemirrorInstanceMount?.(editor.value)
|
platform.ui?.onCodemirrorInstanceMount?.(editor.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ViewPlugin,
|
ViewPlugin,
|
||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
tooltips,
|
||||||
} from "@codemirror/view"
|
} from "@codemirror/view"
|
||||||
import {
|
import {
|
||||||
Extension,
|
Extension,
|
||||||
@@ -269,6 +270,7 @@ export function useCodemirror(
|
|||||||
basicSetup,
|
basicSetup,
|
||||||
baseTheme,
|
baseTheme,
|
||||||
syntaxHighlighting(baseHighlightStyle, { fallback: true }),
|
syntaxHighlighting(baseHighlightStyle, { fallback: true }),
|
||||||
|
|
||||||
ViewPlugin.fromClass(
|
ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
@@ -318,6 +320,7 @@ export function useCodemirror(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
scroll(event) {
|
scroll(event) {
|
||||||
if (event.target && options.contextMenuEnabled) {
|
if (event.target && options.contextMenuEnabled) {
|
||||||
@@ -359,6 +362,10 @@ export function useCodemirror(
|
|||||||
run: indentLess,
|
run: indentLess,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
tooltips({
|
||||||
|
parent: document.body,
|
||||||
|
position: "absolute",
|
||||||
|
}),
|
||||||
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
||||||
additionalExts.of(options.additionalExts ?? []),
|
additionalExts.of(options.additionalExts ?? []),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,7 +13,36 @@ export function useSetting<K extends keyof SettingsDef>(
|
|||||||
settingsStore.dispatch({
|
settingsStore.dispatch({
|
||||||
dispatcher: "applySetting",
|
dispatcher: "applySetting",
|
||||||
payload: {
|
payload: {
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
settingKey,
|
settingKey,
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNestedSetting<
|
||||||
|
K extends keyof SettingsDef,
|
||||||
|
P extends keyof SettingsDef[K],
|
||||||
|
>(settingKey: K, property: P): Ref<SettingsDef[K][P]> {
|
||||||
|
return useStream(
|
||||||
|
settingsStore.subject$.pipe(
|
||||||
|
pluck(settingKey),
|
||||||
|
pluck(property),
|
||||||
|
distinctUntilChanged()
|
||||||
|
),
|
||||||
|
settingsStore.value[settingKey][property],
|
||||||
|
(value: SettingsDef[K][P]) => {
|
||||||
|
settingsStore.dispatch({
|
||||||
|
dispatcher: "applyNestedSetting",
|
||||||
|
payload: {
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
|
settingKey,
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
|
property,
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -35,7 +64,9 @@ export function useSettingStatic<K extends keyof SettingsDef>(
|
|||||||
settingsStore.dispatch({
|
settingsStore.dispatch({
|
||||||
dispatcher: "applySetting",
|
dispatcher: "applySetting",
|
||||||
payload: {
|
payload: {
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
settingKey,
|
settingKey,
|
||||||
|
// @ts-expect-error TS is not able to understand the type semantics here
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
|||||||
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
||||||
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
||||||
import { isJSONContentType } from "./utils/contenttypes"
|
import { isJSONContentType } from "./utils/contenttypes"
|
||||||
|
import {
|
||||||
|
SecretEnvironmentService,
|
||||||
|
SecretVariable,
|
||||||
|
} from "~/services/secret-environment.service"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
|
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||||
|
|
||||||
const getTestableBody = (
|
const getTestableBody = (
|
||||||
res: HoppRESTResponse & { type: "success" | "fail" }
|
res: HoppRESTResponse & { type: "success" | "fail" }
|
||||||
@@ -58,15 +65,63 @@ const getTestableBody = (
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
const combineEnvVariables = (env: {
|
const combineEnvVariables = (envs: {
|
||||||
global: Environment["variables"]
|
global: Environment["variables"]
|
||||||
selected: Environment["variables"]
|
selected: Environment["variables"]
|
||||||
}) => [...env.selected, ...env.global]
|
}) => [...envs.selected, ...envs.global]
|
||||||
|
|
||||||
export const executedResponses$ = new Subject<
|
export const executedResponses$ = new Subject<
|
||||||
HoppRESTResponse & { type: "success" | "fail " }
|
HoppRESTResponse & { type: "success" | "fail " }
|
||||||
>()
|
>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to update the environment schema with the secret variables
|
||||||
|
* and store the secret variable values in the secret environment service
|
||||||
|
* @param envs The environment variables to update
|
||||||
|
* @param type Whether the environment variables are global or selected
|
||||||
|
* @returns the updated environment variables
|
||||||
|
*/
|
||||||
|
const updateEnvironmentsWithSecret = (
|
||||||
|
envs: Environment["variables"] &
|
||||||
|
{
|
||||||
|
secret: true
|
||||||
|
value: string | undefined
|
||||||
|
key: string
|
||||||
|
}[],
|
||||||
|
type: "global" | "selected"
|
||||||
|
) => {
|
||||||
|
const currentEnvID =
|
||||||
|
type === "selected" ? getCurrentEnvironment().id : "Global"
|
||||||
|
|
||||||
|
const updatedSecretEnvironments: SecretVariable[] = []
|
||||||
|
|
||||||
|
const updatedEnv = pipe(
|
||||||
|
envs,
|
||||||
|
A.mapWithIndex((index, e) => {
|
||||||
|
if (e.secret) {
|
||||||
|
updatedSecretEnvironments.push({
|
||||||
|
key: e.key,
|
||||||
|
value: e.value ?? "",
|
||||||
|
varIndex: index,
|
||||||
|
})
|
||||||
|
|
||||||
|
// delete the value from the environment
|
||||||
|
// so that it doesn't get saved in the environment
|
||||||
|
delete e.value
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if (currentEnvID) {
|
||||||
|
secretEnvironmentService.addSecretEnvironment(
|
||||||
|
currentEnvID,
|
||||||
|
updatedSecretEnvironments
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return updatedEnv
|
||||||
|
}
|
||||||
|
|
||||||
export function runRESTRequest$(
|
export function runRESTRequest$(
|
||||||
tab: Ref<HoppTab<HoppRESTDocument>>
|
tab: Ref<HoppTab<HoppRESTDocument>>
|
||||||
): [
|
): [
|
||||||
@@ -154,15 +209,36 @@ export function runRESTRequest$(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (E.isRight(runResult)) {
|
if (E.isRight(runResult)) {
|
||||||
|
const updatedGlobalEnvVariables = updateEnvironmentsWithSecret(
|
||||||
|
cloneDeep(runResult.right.envs.global),
|
||||||
|
"global"
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedSelectedEnvVariables = updateEnvironmentsWithSecret(
|
||||||
|
cloneDeep(runResult.right.envs.selected),
|
||||||
|
"selected"
|
||||||
|
)
|
||||||
|
|
||||||
// set the response in the tab so that multiple tabs can run request simultaneously
|
// set the response in the tab so that multiple tabs can run request simultaneously
|
||||||
tab.value.document.response = res
|
tab.value.document.response = res
|
||||||
|
|
||||||
tab.value.document.testResults = translateToSandboxTestResults(
|
const updatedRunResult = {
|
||||||
runResult.right
|
...runResult.right,
|
||||||
|
envs: {
|
||||||
|
global: updatedGlobalEnvVariables,
|
||||||
|
selected: updatedSelectedEnvVariables,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.value.document.testResults =
|
||||||
|
translateToSandboxTestResults(updatedRunResult)
|
||||||
|
|
||||||
|
setGlobalEnvVariables(
|
||||||
|
updateEnvironmentsWithSecret(
|
||||||
|
runResult.right.envs.global,
|
||||||
|
"global"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
setGlobalEnvVariables(runResult.right.envs.global)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV"
|
environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV"
|
||||||
) {
|
) {
|
||||||
@@ -173,8 +249,10 @@ export function runRESTRequest$(
|
|||||||
updateEnvironment(
|
updateEnvironment(
|
||||||
environmentsStore.value.selectedEnvironmentIndex.index,
|
environmentsStore.value.selectedEnvironmentIndex.index,
|
||||||
{
|
{
|
||||||
...env,
|
name: env.name,
|
||||||
variables: runResult.right.envs.selected,
|
v: 1,
|
||||||
|
id: env.id ?? "",
|
||||||
|
variables: updatedRunResult.envs.selected,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else if (
|
} else if (
|
||||||
@@ -186,7 +264,7 @@ export function runRESTRequest$(
|
|||||||
})
|
})
|
||||||
pipe(
|
pipe(
|
||||||
updateTeamEnvironment(
|
updateTeamEnvironment(
|
||||||
JSON.stringify(runResult.right.envs.selected),
|
JSON.stringify(updatedRunResult.envs.selected),
|
||||||
environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
|
environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
|
||||||
env.name
|
env.name
|
||||||
)
|
)
|
||||||
@@ -275,7 +353,6 @@ function translateToSandboxTestResults(
|
|||||||
|
|
||||||
const globals = cloneDeep(getGlobalVariables())
|
const globals = cloneDeep(getGlobalVariables())
|
||||||
const env = getCurrentEnvironment()
|
const env = getCurrentEnvironment()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: "",
|
description: "",
|
||||||
expectResults: testDesc.tests.expectResults,
|
expectResults: testDesc.tests.expectResults,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { Ref, onBeforeUnmount, onMounted, reactive, watch } from "vue"
|
import { Ref, onBeforeUnmount, onMounted, reactive, watch } from "vue"
|
||||||
import { BehaviorSubject } from "rxjs"
|
import { BehaviorSubject } from "rxjs"
|
||||||
import { HoppRESTDocument } from "./rest/document"
|
import { HoppRESTDocument } from "./rest/document"
|
||||||
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
import { Environment, HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
import { HoppGQLSaveContext } from "./graphql/document"
|
import { HoppGQLSaveContext } from "./graphql/document"
|
||||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
@@ -43,6 +43,7 @@ export type HoppAction =
|
|||||||
| "modals.environment.new" // Add new environment
|
| "modals.environment.new" // Add new environment
|
||||||
| "modals.environment.delete-selected" // Delete Selected Environment
|
| "modals.environment.delete-selected" // Delete Selected Environment
|
||||||
| "modals.my.environment.edit" // Edit current personal environment
|
| "modals.my.environment.edit" // Edit current personal environment
|
||||||
|
| "modals.global.environment.update" // Update global environment
|
||||||
| "modals.team.environment.edit" // Edit current team environment
|
| "modals.team.environment.edit" // Edit current team environment
|
||||||
| "modals.team.new" // Add new team
|
| "modals.team.new" // Add new team
|
||||||
| "modals.team.edit" // Edit selected team
|
| "modals.team.edit" // Edit selected team
|
||||||
@@ -66,6 +67,13 @@ export type HoppAction =
|
|||||||
| "user.login" // Login to Hoppscotch
|
| "user.login" // Login to Hoppscotch
|
||||||
| "user.logout" // Log out of Hoppscotch
|
| "user.logout" // Log out of Hoppscotch
|
||||||
| "editor.format" // Format editor content
|
| "editor.format" // Format editor content
|
||||||
|
| "modals.team.delete" // Delete team
|
||||||
|
| "workspace.switch" // Switch workspace
|
||||||
|
| "rest.request.open" // Open REST request
|
||||||
|
| "request.open-tab" // Open REST request
|
||||||
|
| "share.request" // Share REST request
|
||||||
|
| "tab.duplicate-tab" // Duplicate REST request
|
||||||
|
| "gql.request.open" // Open GraphQL request
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the arguments, if present for a given type that is required to be passed on
|
* Defines the arguments, if present for a given type that is required to be passed on
|
||||||
@@ -86,13 +94,19 @@ type HoppActionArgsMap = {
|
|||||||
}
|
}
|
||||||
text: string | null
|
text: string | null
|
||||||
}
|
}
|
||||||
|
"modals.global.environment.update": {
|
||||||
|
variables?: Environment["variables"]
|
||||||
|
isSecret?: boolean
|
||||||
|
}
|
||||||
"modals.my.environment.edit": {
|
"modals.my.environment.edit": {
|
||||||
envName: string
|
envName: string
|
||||||
variableName?: string
|
variableName?: string
|
||||||
|
isSecret?: boolean
|
||||||
}
|
}
|
||||||
"modals.team.environment.edit": {
|
"modals.team.environment.edit": {
|
||||||
envName: string
|
envName: string
|
||||||
variableName?: string
|
variableName?: string
|
||||||
|
isSecret?: boolean
|
||||||
}
|
}
|
||||||
"modals.team.delete": {
|
"modals.team.delete": {
|
||||||
teamId: string
|
teamId: string
|
||||||
@@ -112,6 +126,7 @@ type HoppActionArgsMap = {
|
|||||||
requestType: "gql"
|
requestType: "gql"
|
||||||
request: HoppGQLRequest
|
request: HoppGQLRequest
|
||||||
}
|
}
|
||||||
|
| undefined
|
||||||
"request.open-tab": {
|
"request.open-tab": {
|
||||||
tab: RESTOptionTabs | GQLOptionTabs
|
tab: RESTOptionTabs | GQLOptionTabs
|
||||||
}
|
}
|
||||||
@@ -121,7 +136,6 @@ type HoppActionArgsMap = {
|
|||||||
"tab.duplicate-tab": {
|
"tab.duplicate-tab": {
|
||||||
tabID?: string
|
tabID?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
"gql.request.open": {
|
"gql.request.open": {
|
||||||
request: HoppGQLRequest
|
request: HoppGQLRequest
|
||||||
saveContext?: HoppGQLSaveContext
|
saveContext?: HoppGQLSaveContext
|
||||||
@@ -132,11 +146,23 @@ type HoppActionArgsMap = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeysWithValueUndefined<T> = {
|
||||||
|
[K in keyof T]: undefined extends T[K] ? K : never
|
||||||
|
}[keyof T]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HoppActions which require arguments for their invocation
|
* HoppActions which require arguments for their invocation
|
||||||
*/
|
*/
|
||||||
export type HoppActionWithArgs = keyof HoppActionArgsMap
|
export type HoppActionWithArgs = keyof HoppActionArgsMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HoppActions which optionally takes in arguments for their invocation
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type HoppActionWithOptionalArgs =
|
||||||
|
| HoppActionWithNoArgs
|
||||||
|
| KeysWithValueUndefined<HoppActionArgsMap>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HoppActions which do not require arguments for their invocation
|
* HoppActions which do not require arguments for their invocation
|
||||||
*/
|
*/
|
||||||
@@ -145,27 +171,26 @@ export type HoppActionWithNoArgs = Exclude<HoppAction, HoppActionWithArgs>
|
|||||||
/**
|
/**
|
||||||
* Resolves the argument type for a given HoppAction
|
* Resolves the argument type for a given HoppAction
|
||||||
*/
|
*/
|
||||||
type ArgOfHoppAction<A extends HoppAction | HoppActionWithArgs> =
|
type ArgOfHoppAction<A extends HoppAction> = A extends HoppActionWithArgs
|
||||||
A extends HoppActionWithArgs ? HoppActionArgsMap[A] : undefined
|
? HoppActionArgsMap[A]
|
||||||
|
: undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the action function for a given HoppAction, used by action handler function defs
|
* Resolves the action function for a given HoppAction, used by action handler function defs
|
||||||
*/
|
*/
|
||||||
type ActionFunc<A extends HoppAction | HoppActionWithArgs> =
|
type ActionFunc<A extends HoppAction> = A extends HoppActionWithArgs
|
||||||
A extends HoppActionWithArgs ? (arg: ArgOfHoppAction<A>) => void : () => void
|
? (arg: ArgOfHoppAction<A>, trigger?: InvocationTriggers) => void
|
||||||
|
: (_?: undefined, trigger?: InvocationTriggers) => void
|
||||||
|
|
||||||
type BoundActionList = {
|
type BoundActionList = {
|
||||||
// eslint-disable-next-line no-unused-vars
|
[A in HoppAction]?: Array<ActionFunc<A>>
|
||||||
[A in HoppAction | HoppActionWithArgs]?: Array<ActionFunc<A>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundActions: BoundActionList = reactive({})
|
const boundActions: BoundActionList = reactive({})
|
||||||
|
|
||||||
export const activeActions$ = new BehaviorSubject<
|
export const activeActions$ = new BehaviorSubject<HoppAction[]>([])
|
||||||
(HoppAction | HoppActionWithArgs)[]
|
|
||||||
>([])
|
|
||||||
|
|
||||||
export function bindAction<A extends HoppAction | HoppActionWithArgs>(
|
export function bindAction<A extends HoppAction>(
|
||||||
action: A,
|
action: A,
|
||||||
handler: ActionFunc<A>
|
handler: ActionFunc<A>
|
||||||
) {
|
) {
|
||||||
@@ -179,27 +204,33 @@ export function bindAction<A extends HoppAction | HoppActionWithArgs>(
|
|||||||
activeActions$.next(Object.keys(boundActions) as HoppAction[])
|
activeActions$.next(Object.keys(boundActions) as HoppAction[])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InvocationTriggers = "keypress" | "mouseclick"
|
||||||
|
|
||||||
type InvokeActionFunc = {
|
type InvokeActionFunc = {
|
||||||
(action: HoppActionWithNoArgs, args?: undefined): void
|
(
|
||||||
|
action: HoppActionWithOptionalArgs,
|
||||||
|
args?: undefined,
|
||||||
|
trigger?: InvocationTriggers
|
||||||
|
): void
|
||||||
<A extends HoppActionWithArgs>(action: A, args: HoppActionArgsMap[A]): void
|
<A extends HoppActionWithArgs>(action: A, args: HoppActionArgsMap[A]): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes a action, triggering action handlers if any registered.
|
* Invokes an action, triggering action handlers if any registered.
|
||||||
* The second argument parameter is optional if your action has no args required
|
* The second and third arguments are optional
|
||||||
* @param action The action to fire
|
* @param action The action to fire
|
||||||
* @param args The argument passed to the action handler. Optional if action has no args required
|
* @param args The argument passed to the action handler. Optional if action has no args required
|
||||||
|
* @param trigger Optionally supply the trigger that invoked the action (keypress/mouseclick)
|
||||||
*/
|
*/
|
||||||
export const invokeAction: InvokeActionFunc = <
|
export const invokeAction: InvokeActionFunc = <A extends HoppAction>(
|
||||||
A extends HoppAction | HoppActionWithArgs,
|
|
||||||
>(
|
|
||||||
action: A,
|
action: A,
|
||||||
args: ArgOfHoppAction<A>
|
args?: ArgOfHoppAction<A>,
|
||||||
|
trigger?: InvocationTriggers
|
||||||
) => {
|
) => {
|
||||||
boundActions[action]?.forEach((handler) => handler(args! as any))
|
boundActions[action]?.forEach((handler) => handler(args! as any, trigger))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unbindAction<A extends HoppAction | HoppActionWithArgs>(
|
export function unbindAction<A extends HoppAction>(
|
||||||
action: A,
|
action: A,
|
||||||
handler: ActionFunc<A>
|
handler: ActionFunc<A>
|
||||||
) {
|
) {
|
||||||
@@ -232,7 +263,7 @@ export function isActionBound(action: HoppAction): Ref<boolean> {
|
|||||||
* @param handler The function to be called when the action is invoked
|
* @param handler The function to be called when the action is invoked
|
||||||
* @param isActive A ref that indicates whether the action is active
|
* @param isActive A ref that indicates whether the action is active
|
||||||
*/
|
*/
|
||||||
export function defineActionHandler<A extends HoppAction | HoppActionWithArgs>(
|
export function defineActionHandler<A extends HoppAction>(
|
||||||
action: A,
|
action: A,
|
||||||
handler: ActionFunc<A>,
|
handler: ActionFunc<A>,
|
||||||
isActive: Ref<boolean> | undefined = undefined
|
isActive: Ref<boolean> | undefined = undefined
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
mutation CreateTeamEnvironment($variables: String!,$teamID: ID!,$name: String!){
|
mutation CreateTeamEnvironment(
|
||||||
createTeamEnvironment( variables: $variables ,teamID: $teamID ,name: $name){
|
$variables: String!
|
||||||
|
$teamID: ID!
|
||||||
|
$name: String!
|
||||||
|
) {
|
||||||
|
createTeamEnvironment(variables: $variables, teamID: $teamID, name: $name) {
|
||||||
variables
|
variables
|
||||||
name
|
name
|
||||||
teamID
|
teamID
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const samples = [
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://echo.hoppscotch.io/",
|
endpoint: "https://echo.hoppscotch.io/",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: "application/x-www-form-urlencoded",
|
||||||
body: rawKeyValueEntriesToString([
|
body: rawKeyValueEntriesToString([
|
||||||
@@ -149,7 +149,7 @@ const samples = [
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://google.com/",
|
endpoint: "https://google.com/",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
body: null,
|
body: null,
|
||||||
@@ -166,7 +166,7 @@ const samples = [
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "http://localhost:1111/hello/world/?buzz",
|
endpoint: "http://localhost:1111/hello/world/?buzz",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body: `{\n "foo": "bar"\n}`,
|
body: `{\n "foo": "bar"\n}`,
|
||||||
@@ -189,7 +189,7 @@ const samples = [
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://example.com/",
|
endpoint: "https://example.com/",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
body: null,
|
body: null,
|
||||||
@@ -217,7 +217,7 @@ const samples = [
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://bing.com/",
|
endpoint: "https://bing.com/",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: "multipart/form-data",
|
contentType: "multipart/form-data",
|
||||||
body: [
|
body: [
|
||||||
@@ -301,7 +301,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "http://localhost:9900/",
|
endpoint: "http://localhost:9900/",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
@@ -345,7 +345,7 @@ const samples = [
|
|||||||
endpoint: "https://hoppscotch.io/?io",
|
endpoint: "https://hoppscotch.io/?io",
|
||||||
auth: {
|
auth: {
|
||||||
authActive: true,
|
authActive: true,
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
@@ -380,7 +380,7 @@ const samples = [
|
|||||||
endpoint: "https://someshadywebsite.com/questionable/path/?so",
|
endpoint: "https://someshadywebsite.com/questionable/path/?so",
|
||||||
auth: {
|
auth: {
|
||||||
authActive: true,
|
authActive: true,
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
contentType: "multipart/form-data",
|
contentType: "multipart/form-data",
|
||||||
@@ -441,7 +441,7 @@ const samples = [
|
|||||||
endpoint: "http://localhost/",
|
endpoint: "http://localhost/",
|
||||||
auth: {
|
auth: {
|
||||||
authActive: true,
|
authActive: true,
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
contentType: "multipart/form-data",
|
contentType: "multipart/form-data",
|
||||||
@@ -473,7 +473,7 @@ const samples = [
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://hoppscotch.io/",
|
endpoint: "https://hoppscotch.io/",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
body: null,
|
body: null,
|
||||||
@@ -528,7 +528,7 @@ const samples = [
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://echo.hoppscotch.io/",
|
endpoint: "https://echo.hoppscotch.io/",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
body: {
|
body: {
|
||||||
contentType: "application/x-www-form-urlencoded",
|
contentType: "application/x-www-form-urlencoded",
|
||||||
body: rawKeyValueEntriesToString([
|
body: rawKeyValueEntriesToString([
|
||||||
@@ -573,7 +573,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://echo.hoppscotch.io/",
|
endpoint: "https://echo.hoppscotch.io/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -615,7 +615,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://muxueqz.top/skybook.html",
|
endpoint: "https://muxueqz.top/skybook.html",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [],
|
headers: [],
|
||||||
body: { contentType: null, body: null },
|
body: { contentType: null, body: null },
|
||||||
params: [],
|
params: [],
|
||||||
@@ -629,7 +629,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://echo.hoppscotch.io/",
|
endpoint: "https://echo.hoppscotch.io/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [],
|
headers: [],
|
||||||
body: {
|
body: {
|
||||||
contentType: "multipart/form-data",
|
contentType: "multipart/form-data",
|
||||||
@@ -653,7 +653,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "http://127.0.0.1/",
|
endpoint: "http://127.0.0.1/",
|
||||||
method: "CUSTOMMETHOD",
|
method: "CUSTOMMETHOD",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [],
|
headers: [],
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
@@ -670,7 +670,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://echo.hoppscotch.io/",
|
endpoint: "https://echo.hoppscotch.io/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -693,7 +693,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://echo.hoppscotch.io/",
|
endpoint: "https://echo.hoppscotch.io/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [],
|
headers: [],
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
@@ -710,7 +710,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://example.org/",
|
endpoint: "https://example.org/",
|
||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
auth: { authType: "none", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [],
|
headers: [],
|
||||||
body: {
|
body: {
|
||||||
contentType: null,
|
contentType: null,
|
||||||
@@ -756,7 +756,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://google.com/",
|
endpoint: "https://google.com/",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
@@ -777,7 +777,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://google.com/",
|
endpoint: "https://google.com/",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
@@ -797,7 +797,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "http://192.168.0.24:8080/ping",
|
endpoint: "http://192.168.0.24:8080/ping",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
@@ -817,7 +817,7 @@ const samples = [
|
|||||||
name: "Untitled",
|
name: "Untitled",
|
||||||
endpoint: "https://example.com/",
|
endpoint: "https://example.com/",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ import { parseTemplateStringE } from "@hoppscotch/data"
|
|||||||
import { StreamSubscriberFunc } from "@composables/stream"
|
import { StreamSubscriberFunc } from "@composables/stream"
|
||||||
import {
|
import {
|
||||||
AggregateEnvironment,
|
AggregateEnvironment,
|
||||||
aggregateEnvs$,
|
aggregateEnvsWithSecrets$,
|
||||||
getAggregateEnvs,
|
getAggregateEnvsWithSecrets,
|
||||||
|
getCurrentEnvironment,
|
||||||
getSelectedEnvironmentType,
|
getSelectedEnvironmentType,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
import IconUser from "~icons/lucide/user?raw"
|
import IconUser from "~icons/lucide/user?raw"
|
||||||
import IconUsers from "~icons/lucide/users?raw"
|
import IconUsers from "~icons/lucide/users?raw"
|
||||||
import IconEdit from "~icons/lucide/edit?raw"
|
import IconEdit from "~icons/lucide/edit?raw"
|
||||||
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||||
|
|
||||||
@@ -28,6 +31,8 @@ const HOPP_ENV_HIGHLIGHT =
|
|||||||
const HOPP_ENV_HIGHLIGHT_FOUND = "env-found"
|
const HOPP_ENV_HIGHLIGHT_FOUND = "env-found"
|
||||||
const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "env-not-found"
|
const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "env-not-found"
|
||||||
|
|
||||||
|
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||||
|
|
||||||
const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||||
hoverTooltip(
|
hoverTooltip(
|
||||||
(view, pos, side) => {
|
(view, pos, side) => {
|
||||||
@@ -66,7 +71,27 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||||||
|
|
||||||
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
||||||
|
|
||||||
const envValue = tooltipEnv?.value ?? "Not found"
|
let envValue = "Not Found"
|
||||||
|
|
||||||
|
const currentSelectedEnvironment = getCurrentEnvironment()
|
||||||
|
|
||||||
|
const hasSecretEnv = secretEnvironmentService.hasSecretValue(
|
||||||
|
tooltipEnv?.sourceEnv !== "Global"
|
||||||
|
? currentSelectedEnvironment.id
|
||||||
|
: "Global",
|
||||||
|
tooltipEnv?.key ?? ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!tooltipEnv?.secret && tooltipEnv?.value) envValue = tooltipEnv.value
|
||||||
|
else if (tooltipEnv?.secret && hasSecretEnv) {
|
||||||
|
envValue = "******"
|
||||||
|
} else if (tooltipEnv?.secret && !hasSecretEnv) {
|
||||||
|
envValue = "Empty"
|
||||||
|
} else if (!tooltipEnv?.sourceEnv) {
|
||||||
|
envValue = "Not Found"
|
||||||
|
} else if (!tooltipEnv?.value) {
|
||||||
|
envValue = "Empty"
|
||||||
|
}
|
||||||
|
|
||||||
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
||||||
|
|
||||||
@@ -83,12 +108,25 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
|||||||
editIcon.className =
|
editIcon.className =
|
||||||
"ml-2 cursor-pointer text-accent hover:text-accentDark"
|
"ml-2 cursor-pointer text-accent hover:text-accentDark"
|
||||||
editIcon.addEventListener("click", () => {
|
editIcon.addEventListener("click", () => {
|
||||||
const isPersonalEnv =
|
let invokeActionType:
|
||||||
envName === "Global" || selectedEnvType !== "TEAM_ENV"
|
| "modals.my.environment.edit"
|
||||||
const action = isPersonalEnv ? "my" : "team"
|
| "modals.team.environment.edit"
|
||||||
invokeAction(`modals.${action}.environment.edit`, {
|
| "modals.global.environment.update" = "modals.my.environment.edit"
|
||||||
envName,
|
|
||||||
|
if (tooltipEnv?.sourceEnv === "Global") {
|
||||||
|
invokeActionType = "modals.global.environment.update"
|
||||||
|
} else if (selectedEnvType === "MY_ENV") {
|
||||||
|
invokeActionType = "modals.my.environment.edit"
|
||||||
|
} else if (selectedEnvType === "TEAM_ENV") {
|
||||||
|
invokeActionType = "modals.team.environment.edit"
|
||||||
|
} else {
|
||||||
|
invokeActionType = "modals.my.environment.edit"
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeAction(invokeActionType, {
|
||||||
|
envName: tooltipEnv?.sourceEnv !== "Global" ? envName : "Global",
|
||||||
variableName: parsedEnvKey,
|
variableName: parsedEnvKey,
|
||||||
|
isSecret: tooltipEnv?.secret,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
editIcon.innerHTML = `<span class="inline-flex items-center justify-center my-1">${IconEdit}</span>`
|
editIcon.innerHTML = `<span class="inline-flex items-center justify-center my-1">${IconEdit}</span>`
|
||||||
@@ -171,11 +209,10 @@ export class HoppEnvironmentPlugin {
|
|||||||
subscribeToStream: StreamSubscriberFunc,
|
subscribeToStream: StreamSubscriberFunc,
|
||||||
private editorView: Ref<EditorView | undefined>
|
private editorView: Ref<EditorView | undefined>
|
||||||
) {
|
) {
|
||||||
this.envs = getAggregateEnvs()
|
this.envs = getAggregateEnvsWithSecrets()
|
||||||
|
|
||||||
subscribeToStream(aggregateEnvs$, (envs) => {
|
subscribeToStream(aggregateEnvsWithSecrets$, (envs) => {
|
||||||
this.envs = envs
|
this.envs = envs
|
||||||
|
|
||||||
this.editorView.value?.dispatch({
|
this.editorView.value?.dispatch({
|
||||||
effects: this.compartment.reconfigure([
|
effects: this.compartment.reconfigure([
|
||||||
cursorTooltipField(this.envs),
|
cursorTooltipField(this.envs),
|
||||||
|
|||||||
@@ -171,9 +171,6 @@ export const baseTheme = EditorView.theme({
|
|||||||
".cm-activeLineGutter": {
|
".cm-activeLineGutter": {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
".cm-scroller::-webkit-scrollbar": {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
".cm-foldPlaceholder": {
|
".cm-foldPlaceholder": {
|
||||||
backgroundColor: "var(--divider-light-color)",
|
backgroundColor: "var(--divider-light-color)",
|
||||||
color: "var(--secondary-dark-color)",
|
color: "var(--secondary-dark-color)",
|
||||||
@@ -320,9 +317,6 @@ export const inputTheme = EditorView.theme({
|
|||||||
".cm-activeLineGutter": {
|
".cm-activeLineGutter": {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
".cm-scroller::-webkit-scrollbar": {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
".cm-foldPlaceholder": {
|
".cm-foldPlaceholder": {
|
||||||
backgroundColor: "var(--divider-light-color)",
|
backgroundColor: "var(--divider-light-color)",
|
||||||
color: "var(--secondary-dark-color)",
|
color: "var(--secondary-dark-color)",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const getDefaultGQLRequest = (): HoppGQLRequest => ({
|
|||||||
}`,
|
}`,
|
||||||
query: DEFAULT_QUERY,
|
query: DEFAULT_QUERY,
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ const getEnvironmentJson = (
|
|||||||
? cloneDeep(environmentObj.environment)
|
? cloneDeep(environmentObj.environment)
|
||||||
: cloneDeep(environmentObj)
|
: cloneDeep(environmentObj)
|
||||||
|
|
||||||
delete newEnvironment.id
|
|
||||||
|
|
||||||
const environmentId =
|
const environmentId =
|
||||||
environmentIndex || environmentIndex === 0
|
environmentIndex || environmentIndex === 0
|
||||||
? environmentIndex
|
? environmentIndex
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { entityReference } from "verzod"
|
||||||
|
|
||||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
|
||||||
import { safeParseJSON } from "~/helpers/functional/json"
|
import { safeParseJSON } from "~/helpers/functional/json"
|
||||||
|
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||||
|
|
||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
const hoppEnvSchema = z.object({
|
|
||||||
id: z.string().optional(),
|
|
||||||
name: z.string(),
|
|
||||||
variables: z.array(
|
|
||||||
z.object({
|
|
||||||
key: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const hoppEnvImporter = (content: string) => {
|
export const hoppEnvImporter = (content: string) => {
|
||||||
const parsedContent = safeParseJSON(content, true)
|
const parsedContent = safeParseJSON(content, true)
|
||||||
|
|
||||||
@@ -25,7 +16,9 @@ export const hoppEnvImporter = (content: string) => {
|
|||||||
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
|
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
|
||||||
}
|
}
|
||||||
|
|
||||||
const validationResult = z.array(hoppEnvSchema).safeParse(parsedContent.value)
|
const validationResult = z
|
||||||
|
.array(entityReference(Environment))
|
||||||
|
.safeParse(parsedContent.value)
|
||||||
|
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
|
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import * as O from "fp-ts/Option"
|
|||||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||||
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { NonSecretEnvironment } from "@hoppscotch/data"
|
||||||
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
||||||
|
import { uniqueId } from "lodash-es"
|
||||||
|
|
||||||
const insomniaResourcesSchema = z.object({
|
const insomniaResourcesSchema = z.object({
|
||||||
resources: z.array(
|
resources: z.array(
|
||||||
@@ -56,16 +57,18 @@ export const insomniaEnvImporter = (content: string) => {
|
|||||||
return { ...envResource, data: stringifiedData }
|
return { ...envResource, data: stringifiedData }
|
||||||
})
|
})
|
||||||
|
|
||||||
const environments: Environment[] = []
|
const environments: NonSecretEnvironment[] = []
|
||||||
|
|
||||||
insomniaEnvs.forEach((insomniaEnv) => {
|
insomniaEnvs.forEach((insomniaEnv) => {
|
||||||
const parsedInsomniaEnv = insomniaEnvSchema.safeParse(insomniaEnv)
|
const parsedInsomniaEnv = insomniaEnvSchema.safeParse(insomniaEnv)
|
||||||
|
|
||||||
if (parsedInsomniaEnv.success) {
|
if (parsedInsomniaEnv.success) {
|
||||||
const environment: Environment = {
|
const environment: NonSecretEnvironment = {
|
||||||
|
id: uniqueId(),
|
||||||
|
v: 1,
|
||||||
name: parsedInsomniaEnv.data.name,
|
name: parsedInsomniaEnv.data.name,
|
||||||
variables: Object.entries(parsedInsomniaEnv.data.data).map(
|
variables: Object.entries(parsedInsomniaEnv.data.data).map(
|
||||||
([key, value]) => ({ key, value })
|
([key, value]) => ({ key, value, secret: false })
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { safeParseJSON } from "~/helpers/functional/json"
|
|||||||
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { uniqueId } from "lodash-es"
|
||||||
|
|
||||||
const postmanEnvSchema = z.object({
|
const postmanEnvSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -34,12 +35,14 @@ export const postmanEnvImporter = (content: string) => {
|
|||||||
const postmanEnv = validationResult.data
|
const postmanEnv = validationResult.data
|
||||||
|
|
||||||
const environment: Environment = {
|
const environment: Environment = {
|
||||||
|
id: uniqueId(),
|
||||||
|
v: 1,
|
||||||
name: postmanEnv.name,
|
name: postmanEnv.name,
|
||||||
variables: [],
|
variables: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
postmanEnv.values.forEach(({ key, value }) =>
|
postmanEnv.values.forEach(({ key, value }) =>
|
||||||
environment.variables.push({ key, value })
|
environment.variables.push({ key, value, secret: false })
|
||||||
)
|
)
|
||||||
|
|
||||||
return TE.right(environment)
|
return TE.right(environment)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { onBeforeUnmount, onMounted } from "vue"
|
import { onBeforeUnmount, onMounted } from "vue"
|
||||||
import { HoppActionWithNoArgs, invokeAction } from "./actions"
|
import { HoppActionWithOptionalArgs, invokeAction } from "./actions"
|
||||||
import { isAppleDevice } from "./platformutils"
|
import { isAppleDevice } from "./platformutils"
|
||||||
import { isDOMElement, isTypableElement } from "./utils/dom"
|
import { isDOMElement, isTypableElement } from "./utils/dom"
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ type SingleCharacterShortcutKey = `${Key}`
|
|||||||
type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey
|
type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey
|
||||||
|
|
||||||
export const bindings: {
|
export const bindings: {
|
||||||
[_ in ShortcutKey]?: HoppActionWithNoArgs
|
[_ in ShortcutKey]?: HoppActionWithOptionalArgs
|
||||||
} = {
|
} = {
|
||||||
"ctrl-enter": "request.send-cancel",
|
"ctrl-enter": "request.send-cancel",
|
||||||
"ctrl-i": "request.reset",
|
"ctrl-i": "request.reset",
|
||||||
@@ -96,7 +96,7 @@ function handleKeyDown(ev: KeyboardEvent) {
|
|||||||
if (!boundAction) return
|
if (!boundAction) return
|
||||||
|
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
invokeAction(boundAction)
|
invokeAction(boundAction, undefined, "keypress")
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
||||||
|
|||||||
@@ -3,41 +3,17 @@ import { PersistenceService } from "~/services/persistence"
|
|||||||
|
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
|
||||||
|
import { AxiosRequestConfig } from "axios"
|
||||||
|
|
||||||
const redirectUri = `${window.location.origin}/oauth`
|
const redirectUri = `${window.location.origin}/oauth`
|
||||||
|
|
||||||
|
const interceptorService = getService(InterceptorService)
|
||||||
const persistenceService = getService(PersistenceService)
|
const persistenceService = getService(PersistenceService)
|
||||||
|
|
||||||
// GENERAL HELPER FUNCTIONS
|
// GENERAL HELPER FUNCTIONS
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a POST request and parse the response as JSON
|
|
||||||
*
|
|
||||||
* @param {String} url - The resource
|
|
||||||
* @param {Object} params - Configuration options
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const sendPostRequest = async (url: string, params: Record<string, string>) => {
|
|
||||||
const body = Object.keys(params)
|
|
||||||
.map((key) => `${key}=${params[key]}`)
|
|
||||||
.join("&")
|
|
||||||
const options = {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
||||||
},
|
|
||||||
body,
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, options)
|
|
||||||
const data = await response.json()
|
|
||||||
return E.right(data)
|
|
||||||
} catch (e) {
|
|
||||||
return E.left("AUTH_TOKEN_REQUEST_FAILED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a query string into an object
|
* Parse a query string into an object
|
||||||
*
|
*
|
||||||
@@ -71,9 +47,16 @@ const getTokenConfiguration = async (endpoint: string) => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(endpoint, options)
|
const res = await runRequestThroughInterceptor({
|
||||||
const config = await response.json()
|
url: endpoint,
|
||||||
return E.right(config)
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return E.left("OIDC_DISCOVERY_FAILED")
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(JSON.parse(res.right))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return E.left("OIDC_DISCOVERY_FAILED")
|
return E.left("OIDC_DISCOVERY_FAILED")
|
||||||
}
|
}
|
||||||
@@ -166,8 +149,7 @@ const tokenRequest = async ({
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
scope,
|
scope,
|
||||||
}: TokenRequestParams) => {
|
}: TokenRequestParams) => {
|
||||||
// Check oauth configuration
|
if (oidcDiscoveryUrl) {
|
||||||
if (oidcDiscoveryUrl !== "") {
|
|
||||||
const res = await getTokenConfiguration(oidcDiscoveryUrl)
|
const res = await getTokenConfiguration(oidcDiscoveryUrl)
|
||||||
|
|
||||||
const OIDCConfigurationSchema = z.object({
|
const OIDCConfigurationSchema = z.object({
|
||||||
@@ -268,18 +250,24 @@ const handleOAuthRedirect = async () => {
|
|||||||
return E.left("NO_CODE_VERIFIER" as const)
|
return E.left("NO_CODE_VERIFIER" as const)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams({
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code: queryParams.code,
|
||||||
|
client_id: clientID,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
code_verifier: codeVerifier,
|
||||||
|
})
|
||||||
|
|
||||||
// Exchange the authorization code for an access token
|
// Exchange the authorization code for an access token
|
||||||
const tokenResponse: E.Either<string, any> = await sendPostRequest(
|
const tokenResponse = await runRequestThroughInterceptor({
|
||||||
tokenEndpoint,
|
url: tokenEndpoint,
|
||||||
{
|
data: data.toString(),
|
||||||
grant_type: "authorization_code",
|
method: "POST",
|
||||||
code: queryParams.code,
|
headers: {
|
||||||
client_id: clientID,
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
client_secret: clientSecret,
|
},
|
||||||
redirect_uri: redirectUri,
|
})
|
||||||
code_verifier: codeVerifier,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Clean these up since we don't need them anymore
|
// Clean these up since we don't need them anymore
|
||||||
clearPKCEState()
|
clearPKCEState()
|
||||||
@@ -293,7 +281,7 @@ const handleOAuthRedirect = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const parsedTokenResponse = withAccessTokenSchema.safeParse(
|
const parsedTokenResponse = withAccessTokenSchema.safeParse(
|
||||||
tokenResponse.right
|
JSON.parse(tokenResponse.right)
|
||||||
)
|
)
|
||||||
|
|
||||||
return parsedTokenResponse.success
|
return parsedTokenResponse.success
|
||||||
@@ -309,4 +297,20 @@ const clearPKCEState = () => {
|
|||||||
persistenceService.removeLocalConfig("client_secret")
|
persistenceService.removeLocalConfig("client_secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runRequestThroughInterceptor(config: AxiosRequestConfig) {
|
||||||
|
const res = await interceptorService.runRequest(config).response
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return E.left("REQUEST_FAILED")
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert ArrayBuffer to string
|
||||||
|
if (!(res.right.data instanceof ArrayBuffer)) {
|
||||||
|
return E.left("REQUEST_FAILED")
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new TextDecoder().decode(res.right.data).replace(/\0+$/, "")
|
||||||
|
return E.right(data)
|
||||||
|
}
|
||||||
|
|
||||||
export { tokenRequest, handleOAuthRedirect }
|
export { tokenRequest, handleOAuthRedirect }
|
||||||
|
|||||||
@@ -8,11 +8,71 @@ import {
|
|||||||
getGlobalVariables,
|
getGlobalVariables,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { TestResult } from "@hoppscotch/js-sandbox"
|
import { TestResult } from "@hoppscotch/js-sandbox"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
|
|
||||||
export const getCombinedEnvVariables = () => ({
|
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||||
global: cloneDeep(getGlobalVariables()),
|
|
||||||
selected: cloneDeep(getCurrentEnvironment().variables),
|
const unsecretEnvironments = (
|
||||||
})
|
global: Environment["variables"],
|
||||||
|
selected: Environment
|
||||||
|
) => {
|
||||||
|
const resolvedGlobalWithSecrets = global.map((globalVar, index) => {
|
||||||
|
const secretVar = secretEnvironmentService.getSecretEnvironmentVariable(
|
||||||
|
"Global",
|
||||||
|
index
|
||||||
|
)
|
||||||
|
if (secretVar) {
|
||||||
|
return {
|
||||||
|
...globalVar,
|
||||||
|
value: secretVar.value,
|
||||||
|
}
|
||||||
|
} else if (!("value" in globalVar) || !globalVar.value) {
|
||||||
|
return {
|
||||||
|
...globalVar,
|
||||||
|
value: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return globalVar
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolvedSelectedWithSecrets = selected.variables.map(
|
||||||
|
(selectedVar, index) => {
|
||||||
|
const secretVar = secretEnvironmentService.getSecretEnvironmentVariable(
|
||||||
|
selected.id,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
if (secretVar) {
|
||||||
|
return {
|
||||||
|
...selectedVar,
|
||||||
|
value: secretVar.value,
|
||||||
|
}
|
||||||
|
} else if (!("value" in selectedVar) || !selectedVar.value) {
|
||||||
|
return {
|
||||||
|
...selectedVar,
|
||||||
|
value: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedVar
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
global: resolvedGlobalWithSecrets,
|
||||||
|
selected: resolvedSelectedWithSecrets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCombinedEnvVariables = () => {
|
||||||
|
const reformedVars = unsecretEnvironments(
|
||||||
|
getGlobalVariables(),
|
||||||
|
getCurrentEnvironment()
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
global: cloneDeep(reformedVars.global),
|
||||||
|
selected: cloneDeep(reformedVars.selected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getFinalEnvsFromPreRequest = (
|
export const getFinalEnvsFromPreRequest = (
|
||||||
script: string,
|
script: string,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const getDefaultRESTRequest = (): HoppRESTRequest => ({
|
|||||||
headers: [],
|
headers: [],
|
||||||
method: "GET",
|
method: "GET",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "inherit",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
},
|
},
|
||||||
preRequestScript: "",
|
preRequestScript: "",
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ export default class TeamEnvironmentAdapter {
|
|||||||
id: x.id,
|
id: x.id,
|
||||||
teamID: x.teamID,
|
teamID: x.teamID,
|
||||||
environment: {
|
environment: {
|
||||||
|
v: 1,
|
||||||
|
id: x.id,
|
||||||
name: x.name,
|
name: x.name,
|
||||||
variables: JSON.parse(x.variables),
|
variables: JSON.parse(x.variables),
|
||||||
},
|
},
|
||||||
@@ -196,6 +198,8 @@ export default class TeamEnvironmentAdapter {
|
|||||||
id: x.id,
|
id: x.id,
|
||||||
teamID: x.teamID,
|
teamID: x.teamID,
|
||||||
environment: {
|
environment: {
|
||||||
|
v: 1,
|
||||||
|
id: x.id,
|
||||||
name: x.name,
|
name: x.name,
|
||||||
variables: JSON.parse(x.variables),
|
variables: JSON.parse(x.variables),
|
||||||
},
|
},
|
||||||
@@ -249,6 +253,8 @@ export default class TeamEnvironmentAdapter {
|
|||||||
id: x.id,
|
id: x.id,
|
||||||
teamID: x.teamID,
|
teamID: x.teamID,
|
||||||
environment: {
|
environment: {
|
||||||
|
v: 1,
|
||||||
|
id: x.id,
|
||||||
name: x.name,
|
name: x.name,
|
||||||
variables: JSON.parse(x.variables),
|
variables: JSON.parse(x.variables),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1256,5 +1256,11 @@
|
|||||||
"!type": "fn(value: ?) -> bool"
|
"!type": "fn(value: ?) -> bool"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"btoa": {
|
||||||
|
"!type": "fn(data: string) -> string"
|
||||||
|
},
|
||||||
|
"atob": {
|
||||||
|
"!type": "fn(data: string) -> string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
export const getComputedAuthHeaders = (
|
export const getComputedAuthHeaders = (
|
||||||
envVars: Environment["variables"],
|
envVars: Environment["variables"],
|
||||||
req?: HoppRESTRequest,
|
req?: HoppRESTRequest,
|
||||||
auth?: HoppRESTRequest["auth"]
|
auth?: HoppRESTRequest["auth"],
|
||||||
|
parse = true
|
||||||
) => {
|
) => {
|
||||||
const request = auth ? { auth: auth ?? { authActive: false } } : req
|
const request = auth ? { auth: auth ?? { authActive: false } } : req
|
||||||
// If Authorization header is also being user-defined, that takes priority
|
// If Authorization header is also being user-defined, that takes priority
|
||||||
@@ -60,8 +61,12 @@ export const getComputedAuthHeaders = (
|
|||||||
|
|
||||||
// TODO: Support a better b64 implementation than btoa ?
|
// TODO: Support a better b64 implementation than btoa ?
|
||||||
if (request.auth.authType === "basic") {
|
if (request.auth.authType === "basic") {
|
||||||
const username = parseTemplateString(request.auth.username, envVars)
|
const username = parse
|
||||||
const password = parseTemplateString(request.auth.password, envVars)
|
? parseTemplateString(request.auth.username, envVars)
|
||||||
|
: request.auth.username
|
||||||
|
const password = parse
|
||||||
|
? parseTemplateString(request.auth.password, envVars)
|
||||||
|
: request.auth.password
|
||||||
|
|
||||||
headers.push({
|
headers.push({
|
||||||
active: true,
|
active: true,
|
||||||
@@ -75,7 +80,11 @@ export const getComputedAuthHeaders = (
|
|||||||
headers.push({
|
headers.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Bearer ${parseTemplateString(request.auth.token, envVars)}`,
|
value: `Bearer ${
|
||||||
|
parse
|
||||||
|
? parseTemplateString(request.auth.token, envVars)
|
||||||
|
: request.auth.token
|
||||||
|
}`,
|
||||||
})
|
})
|
||||||
} else if (request.auth.authType === "api-key") {
|
} else if (request.auth.authType === "api-key") {
|
||||||
const { key, addTo } = request.auth
|
const { key, addTo } = request.auth
|
||||||
@@ -83,7 +92,9 @@ export const getComputedAuthHeaders = (
|
|||||||
headers.push({
|
headers.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: parseTemplateString(key, envVars),
|
key: parseTemplateString(key, envVars),
|
||||||
value: parseTemplateString(request.auth.value ?? "", envVars),
|
value: parse
|
||||||
|
? parseTemplateString(request.auth.value ?? "", envVars)
|
||||||
|
: request.auth.value ?? "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,10 +144,11 @@ export type ComputedHeader = {
|
|||||||
*/
|
*/
|
||||||
export const getComputedHeaders = (
|
export const getComputedHeaders = (
|
||||||
req: HoppRESTRequest,
|
req: HoppRESTRequest,
|
||||||
envVars: Environment["variables"]
|
envVars: Environment["variables"],
|
||||||
|
parse = true
|
||||||
): ComputedHeader[] => {
|
): ComputedHeader[] => {
|
||||||
return [
|
return [
|
||||||
...getComputedAuthHeaders(envVars, req).map((header) => ({
|
...getComputedAuthHeaders(envVars, req, undefined, parse).map((header) => ({
|
||||||
source: "auth" as const,
|
source: "auth" as const,
|
||||||
header,
|
header,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -69,13 +69,15 @@ import "splitpanes/dist/splitpanes.css"
|
|||||||
import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
|
import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
|
||||||
import { RouterView, useRouter } from "vue-router"
|
import { RouterView, useRouter } from "vue-router"
|
||||||
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { InvocationTriggers, defineActionHandler } from "~/helpers/actions"
|
||||||
import { hookKeybindingsListener } from "~/helpers/keybindings"
|
import { hookKeybindingsListener } from "~/helpers/keybindings"
|
||||||
import { applySetting } from "~/newstore/settings"
|
import { applySetting } from "~/newstore/settings"
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { HoppSpotlightSessionEventData } from "~/platform/analytics"
|
||||||
import { PersistenceService } from "~/services/persistence"
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import { SpotlightService } from "~/services/spotlight"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@ const toast = useToast()
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const persistenceService = useService(PersistenceService)
|
const persistenceService = useService(PersistenceService)
|
||||||
|
const spotlightService = useService(SpotlightService)
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (!mdAndLarger.value) {
|
if (!mdAndLarger.value) {
|
||||||
@@ -144,7 +147,18 @@ const spacerClass = computed(() =>
|
|||||||
expandNavigation.value ? "spacer-small" : "spacer-expand"
|
expandNavigation.value ? "spacer-small" : "spacer-expand"
|
||||||
)
|
)
|
||||||
|
|
||||||
defineActionHandler("modals.search.toggle", () => {
|
defineActionHandler("modals.search.toggle", (_, trigger) => {
|
||||||
|
const triggerMethodMap: Record<
|
||||||
|
InvocationTriggers,
|
||||||
|
HoppSpotlightSessionEventData["method"]
|
||||||
|
> = {
|
||||||
|
keypress: "keyboard-shortcut",
|
||||||
|
mouseclick: "click-spotlight-bar",
|
||||||
|
}
|
||||||
|
spotlightService.setAnalyticsData({
|
||||||
|
method: triggerMethodMap[trigger as InvocationTriggers],
|
||||||
|
})
|
||||||
|
|
||||||
showSearch.value = !showSearch.value
|
showSearch.value = !showSearch.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user