mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
gRPC Support (#20)
This commit is contained in:
312
package-lock.json
generated
312
package-lock.json
generated
@@ -28,6 +28,9 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"cm6-graphql": "^0.0.9",
|
"cm6-graphql": "^0.0.9",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
"codemirror-json-schema": "^0.6.1",
|
||||||
|
"codemirror-json5": "^1.0.3",
|
||||||
|
"date-fns": "^3.3.1",
|
||||||
"focus-trap-react": "^10.1.1",
|
"focus-trap-react": "^10.1.1",
|
||||||
"format-graphql": "^1.4.0",
|
"format-graphql": "^1.4.0",
|
||||||
"framer-motion": "^9.0.4",
|
"framer-motion": "^9.0.4",
|
||||||
@@ -469,6 +472,30 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@changesets/changelog-github": {
|
||||||
|
"version": "0.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.4.8.tgz",
|
||||||
|
"integrity": "sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@changesets/get-github-info": "^0.5.2",
|
||||||
|
"@changesets/types": "^5.2.1",
|
||||||
|
"dotenv": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@changesets/get-github-info": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==",
|
||||||
|
"dependencies": {
|
||||||
|
"dataloader": "^1.4.0",
|
||||||
|
"node-fetch": "^2.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@changesets/types": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@changesets/types/-/types-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg=="
|
||||||
|
},
|
||||||
"node_modules/@codemirror/autocomplete": {
|
"node_modules/@codemirror/autocomplete": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.2.0.tgz",
|
||||||
@@ -511,16 +538,6 @@
|
|||||||
"@lezer/javascript": "^1.0.0"
|
"@lezer/javascript": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/lang-javascript/node_modules/@codemirror/view": {
|
|
||||||
"version": "6.21.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.21.4.tgz",
|
|
||||||
"integrity": "sha512-WKVZ7nvN0lwWPfAf05WxWqTpwjC8YN3q5goj3CsSig7//DD81LULgOx3nBegqpqP0iygBqRmW8z0KSc2QTAdAg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@codemirror/state": "^6.1.4",
|
|
||||||
"style-mod": "^4.1.0",
|
|
||||||
"w3c-keyname": "^2.2.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@codemirror/lang-json": {
|
"node_modules/@codemirror/lang-json": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||||
@@ -556,9 +573,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/lint": {
|
"node_modules/@codemirror/lint": {
|
||||||
"version": "6.2.1",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.4.2.tgz",
|
||||||
"integrity": "sha512-y1muai5U/uUPAGRyHMx9mHuHLypPcHWxzlZGknp/U5Mdb5Ol8Q5ZLp67UqyTbNFJJ3unVxZ8iX3g1fMN79S1JQ==",
|
"integrity": "sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.0.0",
|
"@codemirror/view": "^6.0.0",
|
||||||
@@ -576,17 +593,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/state": {
|
"node_modules/@codemirror/state": {
|
||||||
"version": "6.2.0",
|
"version": "6.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.0.tgz",
|
||||||
"integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA=="
|
"integrity": "sha512-hm8XshYj5Fo30Bb922QX9hXB/bxOAVH+qaqHBzw5TKa72vOeslyGwd4X8M0c1dJ9JqxlaMceOQ8RsL9tC7gU0A=="
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.2.1",
|
"version": "6.23.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.23.1.tgz",
|
||||||
"integrity": "sha512-r1svbtAj2Lp/86F3yy1TfDAOAtJRGLINLSEqByETyUaGo1EnLS+P+bbGCVHV62z46BzZYm16noDid69+4bzn0g==",
|
"integrity": "sha512-J2Xnn5lFYT1ZN/5ewEoMBCmLlL71lZ3mBdb7cUEuHhX2ESoSrNEucpsDXpX22EuTGm9LOgC9v4Z0wx+Ez8QmGA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.4.0",
|
||||||
"style-mod": "^4.0.0",
|
"style-mod": "^4.1.0",
|
||||||
"w3c-keyname": "^2.2.4"
|
"w3c-keyname": "^2.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1350,6 +1367,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sagold/json-pointer": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sagold/json-pointer/-/json-pointer-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-/iskWuyGNu09qy09HYmyLnvzpKryymH9T+vTBi2LdFp1TuKvERDADvPMv2ZkQKsrRklOzivmOz9QXof0dKqvgA=="
|
||||||
|
},
|
||||||
|
"node_modules/@sagold/json-query": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sagold/json-query/-/json-query-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-5/Wu0rTnXmO5Uvtm9Of16Vx3mKjSnYA0Um9LgBtyPhIucYlppKgKC4N3g8gD0Fk00a5kizQTs4gwxKPXCpmeww==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sagold/json-pointer": "^5.1.1",
|
||||||
|
"ebnf": "^1.9.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sindresorhus/is": {
|
"node_modules/@sindresorhus/is": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||||
@@ -2174,8 +2205,7 @@
|
|||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.14",
|
"version": "7.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz",
|
||||||
"integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==",
|
"integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
@@ -3537,6 +3567,53 @@
|
|||||||
"@codemirror/view": "^6.0.0"
|
"@codemirror/view": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror-json-schema": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror-json-schema/-/codemirror-json-schema-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-QG12Jy917eStZzxurpAE9QUQxF8SS/AYJ9DDteyJZcRGH8ePaBCfQ4KLCNtY6cUKjFeNBgcd5+c6FPAri6pPQg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@changesets/changelog-github": "^0.4.8",
|
||||||
|
"@sagold/json-pointer": "^5.1.1",
|
||||||
|
"@types/json-schema": "^7.0.12",
|
||||||
|
"@types/node": "^20.4.2",
|
||||||
|
"json-schema": "^0.4.0",
|
||||||
|
"json-schema-library": "^9.1.2"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
|
"codemirror-json5": "^1.0.3",
|
||||||
|
"json5": "^2.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/language": "^6.8.0",
|
||||||
|
"@codemirror/lint": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.2.1",
|
||||||
|
"@codemirror/view": "^6.14.1",
|
||||||
|
"@lezer/common": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror-json-schema/node_modules/@types/node": {
|
||||||
|
"version": "20.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz",
|
||||||
|
"integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror-json5": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror-json5/-/codemirror-json5-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-HmmoYO2huQxoaoG5ARKjqQc9mz7/qmNPvMbISVfIE2Gk1+4vZQg9X3G6g49MYM5IK00Ol3aijd7OKrySuOkA7Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"json5": "^2.2.1",
|
||||||
|
"lezer-json5": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -3724,6 +3801,20 @@
|
|||||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/dataloader": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="
|
||||||
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
@@ -3803,6 +3894,14 @@
|
|||||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/defer-to-connect": {
|
"node_modules/defer-to-connect": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
||||||
@@ -3885,6 +3984,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/discontinuous-range": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ=="
|
||||||
|
},
|
||||||
"node_modules/dlv": {
|
"node_modules/dlv": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
@@ -3924,6 +4028,14 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "8.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
|
||||||
|
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/duplexer3": {
|
"node_modules/duplexer3": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
|
||||||
@@ -3936,6 +4048,14 @@
|
|||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ebnf": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ebnf/-/ebnf-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-uW2UKSsuty9ANJ3YByIQE4ANkD8nqUPO7r6Fwcc1ADKPe9FRdcPpMl3VEput4JSvKBJ4J86npIC2MLP0pYkCuw==",
|
||||||
|
"bin": {
|
||||||
|
"ebnf": "dist/bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron": {
|
"node_modules/electron": {
|
||||||
"version": "23.3.13",
|
"version": "23.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-23.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-23.3.13.tgz",
|
||||||
@@ -4777,6 +4897,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-copy": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA=="
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -6097,6 +6222,25 @@
|
|||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/json-schema": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
|
||||||
|
},
|
||||||
|
"node_modules/json-schema-library": {
|
||||||
|
"version": "9.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-9.1.2.tgz",
|
||||||
|
"integrity": "sha512-uQnFb2V+VakLl6XIGGtUQzfjkP31f/dCT5lJq9NOUdypSSpjbWL/V0R2KvoNJp3hU8VErwh9DqVoZPqlC+B3IA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sagold/json-pointer": "^5.1.1",
|
||||||
|
"@sagold/json-query": "^6.1.1",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"fast-copy": "^3.0.1",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"smtp-address-parser": "1.0.10",
|
||||||
|
"valid-url": "^1.0.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
@@ -6120,7 +6264,6 @@
|
|||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
},
|
},
|
||||||
@@ -6207,6 +6350,14 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lezer-json5": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lezer-json5/-/lezer-json5-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-NRmtBlKW/f8mA7xatKq8IUOq045t8GVHI4kZXrUtYYUdiVeGiO6zKGAV7/nUAnf5q+rYTY+SWX/gvQdFXMjNxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lilconfig": {
|
"node_modules/lilconfig": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||||
@@ -6590,6 +6741,11 @@
|
|||||||
"ufo": "^1.3.0"
|
"ufo": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moo": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
@@ -6654,12 +6810,57 @@
|
|||||||
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
|
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/nearley": {
|
||||||
|
"version": "2.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
|
||||||
|
"integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^2.19.0",
|
||||||
|
"moo": "^0.5.0",
|
||||||
|
"railroad-diagrams": "^1.0.0",
|
||||||
|
"randexp": "0.4.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nearley-railroad": "bin/nearley-railroad.js",
|
||||||
|
"nearley-test": "bin/nearley-test.js",
|
||||||
|
"nearley-unparse": "bin/nearley-unparse.js",
|
||||||
|
"nearleyc": "bin/nearleyc.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://nearley.js.org/#give-to-nearley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nearley/node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||||
|
},
|
||||||
"node_modules/nice-try": {
|
"node_modules/nice-try": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.13",
|
"version": "2.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||||
@@ -7606,6 +7807,23 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/railroad-diagrams": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A=="
|
||||||
|
},
|
||||||
|
"node_modules/randexp": {
|
||||||
|
"version": "0.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
|
||||||
|
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"discontinuous-range": "1.0.0",
|
||||||
|
"ret": "~0.1.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rc": {
|
"node_modules/rc": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
@@ -8114,6 +8332,14 @@
|
|||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ret": {
|
||||||
|
"version": "0.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
||||||
|
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reusify": {
|
"node_modules/reusify": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||||
@@ -8523,6 +8749,17 @@
|
|||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/smtp-address-parser": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-Osg9LmvGeAG/hyao4mldbflLOkkr3a+h4m1lwKCK5U8M6ZAr7tdXEz/+/vr752TSGE4MNUlUl9cIK2cB8cgzXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"nearley": "^2.20.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
@@ -9253,6 +9490,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
"node_modules/ts-easing": {
|
"node_modules/ts-easing": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz",
|
||||||
@@ -9448,8 +9690,7 @@
|
|||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/unique-string": {
|
"node_modules/unique-string": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -9578,6 +9819,11 @@
|
|||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/valid-url": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA=="
|
||||||
|
},
|
||||||
"node_modules/validate-npm-package-license": {
|
"node_modules/validate-npm-package-license": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||||
@@ -9780,6 +10026,20 @@
|
|||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"cm6-graphql": "^0.0.9",
|
"cm6-graphql": "^0.0.9",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
"codemirror-json-schema": "^0.6.1",
|
||||||
|
"codemirror-json5": "^1.0.3",
|
||||||
|
"date-fns": "^3.3.1",
|
||||||
"focus-trap-react": "^10.1.1",
|
"focus-trap-react": "^10.1.1",
|
||||||
"format-graphql": "^1.4.0",
|
"format-graphql": "^1.4.0",
|
||||||
"framer-motion": "^9.0.4",
|
"framer-motion": "^9.0.4",
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO folders (\n id,\n workspace_id,\n folder_id,\n name,\n sort_priority\n )\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 5
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "02506ad41cc94cd937422ef1977a97174431f008a9fb4ce39667d587a858b876"
|
|
||||||
}
|
|
||||||
12
src-tauri/.sqlx/query-11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1.json
generated
Normal file
12
src-tauri/.sqlx/query-11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO http_requests (\n id, workspace_id, folder_id, name, url, url_parameters, method, body, body_type,\n authentication, authentication_type, headers, sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n url_parameters = excluded.url_parameters,\n sort_priority = excluded.sort_priority\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 13
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1"
|
||||||
|
}
|
||||||
12
src-tauri/.sqlx/query-12b265491d1ebba19e1ce8a660e458ffbcd8c0850aef16ba1f70e358623ac66a.json
generated
Normal file
12
src-tauri/.sqlx/query-12b265491d1ebba19e1ce8a660e458ffbcd8c0850aef16ba1f70e358623ac66a.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO workspaces (\n id, name, description, variables, setting_request_timeout,\n setting_follow_redirects, setting_validate_certificates\n )\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n description = excluded.description,\n variables = excluded.variables,\n setting_request_timeout = excluded.setting_request_timeout,\n setting_follow_redirects = excluded.setting_follow_redirects,\n setting_validate_certificates = excluded.setting_validate_certificates\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "12b265491d1ebba19e1ce8a660e458ffbcd8c0850aef16ba1f70e358623ac66a"
|
||||||
|
}
|
||||||
12
src-tauri/.sqlx/query-13cb883199e81966174e6fda9c252bf7213fe01b5346266c0a89dc0ac89eda64.json
generated
Normal file
12
src-tauri/.sqlx/query-13cb883199e81966174e6fda9c252bf7213fe01b5346266c0a89dc0ac89eda64.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO environments (\n id, workspace_id, name, variables\n )\n VALUES (?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n variables = excluded.variables\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "13cb883199e81966174e6fda9c252bf7213fe01b5346266c0a89dc0ac89eda64"
|
||||||
|
}
|
||||||
68
src-tauri/.sqlx/query-196ed792c8d96425d428cb9609b0c1b18e8f1ba3c1fdfb38c91ffd7bada97f59.json
generated
Normal file
68
src-tauri/.sqlx/query-196ed792c8d96425d428cb9609b0c1b18e8f1ba3c1fdfb38c91ffd7bada97f59.json
generated
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT\n id, model, workspace_id, request_id, connection_id, created_at, message,\n is_server, is_info\n FROM grpc_messages\n WHERE connection_id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "request_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "connection_id",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_server",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_info",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "196ed792c8d96425d428cb9609b0c1b18e8f1ba3c1fdfb38c91ffd7bada97f59"
|
||||||
|
}
|
||||||
12
src-tauri/.sqlx/query-2c9658a639c5e4994ae9c8ec30bd4e40a1945d640962991f879928619950ef62.json
generated
Normal file
12
src-tauri/.sqlx/query-2c9658a639c5e4994ae9c8ec30bd4e40a1945d640962991f879928619950ef62.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO http_responses (\n id, request_id, workspace_id, elapsed, elapsed_headers, url, status, status_reason,\n content_length, body_path, headers, version, remote_addr\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 13
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "2c9658a639c5e4994ae9c8ec30bd4e40a1945d640962991f879928619950ef62"
|
||||||
|
}
|
||||||
68
src-tauri/.sqlx/query-3330be44d8851f8e3456c403b5d1067f4e70e85ef8829b7aaad5b1993c3d01e8.json
generated
Normal file
68
src-tauri/.sqlx/query-3330be44d8851f8e3456c403b5d1067f4e70e85ef8829b7aaad5b1993c3d01e8.json
generated
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT\n id, model, workspace_id, request_id, created_at, updated_at, service,\n method, elapsed\n FROM grpc_connections\n WHERE id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "request_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "method",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "elapsed",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Int64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "3330be44d8851f8e3456c403b5d1067f4e70e85ef8829b7aaad5b1993c3d01e8"
|
||||||
|
}
|
||||||
68
src-tauri/.sqlx/query-3c52c0fa3372cdd2657a775c3b93fb65f42d3226cec27220469558e14973328c.json
generated
Normal file
68
src-tauri/.sqlx/query-3c52c0fa3372cdd2657a775c3b93fb65f42d3226cec27220469558e14973328c.json
generated
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT\n id, model, workspace_id, request_id, connection_id, created_at, message,\n is_server, is_info\n FROM grpc_messages\n WHERE id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "request_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "connection_id",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_server",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_info",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "3c52c0fa3372cdd2657a775c3b93fb65f42d3226cec27220469558e14973328c"
|
||||||
|
}
|
||||||
12
src-tauri/.sqlx/query-42bc0ded60b44dab19daf6d8fc7df83d83af5d88ea0b84514fdc877a668c27cd.json
generated
Normal file
12
src-tauri/.sqlx/query-42bc0ded60b44dab19daf6d8fc7df83d83af5d88ea0b84514fdc877a668c27cd.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n DELETE FROM grpc_connections\n WHERE id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "42bc0ded60b44dab19daf6d8fc7df83d83af5d88ea0b84514fdc877a668c27cd"
|
||||||
|
}
|
||||||
12
src-tauri/.sqlx/query-48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0.json
generated
Normal file
12
src-tauri/.sqlx/query-48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n UPDATE settings SET (\n theme, appearance, update_channel\n ) = (?, ?, ?) WHERE id = 'default';\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0"
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n folder_id,\n name,\n url,\n url_parameters,\n method,\n body,\n body_type,\n authentication,\n authentication_type,\n headers,\n sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n url_parameters = excluded.url_parameters,\n sort_priority = excluded.sort_priority\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 13
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "4a5fd6c81401ccafac64b05cb476da92cc30919d5bdb0a0226ea5e30d5b30c0f"
|
|
||||||
}
|
|
||||||
12
src-tauri/.sqlx/query-4b45b681698cbfe8531a7c3ba368a1d8003fa17d5585bc126debb18cae670460.json
generated
Normal file
12
src-tauri/.sqlx/query-4b45b681698cbfe8531a7c3ba368a1d8003fa17d5585bc126debb18cae670460.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO grpc_messages (\n id, workspace_id, request_id, connection_id, message, is_server, is_info\n )\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n message = excluded.message,\n is_server = excluded.is_server,\n is_info = excluded.is_info\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "4b45b681698cbfe8531a7c3ba368a1d8003fa17d5585bc126debb18cae670460"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n folder_id,\n name,\n sort_priority\n FROM folders\n WHERE id = ?\n ",
|
"query": "\n SELECT\n id, model, workspace_id, created_at, updated_at, folder_id, name, sort_priority\n FROM folders\n WHERE workspace_id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "1428d25b6aa3d6ec55742a968571fa951da0406d7bb32408883c584eae7dd53c"
|
"hash": "558e72df3c6f2635c6b3d52a199f9a5f7a3d82b379ff9af36645dcfb92548fdd"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n folder_id,\n created_at,\n updated_at,\n name,\n url,\n url_parameters AS \"url_parameters!: sqlx::types::Json<Vec<HttpUrlParameter>>\",\n method,\n body AS \"body!: Json<HashMap<String, JsonValue>>\",\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n ",
|
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, url, method,\n body_type, authentication_type, sort_priority,\n url_parameters AS \"url_parameters!: sqlx::types::Json<Vec<HttpUrlParameter>>\",\n body AS \"body!: Json<HashMap<String, JsonValue>>\",\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -44,39 +44,39 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "url_parameters!: sqlx::types::Json<Vec<HttpUrlParameter>>",
|
"name": "method",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "method",
|
"name": "body_type",
|
||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body!: Json<HashMap<String, JsonValue>>",
|
"name": "authentication_type",
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body_type",
|
"name": "sort_priority",
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Float"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
"name": "url_parameters!: sqlx::types::Json<Vec<HttpUrlParameter>>",
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "authentication_type",
|
"name": "body!: Json<HashMap<String, JsonValue>>",
|
||||||
"ordinal": 13,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sort_priority",
|
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
||||||
"ordinal": 14,
|
"ordinal": 14,
|
||||||
"type_info": "Float"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
||||||
@@ -97,14 +97,14 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
true,
|
||||||
false,
|
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
false,
|
||||||
|
false,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "6483f3ffeb90e019e9078d98bb831b8e4fbedfb45751d6cd33bd42e518b634dd"
|
"hash": "573db23160de025e5c72efb90be7fff5e3ec4619b962d149fdd4d618fe02c680"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM environments\n WHERE id = ?\n ",
|
"query": "\n SELECT\n id, model, workspace_id, created_at, updated_at, name,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM environments\n WHERE id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "689bcc92b914f50c14921faa796c07a256deb84c832fc3d90200b393fb159417"
|
"hash": "5765e9565a8b89c5bc2d72197e0e4a1093739e9abba69f6fe5527d453fab4db8"
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n UPDATE http_responses SET (\n elapsed,\n elapsed_headers,\n url,\n status,\n status_reason,\n content_length,\n body_path,\n error,\n headers,\n version,\n remote_addr,\n updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 12
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "587aedf827b00bb706c35457a75b811317e66fc84ac0906bf5513d938121a078"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n workspace_id,\n name,\n cookies AS \"cookies!: sqlx::types::Json<Vec<JsonValue>>\"\n FROM cookie_jars WHERE id = ?\n ",
|
"query": "\n SELECT\n id, model, created_at, updated_at, workspace_id, name,\n cookies AS \"cookies!: sqlx::types::Json<Vec<JsonValue>>\"\n FROM cookie_jars WHERE workspace_id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "f2ba4708d4a9ff9ce74c407a730040bd7883e9a5c0eb79ef0d8a6782a8eae299"
|
"hash": "612efa9ac45723dc604a88f5e7e288b4055fec4ba7d9102131bd255c037fa021"
|
||||||
}
|
}
|
||||||
92
src-tauri/.sqlx/query-7398403d3de2dc5c5b4b6392f083041d9a55194bb97819225a2612fdeb60ad42.json
generated
Normal file
92
src-tauri/.sqlx/query-7398403d3de2dc5c5b4b6392f083041d9a55194bb97819225a2612fdeb60ad42.json
generated
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message,\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort_priority",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "url",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "method",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "7398403d3de2dc5c5b4b6392f083041d9a55194bb97819225a2612fdeb60ad42"
|
||||||
|
}
|
||||||
92
src-tauri/.sqlx/query-761d27c3ec425c37ad9abe9c732a9c1746c81ca50d2c413e540b74c8c8e908b7.json
generated
Normal file
92
src-tauri/.sqlx/query-761d27c3ec425c37ad9abe9c732a9c1746c81ca50d2c413e540b74c8c8e908b7.json
generated
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message,\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE workspace_id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort_priority",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "url",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "method",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "761d27c3ec425c37ad9abe9c732a9c1746c81ca50d2c413e540b74c8c8e908b7"
|
||||||
|
}
|
||||||
68
src-tauri/.sqlx/query-80a85f83d0946d532a60f0add87aa0ade7e35a6b56cb058e2caf9ca005ce6407.json
generated
Normal file
68
src-tauri/.sqlx/query-80a85f83d0946d532a60f0add87aa0ade7e35a6b56cb058e2caf9ca005ce6407.json
generated
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT\n id, model, workspace_id, request_id, created_at, updated_at, service,\n method, elapsed\n FROM grpc_connections\n WHERE request_id = ?\n ORDER BY created_at DESC\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "request_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "method",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "elapsed",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Int64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "80a85f83d0946d532a60f0add87aa0ade7e35a6b56cb058e2caf9ca005ce6407"
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n UPDATE settings SET (\n theme,\n appearance,\n update_channel\n ) = (?, ?, ?) WHERE id = 'default';\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "86a9d12d7b00217f3143671908c31c2c6a3c24774a505280dcba169eb5b6b0fb"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n name,\n description,\n setting_request_timeout,\n setting_follow_redirects,\n setting_validate_certificates,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM workspaces\n ",
|
"query": "\n SELECT\n id, model, created_at, updated_at, name, description, setting_request_timeout,\n setting_follow_redirects, setting_validate_certificates,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM workspaces\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -70,5 +70,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "20cf0f8b71e600bc40ee204b60a12b2f3728178f3b8788d2e5cc92821b74d470"
|
"hash": "8dfbae65ddec905ea3734448cc9f7029b6c78de227c6fa3a85d75d0a7f21e0e9"
|
||||||
}
|
}
|
||||||
12
src-tauri/.sqlx/query-9238f94c688d91f42627e5b73c627c514bab4039ab5edadc79b77dfdfd63b208.json
generated
Normal file
12
src-tauri/.sqlx/query-9238f94c688d91f42627e5b73c627c514bab4039ab5edadc79b77dfdfd63b208.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO folders (\n id, workspace_id, folder_id, name, sort_priority\n )\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 5
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "9238f94c688d91f42627e5b73c627c514bab4039ab5edadc79b77dfdfd63b208"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n name,\n description,\n setting_request_timeout,\n setting_follow_redirects,\n setting_validate_certificates,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM workspaces WHERE id = ?\n ",
|
"query": "\n SELECT\n id, model, created_at, updated_at, name, description, setting_request_timeout,\n setting_follow_redirects, setting_validate_certificates,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM workspaces WHERE id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -70,5 +70,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "e08fa4f9b2929f20a01d1dc43d6847a309d3e8c5b324df2d039d1c6e07e6eb2f"
|
"hash": "9ba3f783238b77637ffded4171b2fbb5e5ad0be952a0d832448d65cc5f0effc1"
|
||||||
}
|
}
|
||||||
12
src-tauri/.sqlx/query-9d7bc2b0eb0c09652d9826db4a7ae47591405e1b5bec1229f2e2734c73e66163.json
generated
Normal file
12
src-tauri/.sqlx/query-9d7bc2b0eb0c09652d9826db4a7ae47591405e1b5bec1229f2e2734c73e66163.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO grpc_connections (\n id, workspace_id, request_id, service, method, elapsed\n )\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n service = excluded.service,\n method = excluded.method,\n elapsed = excluded.elapsed\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 6
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "9d7bc2b0eb0c09652d9826db4a7ae47591405e1b5bec1229f2e2734c73e66163"
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n elapsed_headers,\n url,\n status,\n status_reason,\n content_length,\n body_path,\n headers,\n version,\n remote_addr\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 13
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a1c9a862ca6a07476cb8e7d16d73bd109c070603396a890dc717e50020d006f5"
|
|
||||||
}
|
|
||||||
12
src-tauri/.sqlx/query-a690a04cd1ebe8c3dbfd0cd98ae4ef093a1696d7b7ecaf694d12e5fafd62b685.json
generated
Normal file
12
src-tauri/.sqlx/query-a690a04cd1ebe8c3dbfd0cd98ae4ef093a1696d7b7ecaf694d12e5fafd62b685.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n UPDATE grpc_connections\n SET (elapsed) = (-1)\n WHERE elapsed = 0;\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "a690a04cd1ebe8c3dbfd0cd98ae4ef093a1696d7b7ecaf694d12e5fafd62b685"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n folder_id,\n name,\n sort_priority\n FROM folders\n WHERE workspace_id = ?\n ",
|
"query": "\n SELECT\n id, model, workspace_id, created_at, updated_at, folder_id, name, sort_priority\n FROM folders\n WHERE id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "1517b0f86c841b5f1247bd40c3a9b38ab001d846a410b6e3cd36f9e844d50ddb"
|
"hash": "ae98a7b35a5cb80a4bcd04faa22545deac2a5e9bfb814b60191f16b98ed49796"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n theme,\n appearance,\n update_channel\n FROM settings\n WHERE id = 'default'\n ",
|
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance, update_channel\n FROM settings\n WHERE id = 'default'\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "3b3fb6271340c6ec21a10b4f1b20502c86c425e0b53ac07692f8a4ed0be09335"
|
"hash": "b32994b09ae7a06eb0f031069d327e55127a5bce60cbb499b83d1701386a23cb"
|
||||||
}
|
}
|
||||||
12
src-tauri/.sqlx/query-b3fae40a793a6724dd2286a9ca4bc0a9c000a631ee0d751a9dc4f3e76de3d57c.json
generated
Normal file
12
src-tauri/.sqlx/query-b3fae40a793a6724dd2286a9ca4bc0a9c000a631ee0d751a9dc4f3e76de3d57c.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO cookie_jars (\n id, workspace_id, name, cookies\n )\n VALUES (?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n cookies = excluded.cookies\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "b3fae40a793a6724dd2286a9ca4bc0a9c000a631ee0d751a9dc4f3e76de3d57c"
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO cookie_jars (\n id,\n workspace_id,\n name,\n cookies\n )\n VALUES (?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n cookies = excluded.cookies\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 4
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "b5ed4dc77f8cf21de1a06f146e47821bdb51fcfe747170bea41e7a366d736bda"
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO workspaces (\n id,\n name,\n description,\n variables,\n setting_request_timeout,\n setting_follow_redirects,\n setting_validate_certificates\n )\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n description = excluded.description,\n variables = excluded.variables,\n setting_request_timeout = excluded.setting_request_timeout,\n setting_follow_redirects = excluded.setting_follow_redirects,\n setting_validate_certificates = excluded.setting_validate_certificates\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 7
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "cae4e905515ddea1ec2cd685020241f06b49719085a695b897ef8ad409d2cef2"
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO environments (\n id,\n workspace_id,\n name,\n variables\n )\n VALUES (?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n variables = excluded.variables\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 4
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "dcc2f405f8e29d0599d86bcde509187e9cc5fc647067eaa5c738cb24e2f081e5"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n folder_id,\n created_at,\n updated_at,\n name,\n url,\n url_parameters AS \"url_parameters!: sqlx::types::Json<Vec<HttpUrlParameter>>\",\n method,\n body AS \"body!: Json<HashMap<String, JsonValue>>\",\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n ",
|
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, url,\n url_parameters AS \"url_parameters!: sqlx::types::Json<Vec<HttpUrlParameter>>\",\n method, body_type, authentication_type, sort_priority,\n body AS \"body!: Json<HashMap<String, JsonValue>>\",\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -54,29 +54,29 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body!: Json<HashMap<String, JsonValue>>",
|
"name": "body_type",
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body_type",
|
"name": "authentication_type",
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
"name": "sort_priority",
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Float"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "authentication_type",
|
"name": "body!: Json<HashMap<String, JsonValue>>",
|
||||||
"ordinal": 13,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sort_priority",
|
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
||||||
"ordinal": 14,
|
"ordinal": 14,
|
||||||
"type_info": "Float"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
||||||
@@ -98,13 +98,13 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
false,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "7a7bc4df7e52ad3a987c97af8f43b46381e2cc16ba0c553713d0b6c64354eb39"
|
"hash": "e61c0dddb3e86d271cb9399faa4e4443342796cb72bdd43a821fae2994ae8e2f"
|
||||||
}
|
}
|
||||||
12
src-tauri/.sqlx/query-e7124f5570076bfd65985744f48d8e12cf29d6d243fffdd62ade2ab70c7bddda.json
generated
Normal file
12
src-tauri/.sqlx/query-e7124f5570076bfd65985744f48d8e12cf29d6d243fffdd62ade2ab70c7bddda.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n UPDATE http_responses SET (\n elapsed, elapsed_headers, url, status, status_reason, content_length, body_path,\n error, headers, version, remote_addr, updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 12
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "e7124f5570076bfd65985744f48d8e12cf29d6d243fffdd62ade2ab70c7bddda"
|
||||||
|
}
|
||||||
12
src-tauri/.sqlx/query-ee562f85ec28c554c607adde670fc30eaeffeed6883e712bda4b4d6ca49cf740.json
generated
Normal file
12
src-tauri/.sqlx/query-ee562f85ec28c554c607adde670fc30eaeffeed6883e712bda4b4d6ca49cf740.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n proto_files\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n proto_files = excluded.proto_files\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 10
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "ee562f85ec28c554c607adde670fc30eaeffeed6883e712bda4b4d6ca49cf740"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n workspace_id,\n name,\n cookies AS \"cookies!: sqlx::types::Json<Vec<JsonValue>>\"\n FROM cookie_jars WHERE workspace_id = ?\n ",
|
"query": "\n SELECT\n id, model, created_at, updated_at, workspace_id, name,\n cookies AS \"cookies!: sqlx::types::Json<Vec<JsonValue>>\"\n FROM cookie_jars WHERE id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "cb939b45a715d91f7631dea6b2d1bdc59fb3dffbd44ff99bc15adb34ea7093f7"
|
"hash": "f5f20f3b37d932617499a0da50997edad59e4f5998b15c50ed6eae2e97064068"
|
||||||
}
|
}
|
||||||
12
src-tauri/.sqlx/query-fe0652396bc30d926cf99083651c1cbd668bcf00ebe1a5f36616700c84972b39.json
generated
Normal file
12
src-tauri/.sqlx/query-fe0652396bc30d926cf99083651c1cbd668bcf00ebe1a5f36616700c84972b39.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n DELETE FROM grpc_requests\n WHERE id = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "fe0652396bc30d926cf99083651c1cbd668bcf00ebe1a5f36616700c84972b39"
|
||||||
|
}
|
||||||
600
src-tauri/Cargo.lock
generated
600
src-tauri/Cargo.lock
generated
@@ -77,9 +77,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.75"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
@@ -95,6 +95,39 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-stream"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream-impl",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-stream-impl"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atk"
|
name = "atk"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
@@ -128,12 +161,67 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-write-file"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436"
|
||||||
|
dependencies = [
|
||||||
|
"nix",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum"
|
||||||
|
version = "0.6.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum-core",
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"itoa 1.0.9",
|
||||||
|
"matchit",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-core"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"mime",
|
||||||
|
"rustversion",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.69"
|
version = "0.3.69"
|
||||||
@@ -1073,12 +1161,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.6"
|
version = "0.3.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1596,6 +1684,28 @@ dependencies = [
|
|||||||
"system-deps 6.2.0",
|
"system-deps 6.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grpc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"prost",
|
||||||
|
"prost-reflect",
|
||||||
|
"prost-types",
|
||||||
|
"protoc-bin-vendored",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
"tonic-reflection",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gtk"
|
name = "gtk"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -1761,20 +1871,6 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "html5ever"
|
|
||||||
version = "0.25.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"mac",
|
|
||||||
"markup5ever 0.10.1",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@@ -1783,7 +1879,7 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"mac",
|
"mac",
|
||||||
"markup5ever 0.11.0",
|
"markup5ever",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
@@ -1853,6 +1949,34 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"log",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-timeout"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
|
||||||
|
dependencies = [
|
||||||
|
"hyper",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-io-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-tls"
|
name = "hyper-tls"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -2233,18 +2357,6 @@ dependencies = [
|
|||||||
"treediff",
|
"treediff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kuchiki"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
|
|
||||||
dependencies = [
|
|
||||||
"cssparser",
|
|
||||||
"html5ever 0.25.2",
|
|
||||||
"matches",
|
|
||||||
"selectors",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kuchikiki"
|
name = "kuchikiki"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -2252,7 +2364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
|
checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"html5ever 0.26.0",
|
"html5ever",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"matches",
|
"matches",
|
||||||
"selectors",
|
"selectors",
|
||||||
@@ -2269,9 +2381,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.150"
|
version = "0.2.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@@ -2292,9 +2404,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.26.0"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
|
checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
@@ -2312,9 +2424,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.11"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
|
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -2380,20 +2492,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "markup5ever"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"phf 0.8.0",
|
|
||||||
"phf_codegen 0.8.0",
|
|
||||||
"string_cache",
|
|
||||||
"string_cache_codegen",
|
|
||||||
"tendril",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markup5ever"
|
name = "markup5ever"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -2423,6 +2521,12 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchit"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -2549,6 +2653,17 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nodrop"
|
name = "nodrop"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
@@ -2839,6 +2954,15 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_info"
|
name = "os_info"
|
||||||
version = "3.7.0"
|
version = "3.7.0"
|
||||||
@@ -3074,6 +3198,26 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-internal"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@@ -3230,6 +3374,114 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"prost-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-derive"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"itertools",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-reflect"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "057237efdb71cf4b3f9396302a3d6599a92fa94063ba537b66130980ea9909f3"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.5",
|
||||||
|
"once_cell",
|
||||||
|
"prost",
|
||||||
|
"prost-reflect-derive",
|
||||||
|
"prost-types",
|
||||||
|
"serde",
|
||||||
|
"serde-value",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-reflect-derive"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "172da1212c02be2c94901440cb27183cd92bff00ebacca5c323bf7520b8f9c04"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-types"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e"
|
||||||
|
dependencies = [
|
||||||
|
"prost",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d"
|
||||||
|
dependencies = [
|
||||||
|
"protoc-bin-vendored-linux-aarch_64",
|
||||||
|
"protoc-bin-vendored-linux-ppcle_64",
|
||||||
|
"protoc-bin-vendored-linux-x86_32",
|
||||||
|
"protoc-bin-vendored-linux-x86_64",
|
||||||
|
"protoc-bin-vendored-macos-x86_64",
|
||||||
|
"protoc-bin-vendored-win32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-aarch_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-ppcle_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-x86_32"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-linux-x86_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-macos-x86_64"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protoc-bin-vendored-win32"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psl-types"
|
name = "psl-types"
|
||||||
version = "2.0.11"
|
version = "2.0.11"
|
||||||
@@ -3577,15 +3829,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.21"
|
version = "0.38.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3594,11 +3846,24 @@ version = "0.21.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
|
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"sct",
|
"sct",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -3736,18 +4001,28 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.195"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde-value"
|
||||||
version = "1.0.195"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.196"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3756,9 +4031,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.111"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.9",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -4018,9 +4293,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx"
|
name = "sqlx"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33"
|
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-macros",
|
"sqlx-macros",
|
||||||
@@ -4031,9 +4306,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-core"
|
name = "sqlx-core"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d"
|
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"atoi",
|
"atoi",
|
||||||
@@ -4076,9 +4351,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-macros"
|
name = "sqlx-macros"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec"
|
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -4089,10 +4364,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-macros-core"
|
name = "sqlx-macros-core"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc"
|
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"atomic-write-file",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"either",
|
"either",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
@@ -4115,9 +4391,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-mysql"
|
name = "sqlx-mysql"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
|
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@@ -4159,9 +4435,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-postgres"
|
name = "sqlx-postgres"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
|
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@@ -4200,9 +4476,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-sqlite"
|
name = "sqlx-sqlite"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f"
|
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -4220,6 +4496,7 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4314,6 +4591,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -4469,9 +4752,9 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.5.2"
|
version = "1.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9bfe673cf125ef364d6f56b15e8ce7537d9ca7e4dae1cf6fbbdeed2e024db3d9"
|
checksum = "fd27c04b9543776a972c86ccf70660b517ecabbeced9fb58d8b961a13ad129af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@@ -4544,9 +4827,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "1.4.1"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb"
|
checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"brotli",
|
"brotli",
|
||||||
@@ -4570,9 +4853,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "1.4.1"
|
version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613740228de92d9196b795ac455091d3a5fbdac2654abb8bb07d010b62ab43af"
|
checksum = "277abf361a3a6993ec16bcbb179de0d6518009b851090a01adfea12ac89fa875"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -4613,9 +4896,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "0.14.1"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43"
|
checksum = "cf2d0652aa2891ff3e9caa2401405257ea29ab8372cce01f186a5825f1bd0e76"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
@@ -4634,9 +4917,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "0.14.1"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8141d72b6b65f2008911e9ef5b98a68d1e3413b7a1464e8f85eb3673bb19a895"
|
checksum = "6cae61fbc731f690a4899681c9052dde6d05b159b44563ace8186fc1bfb7d158"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa 0.24.1",
|
"cocoa 0.24.1",
|
||||||
"gtk",
|
"gtk",
|
||||||
@@ -4663,7 +4946,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"html5ever 0.26.0",
|
"html5ever",
|
||||||
"infer",
|
"infer",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
@@ -4695,15 +4978,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.8.1"
|
version = "3.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall 0.4.1",
|
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4829,9 +5111,31 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.5.5",
|
"socket2 0.5.5",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-io-timeout"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -4842,6 +5146,16 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
|
dependencies = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
@@ -4935,6 +5249,72 @@ dependencies = [
|
|||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tonic"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream",
|
||||||
|
"async-trait",
|
||||||
|
"axum",
|
||||||
|
"base64 0.21.5",
|
||||||
|
"bytes",
|
||||||
|
"h2",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"hyper-timeout",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
|
"prost",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tonic-reflection"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fa37c513df1339d197f4ba21d28c918b9ef1ac1768265f11ecb6b7f1cba1b76"
|
||||||
|
dependencies = [
|
||||||
|
"prost",
|
||||||
|
"prost-types",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"pin-project",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-layer"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -5096,6 +5476,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -5122,9 +5508,9 @@ checksum = "64a8922555b9500e3d865caed19330172cd67cbf82203f1a3311d8c305cc9f33"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.5.0"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.11",
|
"getrandom 0.2.11",
|
||||||
]
|
]
|
||||||
@@ -5360,12 +5746,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.24.0"
|
version = "0.25.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888"
|
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||||
dependencies = [
|
|
||||||
"rustls-webpki",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webview2-com"
|
name = "webview2-com"
|
||||||
@@ -5558,6 +5941,15 @@ dependencies = [
|
|||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -5847,9 +6239,9 @@ checksum = "dad7bb64b8ef9c0aa27b6da38b452b0ee9fd82beaf276a87dd796fb55cbae14e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wry"
|
name = "wry"
|
||||||
version = "0.24.4"
|
version = "0.24.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e"
|
checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"block",
|
"block",
|
||||||
@@ -5861,9 +6253,9 @@ dependencies = [
|
|||||||
"gio",
|
"gio",
|
||||||
"glib",
|
"glib",
|
||||||
"gtk",
|
"gtk",
|
||||||
"html5ever 0.25.2",
|
"html5ever",
|
||||||
"http",
|
"http",
|
||||||
"kuchiki",
|
"kuchikiki",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -5925,6 +6317,7 @@ dependencies = [
|
|||||||
"cookie 0.18.0",
|
"cookie 0.18.0",
|
||||||
"datetime",
|
"datetime",
|
||||||
"futures",
|
"futures",
|
||||||
|
"grpc",
|
||||||
"http",
|
"http",
|
||||||
"log",
|
"log",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -5940,6 +6333,7 @@ dependencies = [
|
|||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"uuid",
|
"uuid",
|
||||||
"window-shadows",
|
"window-shadows",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
workspace = { members = ["grpc"] }
|
||||||
[package]
|
[package]
|
||||||
name = "yaak-app"
|
name = "yaak-app"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@@ -32,7 +33,7 @@ reqwest = { version = "0.11.23", features = ["multipart", "cookies", "gzip", "br
|
|||||||
cookie = { version = "0.18.0" }
|
cookie = { version = "0.18.0" }
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.111", features = ["raw_value"] }
|
serde_json = { version = "1.0.111", features = ["raw_value"] }
|
||||||
sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
sqlx = { version = "0.7.3", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
||||||
tauri = { version = "1.5.2", features = [
|
tauri = { version = "1.5.2", features = [
|
||||||
"config-toml",
|
"config-toml",
|
||||||
"devtools",
|
"devtools",
|
||||||
@@ -59,6 +60,8 @@ log = "0.4.20"
|
|||||||
datetime = "0.5.2"
|
datetime = "0.5.2"
|
||||||
window-shadows = "0.2.2"
|
window-shadows = "0.2.2"
|
||||||
reqwest_cookie_store = "0.6.0"
|
reqwest_cookie_store = "0.6.0"
|
||||||
|
grpc = { path = "./grpc" }
|
||||||
|
tokio-stream = "0.1.14"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
|||||||
22
src-tauri/grpc/Cargo.toml
Normal file
22
src-tauri/grpc/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "grpc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tonic = "0.10.2"
|
||||||
|
prost = "0.12"
|
||||||
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "fs"] }
|
||||||
|
tonic-reflection = "0.10.2"
|
||||||
|
tokio-stream = "0.1.14"
|
||||||
|
prost-types = "0.12.3"
|
||||||
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
|
serde_json = "1.0.113"
|
||||||
|
prost-reflect = { version = "0.12.0", features = ["serde", "derive"] }
|
||||||
|
log = "0.4.20"
|
||||||
|
once_cell = { version = "1.19.0", features = [] }
|
||||||
|
anyhow = "1.0.79"
|
||||||
|
hyper = { version = "0.14" }
|
||||||
|
hyper-rustls = { version = "0.24.0", features = ["http2"] }
|
||||||
|
protoc-bin-vendored = "3.0.0"
|
||||||
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
52
src-tauri/grpc/src/codec.rs
Normal file
52
src-tauri/grpc/src/codec.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use prost_reflect::prost::Message;
|
||||||
|
use prost_reflect::{DynamicMessage, MethodDescriptor};
|
||||||
|
use tonic::codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder};
|
||||||
|
use tonic::Status;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DynamicCodec(MethodDescriptor);
|
||||||
|
|
||||||
|
impl DynamicCodec {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new(md: MethodDescriptor) -> Self {
|
||||||
|
Self(md)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Codec for DynamicCodec {
|
||||||
|
type Encode = DynamicMessage;
|
||||||
|
type Decode = DynamicMessage;
|
||||||
|
type Encoder = Self;
|
||||||
|
type Decoder = Self;
|
||||||
|
|
||||||
|
fn encoder(&mut self) -> Self::Encoder {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoder(&mut self) -> Self::Decoder {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder for DynamicCodec {
|
||||||
|
type Item = DynamicMessage;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn encode(&mut self, item: Self::Item, dst: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
|
||||||
|
item.encode(dst)
|
||||||
|
.expect("buffer is too small to decode this message");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder for DynamicCodec {
|
||||||
|
type Item = DynamicMessage;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn decode(&mut self, src: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
let mut msg = DynamicMessage::new(self.0.output());
|
||||||
|
msg.merge(src)
|
||||||
|
.map_err(|err| Status::internal(err.to_string()))?;
|
||||||
|
Ok(Some(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
179
src-tauri/grpc/src/json_schema.rs
Normal file
179
src-tauri/grpc/src/json_schema.rs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
use prost_reflect::{DescriptorPool, MessageDescriptor};
|
||||||
|
use prost_types::field_descriptor_proto;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
pub struct JsonSchemaEntry {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
title: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
type_: JsonType,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
properties: Option<HashMap<String, JsonSchemaEntry>>,
|
||||||
|
|
||||||
|
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
|
||||||
|
enum_: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Don't allow any other properties in the object
|
||||||
|
additional_properties: bool,
|
||||||
|
|
||||||
|
/// Set all properties to required
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
required: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
items: Option<Box<JsonSchemaEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JsonType {
|
||||||
|
String,
|
||||||
|
Number,
|
||||||
|
Object,
|
||||||
|
Array,
|
||||||
|
Boolean,
|
||||||
|
Null,
|
||||||
|
_UNKNOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JsonType {
|
||||||
|
fn default() -> Self {
|
||||||
|
JsonType::_UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for JsonType {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
JsonType::String => serializer.serialize_str("string"),
|
||||||
|
JsonType::Number => serializer.serialize_str("number"),
|
||||||
|
JsonType::Object => serializer.serialize_str("object"),
|
||||||
|
JsonType::Array => serializer.serialize_str("array"),
|
||||||
|
JsonType::Boolean => serializer.serialize_str("boolean"),
|
||||||
|
JsonType::Null => serializer.serialize_str("null"),
|
||||||
|
JsonType::_UNKNOWN => serializer.serialize_str("unknown"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for JsonType {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<JsonType, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
match s.as_str() {
|
||||||
|
"string" => Ok(JsonType::String),
|
||||||
|
"number" => Ok(JsonType::Number),
|
||||||
|
"object" => Ok(JsonType::Object),
|
||||||
|
"array" => Ok(JsonType::Array),
|
||||||
|
"boolean" => Ok(JsonType::Boolean),
|
||||||
|
"null" => Ok(JsonType::Null),
|
||||||
|
_ => Ok(JsonType::_UNKNOWN),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_to_json_schema(
|
||||||
|
pool: &DescriptorPool,
|
||||||
|
message: MessageDescriptor,
|
||||||
|
) -> JsonSchemaEntry {
|
||||||
|
let mut schema = JsonSchemaEntry {
|
||||||
|
title: Some(message.name().to_string()),
|
||||||
|
type_: JsonType::Object, // Messages are objects
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut properties = HashMap::new();
|
||||||
|
message.fields().for_each(|f| match f.kind() {
|
||||||
|
prost_reflect::Kind::Message(m) => {
|
||||||
|
properties.insert(f.name().to_string(), message_to_json_schema(pool, m));
|
||||||
|
}
|
||||||
|
prost_reflect::Kind::Enum(e) => {
|
||||||
|
properties.insert(
|
||||||
|
f.name().to_string(),
|
||||||
|
JsonSchemaEntry {
|
||||||
|
type_: map_proto_type_to_json_type(f.field_descriptor_proto().r#type()),
|
||||||
|
enum_: Some(e.values().map(|v| v.name().to_string()).collect::<Vec<_>>()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO: Handle repeated label
|
||||||
|
match f.field_descriptor_proto().label() {
|
||||||
|
field_descriptor_proto::Label::Repeated => {
|
||||||
|
// TODO: Handle more complex repeated types. This just handles primitives for now
|
||||||
|
properties.insert(
|
||||||
|
f.name().to_string(),
|
||||||
|
JsonSchemaEntry {
|
||||||
|
type_: JsonType::Array,
|
||||||
|
items: Some(Box::new(JsonSchemaEntry {
|
||||||
|
type_: map_proto_type_to_json_type(
|
||||||
|
f.field_descriptor_proto().r#type(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Regular JSON field
|
||||||
|
properties.insert(
|
||||||
|
f.name().to_string(),
|
||||||
|
JsonSchemaEntry {
|
||||||
|
type_: map_proto_type_to_json_type(f.field_descriptor_proto().r#type()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
schema.properties = Some(properties);
|
||||||
|
|
||||||
|
// All proto 3 fields are optional, so maybe we could
|
||||||
|
// make this a setting?
|
||||||
|
// schema.required = Some(
|
||||||
|
// message
|
||||||
|
// .fields()
|
||||||
|
// .map(|f| f.name().to_string())
|
||||||
|
// .collect::<Vec<_>>(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
schema
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_proto_type_to_json_type(proto_type: field_descriptor_proto::Type) -> JsonType {
|
||||||
|
match proto_type {
|
||||||
|
field_descriptor_proto::Type::Double => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Float => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Int64 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Uint64 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Int32 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Fixed64 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Fixed32 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Bool => JsonType::Boolean,
|
||||||
|
field_descriptor_proto::Type::String => JsonType::String,
|
||||||
|
field_descriptor_proto::Type::Group => JsonType::_UNKNOWN,
|
||||||
|
field_descriptor_proto::Type::Message => JsonType::Object,
|
||||||
|
field_descriptor_proto::Type::Bytes => JsonType::String,
|
||||||
|
field_descriptor_proto::Type::Uint32 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Enum => JsonType::String,
|
||||||
|
field_descriptor_proto::Type::Sfixed32 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Sfixed64 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Sint32 => JsonType::Number,
|
||||||
|
field_descriptor_proto::Type::Sint64 => JsonType::Number,
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src-tauri/grpc/src/lib.rs
Normal file
40
src-tauri/grpc/src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use prost_reflect::{DynamicMessage, SerializeOptions};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod codec;
|
||||||
|
mod json_schema;
|
||||||
|
pub mod manager;
|
||||||
|
mod proto;
|
||||||
|
|
||||||
|
pub fn serialize_options() -> SerializeOptions {
|
||||||
|
SerializeOptions::new().skip_default_fields(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
pub struct ServiceDefinition {
|
||||||
|
pub name: String,
|
||||||
|
pub methods: Vec<MethodDefinition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
pub struct MethodDefinition {
|
||||||
|
pub name: String,
|
||||||
|
pub schema: String,
|
||||||
|
pub client_streaming: bool,
|
||||||
|
pub server_streaming: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static SERIALIZE_OPTIONS: &'static SerializeOptions = &SerializeOptions::new()
|
||||||
|
.skip_default_fields(false)
|
||||||
|
.stringify_64_bit_integers(false);
|
||||||
|
|
||||||
|
pub fn serialize_message(msg: &DynamicMessage) -> Result<String, String> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let mut se = serde_json::Serializer::pretty(&mut buf);
|
||||||
|
msg.serialize_with_options(&mut se, SERIALIZE_OPTIONS)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let s = String::from_utf8(buf).expect("serde_json to emit valid utf8");
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
280
src-tauri/grpc/src/manager.rs
Normal file
280
src-tauri/grpc/src/manager.rs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use hyper::client::HttpConnector;
|
||||||
|
use hyper::Client;
|
||||||
|
use hyper_rustls::HttpsConnector;
|
||||||
|
use prost_reflect::DescriptorPool;
|
||||||
|
pub use prost_reflect::DynamicMessage;
|
||||||
|
use serde_json::Deserializer;
|
||||||
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tonic::body::BoxBody;
|
||||||
|
use tonic::transport::Uri;
|
||||||
|
use tonic::{IntoRequest, IntoStreamingRequest, Streaming};
|
||||||
|
|
||||||
|
use crate::codec::DynamicCodec;
|
||||||
|
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
|
||||||
|
use crate::{json_schema, MethodDefinition, ServiceDefinition};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GrpcConnection {
|
||||||
|
pool: DescriptorPool,
|
||||||
|
conn: Client<HttpsConnector<HttpConnector>, BoxBody>,
|
||||||
|
pub uri: Uri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GrpcConnection {
|
||||||
|
pub async fn unary(
|
||||||
|
&self,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<DynamicMessage, String> {
|
||||||
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
|
let input_message = method.input();
|
||||||
|
|
||||||
|
let mut deserializer = Deserializer::from_str(message);
|
||||||
|
let req_message = DynamicMessage::deserialize(input_message, &mut deserializer)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
deserializer.end().unwrap();
|
||||||
|
|
||||||
|
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||||
|
|
||||||
|
let req = req_message.into_request();
|
||||||
|
let path = method_desc_to_path(method);
|
||||||
|
let codec = DynamicCodec::new(method.clone());
|
||||||
|
client.ready().await.unwrap();
|
||||||
|
|
||||||
|
Ok(client
|
||||||
|
.unary(req, path, codec)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn streaming(
|
||||||
|
&self,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
stream: ReceiverStream<String>,
|
||||||
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
|
|
||||||
|
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||||
|
|
||||||
|
let method2 = method.clone();
|
||||||
|
let req = stream
|
||||||
|
.map(move |s| {
|
||||||
|
let mut deserializer = Deserializer::from_str(&s);
|
||||||
|
let req_message = DynamicMessage::deserialize(method2.input(), &mut deserializer)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.unwrap();
|
||||||
|
deserializer.end().unwrap();
|
||||||
|
req_message
|
||||||
|
})
|
||||||
|
.into_streaming_request();
|
||||||
|
let path = method_desc_to_path(method);
|
||||||
|
let codec = DynamicCodec::new(method.clone());
|
||||||
|
client.ready().await.unwrap();
|
||||||
|
Ok(client
|
||||||
|
.streaming(req, path, codec)
|
||||||
|
.await
|
||||||
|
.map_err(|s| s.to_string())?
|
||||||
|
.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn client_streaming(
|
||||||
|
&self,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
stream: ReceiverStream<String>,
|
||||||
|
) -> Result<DynamicMessage, String> {
|
||||||
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
|
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||||
|
|
||||||
|
let req = {
|
||||||
|
let method = method.clone();
|
||||||
|
stream
|
||||||
|
.map(move |s| {
|
||||||
|
let mut deserializer = Deserializer::from_str(&s);
|
||||||
|
let req_message =
|
||||||
|
DynamicMessage::deserialize(method.input(), &mut deserializer)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.unwrap();
|
||||||
|
deserializer.end().unwrap();
|
||||||
|
req_message
|
||||||
|
})
|
||||||
|
.into_streaming_request()
|
||||||
|
};
|
||||||
|
let path = method_desc_to_path(method);
|
||||||
|
let codec = DynamicCodec::new(method.clone());
|
||||||
|
client.ready().await.unwrap();
|
||||||
|
Ok(client
|
||||||
|
.client_streaming(req, path, codec)
|
||||||
|
.await
|
||||||
|
.map_err(|s| s.to_string())?
|
||||||
|
.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn server_streaming(
|
||||||
|
&self,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
|
let service = self.pool.get_service_by_name(service).unwrap();
|
||||||
|
let method = &service.methods().find(|m| m.name() == method).unwrap();
|
||||||
|
let input_message = method.input();
|
||||||
|
|
||||||
|
let mut deserializer = Deserializer::from_str(message);
|
||||||
|
let req_message = DynamicMessage::deserialize(input_message, &mut deserializer)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
deserializer.end().unwrap();
|
||||||
|
|
||||||
|
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||||
|
|
||||||
|
let req = req_message.into_request();
|
||||||
|
let path = method_desc_to_path(method);
|
||||||
|
let codec = DynamicCodec::new(method.clone());
|
||||||
|
client.ready().await.unwrap();
|
||||||
|
Ok(client
|
||||||
|
.server_streaming(req, path, codec)
|
||||||
|
.await
|
||||||
|
.map_err(|s| s.to_string())?
|
||||||
|
.into_inner())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrpcHandle {
|
||||||
|
pools: HashMap<String, DescriptorPool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GrpcHandle {
|
||||||
|
fn default() -> Self {
|
||||||
|
let pools = HashMap::new();
|
||||||
|
Self { pools }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GrpcHandle {
|
||||||
|
pub async fn services_from_files(
|
||||||
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: &Uri,
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
) -> Result<Vec<ServiceDefinition>, String> {
|
||||||
|
let pool = fill_pool_from_files(paths).await?;
|
||||||
|
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
|
||||||
|
Ok(self.services_from_pool(&pool))
|
||||||
|
}
|
||||||
|
pub async fn services_from_reflection(
|
||||||
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: &Uri,
|
||||||
|
) -> Result<Vec<ServiceDefinition>, String> {
|
||||||
|
let pool = fill_pool(uri).await?;
|
||||||
|
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
|
||||||
|
Ok(self.services_from_pool(&pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pool_key(&self, id: &str, uri: &Uri) -> String {
|
||||||
|
format!("{}-{}", id, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn services_from_pool(&self, pool: &DescriptorPool) -> Vec<ServiceDefinition> {
|
||||||
|
pool.services()
|
||||||
|
.map(|s| {
|
||||||
|
let mut def = ServiceDefinition {
|
||||||
|
name: s.full_name().to_string(),
|
||||||
|
methods: vec![],
|
||||||
|
};
|
||||||
|
for method in s.methods() {
|
||||||
|
let input_message = method.input();
|
||||||
|
def.methods.push(MethodDefinition {
|
||||||
|
name: method.name().to_string(),
|
||||||
|
server_streaming: method.is_server_streaming(),
|
||||||
|
client_streaming: method.is_client_streaming(),
|
||||||
|
schema: serde_json::to_string_pretty(&json_schema::message_to_json_schema(
|
||||||
|
&pool,
|
||||||
|
input_message,
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
def
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn server_streaming(
|
||||||
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
|
self.connect(id, uri, proto_files)
|
||||||
|
.await?
|
||||||
|
.server_streaming(service, method, message)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn client_streaming(
|
||||||
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
stream: ReceiverStream<String>,
|
||||||
|
) -> Result<DynamicMessage, String> {
|
||||||
|
self.connect(id, uri, proto_files)
|
||||||
|
.await?
|
||||||
|
.client_streaming(service, method, stream)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn streaming(
|
||||||
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
|
service: &str,
|
||||||
|
method: &str,
|
||||||
|
stream: ReceiverStream<String>,
|
||||||
|
) -> Result<Streaming<DynamicMessage>, String> {
|
||||||
|
self.connect(id, uri, proto_files)
|
||||||
|
.await?
|
||||||
|
.streaming(service, method, stream)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect(
|
||||||
|
&mut self,
|
||||||
|
id: &str,
|
||||||
|
uri: Uri,
|
||||||
|
proto_files: Vec<PathBuf>,
|
||||||
|
) -> Result<GrpcConnection, String> {
|
||||||
|
let pool = match self.pools.get(id) {
|
||||||
|
Some(p) => p.clone(),
|
||||||
|
None => match proto_files.len() {
|
||||||
|
0 => fill_pool(&uri).await?,
|
||||||
|
_ => {
|
||||||
|
let pool = fill_pool_from_files(proto_files).await?;
|
||||||
|
self.pools.insert(id.to_string(), pool.clone());
|
||||||
|
pool
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn = get_transport();
|
||||||
|
let connection = GrpcConnection { pool, conn, uri };
|
||||||
|
Ok(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
226
src-tauri/grpc/src/proto.rs
Normal file
226
src-tauri/grpc/src/proto.rs
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
use std::env::temp_dir;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use hyper::client::HttpConnector;
|
||||||
|
use hyper::Client;
|
||||||
|
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
||||||
|
use log::{debug, warn};
|
||||||
|
use prost::Message;
|
||||||
|
use prost_reflect::{DescriptorPool, MethodDescriptor};
|
||||||
|
use prost_types::{FileDescriptorProto, FileDescriptorSet};
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tonic::body::BoxBody;
|
||||||
|
use tonic::codegen::http::uri::PathAndQuery;
|
||||||
|
use tonic::transport::Uri;
|
||||||
|
use tonic::Request;
|
||||||
|
use tonic_reflection::pb::server_reflection_client::ServerReflectionClient;
|
||||||
|
use tonic_reflection::pb::server_reflection_request::MessageRequest;
|
||||||
|
use tonic_reflection::pb::server_reflection_response::MessageResponse;
|
||||||
|
use tonic_reflection::pb::ServerReflectionRequest;
|
||||||
|
|
||||||
|
pub async fn fill_pool_from_files(paths: Vec<PathBuf>) -> Result<DescriptorPool, String> {
|
||||||
|
let mut pool = DescriptorPool::new();
|
||||||
|
let random_file_name = format!("{}.desc", uuid::Uuid::new_v4());
|
||||||
|
let desc_path = temp_dir().join(random_file_name);
|
||||||
|
let bin = protoc_bin_vendored::protoc_bin_path().unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::new(bin.clone());
|
||||||
|
cmd.arg("--include_imports")
|
||||||
|
.arg("--include_source_info")
|
||||||
|
.arg("-o")
|
||||||
|
.arg(&desc_path);
|
||||||
|
|
||||||
|
for p in paths {
|
||||||
|
if p.as_path().exists() {
|
||||||
|
cmd.arg(p.as_path().to_string_lossy().as_ref());
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = p.as_path().parent();
|
||||||
|
if let Some(parent_path) = parent {
|
||||||
|
cmd.arg("-I").arg(parent_path);
|
||||||
|
cmd.arg("-I").arg(parent_path.parent().unwrap());
|
||||||
|
} else {
|
||||||
|
debug!("ignoring {:?} since it does not exist.", parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd.output().map_err(|e| e.to_string())?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"protoc failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = fs::read(desc_path.as_path())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let fdp = FileDescriptorSet::decode(bytes.deref()).map_err(|e| e.to_string())?;
|
||||||
|
pool.add_file_descriptor_set(fdp)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
fs::remove_file(desc_path.as_path())
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fill_pool(uri: &Uri) -> Result<DescriptorPool, String> {
|
||||||
|
let mut pool = DescriptorPool::new();
|
||||||
|
let mut client = ServerReflectionClient::with_origin(get_transport(), uri.clone());
|
||||||
|
|
||||||
|
for service in list_services(&mut client).await? {
|
||||||
|
if service == "grpc.reflection.v1alpha.ServerReflection" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
file_descriptor_set_from_service_name(&service, &mut pool, &mut client).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_transport() -> Client<HttpsConnector<HttpConnector>, BoxBody> {
|
||||||
|
let connector = HttpsConnectorBuilder::new().with_native_roots();
|
||||||
|
let connector = connector.https_or_http().enable_http2().wrap_connector({
|
||||||
|
let mut http_connector = HttpConnector::new();
|
||||||
|
http_connector.enforce_http(false);
|
||||||
|
http_connector
|
||||||
|
});
|
||||||
|
Client::builder()
|
||||||
|
.pool_max_idle_per_host(0)
|
||||||
|
.http2_only(true)
|
||||||
|
.build(connector)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_services(
|
||||||
|
reflect_client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
|
||||||
|
) -> Result<Vec<String>, String> {
|
||||||
|
let response =
|
||||||
|
send_reflection_request(reflect_client, MessageRequest::ListServices("".into())).await?;
|
||||||
|
|
||||||
|
let list_services_response = match response {
|
||||||
|
MessageResponse::ListServicesResponse(resp) => resp,
|
||||||
|
_ => panic!("Expected a ListServicesResponse variant"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(list_services_response
|
||||||
|
.service
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.name.clone())
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn file_descriptor_set_from_service_name(
|
||||||
|
service_name: &str,
|
||||||
|
pool: &mut DescriptorPool,
|
||||||
|
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
|
||||||
|
) {
|
||||||
|
let response = match send_reflection_request(
|
||||||
|
client,
|
||||||
|
MessageRequest::FileContainingSymbol(service_name.into()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
"Error fetching file descriptor for service {}: {}",
|
||||||
|
service_name, e
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_descriptor_response = match response {
|
||||||
|
MessageResponse::FileDescriptorResponse(resp) => resp,
|
||||||
|
_ => panic!("Expected a FileDescriptorResponse variant"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for fd in file_descriptor_response.file_descriptor_proto {
|
||||||
|
let fdp = FileDescriptorProto::decode(fd.deref()).unwrap();
|
||||||
|
|
||||||
|
// Add deps first or else we'll get an error
|
||||||
|
for dep_name in fdp.clone().dependency {
|
||||||
|
file_descriptor_set_by_filename(&dep_name, pool, client).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.add_file_descriptor_proto(fdp)
|
||||||
|
.expect("add file descriptor proto");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn file_descriptor_set_by_filename(
|
||||||
|
filename: &str,
|
||||||
|
pool: &mut DescriptorPool,
|
||||||
|
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
|
||||||
|
) {
|
||||||
|
// We already fetched this file
|
||||||
|
if let Some(_) = pool.get_file_by_name(filename) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response =
|
||||||
|
send_reflection_request(client, MessageRequest::FileByFilename(filename.into())).await;
|
||||||
|
let file_descriptor_response = match response {
|
||||||
|
Ok(MessageResponse::FileDescriptorResponse(resp)) => resp,
|
||||||
|
Ok(_) => {
|
||||||
|
panic!("Expected a FileDescriptorResponse variant")
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Error fetching file descriptor for {}: {}", filename, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for fd in file_descriptor_response.file_descriptor_proto {
|
||||||
|
let fdp = FileDescriptorProto::decode(fd.deref()).unwrap();
|
||||||
|
pool.add_file_descriptor_proto(fdp)
|
||||||
|
.expect("add file descriptor proto");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_reflection_request(
|
||||||
|
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
|
||||||
|
message: MessageRequest,
|
||||||
|
) -> Result<MessageResponse, String> {
|
||||||
|
let reflection_request = ServerReflectionRequest {
|
||||||
|
host: "".into(), // Doesn't matter
|
||||||
|
message_request: Some(message),
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = Request::new(tokio_stream::once(reflection_request));
|
||||||
|
|
||||||
|
client
|
||||||
|
.server_reflection_info(request)
|
||||||
|
.await
|
||||||
|
.map_err(|e| match e.code() {
|
||||||
|
tonic::Code::Unavailable => "Failed to connect to endpoint".to_string(),
|
||||||
|
tonic::Code::Unauthenticated => "Authentication failed".to_string(),
|
||||||
|
tonic::Code::DeadlineExceeded => "Deadline exceeded".to_string(),
|
||||||
|
_ => e.to_string(),
|
||||||
|
})?
|
||||||
|
.into_inner()
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("steamed response")
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.message_response
|
||||||
|
.ok_or("No reflection response".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method_desc_to_path(md: &MethodDescriptor) -> PathAndQuery {
|
||||||
|
let full_name = md.full_name();
|
||||||
|
let (namespace, method_name) = full_name
|
||||||
|
.rsplit_once('.')
|
||||||
|
.ok_or_else(|| anyhow!("invalid method path"))
|
||||||
|
.expect("invalid method path");
|
||||||
|
PathAndQuery::from_str(&format!("/{}/{}", namespace, method_name)).expect("invalid method path")
|
||||||
|
}
|
||||||
59
src-tauri/migrations/20240203164833_grpc.sql
Normal file
59
src-tauri/migrations/20240203164833_grpc.sql
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
CREATE TABLE grpc_requests
|
||||||
|
(
|
||||||
|
id TEXT NOT NULL
|
||||||
|
PRIMARY KEY,
|
||||||
|
model TEXT DEFAULT 'grpc_request' NOT NULL,
|
||||||
|
workspace_id TEXT NOT NULL
|
||||||
|
REFERENCES workspaces
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
folder_id TEXT NULL
|
||||||
|
REFERENCES folders
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
sort_priority REAL NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
service TEXT NULL,
|
||||||
|
method TEXT NULL,
|
||||||
|
message TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE grpc_connections
|
||||||
|
(
|
||||||
|
id TEXT NOT NULL
|
||||||
|
PRIMARY KEY,
|
||||||
|
model TEXT DEFAULT 'grpc_connection' NOT NULL,
|
||||||
|
workspace_id TEXT NOT NULL
|
||||||
|
REFERENCES workspaces
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
request_id TEXT NOT NULL
|
||||||
|
REFERENCES grpc_requests
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
service TEXT NOT NULL,
|
||||||
|
method TEXT NOT NULL,
|
||||||
|
elapsed INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE grpc_messages
|
||||||
|
(
|
||||||
|
id TEXT NOT NULL
|
||||||
|
PRIMARY KEY,
|
||||||
|
model TEXT DEFAULT 'grpc_message' NOT NULL,
|
||||||
|
workspace_id TEXT NOT NULL
|
||||||
|
REFERENCES workspaces
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
request_id TEXT NOT NULL
|
||||||
|
REFERENCES grpc_requests
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
connection_id TEXT NOT NULL
|
||||||
|
REFERENCES grpc_connections
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
is_server BOOLEAN NOT NULL,
|
||||||
|
is_info BOOLEAN NOT NULL,
|
||||||
|
message TEXT NOT NULL
|
||||||
|
);
|
||||||
1
src-tauri/migrations/20240206191206_grpc-protos.sql
Normal file
1
src-tauri/migrations/20240206191206_grpc-protos.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE grpc_requests ADD COLUMN proto_files TEXT DEFAULT '[]' NOT NULL;
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
use sqlx::types::JsonValue;
|
use sqlx::types::JsonValue;
|
||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager};
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
use crate::{is_dev, models};
|
use crate::{is_dev, models};
|
||||||
|
|
||||||
@@ -16,6 +14,9 @@ pub enum AnalyticsResource {
|
|||||||
Dialog,
|
Dialog,
|
||||||
Environment,
|
Environment,
|
||||||
Folder,
|
Folder,
|
||||||
|
GrpcConnection,
|
||||||
|
GrpcMessage,
|
||||||
|
GrpcRequest,
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
KeyValue,
|
KeyValue,
|
||||||
@@ -31,6 +32,9 @@ impl AnalyticsResource {
|
|||||||
"CookieJar" => Some(AnalyticsResource::CookieJar),
|
"CookieJar" => Some(AnalyticsResource::CookieJar),
|
||||||
"Environment" => Some(AnalyticsResource::Environment),
|
"Environment" => Some(AnalyticsResource::Environment),
|
||||||
"Folder" => Some(AnalyticsResource::Folder),
|
"Folder" => Some(AnalyticsResource::Folder),
|
||||||
|
"GrpcConnection" => Some(AnalyticsResource::GrpcConnection),
|
||||||
|
"GrpcMessage" => Some(AnalyticsResource::GrpcMessage),
|
||||||
|
"GrpcRequest" => Some(AnalyticsResource::GrpcRequest),
|
||||||
"HttpRequest" => Some(AnalyticsResource::HttpRequest),
|
"HttpRequest" => Some(AnalyticsResource::HttpRequest),
|
||||||
"HttpResponse" => Some(AnalyticsResource::HttpResponse),
|
"HttpResponse" => Some(AnalyticsResource::HttpResponse),
|
||||||
"KeyValue" => Some(AnalyticsResource::KeyValue),
|
"KeyValue" => Some(AnalyticsResource::KeyValue),
|
||||||
@@ -90,6 +94,9 @@ fn resource_name(resource: AnalyticsResource) -> &'static str {
|
|||||||
AnalyticsResource::Dialog => "dialog",
|
AnalyticsResource::Dialog => "dialog",
|
||||||
AnalyticsResource::Environment => "environment",
|
AnalyticsResource::Environment => "environment",
|
||||||
AnalyticsResource::Folder => "folder",
|
AnalyticsResource::Folder => "folder",
|
||||||
|
AnalyticsResource::GrpcRequest => "grpc_request",
|
||||||
|
AnalyticsResource::GrpcConnection => "grpc_connection",
|
||||||
|
AnalyticsResource::GrpcMessage => "grpc_message",
|
||||||
AnalyticsResource::HttpRequest => "http_request",
|
AnalyticsResource::HttpRequest => "http_request",
|
||||||
AnalyticsResource::HttpResponse => "http_response",
|
AnalyticsResource::HttpResponse => "http_response",
|
||||||
AnalyticsResource::KeyValue => "key_value",
|
AnalyticsResource::KeyValue => "key_value",
|
||||||
@@ -129,14 +136,13 @@ pub struct LaunchEventInfo {
|
|||||||
pub async fn track_launch_event(app_handle: &AppHandle) -> LaunchEventInfo {
|
pub async fn track_launch_event(app_handle: &AppHandle) -> LaunchEventInfo {
|
||||||
let namespace = "analytics";
|
let namespace = "analytics";
|
||||||
let last_tracked_version_key = "last_tracked_version";
|
let last_tracked_version_key = "last_tracked_version";
|
||||||
let db_instance: State<'_, Mutex<Pool<Sqlite>>> = app_handle.state();
|
|
||||||
let pool = &*db_instance.lock().await;
|
|
||||||
|
|
||||||
let mut info = LaunchEventInfo::default();
|
let mut info = LaunchEventInfo::default();
|
||||||
|
|
||||||
info.num_launches = models::get_key_value_int(namespace, "num_launches", 0, pool).await + 1;
|
info.num_launches =
|
||||||
|
models::get_key_value_int(app_handle, namespace, "num_launches", 0).await + 1;
|
||||||
info.previous_version =
|
info.previous_version =
|
||||||
models::get_key_value_string(namespace, last_tracked_version_key, "", pool).await;
|
models::get_key_value_string(app_handle, namespace, last_tracked_version_key, "").await;
|
||||||
info.current_version = app_handle.package_info().version.to_string();
|
info.current_version = app_handle.package_info().version.to_string();
|
||||||
|
|
||||||
if info.previous_version.is_empty() {
|
if info.previous_version.is_empty() {
|
||||||
@@ -167,19 +173,18 @@ pub async fn track_launch_event(app_handle: &AppHandle) -> LaunchEventInfo {
|
|||||||
AnalyticsAction::Launch,
|
AnalyticsAction::Launch,
|
||||||
Some(json!({ "num_launches": info.num_launches })),
|
Some(json!({ "num_launches": info.num_launches })),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
||||||
// Update key values
|
// Update key values
|
||||||
|
|
||||||
models::set_key_value_string(
|
models::set_key_value_string(
|
||||||
|
app_handle,
|
||||||
namespace,
|
namespace,
|
||||||
last_tracked_version_key,
|
last_tracked_version_key,
|
||||||
info.current_version.as_str(),
|
info.current_version.as_str(),
|
||||||
pool,
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
models::set_key_value_int(namespace, "num_launches", info.num_launches, pool).await;
|
models::set_key_value_int(app_handle, namespace, "num_launches", info.num_launches).await;
|
||||||
|
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::fs::{create_dir_all, File};
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -7,28 +7,26 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use http::{HeaderMap, HeaderName, HeaderValue, Method};
|
|
||||||
use http::header::{ACCEPT, USER_AGENT};
|
use http::header::{ACCEPT, USER_AGENT};
|
||||||
|
use http::{HeaderMap, HeaderName, HeaderValue, Method};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use reqwest::{multipart, Url};
|
|
||||||
use reqwest::redirect::Policy;
|
use reqwest::redirect::Policy;
|
||||||
use sqlx::{Pool, Sqlite};
|
use reqwest::{multipart, Url};
|
||||||
use sqlx::types::{Json, JsonValue};
|
use sqlx::types::{Json, JsonValue};
|
||||||
use tauri::{AppHandle, Wry};
|
use tauri::AppHandle;
|
||||||
|
|
||||||
use crate::{emit_side_effect, models, render, response_err};
|
use crate::{models, render, response_err};
|
||||||
|
|
||||||
pub async fn send_http_request(
|
pub async fn send_http_request(
|
||||||
|
app_handle: &AppHandle,
|
||||||
request: models::HttpRequest,
|
request: models::HttpRequest,
|
||||||
response: &models::HttpResponse,
|
response: &models::HttpResponse,
|
||||||
environment: Option<models::Environment>,
|
environment: Option<models::Environment>,
|
||||||
cookie_jar: Option<models::CookieJar>,
|
cookie_jar: Option<models::CookieJar>,
|
||||||
app_handle: &AppHandle<Wry>,
|
|
||||||
pool: &Pool<Sqlite>,
|
|
||||||
download_path: Option<PathBuf>,
|
download_path: Option<PathBuf>,
|
||||||
) -> Result<models::HttpResponse, String> {
|
) -> Result<models::HttpResponse, String> {
|
||||||
let environment_ref = environment.as_ref();
|
let environment_ref = environment.as_ref();
|
||||||
let workspace = models::get_workspace(&request.workspace_id, pool)
|
let workspace = models::get_workspace(app_handle, &request.workspace_id)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get Workspace");
|
.expect("Failed to get Workspace");
|
||||||
|
|
||||||
@@ -88,7 +86,7 @@ pub async fn send_http_request(
|
|||||||
let url = match Url::from_str(url_string.as_str()) {
|
let url = match Url::from_str(url_string.as_str()) {
|
||||||
Ok(u) => u,
|
Ok(u) => u,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return response_err(response, e.to_string(), app_handle, pool).await;
|
return response_err(response, e.to_string(), app_handle).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -293,7 +291,7 @@ pub async fn send_http_request(
|
|||||||
let sendable_req = match request_builder.build() {
|
let sendable_req = match request_builder.build() {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return response_err(response, e.to_string(), app_handle, pool).await;
|
return response_err(response, e.to_string(), app_handle).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -362,12 +360,9 @@ pub async fn send_http_request(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
response = models::update_response_if_id(&response, pool)
|
response = models::update_response_if_id(app_handle, &response)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to update response");
|
.expect("Failed to update response");
|
||||||
if !request.id.is_empty() {
|
|
||||||
emit_side_effect(app_handle, "updated_model", &response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy response to download path, if specified
|
// Copy response to download path, if specified
|
||||||
match (download_path, response.body_path.clone()) {
|
match (download_path, response.body_path.clone()) {
|
||||||
@@ -397,18 +392,13 @@ pub async fn send_http_request(
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
cookie_jar.cookies = json_cookies;
|
cookie_jar.cookies = json_cookies;
|
||||||
match models::upsert_cookie_jar(pool, &cookie_jar).await {
|
if let Err(e) = models::upsert_cookie_jar(&app_handle, &cookie_jar).await {
|
||||||
Ok(updated_jar) => {
|
error!("Failed to update cookie jar: {}", e);
|
||||||
emit_side_effect(app_handle, "updated_model", &updated_jar);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to update cookie jar: {}", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
Err(e) => response_err(response, e.to_string(), app_handle, pool).await,
|
Err(e) => response_err(response, e.to_string(), app_handle).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use tauri::{AppHandle, updater, Window, Wry};
|
|
||||||
use tauri::api::dialog;
|
use tauri::api::dialog;
|
||||||
|
use tauri::{updater, AppHandle, Window};
|
||||||
|
|
||||||
use crate::is_dev;
|
use crate::is_dev;
|
||||||
|
|
||||||
@@ -27,14 +27,17 @@ impl YaakUpdater {
|
|||||||
}
|
}
|
||||||
pub async fn force_check(
|
pub async fn force_check(
|
||||||
&mut self,
|
&mut self,
|
||||||
app_handle: &AppHandle<Wry>,
|
app_handle: &AppHandle,
|
||||||
mode: UpdateMode,
|
mode: UpdateMode,
|
||||||
) -> Result<bool, updater::Error> {
|
) -> Result<bool, updater::Error> {
|
||||||
self.last_update_check = SystemTime::now();
|
self.last_update_check = SystemTime::now();
|
||||||
|
|
||||||
let update_mode = get_update_mode_str(mode);
|
let update_mode = get_update_mode_str(mode);
|
||||||
let enabled = !is_dev();
|
let enabled = !is_dev();
|
||||||
info!("Checking for updates mode={} enabled={}", update_mode, enabled);
|
info!(
|
||||||
|
"Checking for updates mode={} enabled={}",
|
||||||
|
update_mode, enabled
|
||||||
|
);
|
||||||
|
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@@ -89,10 +92,11 @@ impl YaakUpdater {
|
|||||||
}
|
}
|
||||||
pub async fn check(
|
pub async fn check(
|
||||||
&mut self,
|
&mut self,
|
||||||
app_handle: &AppHandle<Wry>,
|
app_handle: &AppHandle,
|
||||||
mode: UpdateMode,
|
mode: UpdateMode,
|
||||||
) -> Result<bool, updater::Error> {
|
) -> Result<bool, updater::Error> {
|
||||||
let ignore_check = self.last_update_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
let ignore_check =
|
||||||
|
self.last_update_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
||||||
if ignore_check {
|
if ignore_check {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
|
||||||
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { useRequests } from '../hooks/useRequests';
|
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||||
import { GlobalHooks } from './GlobalHooks';
|
import { GlobalHooks } from './GlobalHooks';
|
||||||
import Workspace from './Workspace';
|
import Workspace from './Workspace';
|
||||||
import Workspaces from './Workspaces';
|
import Workspaces from './Workspaces';
|
||||||
@@ -49,7 +49,7 @@ export function AppRouter() {
|
|||||||
function WorkspaceOrRedirect() {
|
function WorkspaceOrRedirect() {
|
||||||
const recentRequests = useRecentRequests();
|
const recentRequests = useRecentRequests();
|
||||||
const activeEnvironmentId = useActiveEnvironmentId();
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
const requests = useRequests();
|
const requests = useHttpRequests();
|
||||||
const request = requests.find((r) => r.id === recentRequests[0]);
|
const request = requests.find((r) => r.id === recentRequests[0]);
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { Input } from './core/Input';
|
import { Input } from './core/Input';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function BasicAuth({ requestId, authentication }: Props) {
|
export function BasicAuth({ requestId, authentication }: Props) {
|
||||||
const updateRequest = useUpdateRequest(requestId);
|
const updateRequest = useUpdateHttpRequest(requestId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack className="my-2" space={2}>
|
<VStack className="my-2" space={2}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { Input } from './core/Input';
|
import { Input } from './core/Input';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function BearerAuth({ requestId, authentication }: Props) {
|
export function BearerAuth({ requestId, authentication }: Props) {
|
||||||
const updateRequest = useUpdateRequest(requestId);
|
const updateRequest = useUpdateHttpRequest(requestId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack className="my-2" space={2}>
|
<VStack className="my-2" space={2}>
|
||||||
|
|||||||
@@ -3,20 +3,23 @@ import { appWindow } from '@tauri-apps/api/window';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
||||||
|
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
|
||||||
|
import { grpcMessagesQueryKey } from '../hooks/useGrpcMessages';
|
||||||
|
import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests';
|
||||||
|
import { httpRequestsQueryKey } from '../hooks/useHttpRequests';
|
||||||
|
import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
|
||||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||||
import { requestsQueryKey } from '../hooks/useRequests';
|
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { responsesQueryKey } from '../hooks/useResponses';
|
|
||||||
import { settingsQueryKey } from '../hooks/useSettings';
|
import { settingsQueryKey } from '../hooks/useSettings';
|
||||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
|
||||||
import { useSyncAppearance } from '../hooks/useSyncAppearance';
|
import { useSyncAppearance } from '../hooks/useSyncAppearance';
|
||||||
|
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models';
|
import type { Model } from '../lib/models';
|
||||||
import { modelsEq } from '../lib/models';
|
import { modelsEq } from '../lib/models';
|
||||||
import { setPathname } from '../lib/persistPathname';
|
import { setPathname } from '../lib/persistPathname';
|
||||||
|
|
||||||
@@ -42,43 +45,20 @@ export function GlobalHooks() {
|
|||||||
setPathname(location.pathname).catch(console.error);
|
setPathname(location.pathname).catch(console.error);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
useListenToTauriEvent<Model>('created_model', ({ payload, windowLabel }) => {
|
useListenToTauriEvent<Model>('upserted_model', ({ payload, windowLabel }) => {
|
||||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
payload.model === 'http_request'
|
payload.model === 'http_request'
|
||||||
? requestsQueryKey(payload)
|
? httpRequestsQueryKey(payload)
|
||||||
: payload.model === 'http_response'
|
: payload.model === 'http_response'
|
||||||
? responsesQueryKey(payload)
|
? httpResponsesQueryKey(payload)
|
||||||
: payload.model === 'workspace'
|
: payload.model === 'grpc_connection'
|
||||||
? workspacesQueryKey(payload)
|
? grpcConnectionsQueryKey(payload)
|
||||||
: payload.model === 'key_value'
|
: payload.model === 'grpc_message'
|
||||||
? keyValueQueryKey(payload)
|
? grpcMessagesQueryKey(payload)
|
||||||
: payload.model === 'settings'
|
: payload.model === 'grpc_request'
|
||||||
? settingsQueryKey()
|
? grpcRequestsQueryKey(payload)
|
||||||
: payload.model === 'cookie_jar'
|
|
||||||
? cookieJarsQueryKey(payload)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (queryKey === null) {
|
|
||||||
console.log('Unrecognized created model:', payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldIgnoreModel(payload)) {
|
|
||||||
// Order newest first
|
|
||||||
queryClient.setQueryData<Model[]>(queryKey, (values) => [payload, ...(values ?? [])]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useListenToTauriEvent<Model>('updated_model', ({ payload, windowLabel }) => {
|
|
||||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
|
||||||
|
|
||||||
const queryKey =
|
|
||||||
payload.model === 'http_request'
|
|
||||||
? requestsQueryKey(payload)
|
|
||||||
: payload.model === 'http_response'
|
|
||||||
? responsesQueryKey(payload)
|
|
||||||
: payload.model === 'workspace'
|
: payload.model === 'workspace'
|
||||||
? workspacesQueryKey(payload)
|
? workspacesQueryKey(payload)
|
||||||
: payload.model === 'key_value'
|
: payload.model === 'key_value'
|
||||||
@@ -98,12 +78,19 @@ export function GlobalHooks() {
|
|||||||
wasUpdatedExternally(payload.id);
|
wasUpdatedExternally(payload.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pushToFront = (['http_response', 'grpc_connection'] as Model['model'][]).includes(
|
||||||
|
payload.model,
|
||||||
|
);
|
||||||
|
|
||||||
if (!shouldIgnoreModel(payload)) {
|
if (!shouldIgnoreModel(payload)) {
|
||||||
console.time('set query date');
|
queryClient.setQueryData<Model[]>(queryKey, (values = []) => {
|
||||||
queryClient.setQueryData<Model[]>(queryKey, (values) =>
|
const index = values.findIndex((v) => modelsEq(v, payload)) ?? -1;
|
||||||
values?.map((v) => (modelsEq(v, payload) ? payload : v)),
|
if (index >= 0) {
|
||||||
);
|
return [...values.slice(0, index), payload, ...values.slice(index + 1)];
|
||||||
console.timeEnd('set query date');
|
} else {
|
||||||
|
return pushToFront ? [payload, ...(values ?? [])] : [...(values ?? []), payload];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,11 +100,17 @@ export function GlobalHooks() {
|
|||||||
if (shouldIgnoreModel(payload)) return;
|
if (shouldIgnoreModel(payload)) return;
|
||||||
|
|
||||||
if (payload.model === 'workspace') {
|
if (payload.model === 'workspace') {
|
||||||
queryClient.setQueryData<Workspace[]>(workspacesQueryKey(), removeById(payload));
|
queryClient.setQueryData(workspacesQueryKey(), removeById(payload));
|
||||||
} else if (payload.model === 'http_request') {
|
} else if (payload.model === 'http_request') {
|
||||||
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey(payload), removeById(payload));
|
queryClient.setQueryData(httpRequestsQueryKey(payload), removeById(payload));
|
||||||
} else if (payload.model === 'http_response') {
|
} else if (payload.model === 'http_response') {
|
||||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey(payload), removeById(payload));
|
queryClient.setQueryData(httpResponsesQueryKey(payload), removeById(payload));
|
||||||
|
} else if (payload.model === 'grpc_request') {
|
||||||
|
queryClient.setQueryData(grpcRequestsQueryKey(payload), removeById(payload));
|
||||||
|
} else if (payload.model === 'grpc_connection') {
|
||||||
|
queryClient.setQueryData(grpcConnectionsQueryKey(payload), removeById(payload));
|
||||||
|
} else if (payload.model === 'grpc_message') {
|
||||||
|
queryClient.setQueryData(grpcMessagesQueryKey(payload), removeById(payload));
|
||||||
} else if (payload.model === 'key_value') {
|
} else if (payload.model === 'key_value') {
|
||||||
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
||||||
} else if (payload.model === 'cookie_jar') {
|
} else if (payload.model === 'cookie_jar') {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-2 h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
|
||||||
<Editor
|
<Editor
|
||||||
contentType="application/graphql"
|
contentType="application/graphql"
|
||||||
defaultValue={query ?? ''}
|
defaultValue={query ?? ''}
|
||||||
@@ -124,19 +124,22 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
}
|
}
|
||||||
{...extraEditorProps}
|
{...extraEditorProps}
|
||||||
/>
|
/>
|
||||||
<Separator variant="primary" />
|
<div className="grid min-h-[5rem]">
|
||||||
<p className="pt-1 text-gray-500 text-sm">Variables</p>
|
<Separator variant="primary" className="pb-1">
|
||||||
<Editor
|
Variables
|
||||||
format={tryFormatJson}
|
</Separator>
|
||||||
contentType="application/json"
|
<Editor
|
||||||
defaultValue={JSON.stringify(variables, null, 2)}
|
format={tryFormatJson}
|
||||||
heightMode="auto"
|
contentType="application/json"
|
||||||
onChange={handleChangeVariables}
|
defaultValue={JSON.stringify(variables, null, 2)}
|
||||||
placeholder="{}"
|
heightMode="auto"
|
||||||
useTemplating
|
onChange={handleChangeVariables}
|
||||||
autocompleteVariables
|
placeholder="{}"
|
||||||
{...extraEditorProps}
|
useTemplating
|
||||||
/>
|
autocompleteVariables
|
||||||
|
{...extraEditorProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
119
src-web/components/GrpcConnectionLayout.tsx
Normal file
119
src-web/components/GrpcConnectionLayout.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
|
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||||
|
import { useGrpcMessages } from '../hooks/useGrpcMessages';
|
||||||
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
|
import { Banner } from './core/Banner';
|
||||||
|
import { HotKeyList } from './core/HotKeyList';
|
||||||
|
import { SplitLayout } from './core/SplitLayout';
|
||||||
|
import { GrpcConnectionMessagesPane } from './GrpcConnectionMessagesPane';
|
||||||
|
import { GrpcConnectionSetupPane } from './GrpcConnectionSetupPane';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
style: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GrpcConnectionLayout({ style }: Props) {
|
||||||
|
const activeRequest = useActiveRequest('grpc_request');
|
||||||
|
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
|
||||||
|
const connections = useGrpcConnections(activeRequest?.id ?? null);
|
||||||
|
const activeConnection = connections[0] ?? null;
|
||||||
|
const messages = useGrpcMessages(activeConnection?.id ?? null);
|
||||||
|
const grpc = useGrpc(activeRequest, activeConnection);
|
||||||
|
|
||||||
|
const services = grpc.reflect.data ?? null;
|
||||||
|
useEffect(() => {
|
||||||
|
if (services == null || activeRequest == null) return;
|
||||||
|
const s = services.find((s) => s.name === activeRequest.service);
|
||||||
|
if (s == null) {
|
||||||
|
updateRequest.mutate({
|
||||||
|
service: services[0]?.name ?? null,
|
||||||
|
method: services[0]?.methods[0]?.name ?? null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = s.methods.find((m) => m.name === activeRequest.method);
|
||||||
|
if (m == null) {
|
||||||
|
updateRequest.mutate({ method: s.methods[0]?.name ?? null });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [activeRequest, services, updateRequest]);
|
||||||
|
|
||||||
|
const activeMethod = useMemo(() => {
|
||||||
|
if (services == null || activeRequest == null) return null;
|
||||||
|
|
||||||
|
const s = services.find((s) => s.name === activeRequest.service);
|
||||||
|
if (s == null) return null;
|
||||||
|
return s.methods.find((m) => m.name === activeRequest.method);
|
||||||
|
}, [activeRequest, services]);
|
||||||
|
|
||||||
|
const methodType:
|
||||||
|
| 'unary'
|
||||||
|
| 'server_streaming'
|
||||||
|
| 'client_streaming'
|
||||||
|
| 'streaming'
|
||||||
|
| 'no-schema'
|
||||||
|
| 'no-method' = useMemo(() => {
|
||||||
|
if (services == null) return 'no-schema';
|
||||||
|
if (activeMethod == null) return 'no-method';
|
||||||
|
if (activeMethod.clientStreaming && activeMethod.serverStreaming) return 'streaming';
|
||||||
|
if (activeMethod.clientStreaming) return 'client_streaming';
|
||||||
|
if (activeMethod.serverStreaming) return 'server_streaming';
|
||||||
|
return 'unary';
|
||||||
|
}, [activeMethod, services]);
|
||||||
|
|
||||||
|
if (activeRequest == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SplitLayout
|
||||||
|
name="grpc_layout"
|
||||||
|
className="p-3 gap-1.5"
|
||||||
|
style={style}
|
||||||
|
firstSlot={({ style }) => (
|
||||||
|
<GrpcConnectionSetupPane
|
||||||
|
style={style}
|
||||||
|
activeRequest={activeRequest}
|
||||||
|
methodType={methodType}
|
||||||
|
onUnary={grpc.unary.mutate}
|
||||||
|
onServerStreaming={grpc.serverStreaming.mutate}
|
||||||
|
onClientStreaming={grpc.clientStreaming.mutate}
|
||||||
|
onStreaming={grpc.streaming.mutate}
|
||||||
|
onCommit={grpc.commit.mutate}
|
||||||
|
onCancel={grpc.cancel.mutate}
|
||||||
|
onSend={grpc.send.mutate}
|
||||||
|
services={services ?? null}
|
||||||
|
reflectionError={grpc.reflect.error as string | undefined}
|
||||||
|
reflectionLoading={grpc.reflect.isFetching}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
secondSlot={({ style }) =>
|
||||||
|
!grpc.unary.isLoading && (
|
||||||
|
<div
|
||||||
|
style={style}
|
||||||
|
className={classNames(
|
||||||
|
'max-h-full h-full grid grid-rows-[minmax(0,1fr)] grid-cols-1',
|
||||||
|
'bg-gray-50 dark:bg-gray-100 rounded-md border border-highlight',
|
||||||
|
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{grpc.unary.error ? (
|
||||||
|
<Banner color="danger" className="m-2">
|
||||||
|
{grpc.unary.error}
|
||||||
|
</Banner>
|
||||||
|
) : messages.length >= 0 ? (
|
||||||
|
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
|
||||||
|
) : (
|
||||||
|
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
127
src-web/components/GrpcConnectionMessagesPane.tsx
Normal file
127
src-web/components/GrpcConnectionMessagesPane.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||||
|
import { useGrpcMessages } from '../hooks/useGrpcMessages';
|
||||||
|
import type { GrpcRequest } from '../lib/models';
|
||||||
|
import { Icon } from './core/Icon';
|
||||||
|
import { JsonAttributeTree } from './core/JsonAttributeTree';
|
||||||
|
import { Separator } from './core/Separator';
|
||||||
|
import { SplitLayout } from './core/SplitLayout';
|
||||||
|
import { HStack } from './core/Stacks';
|
||||||
|
import { RecentConnectionsDropdown } from './RecentConnectionsDropdown';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
style?: CSSProperties;
|
||||||
|
className?: string;
|
||||||
|
activeRequest: GrpcRequest;
|
||||||
|
methodType:
|
||||||
|
| 'unary'
|
||||||
|
| 'client_streaming'
|
||||||
|
| 'server_streaming'
|
||||||
|
| 'streaming'
|
||||||
|
| 'no-schema'
|
||||||
|
| 'no-method';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
|
||||||
|
const [activeMessageId, setActiveMessageId] = useState<string | null>(null);
|
||||||
|
const connections = useGrpcConnections(activeRequest.id ?? null);
|
||||||
|
const activeConnection = connections[0] ?? null;
|
||||||
|
const messages = useGrpcMessages(activeConnection?.id ?? null);
|
||||||
|
|
||||||
|
const activeMessage = useMemo(
|
||||||
|
() => messages.find((m) => m.id === activeMessageId) ?? null,
|
||||||
|
[activeMessageId, messages],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SplitLayout
|
||||||
|
forceVertical
|
||||||
|
style={style}
|
||||||
|
name={methodType === 'unary' ? 'grpc_messages_unary' : 'grpc_messages_streaming'}
|
||||||
|
defaultRatio={methodType === 'unary' ? 0.75 : 0.3}
|
||||||
|
minHeightPx={20}
|
||||||
|
firstSlot={() => (
|
||||||
|
<div className="w-full grid grid-rows-[auto_minmax(0,1fr)] items-center">
|
||||||
|
<HStack className="pl-3 mb-1 font-mono" alignItems="center">
|
||||||
|
<HStack alignItems="center" space={2}>
|
||||||
|
<span>{messages.filter((m) => !m.isInfo).length} messages</span>
|
||||||
|
{activeConnection?.elapsed === 0 && (
|
||||||
|
<Icon icon="refresh" size="sm" spin className="text-gray-600" />
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
{activeConnection && (
|
||||||
|
<RecentConnectionsDropdown
|
||||||
|
connections={connections}
|
||||||
|
activeConnection={activeConnection}
|
||||||
|
onPinned={() => {
|
||||||
|
// todo
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
<div className="overflow-y-auto h-full">
|
||||||
|
{...messages.map((m) => (
|
||||||
|
<HStack
|
||||||
|
role="button"
|
||||||
|
key={m.id}
|
||||||
|
space={2}
|
||||||
|
onClick={() => {
|
||||||
|
if (m.id === activeMessageId) setActiveMessageId(null);
|
||||||
|
else setActiveMessageId(m.id);
|
||||||
|
}}
|
||||||
|
alignItems="center"
|
||||||
|
className={classNames(
|
||||||
|
'px-2 py-1 font-mono cursor-default group',
|
||||||
|
m === activeMessage && '!bg-highlight',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={
|
||||||
|
m.isInfo ? 'text-gray-600' : m.isServer ? 'text-blue-600' : 'text-green-600'
|
||||||
|
}
|
||||||
|
icon={m.isInfo ? 'info' : m.isServer ? 'arrowBigDownDash' : 'arrowBigUpDash'}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'w-full truncate text-gray-800 text-2xs group-hover:text-gray-900',
|
||||||
|
m.id === activeMessageId && 'text-gray-900',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{m.message}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'text-gray-600 text-2xs group-hover:text-gray-700',
|
||||||
|
m.id === activeMessageId && 'text-gray-700',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{format(m.createdAt, 'HH:mm:ss')}
|
||||||
|
</div>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
secondSlot={
|
||||||
|
activeMessage &&
|
||||||
|
(() => (
|
||||||
|
<div className="grid grid-rows-[auto_minmax(0,1fr)]">
|
||||||
|
<div className="pb-3 px-2">
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
<div className="pl-2 overflow-y-auto">
|
||||||
|
{activeMessage.isInfo ? (
|
||||||
|
<span>{activeMessage.message}</span>
|
||||||
|
) : (
|
||||||
|
<JsonAttributeTree attrValue={JSON.parse(activeMessage?.message ?? '{}')} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
235
src-web/components/GrpcConnectionSetupPane.tsx
Normal file
235
src-web/components/GrpcConnectionSetupPane.tsx
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import useResizeObserver from '@react-hook/resize-observer';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { CSSProperties, FormEvent } from 'react';
|
||||||
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||||
|
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||||
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
|
import type { GrpcRequest } from '../lib/models';
|
||||||
|
import { Button } from './core/Button';
|
||||||
|
import { Icon } from './core/Icon';
|
||||||
|
import { IconButton } from './core/IconButton';
|
||||||
|
import { RadioDropdown } from './core/RadioDropdown';
|
||||||
|
import { HStack, VStack } from './core/Stacks';
|
||||||
|
import { GrpcEditor } from './GrpcEditor';
|
||||||
|
import { UrlBar } from './UrlBar';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
style?: CSSProperties;
|
||||||
|
className?: string;
|
||||||
|
activeRequest: GrpcRequest;
|
||||||
|
reflectionError?: string;
|
||||||
|
reflectionLoading?: boolean;
|
||||||
|
methodType:
|
||||||
|
| 'unary'
|
||||||
|
| 'client_streaming'
|
||||||
|
| 'server_streaming'
|
||||||
|
| 'streaming'
|
||||||
|
| 'no-schema'
|
||||||
|
| 'no-method';
|
||||||
|
onUnary: () => void;
|
||||||
|
onCommit: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSend: (v: { message: string }) => void;
|
||||||
|
onClientStreaming: () => void;
|
||||||
|
onServerStreaming: () => void;
|
||||||
|
onStreaming: () => void;
|
||||||
|
services: ReflectResponseService[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GrpcConnectionSetupPane({
|
||||||
|
style,
|
||||||
|
services,
|
||||||
|
methodType,
|
||||||
|
activeRequest,
|
||||||
|
reflectionError,
|
||||||
|
reflectionLoading,
|
||||||
|
onStreaming,
|
||||||
|
onClientStreaming,
|
||||||
|
onServerStreaming,
|
||||||
|
onCommit,
|
||||||
|
onCancel,
|
||||||
|
onSend,
|
||||||
|
onUnary,
|
||||||
|
}: Props) {
|
||||||
|
const connections = useGrpcConnections(activeRequest.id ?? null);
|
||||||
|
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
|
||||||
|
const activeConnection = connections[0] ?? null;
|
||||||
|
const isStreaming = activeConnection?.elapsed === 0;
|
||||||
|
|
||||||
|
const [paneSize, setPaneSize] = useState(99999);
|
||||||
|
const urlContainerEl = useRef<HTMLDivElement>(null);
|
||||||
|
useResizeObserver<HTMLDivElement>(urlContainerEl.current, (entry) => {
|
||||||
|
setPaneSize(entry.contentRect.width);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeUrl = useCallback(
|
||||||
|
(url: string) => updateRequest.mutateAsync({ url }),
|
||||||
|
[updateRequest],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeMessage = useCallback(
|
||||||
|
(message: string) => updateRequest.mutateAsync({ message }),
|
||||||
|
[updateRequest],
|
||||||
|
);
|
||||||
|
|
||||||
|
const select = useMemo(() => {
|
||||||
|
const options =
|
||||||
|
services?.flatMap((s) =>
|
||||||
|
s.methods.map((m) => ({
|
||||||
|
label: `${s.name.split('.', 2).pop() ?? s.name}/${m.name}`,
|
||||||
|
value: `${s.name}/${m.name}`,
|
||||||
|
})),
|
||||||
|
) ?? [];
|
||||||
|
const value = `${activeRequest?.service ?? ''}/${activeRequest?.method ?? ''}`;
|
||||||
|
return { value, options };
|
||||||
|
}, [activeRequest?.method, activeRequest?.service, services]);
|
||||||
|
|
||||||
|
const handleChangeService = useCallback(
|
||||||
|
async (v: string) => {
|
||||||
|
const [serviceName, methodName] = v.split('/', 2);
|
||||||
|
if (serviceName == null || methodName == null) throw new Error('Should never happen');
|
||||||
|
await updateRequest.mutateAsync({
|
||||||
|
service: serviceName,
|
||||||
|
method: methodName,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[updateRequest],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConnect = useCallback(
|
||||||
|
async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (activeRequest == null) return;
|
||||||
|
|
||||||
|
if (activeRequest.service == null || activeRequest.method == null) {
|
||||||
|
alert({
|
||||||
|
id: 'grpc-invalid-service-method',
|
||||||
|
title: 'Error',
|
||||||
|
body: 'Service or method not selected',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (methodType === 'streaming') {
|
||||||
|
onStreaming();
|
||||||
|
} else if (methodType === 'server_streaming') {
|
||||||
|
onServerStreaming();
|
||||||
|
} else if (methodType === 'client_streaming') {
|
||||||
|
onClientStreaming();
|
||||||
|
} else {
|
||||||
|
onUnary();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activeRequest, methodType, onStreaming, onServerStreaming, onClientStreaming, onUnary],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VStack space={2} style={style}>
|
||||||
|
<div
|
||||||
|
ref={urlContainerEl}
|
||||||
|
className={classNames(
|
||||||
|
'grid grid-cols-[minmax(0,1fr)_auto] gap-1.5',
|
||||||
|
paneSize < 400 && '!grid-cols-1',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<UrlBar
|
||||||
|
url={activeRequest.url ?? ''}
|
||||||
|
method={null}
|
||||||
|
submitIcon={null}
|
||||||
|
forceUpdateKey={activeRequest?.id ?? ''}
|
||||||
|
placeholder="localhost:50051"
|
||||||
|
onSubmit={handleConnect}
|
||||||
|
onUrlChange={handleChangeUrl}
|
||||||
|
isLoading={false}
|
||||||
|
/>
|
||||||
|
<HStack space={1.5}>
|
||||||
|
<RadioDropdown
|
||||||
|
value={select.value}
|
||||||
|
onChange={handleChangeService}
|
||||||
|
items={select.options.map((o) => ({
|
||||||
|
label: o.label,
|
||||||
|
value: o.value,
|
||||||
|
type: 'default',
|
||||||
|
shortLabel: o.label,
|
||||||
|
}))}
|
||||||
|
extraItems={[
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Refresh',
|
||||||
|
type: 'default',
|
||||||
|
key: 'custom',
|
||||||
|
leftSlot: <Icon className="text-gray-600" size="sm" icon="refresh" />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="border"
|
||||||
|
rightSlot={<Icon className="text-gray-600" size="sm" icon="chevronDown" />}
|
||||||
|
disabled={isStreaming || services == null}
|
||||||
|
className={classNames(
|
||||||
|
'font-mono text-xs min-w-[5rem] !ring-0',
|
||||||
|
paneSize < 400 && 'flex-1',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{select.options.find((o) => o.value === select.value)?.label ?? 'No Schema'}
|
||||||
|
</Button>
|
||||||
|
</RadioDropdown>
|
||||||
|
{!isStreaming && (
|
||||||
|
<IconButton
|
||||||
|
className="border border-highlight"
|
||||||
|
size="sm"
|
||||||
|
title={methodType === 'unary' ? 'Send' : 'Connect'}
|
||||||
|
hotkeyAction={isStreaming ? undefined : 'http_request.send'}
|
||||||
|
onClick={handleConnect}
|
||||||
|
disabled={methodType === 'no-schema' || methodType === 'no-method'}
|
||||||
|
icon={
|
||||||
|
isStreaming
|
||||||
|
? 'refresh'
|
||||||
|
: methodType.includes('streaming')
|
||||||
|
? 'arrowUpDown'
|
||||||
|
: 'sendHorizontal'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isStreaming && (
|
||||||
|
<IconButton
|
||||||
|
className="border border-highlight"
|
||||||
|
size="sm"
|
||||||
|
title="Cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
icon="x"
|
||||||
|
disabled={!isStreaming}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{methodType === 'client_streaming' && isStreaming && (
|
||||||
|
<IconButton
|
||||||
|
className="border border-highlight"
|
||||||
|
size="sm"
|
||||||
|
title="to-do"
|
||||||
|
onClick={onCommit}
|
||||||
|
icon="check"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(methodType === 'client_streaming' || methodType === 'streaming') && isStreaming && (
|
||||||
|
<IconButton
|
||||||
|
className="border border-highlight"
|
||||||
|
size="sm"
|
||||||
|
title="to-do"
|
||||||
|
hotkeyAction="grpc_request.send"
|
||||||
|
onClick={() => onSend({ message: activeRequest.message ?? '' })}
|
||||||
|
icon="sendHorizontal"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</div>
|
||||||
|
<GrpcEditor
|
||||||
|
onChange={handleChangeMessage}
|
||||||
|
services={services}
|
||||||
|
className="bg-gray-50"
|
||||||
|
reflectionError={reflectionError}
|
||||||
|
reflectionLoading={reflectionLoading}
|
||||||
|
request={activeRequest}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
153
src-web/components/GrpcEditor.tsx
Normal file
153
src-web/components/GrpcEditor.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { EditorView } from 'codemirror';
|
||||||
|
import { updateSchema } from 'codemirror-json-schema';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { useAlert } from '../hooks/useAlert';
|
||||||
|
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||||
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
|
import type { GrpcRequest } from '../lib/models';
|
||||||
|
import { count } from '../lib/pluralize';
|
||||||
|
import { Button } from './core/Button';
|
||||||
|
import type { EditorProps } from './core/Editor';
|
||||||
|
import { Editor } from './core/Editor';
|
||||||
|
import { FormattedError } from './core/FormattedError';
|
||||||
|
import { InlineCode } from './core/InlineCode';
|
||||||
|
import { VStack } from './core/Stacks';
|
||||||
|
import { useDialog } from './DialogContext';
|
||||||
|
import { GrpcProtoSelection } from './GrpcProtoSelection';
|
||||||
|
|
||||||
|
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'className'> & {
|
||||||
|
services: ReflectResponseService[] | null;
|
||||||
|
reflectionError?: string;
|
||||||
|
reflectionLoading?: boolean;
|
||||||
|
request: GrpcRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GrpcEditor({
|
||||||
|
services,
|
||||||
|
reflectionError,
|
||||||
|
reflectionLoading,
|
||||||
|
request,
|
||||||
|
...extraEditorProps
|
||||||
|
}: Props) {
|
||||||
|
const editorViewRef = useRef<EditorView>(null);
|
||||||
|
const alert = useAlert();
|
||||||
|
const dialog = useDialog();
|
||||||
|
|
||||||
|
// Find the schema for the selected service and method and update the editor
|
||||||
|
useEffect(() => {
|
||||||
|
if (editorViewRef.current == null || services === null) return;
|
||||||
|
|
||||||
|
const s = services.find((s) => s.name === request.service);
|
||||||
|
if (request.service != null && s == null) {
|
||||||
|
alert({
|
||||||
|
id: 'grpc-find-service-error',
|
||||||
|
title: "Couldn't Find Service",
|
||||||
|
body: (
|
||||||
|
<>
|
||||||
|
Failed to find service <InlineCode>{request.service}</InlineCode> in schema
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = s?.methods.find((m) => m.name === request.method)?.schema;
|
||||||
|
if (request.method != null && schema == null) {
|
||||||
|
alert({
|
||||||
|
id: 'grpc-find-schema-error',
|
||||||
|
title: "Couldn't Find Method",
|
||||||
|
body: (
|
||||||
|
<>
|
||||||
|
Failed to find method <InlineCode>{request.method}</InlineCode> for{' '}
|
||||||
|
<InlineCode>{request.service}</InlineCode> in schema
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateSchema(editorViewRef.current, JSON.parse(schema));
|
||||||
|
} catch (err) {
|
||||||
|
alert({
|
||||||
|
id: 'grpc-parse-schema-error',
|
||||||
|
title: 'Failed to Parse Schema',
|
||||||
|
body: (
|
||||||
|
<VStack space={4}>
|
||||||
|
<p>
|
||||||
|
For service <InlineCode>{request.service}</InlineCode> and method{' '}
|
||||||
|
<InlineCode>{request.method}</InlineCode>
|
||||||
|
</p>
|
||||||
|
<FormattedError>{String(err)}</FormattedError>
|
||||||
|
</VStack>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [alert, services, request.method, request.service]);
|
||||||
|
|
||||||
|
const reflectionUnavailable = reflectionError?.match(/unimplemented/i);
|
||||||
|
reflectionError = reflectionUnavailable ? undefined : reflectionError;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||||
|
<Editor
|
||||||
|
contentType="application/grpc"
|
||||||
|
forceUpdateKey={request.id}
|
||||||
|
defaultValue={request.message}
|
||||||
|
format={tryFormatJson}
|
||||||
|
heightMode="auto"
|
||||||
|
placeholder="..."
|
||||||
|
ref={editorViewRef}
|
||||||
|
actions={[
|
||||||
|
<div key="reflection" className={classNames(services == null && '!opacity-100')}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
color={
|
||||||
|
reflectionLoading
|
||||||
|
? 'gray'
|
||||||
|
: reflectionUnavailable
|
||||||
|
? 'secondary'
|
||||||
|
: reflectionError
|
||||||
|
? 'danger'
|
||||||
|
: 'gray'
|
||||||
|
}
|
||||||
|
isLoading={reflectionLoading}
|
||||||
|
onClick={() => {
|
||||||
|
dialog.show({
|
||||||
|
title: 'Configure Schema',
|
||||||
|
size: 'md',
|
||||||
|
id: 'reflection-failed',
|
||||||
|
render: ({ hide }) => {
|
||||||
|
return (
|
||||||
|
<VStack space={6} className="pb-5">
|
||||||
|
<GrpcProtoSelection onDone={hide} requestId={request.id} />
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{reflectionLoading
|
||||||
|
? 'Inspecting Schema'
|
||||||
|
: reflectionUnavailable
|
||||||
|
? 'Select Proto Files'
|
||||||
|
: reflectionError
|
||||||
|
? 'Server Error'
|
||||||
|
: request.protoFiles.length > 0
|
||||||
|
? count('File', request.protoFiles.length)
|
||||||
|
: services != null && request.protoFiles.length === 0
|
||||||
|
? 'Schema Detected'
|
||||||
|
: 'Select Schema'}
|
||||||
|
</Button>
|
||||||
|
</div>,
|
||||||
|
]}
|
||||||
|
{...extraEditorProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
148
src-web/components/GrpcProtoSelection.tsx
Normal file
148
src-web/components/GrpcProtoSelection.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { open } from '@tauri-apps/api/dialog';
|
||||||
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
|
import { useGrpcRequest } from '../hooks/useGrpcRequest';
|
||||||
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
|
import { count } from '../lib/pluralize';
|
||||||
|
import { Banner } from './core/Banner';
|
||||||
|
import { Button } from './core/Button';
|
||||||
|
import { FormattedError } from './core/FormattedError';
|
||||||
|
import { IconButton } from './core/IconButton';
|
||||||
|
import { InlineCode } from './core/InlineCode';
|
||||||
|
import { Link } from './core/Link';
|
||||||
|
import { HStack, VStack } from './core/Stacks';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
requestId: string;
|
||||||
|
onDone: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GrpcProtoSelection({ requestId }: Props) {
|
||||||
|
const request = useGrpcRequest(requestId);
|
||||||
|
const grpc = useGrpc(request, null);
|
||||||
|
const updateRequest = useUpdateGrpcRequest(request?.id ?? null);
|
||||||
|
const services = grpc.reflect.data;
|
||||||
|
const serverReflection = request?.protoFiles.length === 0 && services != null;
|
||||||
|
let reflectError = grpc.reflect.error ?? null;
|
||||||
|
const reflectionUnimplemented = `${reflectError}`.match(/unimplemented/i);
|
||||||
|
|
||||||
|
if (reflectionUnimplemented) {
|
||||||
|
reflectError = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VStack className="flex-col-reverse" space={3}>
|
||||||
|
{/* Buttons on top so they get focus first */}
|
||||||
|
<HStack space={2} justifyContent="start" className="flex-row-reverse">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={async () => {
|
||||||
|
const files = await open({
|
||||||
|
title: 'Select Proto Files',
|
||||||
|
multiple: true,
|
||||||
|
filters: [{ name: 'Proto Files', extensions: ['proto'] }],
|
||||||
|
});
|
||||||
|
if (files == null || typeof files === 'string') return;
|
||||||
|
const newFiles = files.filter((f) => !request.protoFiles.includes(f));
|
||||||
|
await updateRequest.mutateAsync({ protoFiles: [...request.protoFiles, ...newFiles] });
|
||||||
|
await grpc.reflect.refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add File
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
isLoading={grpc.reflect.isFetching}
|
||||||
|
disabled={grpc.reflect.isFetching}
|
||||||
|
color="gray"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => grpc.reflect.refetch()}
|
||||||
|
>
|
||||||
|
Refresh Schema
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
<VStack space={5}>
|
||||||
|
{!serverReflection && services != null && services.length > 0 && (
|
||||||
|
<Banner className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
Found services
|
||||||
|
{services?.slice(0, 5).map((s, i) => {
|
||||||
|
return (
|
||||||
|
<span key={i}>
|
||||||
|
<InlineCode>{s.name}</InlineCode>
|
||||||
|
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{services?.length > 5 && count('other', services?.length - 5)}
|
||||||
|
</p>
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
{serverReflection && services != null && services.length > 0 && (
|
||||||
|
<Banner className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
Server reflection found services
|
||||||
|
{services?.map((s, i) => {
|
||||||
|
return (
|
||||||
|
<span key={i}>
|
||||||
|
<InlineCode>{s.name}</InlineCode>
|
||||||
|
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
. You can override this schema by manually selecting <InlineCode>*.proto</InlineCode>{' '}
|
||||||
|
files.
|
||||||
|
</p>
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{request.protoFiles.length > 0 && (
|
||||||
|
<table className="w-full divide-y">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-gray-600">
|
||||||
|
<span className="font-mono text-sm">*.proto</span> Files
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y">
|
||||||
|
{request.protoFiles.map((f, i) => (
|
||||||
|
<tr key={f + i} className="group">
|
||||||
|
<td className="pl-1 text-sm font-mono">{f.split('/').pop()}</td>
|
||||||
|
<td className="w-0 py-0.5">
|
||||||
|
<IconButton
|
||||||
|
title="Remove file"
|
||||||
|
size="sm"
|
||||||
|
icon="trash"
|
||||||
|
className="ml-auto opacity-30 transition-opacity group-hover:opacity-100"
|
||||||
|
onClick={async () => {
|
||||||
|
await updateRequest.mutateAsync({
|
||||||
|
protoFiles: request.protoFiles.filter((p) => p !== f),
|
||||||
|
});
|
||||||
|
grpc.reflect.remove();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
{reflectError && <FormattedError>{reflectError}</FormattedError>}
|
||||||
|
{reflectionUnimplemented && request.protoFiles.length === 0 && (
|
||||||
|
<Banner>
|
||||||
|
<InlineCode>{request.url}</InlineCode> doesn't implement{' '}
|
||||||
|
<Link href="https://github.com/grpc/grpc/blob/9aa3c5835a4ed6afae9455b63ed45c761d695bca/doc/server-reflection.md">
|
||||||
|
Server Reflection
|
||||||
|
</Link>{' '}
|
||||||
|
. Please manually add the <InlineCode>.proto</InlineCode> file to get started.
|
||||||
|
</Banner>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
src-web/components/HttpRequestLayout.tsx
Normal file
29
src-web/components/HttpRequestLayout.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { SplitLayout } from './core/SplitLayout';
|
||||||
|
import { RequestPane } from './RequestPane';
|
||||||
|
import { ResponsePane } from './ResponsePane';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activeRequest: HttpRequest;
|
||||||
|
style: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HttpRequestLayout({ activeRequest, style }: Props) {
|
||||||
|
return (
|
||||||
|
<SplitLayout
|
||||||
|
name="http_layout"
|
||||||
|
className="p-3 gap-1.5"
|
||||||
|
style={style}
|
||||||
|
firstSlot={({ orientation, style }) => (
|
||||||
|
<RequestPane
|
||||||
|
style={style}
|
||||||
|
activeRequest={activeRequest}
|
||||||
|
fullHeight={orientation === 'horizontal'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
secondSlot={({ style }) => <ResponsePane activeRequest={activeRequest} style={style} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
60
src-web/components/RecentConnectionsDropdown.tsx
Normal file
60
src-web/components/RecentConnectionsDropdown.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { formatDistanceToNowStrict } from 'date-fns';
|
||||||
|
import { useDeleteGrpcConnection } from '../hooks/useDeleteGrpcConnection';
|
||||||
|
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
|
||||||
|
import type { GrpcConnection } from '../lib/models';
|
||||||
|
import { count, pluralize } from '../lib/pluralize';
|
||||||
|
import { Dropdown } from './core/Dropdown';
|
||||||
|
import { Icon } from './core/Icon';
|
||||||
|
import { IconButton } from './core/IconButton';
|
||||||
|
import { HStack } from './core/Stacks';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
connections: GrpcConnection[];
|
||||||
|
activeConnection: GrpcConnection;
|
||||||
|
onPinned: (r: GrpcConnection) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RecentConnectionsDropdown({ activeConnection, connections, onPinned }: Props) {
|
||||||
|
const deleteConnection = useDeleteGrpcConnection(activeConnection?.id ?? null);
|
||||||
|
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'clear-single',
|
||||||
|
label: 'Clear Connection',
|
||||||
|
onSelect: deleteConnection.mutate,
|
||||||
|
disabled: connections.length === 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clear-all',
|
||||||
|
label: `Clear ${count('Connection', connections.length)}`,
|
||||||
|
onSelect: deleteAllConnections.mutate,
|
||||||
|
hidden: connections.length <= 1,
|
||||||
|
disabled: connections.length === 0,
|
||||||
|
},
|
||||||
|
{ type: 'separator', label: 'History' },
|
||||||
|
...connections.slice(0, 20).map((c) => ({
|
||||||
|
key: c.id,
|
||||||
|
label: (
|
||||||
|
<HStack space={2} alignItems="center">
|
||||||
|
{formatDistanceToNowStrict(c.createdAt + 'Z')} ago •{' '}
|
||||||
|
<span className="font-mono text-xs">{c.elapsed}ms</span>
|
||||||
|
</HStack>
|
||||||
|
),
|
||||||
|
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||||
|
onSelect: () => onPinned(c),
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
title="Show connection history"
|
||||||
|
icon="chevronDown"
|
||||||
|
className="ml-auto"
|
||||||
|
size="sm"
|
||||||
|
iconSize="md"
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
|||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { useRequests } from '../hooks/useRequests';
|
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -19,7 +19,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
|
|||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const activeWorkspaceId = useActiveWorkspaceId();
|
const activeWorkspaceId = useActiveWorkspaceId();
|
||||||
const activeEnvironmentId = useActiveEnvironmentId();
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
const requests = useRequests();
|
const requests = useHttpRequests();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
const allRecentRequestIds = useRecentRequests();
|
const allRecentRequestIds = useRecentRequests();
|
||||||
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
|
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useDeleteResponse } from '../hooks/useDeleteResponse';
|
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
||||||
import { useDeleteResponses } from '../hooks/useDeleteResponses';
|
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { pluralize } from '../lib/pluralize';
|
import { pluralize } from '../lib/pluralize';
|
||||||
@@ -19,8 +19,8 @@ export const RecentResponsesDropdown = function ResponsePane({
|
|||||||
responses,
|
responses,
|
||||||
onPinnedResponse,
|
onPinnedResponse,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const deleteResponse = useDeleteResponse(activeResponse?.id ?? null);
|
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
|
||||||
const deleteAllResponses = useDeleteResponses(activeResponse?.requestId);
|
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties, FormEvent } from 'react';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
|
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import type { HttpHeader, HttpRequest, HttpUrlParameter } from '../lib/models';
|
import type { HttpHeader, HttpRequest, HttpUrlParameter } from '../lib/models';
|
||||||
import {
|
import {
|
||||||
@@ -33,131 +34,131 @@ import { UrlBar } from './UrlBar';
|
|||||||
import { UrlParametersEditor } from './UrlParameterEditor';
|
import { UrlParametersEditor } from './UrlParameterEditor';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
style?: CSSProperties;
|
style: CSSProperties;
|
||||||
fullHeight: boolean;
|
fullHeight: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
activeRequest: HttpRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useActiveTab = createGlobalState<string>('body');
|
const useActiveTab = createGlobalState<string>('body');
|
||||||
|
|
||||||
export const RequestPane = memo(function RequestPane({ style, fullHeight, className }: Props) {
|
export const RequestPane = memo(function RequestPane({
|
||||||
const activeRequest = useActiveRequest();
|
style,
|
||||||
const activeRequestId = activeRequest?.id ?? null;
|
fullHeight,
|
||||||
const updateRequest = useUpdateRequest(activeRequestId);
|
className,
|
||||||
|
activeRequest,
|
||||||
|
}: Props) {
|
||||||
|
const activeRequestId = activeRequest.id;
|
||||||
|
const updateRequest = useUpdateHttpRequest(activeRequestId);
|
||||||
const [activeTab, setActiveTab] = useActiveTab();
|
const [activeTab, setActiveTab] = useActiveTab();
|
||||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest?.id ?? null);
|
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||||
|
|
||||||
const tabs: TabItem[] = useMemo(
|
const tabs: TabItem[] = useMemo(
|
||||||
() =>
|
() => [
|
||||||
activeRequest === null
|
{
|
||||||
? []
|
value: 'body',
|
||||||
: [
|
options: {
|
||||||
{
|
value: activeRequest.bodyType,
|
||||||
value: 'body',
|
items: [
|
||||||
options: {
|
{ type: 'separator', label: 'Form Data' },
|
||||||
value: activeRequest.bodyType,
|
{ label: 'Url Encoded', value: BODY_TYPE_FORM_URLENCODED },
|
||||||
items: [
|
{ label: 'Multi-Part', value: BODY_TYPE_FORM_MULTIPART },
|
||||||
{ type: 'separator', label: 'Form Data' },
|
{ type: 'separator', label: 'Text Content' },
|
||||||
{ label: 'Url Encoded', value: BODY_TYPE_FORM_URLENCODED },
|
{ label: 'JSON', value: BODY_TYPE_JSON },
|
||||||
{ label: 'Multi-Part', value: BODY_TYPE_FORM_MULTIPART },
|
{ label: 'XML', value: BODY_TYPE_XML },
|
||||||
{ type: 'separator', label: 'Text Content' },
|
{ label: 'GraphQL', value: BODY_TYPE_GRAPHQL },
|
||||||
{ label: 'JSON', value: BODY_TYPE_JSON },
|
{ type: 'separator', label: 'Other' },
|
||||||
{ label: 'XML', value: BODY_TYPE_XML },
|
{ label: 'No Body', shortLabel: 'Body', value: BODY_TYPE_NONE },
|
||||||
{ label: 'GraphQL', value: BODY_TYPE_GRAPHQL },
|
|
||||||
{ type: 'separator', label: 'Other' },
|
|
||||||
{ label: 'No Body', shortLabel: 'Body', value: BODY_TYPE_NONE },
|
|
||||||
],
|
|
||||||
onChange: async (bodyType) => {
|
|
||||||
const patch: Partial<HttpRequest> = { bodyType };
|
|
||||||
if (bodyType === BODY_TYPE_NONE) {
|
|
||||||
patch.headers = activeRequest?.headers.filter(
|
|
||||||
(h) => h.name.toLowerCase() !== 'content-type',
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
bodyType === BODY_TYPE_FORM_URLENCODED ||
|
|
||||||
bodyType === BODY_TYPE_FORM_MULTIPART ||
|
|
||||||
bodyType === BODY_TYPE_JSON ||
|
|
||||||
bodyType === BODY_TYPE_XML
|
|
||||||
) {
|
|
||||||
patch.method = 'POST';
|
|
||||||
patch.headers = [
|
|
||||||
...(activeRequest?.headers.filter(
|
|
||||||
(h) => h.name.toLowerCase() !== 'content-type',
|
|
||||||
) ?? []),
|
|
||||||
{
|
|
||||||
name: 'Content-Type',
|
|
||||||
value: bodyType,
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else if (bodyType == BODY_TYPE_GRAPHQL) {
|
|
||||||
patch.method = 'POST';
|
|
||||||
patch.headers = [
|
|
||||||
...(activeRequest?.headers.filter(
|
|
||||||
(h) => h.name.toLowerCase() !== 'content-type',
|
|
||||||
) ?? []),
|
|
||||||
{
|
|
||||||
name: 'Content-Type',
|
|
||||||
value: 'application/json',
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force update header editor so any changed headers are reflected
|
|
||||||
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
|
||||||
|
|
||||||
updateRequest.mutate(patch);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'params',
|
|
||||||
label: (
|
|
||||||
<div className="flex items-center">
|
|
||||||
Params
|
|
||||||
<CountBadge count={activeRequest.urlParameters.filter((p) => p.name).length} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'headers',
|
|
||||||
label: (
|
|
||||||
<div className="flex items-center">
|
|
||||||
Headers
|
|
||||||
<CountBadge count={activeRequest.headers.filter((h) => h.name).length} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'auth',
|
|
||||||
label: 'Auth',
|
|
||||||
options: {
|
|
||||||
value: activeRequest.authenticationType,
|
|
||||||
items: [
|
|
||||||
{ label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC },
|
|
||||||
{ label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE },
|
|
||||||
],
|
|
||||||
onChange: async (authenticationType) => {
|
|
||||||
let authentication: HttpRequest['authentication'] = activeRequest?.authentication;
|
|
||||||
if (authenticationType === AUTH_TYPE_BASIC) {
|
|
||||||
authentication = {
|
|
||||||
username: authentication.username ?? '',
|
|
||||||
password: authentication.password ?? '',
|
|
||||||
};
|
|
||||||
} else if (authenticationType === AUTH_TYPE_BEARER) {
|
|
||||||
authentication = {
|
|
||||||
token: authentication.token ?? '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
updateRequest.mutate({ authenticationType, authentication });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
onChange: async (bodyType) => {
|
||||||
|
const patch: Partial<HttpRequest> = { bodyType };
|
||||||
|
if (bodyType === BODY_TYPE_NONE) {
|
||||||
|
patch.headers = activeRequest.headers.filter(
|
||||||
|
(h) => h.name.toLowerCase() !== 'content-type',
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
bodyType === BODY_TYPE_FORM_URLENCODED ||
|
||||||
|
bodyType === BODY_TYPE_FORM_MULTIPART ||
|
||||||
|
bodyType === BODY_TYPE_JSON ||
|
||||||
|
bodyType === BODY_TYPE_XML
|
||||||
|
) {
|
||||||
|
patch.method = 'POST';
|
||||||
|
patch.headers = [
|
||||||
|
...(activeRequest.headers.filter((h) => h.name.toLowerCase() !== 'content-type') ??
|
||||||
|
[]),
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: bodyType,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (bodyType == BODY_TYPE_GRAPHQL) {
|
||||||
|
patch.method = 'POST';
|
||||||
|
patch.headers = [
|
||||||
|
...(activeRequest.headers.filter((h) => h.name.toLowerCase() !== 'content-type') ??
|
||||||
|
[]),
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'application/json',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force update header editor so any changed headers are reflected
|
||||||
|
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
||||||
|
|
||||||
|
updateRequest.mutate(patch);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'params',
|
||||||
|
label: (
|
||||||
|
<div className="flex items-center">
|
||||||
|
Params
|
||||||
|
<CountBadge count={activeRequest.urlParameters.filter((p) => p.name).length} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'headers',
|
||||||
|
label: (
|
||||||
|
<div className="flex items-center">
|
||||||
|
Headers
|
||||||
|
<CountBadge count={activeRequest.headers.filter((h) => h.name).length} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'auth',
|
||||||
|
label: 'Auth',
|
||||||
|
options: {
|
||||||
|
value: activeRequest.authenticationType,
|
||||||
|
items: [
|
||||||
|
{ label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC },
|
||||||
|
{ label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE },
|
||||||
|
],
|
||||||
|
onChange: async (authenticationType) => {
|
||||||
|
let authentication: HttpRequest['authentication'] = activeRequest.authentication;
|
||||||
|
if (authenticationType === AUTH_TYPE_BASIC) {
|
||||||
|
authentication = {
|
||||||
|
username: authentication.username ?? '',
|
||||||
|
password: authentication.password ?? '',
|
||||||
|
};
|
||||||
|
} else if (authenticationType === AUTH_TYPE_BEARER) {
|
||||||
|
authentication = {
|
||||||
|
token: authentication.token ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
updateRequest.mutate({ authenticationType, authentication });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
[activeRequest, updateRequest],
|
[activeRequest, updateRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -178,6 +179,27 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
[updateRequest],
|
[updateRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sendRequest = useSendRequest(activeRequest.id ?? null);
|
||||||
|
const handleSend = useCallback(
|
||||||
|
async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await sendRequest.mutateAsync();
|
||||||
|
},
|
||||||
|
[sendRequest],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMethodChange = useCallback(
|
||||||
|
(method: string) => updateRequest.mutate({ method }),
|
||||||
|
[updateRequest],
|
||||||
|
);
|
||||||
|
const handleUrlChange = useCallback(
|
||||||
|
(url: string) => updateRequest.mutate({ url }),
|
||||||
|
[updateRequest],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isLoading = useIsResponseLoading(activeRequestId ?? null);
|
||||||
|
const { updateKey } = useRequestUpdateKey(activeRequestId ?? null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
@@ -186,10 +208,14 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
{activeRequest && (
|
{activeRequest && (
|
||||||
<>
|
<>
|
||||||
<UrlBar
|
<UrlBar
|
||||||
key={activeRequest.id} // Force-reset the url bar when the active request changes
|
|
||||||
id={activeRequest.id}
|
|
||||||
url={activeRequest.url}
|
url={activeRequest.url}
|
||||||
method={activeRequest.method}
|
method={activeRequest.method}
|
||||||
|
placeholder="https://example.com"
|
||||||
|
onSubmit={handleSend}
|
||||||
|
onMethodChange={handleMethodChange}
|
||||||
|
onUrlChange={handleUrlChange}
|
||||||
|
forceUpdateKey={updateKey}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
@@ -240,7 +266,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
placeholder="..."
|
placeholder="..."
|
||||||
className="!bg-gray-50"
|
className="!bg-gray-50"
|
||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
defaultValue={`${activeRequest?.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
contentType="application/json"
|
contentType="application/json"
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyTextChange}
|
||||||
format={tryFormatJson}
|
format={tryFormatJson}
|
||||||
@@ -253,7 +279,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
placeholder="..."
|
placeholder="..."
|
||||||
className="!bg-gray-50"
|
className="!bg-gray-50"
|
||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
defaultValue={`${activeRequest?.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
contentType="text/xml"
|
contentType="text/xml"
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyTextChange}
|
||||||
/>
|
/>
|
||||||
@@ -262,7 +288,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
baseRequest={activeRequest}
|
baseRequest={activeRequest}
|
||||||
className="!bg-gray-50"
|
className="!bg-gray-50"
|
||||||
defaultValue={`${activeRequest?.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyTextChange}
|
||||||
/>
|
/>
|
||||||
) : activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED ? (
|
) : activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED ? (
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
import useResizeObserver from '@react-hook/resize-observer';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
|
||||||
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
|
||||||
import { useLocalStorage } from 'react-use';
|
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
|
||||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
|
||||||
import { clamp } from '../lib/clamp';
|
|
||||||
import { HotKeyList } from './core/HotKeyList';
|
|
||||||
import { RequestPane } from './RequestPane';
|
|
||||||
import { ResizeHandle } from './ResizeHandle';
|
|
||||||
import { ResponsePane } from './ResponsePane';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
style: CSSProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rqst = { gridArea: 'rqst' };
|
|
||||||
const resp = { gridArea: 'resp' };
|
|
||||||
const drag = { gridArea: 'drag' };
|
|
||||||
|
|
||||||
const DEFAULT = 0.5;
|
|
||||||
const MIN_WIDTH_PX = 10;
|
|
||||||
const MIN_HEIGHT_PX = 30;
|
|
||||||
const STACK_VERTICAL_WIDTH = 700;
|
|
||||||
|
|
||||||
export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const activeRequest = useActiveRequest();
|
|
||||||
const [vertical, setVertical] = useState<boolean>(false);
|
|
||||||
const [widthRaw, setWidth] = useLocalStorage<number>(`body_width::${useActiveWorkspaceId()}`);
|
|
||||||
const [heightRaw, setHeight] = useLocalStorage<number>(`body_height::${useActiveWorkspaceId()}`);
|
|
||||||
const width = widthRaw ?? DEFAULT;
|
|
||||||
const height = heightRaw ?? DEFAULT;
|
|
||||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
|
||||||
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
useResizeObserver(containerRef.current, ({ contentRect }) => {
|
|
||||||
setVertical(contentRect.width < STACK_VERTICAL_WIDTH);
|
|
||||||
});
|
|
||||||
|
|
||||||
const styles = useMemo<CSSProperties>(
|
|
||||||
() => ({
|
|
||||||
...style,
|
|
||||||
gridTemplate: vertical
|
|
||||||
? `
|
|
||||||
' ${rqst.gridArea}' minmax(0,${1 - height}fr)
|
|
||||||
' ${drag.gridArea}' 0
|
|
||||||
' ${resp.gridArea}' minmax(0,${height}fr)
|
|
||||||
/ 1fr
|
|
||||||
`
|
|
||||||
: `
|
|
||||||
' ${rqst.gridArea} ${drag.gridArea} ${resp.gridArea}' minmax(0,1fr)
|
|
||||||
/ ${1 - width}fr 0 ${width}fr
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
[vertical, width, height, style],
|
|
||||||
);
|
|
||||||
|
|
||||||
const unsub = () => {
|
|
||||||
if (moveState.current !== null) {
|
|
||||||
document.documentElement.removeEventListener('mousemove', moveState.current.move);
|
|
||||||
document.documentElement.removeEventListener('mouseup', moveState.current.up);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = useCallback(
|
|
||||||
() => (vertical ? setHeight(DEFAULT) : setWidth(DEFAULT)),
|
|
||||||
[setHeight, vertical, setWidth],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleResizeStart = useCallback(
|
|
||||||
(e: ReactMouseEvent<HTMLDivElement>) => {
|
|
||||||
if (containerRef.current === null) return;
|
|
||||||
unsub();
|
|
||||||
|
|
||||||
const containerRect = containerRef.current.getBoundingClientRect();
|
|
||||||
|
|
||||||
const mouseStartX = e.clientX;
|
|
||||||
const mouseStartY = e.clientY;
|
|
||||||
const startWidth = containerRect.width * width;
|
|
||||||
const startHeight = containerRect.height * height;
|
|
||||||
|
|
||||||
moveState.current = {
|
|
||||||
move: (e: MouseEvent) => {
|
|
||||||
e.preventDefault(); // Prevent text selection and things
|
|
||||||
if (vertical) {
|
|
||||||
const maxHeightPx = containerRect.height - MIN_HEIGHT_PX;
|
|
||||||
const newHeightPx = clamp(
|
|
||||||
startHeight - (e.clientY - mouseStartY),
|
|
||||||
MIN_HEIGHT_PX,
|
|
||||||
maxHeightPx,
|
|
||||||
);
|
|
||||||
setHeight(newHeightPx / containerRect.height);
|
|
||||||
} else {
|
|
||||||
const maxWidthPx = containerRect.width - MIN_WIDTH_PX;
|
|
||||||
const newWidthPx = clamp(
|
|
||||||
startWidth - (e.clientX - mouseStartX),
|
|
||||||
MIN_WIDTH_PX,
|
|
||||||
maxWidthPx,
|
|
||||||
);
|
|
||||||
setWidth(newWidthPx / containerRect.width);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
up: (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
unsub();
|
|
||||||
setIsResizing(false);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
document.documentElement.addEventListener('mousemove', moveState.current.move);
|
|
||||||
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
|
||||||
setIsResizing(true);
|
|
||||||
},
|
|
||||||
[width, height, vertical, setHeight, setWidth],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (activeRequest === null) {
|
|
||||||
return <HotKeyList hotkeys={['request.create', 'sidebar.toggle']} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={containerRef} className="grid gap-1.5 w-full h-full p-3" style={styles}>
|
|
||||||
<RequestPane style={rqst} fullHeight={!vertical} />
|
|
||||||
<ResizeHandle
|
|
||||||
style={drag}
|
|
||||||
isResizing={isResizing}
|
|
||||||
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
|
||||||
onResizeStart={handleResizeStart}
|
|
||||||
onReset={handleReset}
|
|
||||||
side={vertical ? 'top' : 'left'}
|
|
||||||
justify="center"
|
|
||||||
/>
|
|
||||||
<ResponsePane style={resp} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Separator } from './core/Separator';
|
||||||
|
|
||||||
interface ResizeBarProps {
|
interface ResizeBarProps {
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
@@ -17,6 +18,7 @@ export function ResizeHandle({
|
|||||||
style,
|
style,
|
||||||
justify,
|
justify,
|
||||||
className,
|
className,
|
||||||
|
barClassName,
|
||||||
onResizeStart,
|
onResizeStart,
|
||||||
onReset,
|
onReset,
|
||||||
isResizing,
|
isResizing,
|
||||||
@@ -28,6 +30,8 @@ export function ResizeHandle({
|
|||||||
aria-hidden
|
aria-hidden
|
||||||
draggable
|
draggable
|
||||||
style={style}
|
style={style}
|
||||||
|
onDragStart={onResizeStart}
|
||||||
|
onDoubleClick={onReset}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'group z-10 flex',
|
'group z-10 flex',
|
||||||
@@ -39,8 +43,6 @@ export function ResizeHandle({
|
|||||||
side === 'left' && 'left-0',
|
side === 'left' && 'left-0',
|
||||||
side === 'top' && 'top-0',
|
side === 'top' && 'top-0',
|
||||||
)}
|
)}
|
||||||
onDragStart={onResizeStart}
|
|
||||||
onDoubleClick={onReset}
|
|
||||||
>
|
>
|
||||||
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
|
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
|
||||||
{isResizing && (
|
{isResizing && (
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ import classNames from 'classnames';
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useHttpResponses } from '../hooks/useHttpResponses';
|
||||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
||||||
import { useResponseContentType } from '../hooks/useResponseContentType';
|
import { useResponseContentType } from '../hooks/useResponseContentType';
|
||||||
import { useResponses } from '../hooks/useResponses';
|
|
||||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpRequest, HttpResponse } from '../lib/models';
|
||||||
import { isResponseLoading } from '../lib/models';
|
import { isResponseLoading } from '../lib/models';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { CountBadge } from './core/CountBadge';
|
import { CountBadge } from './core/CountBadge';
|
||||||
@@ -29,15 +28,15 @@ import { WebPageViewer } from './responseViewers/WebPageViewer';
|
|||||||
interface Props {
|
interface Props {
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
activeRequest: HttpRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useActiveTab = createGlobalState<string>('body');
|
const useActiveTab = createGlobalState<string>('body');
|
||||||
|
|
||||||
export const ResponsePane = memo(function ResponsePane({ style, className }: Props) {
|
export const ResponsePane = memo(function ResponsePane({ style, className, activeRequest }: Props) {
|
||||||
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
||||||
const activeRequest = useActiveRequest();
|
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
||||||
const latestResponse = useLatestResponse(activeRequest?.id ?? null);
|
const responses = useHttpResponses(activeRequest.id);
|
||||||
const responses = useResponses(activeRequest?.id ?? null);
|
|
||||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
const activeResponse: HttpResponse | null = pinnedResponseId
|
||||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
||||||
: latestResponse ?? null;
|
: latestResponse ?? null;
|
||||||
@@ -85,10 +84,6 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
[activeResponse?.headers, contentType, setViewMode, viewMode],
|
[activeResponse?.headers, contentType, setViewMode, viewMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (activeRequest === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
@@ -108,7 +103,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
<>
|
<>
|
||||||
<span />
|
<span />
|
||||||
<HotKeyList
|
<HotKeyList
|
||||||
hotkeys={['request.send', 'request.create', 'sidebar.toggle', 'urlBar.focus']}
|
hotkeys={['http_request.send', 'http_request.create', 'sidebar.toggle', 'urlBar.focus']}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -179,6 +174,8 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
) : contentType?.match(/csv|tab-separated/) ? (
|
) : contentType?.match(/csv|tab-separated/) ? (
|
||||||
<CsvViewer className="pb-2" response={activeResponse} />
|
<CsvViewer className="pb-2" response={activeResponse} />
|
||||||
) : (
|
) : (
|
||||||
|
// ) : contentType?.startsWith('application/json') ? (
|
||||||
|
// <JsonViewer response={activeResponse} />
|
||||||
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
|
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
|
||||||
)}
|
)}
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|||||||
@@ -29,11 +29,20 @@ export const SettingsDialog = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
value={settings.appearance}
|
value={settings.appearance}
|
||||||
onChange={(appearance) => updateSettings.mutateAsync({ ...settings, appearance })}
|
onChange={(appearance) => updateSettings.mutateAsync({ ...settings, appearance })}
|
||||||
options={{
|
options={[
|
||||||
system: 'System',
|
{
|
||||||
light: 'Light',
|
label: 'System',
|
||||||
dark: 'Dark',
|
value: 'system',
|
||||||
}}
|
},
|
||||||
|
{
|
||||||
|
label: 'Light',
|
||||||
|
value: 'light',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dark',
|
||||||
|
value: 'dark',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
@@ -44,10 +53,16 @@ export const SettingsDialog = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
value={settings.updateChannel}
|
value={settings.updateChannel}
|
||||||
onChange={(updateChannel) => updateSettings.mutateAsync({ ...settings, updateChannel })}
|
onChange={(updateChannel) => updateSettings.mutateAsync({ ...settings, updateChannel })}
|
||||||
options={{
|
options={[
|
||||||
stable: 'Release',
|
{
|
||||||
beta: 'Early Bird (Beta)',
|
label: 'Release',
|
||||||
}}
|
value: 'stable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Early Bird (Beta)',
|
||||||
|
value: 'beta',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function SettingsDropdown() {
|
|||||||
label: 'Check for Updates',
|
label: 'Check for Updates',
|
||||||
leftSlot: <Icon icon="update" />,
|
leftSlot: <Icon icon="update" />,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const hasUpdate: boolean = await invoke('check_for_updates');
|
const hasUpdate: boolean = await invoke('cmd_check_for_updates');
|
||||||
if (!hasUpdate) {
|
if (!hasUpdate) {
|
||||||
alert({
|
alert({
|
||||||
id: 'no-updates',
|
id: 'no-updates',
|
||||||
|
|||||||
@@ -7,31 +7,37 @@ import { useKey, useKeyPressEvent } from 'react-use';
|
|||||||
|
|
||||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { useCreateFolder } from '../hooks/useCreateFolder';
|
import { useCreateFolder } from '../hooks/useCreateFolder';
|
||||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
import { useDeleteAnyGrpcRequest } from '../hooks/useDeleteAnyGrpcRequest';
|
||||||
|
import { useDeleteAnyHttpRequest } from '../hooks/useDeleteAnyHttpRequest';
|
||||||
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
||||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
||||||
|
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
||||||
import { useFolders } from '../hooks/useFolders';
|
import { useFolders } from '../hooks/useFolders';
|
||||||
|
import { useGrpcRequests } from '../hooks/useGrpcRequests';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
|
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
import { useLatestGrpcConnection } from '../hooks/useLatestGrpcConnection';
|
||||||
|
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { useRequests } from '../hooks/useRequests';
|
|
||||||
import { useSendManyRequests } from '../hooks/useSendFolder';
|
import { useSendManyRequests } from '../hooks/useSendFolder';
|
||||||
import { useSendRequest } from '../hooks/useSendRequest';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||||
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||||
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
|
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
import type { Folder, HttpRequest, Workspace } from '../lib/models';
|
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
|
||||||
import { isResponseLoading } from '../lib/models';
|
import { isResponseLoading } from '../lib/models';
|
||||||
|
import type { DropdownItem } from './core/Dropdown';
|
||||||
import { ContextMenu } from './core/Dropdown';
|
import { ContextMenu } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
@@ -48,7 +54,7 @@ enum ItemTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TreeNode {
|
interface TreeNode {
|
||||||
item: Workspace | Folder | HttpRequest;
|
item: Workspace | Folder | HttpRequest | GrpcRequest;
|
||||||
children: TreeNode[];
|
children: TreeNode[];
|
||||||
depth: number;
|
depth: number;
|
||||||
}
|
}
|
||||||
@@ -56,18 +62,28 @@ interface TreeNode {
|
|||||||
export function Sidebar({ className }: Props) {
|
export function Sidebar({ className }: Props) {
|
||||||
const { hidden } = useSidebarHidden();
|
const { hidden } = useSidebarHidden();
|
||||||
const sidebarRef = useRef<HTMLLIElement>(null);
|
const sidebarRef = useRef<HTMLLIElement>(null);
|
||||||
const activeRequestId = useActiveRequestId();
|
const activeRequest = useActiveRequest();
|
||||||
const activeEnvironmentId = useActiveEnvironmentId();
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
const requests = useRequests();
|
const httpRequests = useHttpRequests();
|
||||||
|
const grpcRequests = useGrpcRequests();
|
||||||
const folders = useFolders();
|
const folders = useFolders();
|
||||||
const deleteAnyRequest = useDeleteAnyRequest();
|
const deleteAnyHttpRequest = useDeleteAnyHttpRequest();
|
||||||
|
const deleteAnyGrpcRequest = useDeleteAnyGrpcRequest();
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
const duplicateRequest = useDuplicateRequest({ id: activeRequestId, navigateAfter: true });
|
const duplicateHttpRequest = useDuplicateHttpRequest({
|
||||||
|
id: activeRequest?.id ?? null,
|
||||||
|
navigateAfter: true,
|
||||||
|
});
|
||||||
|
const duplicateGrpcRequest = useDuplicateGrpcRequest({
|
||||||
|
id: activeRequest?.id ?? null,
|
||||||
|
navigateAfter: true,
|
||||||
|
});
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const [selectedTree, setSelectedTree] = useState<TreeNode | null>(null);
|
const [selectedTree, setSelectedTree] = useState<TreeNode | null>(null);
|
||||||
const updateAnyRequest = useUpdateAnyRequest();
|
const updateAnyHttpRequest = useUpdateAnyHttpRequest();
|
||||||
|
const updateAnyGrpcRequest = useUpdateAnyGrpcRequest();
|
||||||
const updateAnyFolder = useUpdateAnyFolder();
|
const updateAnyFolder = useUpdateAnyFolder();
|
||||||
const [draggingId, setDraggingId] = useState<string | null>(null);
|
const [draggingId, setDraggingId] = useState<string | null>(null);
|
||||||
const [hoveredTree, setHoveredTree] = useState<TreeNode | null>(null);
|
const [hoveredTree, setHoveredTree] = useState<TreeNode | null>(null);
|
||||||
@@ -78,8 +94,12 @@ export function Sidebar({ className }: Props) {
|
|||||||
namespace: NAMESPACE_NO_SYNC,
|
namespace: NAMESPACE_NO_SYNC,
|
||||||
});
|
});
|
||||||
|
|
||||||
useHotKey('request.duplicate', () => {
|
useHotKey('http_request.duplicate', async () => {
|
||||||
duplicateRequest.mutate();
|
if (activeRequest?.model === 'http_request') {
|
||||||
|
await duplicateHttpRequest.mutateAsync();
|
||||||
|
} else {
|
||||||
|
await duplicateGrpcRequest.mutateAsync();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCollapsed = useCallback(
|
const isCollapsed = useCallback(
|
||||||
@@ -110,7 +130,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
|
|
||||||
// Put requests and folders into a tree structure
|
// Put requests and folders into a tree structure
|
||||||
const next = (node: TreeNode): TreeNode => {
|
const next = (node: TreeNode): TreeNode => {
|
||||||
const childItems = [...requests, ...folders].filter((f) =>
|
const childItems = [...httpRequests, ...grpcRequests, ...folders].filter((f) =>
|
||||||
node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id,
|
node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -119,7 +139,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
for (const item of childItems) {
|
for (const item of childItems) {
|
||||||
treeParentMap[item.id] = node;
|
treeParentMap[item.id] = node;
|
||||||
node.children.push(next({ item, children: [], depth }));
|
node.children.push(next({ item, children: [], depth }));
|
||||||
if (item.model === 'http_request') {
|
if (item.model !== 'folder') {
|
||||||
selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node });
|
selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +149,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
|
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
|
||||||
|
|
||||||
return { tree, treeParentMap, selectableRequests };
|
return { tree, treeParentMap, selectableRequests };
|
||||||
}, [activeWorkspace, requests, folders]);
|
}, [activeWorkspace, httpRequests, grpcRequests, folders]);
|
||||||
|
|
||||||
const focusActiveRequest = useCallback(
|
const focusActiveRequest = useCallback(
|
||||||
(
|
(
|
||||||
@@ -142,9 +162,10 @@ export function Sidebar({ className }: Props) {
|
|||||||
} = {},
|
} = {},
|
||||||
) => {
|
) => {
|
||||||
const { forced, noFocusSidebar } = args;
|
const { forced, noFocusSidebar } = args;
|
||||||
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
const tree = forced?.tree ?? treeParentMap[activeRequest?.id ?? 'n/a'] ?? null;
|
||||||
const children = tree?.children ?? [];
|
const children = tree?.children ?? [];
|
||||||
const id = forced?.id ?? children.find((m) => m.item.id === activeRequestId)?.item.id ?? null;
|
const id =
|
||||||
|
forced?.id ?? children.find((m) => m.item.id === activeRequest?.id)?.item.id ?? null;
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,11 +177,11 @@ export function Sidebar({ className }: Props) {
|
|||||||
sidebarRef.current?.focus();
|
sidebarRef.current?.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeRequestId, treeParentMap],
|
[activeRequest, treeParentMap],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(id: string) => {
|
async (id: string) => {
|
||||||
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
||||||
const children = tree?.children ?? [];
|
const children = tree?.children ?? [];
|
||||||
const node = children.find((m) => m.item.id === id) ?? null;
|
const node = children.find((m) => m.item.id === id) ?? null;
|
||||||
@@ -171,7 +192,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
const { item } = node;
|
const { item } = node;
|
||||||
|
|
||||||
if (item.model === 'folder') {
|
if (item.model === 'folder') {
|
||||||
collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] }));
|
await collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] }));
|
||||||
} else {
|
} else {
|
||||||
routes.navigate('request', {
|
routes.navigate('request', {
|
||||||
requestId: id,
|
requestId: id,
|
||||||
@@ -205,9 +226,10 @@ export function Sidebar({ className }: Props) {
|
|||||||
|
|
||||||
const selected = selectableRequests.find((r) => r.id === selectedId);
|
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||||
if (selected == null) return;
|
if (selected == null) return;
|
||||||
deleteAnyRequest.mutate(selected.id);
|
deleteAnyHttpRequest.mutate(selected.id);
|
||||||
|
deleteAnyGrpcRequest.mutate(selected.id);
|
||||||
},
|
},
|
||||||
[deleteAnyRequest, hasFocus, selectableRequests, selectedId],
|
[deleteAnyHttpRequest, deleteAnyGrpcRequest, hasFocus, selectableRequests, selectedId],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeyPressEvent('Backspace', handleDeleteKey);
|
useKeyPressEvent('Backspace', handleDeleteKey);
|
||||||
@@ -226,7 +248,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
useKeyPressEvent('Enter', (e) => {
|
useKeyPressEvent('Enter', (e) => {
|
||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
const selected = selectableRequests.find((r) => r.id === selectedId);
|
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||||
if (!selected || selected.id === activeRequestId || activeWorkspace == null) {
|
if (!selected || selected.id === activeRequest?.id || activeWorkspace == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,9 +361,12 @@ export function Sidebar({ className }: Props) {
|
|||||||
if (child.item.model === 'folder') {
|
if (child.item.model === 'folder') {
|
||||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||||
return updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
|
return updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
|
||||||
|
} else if (child.item.model === 'grpc_request') {
|
||||||
|
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||||
|
return updateAnyGrpcRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||||
} else if (child.item.model === 'http_request') {
|
} else if (child.item.model === 'http_request') {
|
||||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||||
return updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
return updateAnyHttpRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -350,20 +375,24 @@ export function Sidebar({ className }: Props) {
|
|||||||
if (child.item.model === 'folder') {
|
if (child.item.model === 'folder') {
|
||||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||||
await updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
|
await updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
|
||||||
|
} else if (child.item.model === 'grpc_request') {
|
||||||
|
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||||
|
await updateAnyGrpcRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||||
} else if (child.item.model === 'http_request') {
|
} else if (child.item.model === 'http_request') {
|
||||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||||
await updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
await updateAnyHttpRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDraggingId(null);
|
setDraggingId(null);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
hoveredIndex,
|
|
||||||
hoveredTree,
|
|
||||||
handleClearSelected,
|
handleClearSelected,
|
||||||
|
hoveredTree,
|
||||||
|
hoveredIndex,
|
||||||
treeParentMap,
|
treeParentMap,
|
||||||
updateAnyFolder,
|
updateAnyFolder,
|
||||||
updateAnyRequest,
|
updateAnyGrpcRequest,
|
||||||
|
updateAnyHttpRequest,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -454,7 +483,9 @@ function SidebarItems({
|
|||||||
itemId={child.item.id}
|
itemId={child.item.id}
|
||||||
itemName={child.item.name}
|
itemName={child.item.name}
|
||||||
itemFallbackName={
|
itemFallbackName={
|
||||||
child.item.model === 'http_request' ? fallbackRequestName(child.item) : 'New Folder'
|
child.item.model === 'http_request' || child.item.model === 'grpc_request'
|
||||||
|
? fallbackRequestName(child.item)
|
||||||
|
: 'New Folder'
|
||||||
}
|
}
|
||||||
itemModel={child.item.model}
|
itemModel={child.item.model}
|
||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
@@ -524,16 +555,18 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
ref: ForwardedRef<HTMLLIElement>,
|
ref: ForwardedRef<HTMLLIElement>,
|
||||||
) {
|
) {
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const createRequest = useCreateRequest();
|
const createRequest = useCreateHttpRequest();
|
||||||
const createFolder = useCreateFolder();
|
const createFolder = useCreateFolder();
|
||||||
const deleteFolder = useDeleteFolder(itemId);
|
const deleteFolder = useDeleteFolder(itemId);
|
||||||
const deleteRequest = useDeleteRequest(itemId);
|
const deleteRequest = useDeleteRequest(itemId);
|
||||||
const duplicateRequest = useDuplicateRequest({ id: itemId, navigateAfter: true });
|
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
|
||||||
|
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
||||||
const sendRequest = useSendRequest(itemId);
|
const sendRequest = useSendRequest(itemId);
|
||||||
const sendAndDownloadRequest = useSendRequest(itemId, { download: true });
|
|
||||||
const sendManyRequests = useSendManyRequests();
|
const sendManyRequests = useSendManyRequests();
|
||||||
const latestResponse = useLatestResponse(itemId);
|
const latestHttpResponse = useLatestHttpResponse(itemId);
|
||||||
const updateRequest = useUpdateRequest(itemId);
|
const latestGrpcConnection = useLatestGrpcConnection(itemId);
|
||||||
|
const updateHttpRequest = useUpdateHttpRequest(itemId);
|
||||||
|
const updateGrpcRequest = useUpdateGrpcRequest(itemId);
|
||||||
const updateAnyFolder = useUpdateAnyFolder();
|
const updateAnyFolder = useUpdateAnyFolder();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const [editing, setEditing] = useState<boolean>(false);
|
const [editing, setEditing] = useState<boolean>(false);
|
||||||
@@ -541,10 +574,15 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
|
|
||||||
const handleSubmitNameEdit = useCallback(
|
const handleSubmitNameEdit = useCallback(
|
||||||
(el: HTMLInputElement) => {
|
(el: HTMLInputElement) => {
|
||||||
updateRequest.mutate((r) => ({ ...r, name: el.value }));
|
if (activeRequest == null) return;
|
||||||
|
if (activeRequest.model === 'http_request') {
|
||||||
|
updateHttpRequest.mutate((r) => ({ ...r, name: el.value }));
|
||||||
|
} else if (activeRequest.model === 'grpc_request') {
|
||||||
|
updateGrpcRequest.mutate((r) => ({ ...r, name: el.value }));
|
||||||
|
}
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
},
|
},
|
||||||
[updateRequest],
|
[activeRequest, updateGrpcRequest, updateHttpRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
||||||
@@ -570,7 +608,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleStartEditing = useCallback(() => {
|
const handleStartEditing = useCallback(() => {
|
||||||
if (itemModel !== 'http_request') return;
|
if (itemModel !== 'http_request' && itemModel !== 'grpc_request') return;
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
}, [setEditing, itemModel]);
|
}, [setEditing, itemModel]);
|
||||||
|
|
||||||
@@ -649,23 +687,29 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
...((itemModel === 'http_request'
|
||||||
key: 'sendRequest',
|
? [
|
||||||
label: 'Send',
|
{
|
||||||
hotKeyAction: 'request.send',
|
key: 'sendRequest',
|
||||||
hotKeyLabelOnly: true, // Already bound in URL bar
|
label: 'Send',
|
||||||
leftSlot: <Icon icon="sendHorizontal" />,
|
hotKeyAction: 'http_request.send',
|
||||||
onSelect: () => sendRequest.mutate(),
|
hotKeyLabelOnly: true, // Already bound in URL bar
|
||||||
},
|
leftSlot: <Icon icon="sendHorizontal" />,
|
||||||
{ type: 'separator' },
|
onSelect: () => sendRequest.mutate(),
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
]
|
||||||
|
: []) as DropdownItem[]),
|
||||||
{
|
{
|
||||||
key: 'duplicateRequest',
|
key: 'duplicateRequest',
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
hotKeyAction: 'request.duplicate',
|
hotKeyAction: 'http_request.duplicate',
|
||||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||||
leftSlot: <Icon icon="copy" />,
|
leftSlot: <Icon icon="copy" />,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
duplicateRequest.mutate();
|
itemModel === 'http_request'
|
||||||
|
? duplicateHttpRequest.mutate()
|
||||||
|
: duplicateGrpcRequest.mutate();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -717,15 +761,21 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
) : (
|
) : (
|
||||||
<span className="truncate">{itemName || itemFallbackName}</span>
|
<span className="truncate">{itemName || itemFallbackName}</span>
|
||||||
)}
|
)}
|
||||||
{latestResponse && (
|
{latestGrpcConnection ? (
|
||||||
<div className="ml-auto">
|
<div className="ml-auto">
|
||||||
{isResponseLoading(latestResponse) ? (
|
{latestGrpcConnection.elapsed === 0 && (
|
||||||
<Icon spin size="sm" icon="update" />
|
<Icon spin size="sm" icon="update" className="text-gray-400" />
|
||||||
) : (
|
|
||||||
<StatusTag className="text-2xs dark:opacity-80" response={latestResponse} />
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : latestHttpResponse ? (
|
||||||
|
<div className="ml-auto">
|
||||||
|
{isResponseLoading(latestHttpResponse) ? (
|
||||||
|
<Icon spin size="sm" icon="update" className="text-gray-400" />
|
||||||
|
) : (
|
||||||
|
<StatusTag className="text-2xs dark:opacity-80" response={latestHttpResponse} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { Simulate } from 'react-dom/test-utils';
|
||||||
import { useCreateFolder } from '../hooks/useCreateFolder';
|
import { useCreateFolder } from '../hooks/useCreateFolder';
|
||||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
|
||||||
|
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { trackEvent } from '../lib/analytics';
|
import { trackEvent } from '../lib/analytics';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
@@ -8,16 +10,21 @@ import { IconButton } from './core/IconButton';
|
|||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
|
|
||||||
export const SidebarActions = memo(function SidebarActions() {
|
export const SidebarActions = memo(function SidebarActions() {
|
||||||
const createRequest = useCreateRequest();
|
const createHttpRequest = useCreateHttpRequest();
|
||||||
|
const createGrpcRequest = useCreateGrpcRequest();
|
||||||
const createFolder = useCreateFolder();
|
const createFolder = useCreateFolder();
|
||||||
const { hidden, toggle } = useSidebarHidden();
|
const { hidden, show, hide } = useSidebarHidden();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack>
|
<HStack>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
trackEvent('Sidebar', 'Toggle');
|
trackEvent('Sidebar', 'Toggle');
|
||||||
toggle();
|
|
||||||
|
// NOTE: We're not using `toggle` because it may be out of sync
|
||||||
|
// from changes in other windows
|
||||||
|
if (hidden) await show();
|
||||||
|
else await hide();
|
||||||
}}
|
}}
|
||||||
className="pointer-events-auto"
|
className="pointer-events-auto"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -28,14 +35,19 @@ export const SidebarActions = memo(function SidebarActions() {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'create-request',
|
key: 'create-http-request',
|
||||||
label: 'New Request',
|
label: 'HTTP Request',
|
||||||
hotKeyAction: 'request.create',
|
hotKeyAction: 'http_request.create',
|
||||||
onSelect: () => createRequest.mutate({}),
|
onSelect: () => createHttpRequest.mutate({}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'create-grpc-request',
|
||||||
|
label: 'GRPC Request',
|
||||||
|
onSelect: () => createGrpcRequest.mutate({}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'create-folder',
|
key: 'create-folder',
|
||||||
label: 'New Folder',
|
label: 'Folder',
|
||||||
onSelect: () => createFolder.mutate({}),
|
onSelect: () => createFolder.mutate({}),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,43 +1,39 @@
|
|||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { FormEvent } from 'react';
|
import type { FormEvent } from 'react';
|
||||||
import { memo, useCallback, useRef, useState } from 'react';
|
import { memo, useRef, useState } from 'react';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
|
||||||
import { useSendRequest } from '../hooks/useSendRequest';
|
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import type { IconProps } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { Input } from './core/Input';
|
import { Input } from './core/Input';
|
||||||
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
||||||
|
|
||||||
type Props = Pick<HttpRequest, 'id' | 'url' | 'method'> & {
|
type Props = Pick<HttpRequest, 'url'> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
method: HttpRequest['method'] | null;
|
||||||
|
placeholder: string;
|
||||||
|
onSubmit: (e: FormEvent) => void;
|
||||||
|
onUrlChange: (url: string) => void;
|
||||||
|
submitIcon?: IconProps['icon'] | null;
|
||||||
|
onMethodChange?: (method: string) => void;
|
||||||
|
isLoading: boolean;
|
||||||
|
forceUpdateKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UrlBar = memo(function UrlBar({ id: requestId, url, method, className }: Props) {
|
export const UrlBar = memo(function UrlBar({
|
||||||
|
forceUpdateKey,
|
||||||
|
onUrlChange,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
onSubmit,
|
||||||
|
onMethodChange,
|
||||||
|
submitIcon = 'sendHorizontal',
|
||||||
|
isLoading,
|
||||||
|
}: Props) {
|
||||||
const inputRef = useRef<EditorView>(null);
|
const inputRef = useRef<EditorView>(null);
|
||||||
const sendRequest = useSendRequest(requestId);
|
|
||||||
const updateRequest = useUpdateRequest(requestId);
|
|
||||||
const [isFocused, setIsFocused] = useState<boolean>(false);
|
const [isFocused, setIsFocused] = useState<boolean>(false);
|
||||||
const handleMethodChange = useCallback(
|
|
||||||
(method: string) => updateRequest.mutate({ method }),
|
|
||||||
[updateRequest],
|
|
||||||
);
|
|
||||||
const handleUrlChange = useCallback(
|
|
||||||
(url: string) => updateRequest.mutate({ url }),
|
|
||||||
[updateRequest],
|
|
||||||
);
|
|
||||||
const loading = useIsResponseLoading(requestId);
|
|
||||||
const { updateKey } = useRequestUpdateKey(requestId);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
|
||||||
async (e: FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
sendRequest.mutate();
|
|
||||||
},
|
|
||||||
[sendRequest],
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotKey('urlBar.focus', () => {
|
useHotKey('urlBar.focus', () => {
|
||||||
const head = inputRef.current?.state.doc.length ?? 0;
|
const head = inputRef.current?.state.doc.length ?? 0;
|
||||||
@@ -48,7 +44,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={className}>
|
<form onSubmit={onSubmit} className={className}>
|
||||||
<Input
|
<Input
|
||||||
autocompleteVariables
|
autocompleteVariables
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -60,31 +56,36 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
|||||||
className="px-0 py-0.5"
|
className="px-0 py-0.5"
|
||||||
name="url"
|
name="url"
|
||||||
label="Enter URL"
|
label="Enter URL"
|
||||||
forceUpdateKey={updateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
containerClassName="shadow shadow-gray-100 dark:shadow-gray-50"
|
containerClassName="shadow shadow-gray-100 dark:shadow-gray-50"
|
||||||
onChange={handleUrlChange}
|
onChange={onUrlChange}
|
||||||
defaultValue={url}
|
defaultValue={url}
|
||||||
placeholder="https://example.com"
|
placeholder={placeholder}
|
||||||
leftSlot={
|
leftSlot={
|
||||||
<RequestMethodDropdown
|
method != null &&
|
||||||
method={method}
|
onMethodChange != null && (
|
||||||
onChange={handleMethodChange}
|
<RequestMethodDropdown
|
||||||
className="mx-0.5 my-0.5"
|
method={method}
|
||||||
/>
|
onChange={onMethodChange}
|
||||||
|
className="mx-0.5 my-0.5"
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
rightSlot={
|
rightSlot={
|
||||||
<IconButton
|
submitIcon !== null && (
|
||||||
size="xs"
|
<IconButton
|
||||||
iconSize="md"
|
size="xs"
|
||||||
title="Send Request"
|
iconSize="md"
|
||||||
type="submit"
|
title="Send Request"
|
||||||
className="w-8 mr-0.5 my-0.5"
|
type="submit"
|
||||||
icon={loading ? 'update' : 'sendHorizontal'}
|
className="w-8 mr-0.5 my-0.5"
|
||||||
spin={loading}
|
icon={isLoading ? 'update' : submitIcon}
|
||||||
hotkeyAction="request.send"
|
spin={isLoading}
|
||||||
/>
|
hotkeyAction="http_request.send"
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -8,14 +8,17 @@ import type {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
import { useOsInfo } from '../hooks/useOsInfo';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
|
import { HotKeyList } from './core/HotKeyList';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
|
import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
||||||
|
import { HttpRequestLayout } from './HttpRequestLayout';
|
||||||
import { Overlay } from './Overlay';
|
import { Overlay } from './Overlay';
|
||||||
import { RequestResponse } from './RequestResponse';
|
|
||||||
import { ResizeHandle } from './ResizeHandle';
|
import { ResizeHandle } from './ResizeHandle';
|
||||||
import { Sidebar } from './Sidebar';
|
import { Sidebar } from './Sidebar';
|
||||||
import { SidebarActions } from './SidebarActions';
|
import { SidebarActions } from './SidebarActions';
|
||||||
@@ -31,7 +34,7 @@ const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
|
|||||||
export default function Workspace() {
|
export default function Workspace() {
|
||||||
const { setWidth, width, resetWidth } = useSidebarWidth();
|
const { setWidth, width, resetWidth } = useSidebarWidth();
|
||||||
const { hide, show, hidden } = useSidebarHidden();
|
const { hide, show, hidden } = useSidebarHidden();
|
||||||
|
const activeRequest = useActiveRequest();
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const [floating, setFloating] = useState<boolean>(false);
|
const [floating, setFloating] = useState<boolean>(false);
|
||||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||||
@@ -44,7 +47,7 @@ export default function Workspace() {
|
|||||||
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
||||||
if (shouldHide && !floating) {
|
if (shouldHide && !floating) {
|
||||||
setFloating(true);
|
setFloating(true);
|
||||||
hide();
|
hide().catch(console.error);
|
||||||
} else if (!shouldHide && floating) {
|
} else if (!shouldHide && floating) {
|
||||||
setFloating(false);
|
setFloating(false);
|
||||||
}
|
}
|
||||||
@@ -69,10 +72,10 @@ export default function Workspace() {
|
|||||||
e.preventDefault(); // Prevent text selection and things
|
e.preventDefault(); // Prevent text selection and things
|
||||||
const newWidth = startWidth + (e.clientX - mouseStartX);
|
const newWidth = startWidth + (e.clientX - mouseStartX);
|
||||||
if (newWidth < 100) {
|
if (newWidth < 100) {
|
||||||
hide();
|
await hide();
|
||||||
resetWidth();
|
resetWidth();
|
||||||
} else {
|
} else {
|
||||||
show();
|
await show();
|
||||||
setWidth(newWidth);
|
setWidth(newWidth);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -163,7 +166,13 @@ export default function Workspace() {
|
|||||||
>
|
>
|
||||||
<WorkspaceHeader className="pointer-events-none" />
|
<WorkspaceHeader className="pointer-events-none" />
|
||||||
</HeaderSize>
|
</HeaderSize>
|
||||||
<RequestResponse style={body} />
|
{activeRequest == null ? (
|
||||||
|
<HotKeyList hotkeys={['http_request.create', 'sidebar.toggle', 'settings.show']} />
|
||||||
|
) : activeRequest.model === 'grpc_request' ? (
|
||||||
|
<GrpcConnectionLayout style={body} />
|
||||||
|
) : (
|
||||||
|
<HttpRequestLayout activeRequest={activeRequest} style={body} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,23 +51,13 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
),
|
),
|
||||||
render: ({ hide }) => {
|
render: ({ hide }) => {
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="end" alignItems="center" className="mt-4 mb-6">
|
<HStack
|
||||||
|
space={2}
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
className="mt-4 mb-6 flex-row-reverse"
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
className="focus"
|
|
||||||
color="gray"
|
|
||||||
rightSlot={<Icon icon="externalLink" />}
|
|
||||||
onClick={async () => {
|
|
||||||
hide();
|
|
||||||
const environmentId = (await getRecentEnvironments(w.id))[0];
|
|
||||||
await invoke('new_window', {
|
|
||||||
url: routes.paths.workspace({ workspaceId: w.id, environmentId }),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
New Window
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
autoFocus
|
|
||||||
className="focus"
|
className="focus"
|
||||||
color="gray"
|
color="gray"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -78,6 +68,20 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
>
|
>
|
||||||
This Window
|
This Window
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="focus"
|
||||||
|
color="gray"
|
||||||
|
rightSlot={<Icon icon="externalLink" />}
|
||||||
|
onClick={async () => {
|
||||||
|
hide();
|
||||||
|
const environmentId = (await getRecentEnvironments(w.id))[0];
|
||||||
|
await invoke('cmd_new_window', {
|
||||||
|
url: routes.paths.workspace({ workspaceId: w.id, environmentId }),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New Window
|
||||||
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Icon } from './Icon';
|
|||||||
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
|
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
|
||||||
innerClassName?: string;
|
innerClassName?: string;
|
||||||
color?: 'custom' | 'default' | 'gray' | 'primary' | 'secondary' | 'warning' | 'danger';
|
color?: 'custom' | 'default' | 'gray' | 'primary' | 'secondary' | 'warning' | 'danger';
|
||||||
|
variant?: 'border' | 'solid';
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
size?: 'sm' | 'md' | 'xs';
|
size?: 'sm' | 'md' | 'xs';
|
||||||
justify?: 'start' | 'center';
|
justify?: 'start' | 'center';
|
||||||
@@ -27,10 +28,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
innerClassName,
|
innerClassName,
|
||||||
children,
|
children,
|
||||||
forDropdown,
|
forDropdown,
|
||||||
color,
|
color = 'default',
|
||||||
type = 'button',
|
type = 'button',
|
||||||
justify = 'center',
|
justify = 'center',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
|
variant = 'solid',
|
||||||
leftSlot,
|
leftSlot,
|
||||||
rightSlot,
|
rightSlot,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -53,24 +55,45 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
'flex-shrink-0 flex items-center',
|
'flex-shrink-0 flex items-center',
|
||||||
'focus-visible-or-class:ring rounded-md',
|
'focus-visible-or-class:ring rounded-md',
|
||||||
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
|
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
|
||||||
color === 'custom' && 'ring-blue-500/50',
|
|
||||||
color === 'default' &&
|
|
||||||
'text-gray-700 enabled:hocus:bg-gray-700/10 enabled:hocus:text-gray-1000 ring-blue-500/50',
|
|
||||||
color === 'gray' &&
|
|
||||||
'text-gray-800 bg-highlight enabled:hocus:bg-gray-500/20 enabled:hocus:text-gray-1000 ring-blue-500/50',
|
|
||||||
color === 'primary' && 'bg-blue-400 text-white enabled:hocus:bg-blue-500 ring-blue-500/50',
|
|
||||||
color === 'secondary' &&
|
|
||||||
'bg-violet-400 text-white enabled:hocus:bg-violet-500 ring-violet-500/50',
|
|
||||||
color === 'warning' &&
|
|
||||||
'bg-orange-400 text-white enabled:hocus:bg-orange-500 ring-orange-500/50',
|
|
||||||
color === 'danger' && 'bg-red-400 text-white enabled:hocus:bg-red-500 ring-red-500/50',
|
|
||||||
justify === 'start' && 'justify-start',
|
justify === 'start' && 'justify-start',
|
||||||
justify === 'center' && 'justify-center',
|
justify === 'center' && 'justify-center',
|
||||||
size === 'md' && 'h-md px-3',
|
size === 'md' && 'h-md px-3',
|
||||||
size === 'sm' && 'h-sm px-2.5 text-sm',
|
size === 'sm' && 'h-sm px-2.5 text-sm',
|
||||||
size === 'xs' && 'h-xs px-2 text-sm',
|
size === 'xs' && 'h-xs px-2 text-sm',
|
||||||
|
// Solids
|
||||||
|
variant === 'solid' && color === 'custom' && 'ring-blue-500/50',
|
||||||
|
variant === 'solid' &&
|
||||||
|
color === 'default' &&
|
||||||
|
'text-gray-700 enabled:hocus:bg-gray-700/10 enabled:hocus:text-gray-800 ring-blue-500/50',
|
||||||
|
variant === 'solid' &&
|
||||||
|
color === 'gray' &&
|
||||||
|
'text-gray-800 bg-highlight enabled:hocus:text-gray-1000 ring-gray-400',
|
||||||
|
variant === 'solid' && color === 'primary' && 'bg-blue-400 text-white ring-blue-700',
|
||||||
|
variant === 'solid' && color === 'secondary' && 'bg-violet-400 text-white ring-violet-700',
|
||||||
|
variant === 'solid' && color === 'warning' && 'bg-orange-400 text-white ring-orange-700',
|
||||||
|
variant === 'solid' && color === 'danger' && 'bg-red-400 text-white ring-red-700',
|
||||||
|
// Borders
|
||||||
|
variant === 'border' && 'border',
|
||||||
|
variant === 'border' &&
|
||||||
|
color === 'default' &&
|
||||||
|
'border-highlight text-gray-700 enabled:hocus:border-focus enabled:hocus:text-gray-800 ring-blue-500/50',
|
||||||
|
variant === 'border' &&
|
||||||
|
color === 'gray' &&
|
||||||
|
'border-gray-500/70 text-gray-700 enabled:hocus:bg-gray-500/20 enabled:hocus:text-gray-800 ring-blue-500/50',
|
||||||
|
variant === 'border' &&
|
||||||
|
color === 'primary' &&
|
||||||
|
'border-blue-500/70 text-blue-700 enabled:hocus:border-blue-500 ring-blue-500/50',
|
||||||
|
variant === 'border' &&
|
||||||
|
color === 'secondary' &&
|
||||||
|
'border-violet-500/70 text-violet-700 enabled:hocus:border-violet-500 ring-violet-500/50',
|
||||||
|
variant === 'border' &&
|
||||||
|
color === 'warning' &&
|
||||||
|
'border-orange-500/70 text-orange-700 enabled:hocus:border-orange-500 ring-orange-500/50',
|
||||||
|
variant === 'border' &&
|
||||||
|
color === 'danger' &&
|
||||||
|
'border-red-500/70 text-red-700 enabled:hocus:border-red-500 ring-red-500/50',
|
||||||
),
|
),
|
||||||
[className, disabled, color, justify, size],
|
[className, disabled, justify, size, variant, color],
|
||||||
);
|
);
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
@@ -100,7 +123,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
) : null}
|
) : null}
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'max-w-[15em] truncate w-full',
|
'truncate w-full',
|
||||||
justify === 'start' ? 'text-left' : 'text-center',
|
justify === 'start' ? 'text-left' : 'text-center',
|
||||||
innerClassName,
|
innerClassName,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function CountBadge({ count, className }: Props) {
|
|||||||
aria-hidden
|
aria-hidden
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'opacity-70 border border-highlight text-3xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
'opacity-70 border border-highlight text-4xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{count}
|
{count}
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
if (item.type === 'separator') {
|
if (item.type === 'separator') {
|
||||||
return (
|
return (
|
||||||
<Separator key={i} className="ml-2 my-1.5">
|
<Separator key={i} className={classNames('my-1.5', item.label && 'ml-2')}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Separator>
|
</Separator>
|
||||||
);
|
);
|
||||||
@@ -473,7 +473,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
|
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
|
||||||
'focus:bg-highlight focus:text-gray-900 rounded',
|
'focus:bg-highlight focus:text-gray-800 rounded',
|
||||||
item.variant === 'danger' && 'text-red-600',
|
item.variant === 'danger' && 'text-red-600',
|
||||||
item.variant === 'notify' && 'text-pink-600',
|
item.variant === 'notify' && 'text-pink-600',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export interface EditorProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
heightMode?: 'auto' | 'full';
|
heightMode?: 'auto' | 'full';
|
||||||
contentType?: string | null;
|
contentType?: string | null;
|
||||||
forceUpdateKey?: string;
|
forceUpdateKey?: string | number;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
autoSelect?: boolean;
|
autoSelect?: boolean;
|
||||||
defaultValue?: string | null;
|
defaultValue?: string | null;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { json } from '@codemirror/lang-json';
|
|||||||
import { xml } from '@codemirror/lang-xml';
|
import { xml } from '@codemirror/lang-xml';
|
||||||
import type { LanguageSupport } from '@codemirror/language';
|
import type { LanguageSupport } from '@codemirror/language';
|
||||||
import {
|
import {
|
||||||
bracketMatching,
|
|
||||||
foldGutter,
|
foldGutter,
|
||||||
foldKeymap,
|
foldKeymap,
|
||||||
HighlightStyle,
|
HighlightStyle,
|
||||||
@@ -32,6 +31,7 @@ import {
|
|||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import { tags as t } from '@lezer/highlight';
|
import { tags as t } from '@lezer/highlight';
|
||||||
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
||||||
|
import { jsonSchema } from 'codemirror-json-schema';
|
||||||
import type { Environment, Workspace } from '../../../lib/models';
|
import type { Environment, Workspace } from '../../../lib/models';
|
||||||
import type { EditorProps } from './index';
|
import type { EditorProps } from './index';
|
||||||
import { text } from './text/extension';
|
import { text } from './text/extension';
|
||||||
@@ -83,6 +83,7 @@ export const myHighlightStyle = HighlightStyle.define([
|
|||||||
// ]);
|
// ]);
|
||||||
|
|
||||||
const syntaxExtensions: Record<string, LanguageSupport> = {
|
const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||||
|
'application/grpc': jsonSchema() as any, // TODO: Fix this
|
||||||
'application/graphql': graphqlLanguageSupport(),
|
'application/graphql': graphqlLanguageSupport(),
|
||||||
'application/json': json(),
|
'application/json': json(),
|
||||||
'application/javascript': javascript(),
|
'application/javascript': javascript(),
|
||||||
@@ -119,7 +120,6 @@ export const baseExtensions = [
|
|||||||
history(),
|
history(),
|
||||||
dropCursor(),
|
dropCursor(),
|
||||||
drawSelection(),
|
drawSelection(),
|
||||||
bracketMatching(),
|
|
||||||
// TODO: Figure out how to debounce showing of autocomplete in a good way
|
// TODO: Figure out how to debounce showing of autocomplete in a good way
|
||||||
// debouncedAutocompletionDisplay({ millis: 1000 }),
|
// debouncedAutocompletionDisplay({ millis: 1000 }),
|
||||||
// autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }),
|
// autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: string;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormattedError({ children }: Props) {
|
export function FormattedError({ children }: Props) {
|
||||||
console.log('ERROR', children);
|
|
||||||
return (
|
return (
|
||||||
<pre
|
<pre
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|||||||
@@ -5,33 +5,43 @@ import { memo } from 'react';
|
|||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
archive: lucide.ArchiveIcon,
|
archive: lucide.ArchiveIcon,
|
||||||
|
arrowBigDownDash: lucide.ArrowBigDownDashIcon,
|
||||||
|
arrowBigUpDash: lucide.ArrowBigUpDashIcon,
|
||||||
|
arrowDown: lucide.ArrowDownIcon,
|
||||||
|
arrowDownToDot: lucide.ArrowDownToDotIcon,
|
||||||
|
arrowUp: lucide.ArrowUpIcon,
|
||||||
|
arrowUpDown: lucide.ArrowUpDownIcon,
|
||||||
|
arrowUpFromDot: lucide.ArrowUpFromDotIcon,
|
||||||
box: lucide.BoxIcon,
|
box: lucide.BoxIcon,
|
||||||
cake: lucide.CakeIcon,
|
cake: lucide.CakeIcon,
|
||||||
chat: lucide.MessageSquare,
|
chat: lucide.MessageSquare,
|
||||||
check: lucide.CheckIcon,
|
check: lucide.CheckIcon,
|
||||||
chevronDown: lucide.ChevronDownIcon,
|
chevronDown: lucide.ChevronDownIcon,
|
||||||
chevronRight: lucide.ChevronRightIcon,
|
chevronRight: lucide.ChevronRightIcon,
|
||||||
cookie: lucide.CookieIcon,
|
|
||||||
code: lucide.CodeIcon,
|
code: lucide.CodeIcon,
|
||||||
|
cookie: lucide.CookieIcon,
|
||||||
copy: lucide.CopyIcon,
|
copy: lucide.CopyIcon,
|
||||||
download: lucide.DownloadIcon,
|
download: lucide.DownloadIcon,
|
||||||
folderInput: lucide.FolderInputIcon,
|
|
||||||
folderOutput: lucide.FolderOutputIcon,
|
|
||||||
externalLink: lucide.ExternalLinkIcon,
|
externalLink: lucide.ExternalLinkIcon,
|
||||||
eye: lucide.EyeIcon,
|
eye: lucide.EyeIcon,
|
||||||
eyeClosed: lucide.EyeOffIcon,
|
eyeClosed: lucide.EyeOffIcon,
|
||||||
filter: lucide.FilterIcon,
|
filter: lucide.FilterIcon,
|
||||||
flask: lucide.FlaskConicalIcon,
|
flask: lucide.FlaskConicalIcon,
|
||||||
|
folderInput: lucide.FolderInputIcon,
|
||||||
|
folderOutput: lucide.FolderOutputIcon,
|
||||||
gripVertical: lucide.GripVerticalIcon,
|
gripVertical: lucide.GripVerticalIcon,
|
||||||
|
info: lucide.InfoIcon,
|
||||||
keyboard: lucide.KeyboardIcon,
|
keyboard: lucide.KeyboardIcon,
|
||||||
leftPanelHidden: lucide.PanelLeftOpenIcon,
|
leftPanelHidden: lucide.PanelLeftOpenIcon,
|
||||||
leftPanelVisible: lucide.PanelLeftCloseIcon,
|
leftPanelVisible: lucide.PanelLeftCloseIcon,
|
||||||
magicWand: lucide.Wand2Icon,
|
magicWand: lucide.Wand2Icon,
|
||||||
moreVertical: lucide.MoreVerticalIcon,
|
moreVertical: lucide.MoreVerticalIcon,
|
||||||
pencil: lucide.PencilIcon,
|
pencil: lucide.PencilIcon,
|
||||||
|
plug: lucide.Plug,
|
||||||
plus: lucide.PlusIcon,
|
plus: lucide.PlusIcon,
|
||||||
plusCircle: lucide.PlusCircleIcon,
|
plusCircle: lucide.PlusCircleIcon,
|
||||||
question: lucide.ShieldQuestionIcon,
|
question: lucide.ShieldQuestionIcon,
|
||||||
|
refresh: lucide.RefreshCwIcon,
|
||||||
sendHorizontal: lucide.SendHorizonalIcon,
|
sendHorizontal: lucide.SendHorizonalIcon,
|
||||||
settings2: lucide.Settings2Icon,
|
settings2: lucide.Settings2Icon,
|
||||||
settings: lucide.SettingsIcon,
|
settings: lucide.SettingsIcon,
|
||||||
@@ -47,7 +57,7 @@ const icons = {
|
|||||||
export interface IconProps {
|
export interface IconProps {
|
||||||
icon: keyof typeof icons;
|
icon: keyof typeof icons;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: 'xs' | 'sm' | 'md';
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
spin?: boolean;
|
spin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +67,8 @@ export const Icon = memo(function Icon({ icon, spin, size = 'md', className }: I
|
|||||||
<Component
|
<Component
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'text-inherit',
|
'text-inherit flex-shrink-0',
|
||||||
|
size === 'lg' && 'h-5 w-5',
|
||||||
size === 'md' && 'h-4 w-4',
|
size === 'md' && 'h-4 w-4',
|
||||||
size === 'sm' && 'h-3.5 w-3.5',
|
size === 'sm' && 'h-3.5 w-3.5',
|
||||||
size === 'xs' && 'h-3 w-3',
|
size === 'xs' && 'h-3 w-3',
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
|
|||||||
<code
|
<code
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-mono text-sm bg-highlight border-0 border-gray-200 px-1.5 py-0.5 rounded text-gray-800 shadow-inner',
|
'font-mono text-xs bg-highlight border-0 border-gray-200/30',
|
||||||
|
'px-1.5 py-0.5 rounded text-gray-800 shadow-inner',
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
122
src-web/components/core/JsonAttributeTree.tsx
Normal file
122
src-web/components/core/JsonAttributeTree.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
depth?: number;
|
||||||
|
attrValue: any;
|
||||||
|
attrKey?: string | number;
|
||||||
|
attrKeyJsonPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPath }: Props) => {
|
||||||
|
attrKeyJsonPath = attrKeyJsonPath ?? `${attrKey}`;
|
||||||
|
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
const toggleExpanded = () => setIsExpanded((v) => !v);
|
||||||
|
|
||||||
|
const { isExpandable, children, label, labelClassName } = useMemo<{
|
||||||
|
isExpandable: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
label?: string;
|
||||||
|
labelClassName?: string;
|
||||||
|
}>(() => {
|
||||||
|
const jsonType = Object.prototype.toString.call(attrValue);
|
||||||
|
if (jsonType === '[object Object]') {
|
||||||
|
return {
|
||||||
|
children: isExpanded
|
||||||
|
? Object.keys(attrValue)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
.flatMap((k) => (
|
||||||
|
<JsonAttributeTree
|
||||||
|
depth={depth + 1}
|
||||||
|
attrValue={attrValue[k]}
|
||||||
|
attrKey={k}
|
||||||
|
attrKeyJsonPath={joinObjectKey(attrKeyJsonPath, k)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: null,
|
||||||
|
isExpandable: true,
|
||||||
|
label: isExpanded ? '{ }' : `{⋯}`,
|
||||||
|
labelClassName: 'text-gray-600',
|
||||||
|
};
|
||||||
|
} else if (jsonType === '[object Array]') {
|
||||||
|
return {
|
||||||
|
children: isExpanded
|
||||||
|
? attrValue.flatMap((v: any, i: number) => (
|
||||||
|
<JsonAttributeTree
|
||||||
|
depth={depth + 1}
|
||||||
|
attrValue={v}
|
||||||
|
attrKey={i}
|
||||||
|
attrKeyJsonPath={joinArrayKey(attrKeyJsonPath, i)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: null,
|
||||||
|
isExpandable: true,
|
||||||
|
label: isExpanded ? '[ ]' : `[⋯]`,
|
||||||
|
labelClassName: 'text-gray-600',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
children: null,
|
||||||
|
isExpandable: false,
|
||||||
|
label: jsonType === '[object String]' ? `"${attrValue}"` : `${attrValue}`,
|
||||||
|
labelClassName: classNames(
|
||||||
|
jsonType === '[object Boolean]' && 'text-pink-600',
|
||||||
|
jsonType === '[object Number]' && 'text-blue-600',
|
||||||
|
jsonType === '[object String]' && 'text-yellow-600',
|
||||||
|
jsonType === '[object Null]' && 'text-red-600',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
||||||
|
|
||||||
|
const labelEl = (
|
||||||
|
<span className={classNames(labelClassName, 'select-text group-hover:text-gray-800')}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className={classNames(/*depth === 0 && '-ml-4',*/ 'font-mono text-2xs')}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{isExpandable ? (
|
||||||
|
<button className="group relative flex items-center pl-4 w-full" onClick={toggleExpanded}>
|
||||||
|
<Icon
|
||||||
|
size="xs"
|
||||||
|
icon="chevronRight"
|
||||||
|
className={classNames(
|
||||||
|
'left-0 absolute transition-transform text-gray-600 flex items-center',
|
||||||
|
'group-hover:text-gray-900',
|
||||||
|
isExpanded ? 'rotate-90' : '',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span className="text-violet-600 mr-1.5 whitespace-nowrap">
|
||||||
|
{attrKey === undefined ? '$' : attrKey}:
|
||||||
|
</span>
|
||||||
|
{labelEl}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="text-violet-600 mr-1.5 pl-4 whitespace-nowrap select-text">
|
||||||
|
{attrKey}:
|
||||||
|
</span>
|
||||||
|
{labelEl}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{children && <div className="ml-4 whitespace-nowrap">{children}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function joinObjectKey(baseKey: string | undefined, key: string): string {
|
||||||
|
const quotedKey = key.match(/^[a-z0-9_]+$/i) ? key : `\`${key}\``;
|
||||||
|
|
||||||
|
if (baseKey == null) return quotedKey;
|
||||||
|
else return `${baseKey}.${quotedKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinArrayKey(baseKey: string | undefined, index: number): string {
|
||||||
|
return `${baseKey ?? ''}[${index}]`;
|
||||||
|
}
|
||||||
35
src-web/components/core/Link.tsx
Normal file
35
src-web/components/core/Link.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { HTMLAttributes } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
|
interface Props extends HTMLAttributes<HTMLAnchorElement> {
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Link({ href, children, className, ...other }: Props) {
|
||||||
|
const isExternal = href.match(/^https?:\/\//);
|
||||||
|
|
||||||
|
className = classNames(className, 'relative underline hover:text-violet-600');
|
||||||
|
|
||||||
|
if (isExternal) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={classNames(className, 'pr-4')}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
<span className="underline">{children}</span>
|
||||||
|
<Icon className="inline absolute right-0.5 top-0.5" size="xs" icon="externalLink" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouterLink to={href} className={className} {...other}>
|
||||||
|
{children}
|
||||||
|
</RouterLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,10 +6,11 @@ interface Props<T extends string> {
|
|||||||
labelPosition?: 'top' | 'left';
|
labelPosition?: 'top' | 'left';
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
hideLabel?: boolean;
|
hideLabel?: boolean;
|
||||||
value: string;
|
value: T;
|
||||||
options: Record<T, string>;
|
options: { label: string; value: T }[];
|
||||||
onChange: (value: T) => void;
|
onChange: (value: T) => void;
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Select<T extends string>({
|
export function Select<T extends string>({
|
||||||
@@ -21,12 +22,14 @@ export function Select<T extends string>({
|
|||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
onChange,
|
onChange,
|
||||||
|
className,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
}: Props<T>) {
|
}: Props<T>) {
|
||||||
const id = `input-${name}`;
|
const id = `input-${name}`;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
className,
|
||||||
'w-full',
|
'w-full',
|
||||||
'pointer-events-auto', // Just in case we're placing in disabled parent
|
'pointer-events-auto', // Just in case we're placing in disabled parent
|
||||||
labelPosition === 'left' && 'flex items-center gap-2',
|
labelPosition === 'left' && 'flex items-center gap-2',
|
||||||
@@ -48,7 +51,7 @@ export function Select<T extends string>({
|
|||||||
style={selectBackgroundStyles}
|
style={selectBackgroundStyles}
|
||||||
onChange={(e) => onChange(e.target.value as T)}
|
onChange={(e) => onChange(e.target.value as T)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'font-mono text-xs border w-full px-2 outline-none bg-transparent',
|
'font-mono text-xs border w-full outline-none bg-transparent pl-2 pr-7',
|
||||||
'border-highlight focus:border-focus',
|
'border-highlight focus:border-focus',
|
||||||
size === 'xs' && 'h-xs',
|
size === 'xs' && 'h-xs',
|
||||||
size === 'sm' && 'h-sm',
|
size === 'sm' && 'h-sm',
|
||||||
@@ -56,8 +59,8 @@ export function Select<T extends string>({
|
|||||||
size === 'lg' && 'h-lg',
|
size === 'lg' && 'h-lg',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Object.entries<string>(options).map(([value, label]) => (
|
{options.map(({ label, value }) => (
|
||||||
<option key={value} value={value}>
|
<option key={label} value={value}>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@@ -68,7 +71,7 @@ export function Select<T extends string>({
|
|||||||
|
|
||||||
const selectBackgroundStyles = {
|
const selectBackgroundStyles = {
|
||||||
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
|
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
|
||||||
backgroundPosition: 'right 0.5rem center',
|
backgroundPosition: 'right 0.3rem center',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundSize: '1.5em 1.5em',
|
backgroundSize: '1.5em 1.5em',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
orientation?: 'horizontal' | 'vertical';
|
orientation?: 'horizontal' | 'vertical';
|
||||||
variant?: 'primary' | 'secondary';
|
variant?: 'primary' | 'secondary';
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: string;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Separator({
|
export function Separator({
|
||||||
|
|||||||
169
src-web/components/core/SplitLayout.tsx
Normal file
169
src-web/components/core/SplitLayout.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import useResizeObserver from '@react-hook/resize-observer';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||||
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useLocalStorage } from 'react-use';
|
||||||
|
import { useActiveRequestId } from '../../hooks/useActiveRequestId';
|
||||||
|
import { useActiveWorkspaceId } from '../../hooks/useActiveWorkspaceId';
|
||||||
|
import { clamp } from '../../lib/clamp';
|
||||||
|
import { ResizeHandle } from '../ResizeHandle';
|
||||||
|
import { HotKeyList } from './HotKeyList';
|
||||||
|
|
||||||
|
interface SlotProps {
|
||||||
|
orientation: 'horizontal' | 'vertical';
|
||||||
|
style: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
firstSlot: (props: SlotProps) => ReactNode;
|
||||||
|
secondSlot: null | ((props: SlotProps) => ReactNode);
|
||||||
|
style?: CSSProperties;
|
||||||
|
className?: string;
|
||||||
|
defaultRatio?: number;
|
||||||
|
minHeightPx?: number;
|
||||||
|
minWidthPx?: number;
|
||||||
|
forceVertical?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const areaL = { gridArea: 'left' };
|
||||||
|
const areaR = { gridArea: 'right' };
|
||||||
|
const areaD = { gridArea: 'drag' };
|
||||||
|
|
||||||
|
const STACK_VERTICAL_WIDTH = 700;
|
||||||
|
|
||||||
|
export function SplitLayout({
|
||||||
|
style,
|
||||||
|
firstSlot,
|
||||||
|
secondSlot,
|
||||||
|
className,
|
||||||
|
name,
|
||||||
|
forceVertical,
|
||||||
|
defaultRatio = 0.5,
|
||||||
|
minHeightPx = 10,
|
||||||
|
minWidthPx = 10,
|
||||||
|
}: Props) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [vertical, setVertical] = useState<boolean>(false);
|
||||||
|
const [widthRaw, setWidth] = useLocalStorage<number>(`${name}_width::${useActiveWorkspaceId()}`);
|
||||||
|
const [heightRaw, setHeight] = useLocalStorage<number>(
|
||||||
|
`${name}_height::${useActiveWorkspaceId()}`,
|
||||||
|
);
|
||||||
|
const width = widthRaw ?? defaultRatio;
|
||||||
|
let height = heightRaw ?? defaultRatio;
|
||||||
|
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||||
|
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!secondSlot) {
|
||||||
|
height = 0;
|
||||||
|
minHeightPx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
useResizeObserver(containerRef.current, ({ contentRect }) => {
|
||||||
|
setVertical(contentRect.width < STACK_VERTICAL_WIDTH);
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles = useMemo<CSSProperties>(() => {
|
||||||
|
return {
|
||||||
|
...style,
|
||||||
|
gridTemplate:
|
||||||
|
forceVertical || vertical
|
||||||
|
? `
|
||||||
|
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
||||||
|
' ${areaD.gridArea}' 0
|
||||||
|
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
||||||
|
/ 1fr
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
||||||
|
/ ${1 - width}fr 0 ${width}fr
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}, [style, vertical, height, minHeightPx, width]);
|
||||||
|
|
||||||
|
const unsub = () => {
|
||||||
|
if (moveState.current !== null) {
|
||||||
|
document.documentElement.removeEventListener('mousemove', moveState.current.move);
|
||||||
|
document.documentElement.removeEventListener('mouseup', moveState.current.up);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = useCallback(
|
||||||
|
() => (vertical ? setHeight(defaultRatio) : setWidth(defaultRatio)),
|
||||||
|
[vertical, setHeight, defaultRatio, setWidth],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleResizeStart = useCallback(
|
||||||
|
(e: ReactMouseEvent<HTMLDivElement>) => {
|
||||||
|
if (containerRef.current === null) return;
|
||||||
|
unsub();
|
||||||
|
|
||||||
|
const containerRect = containerRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
const mouseStartX = e.clientX;
|
||||||
|
const mouseStartY = e.clientY;
|
||||||
|
const startWidth = containerRect.width * width;
|
||||||
|
const startHeight = containerRect.height * height;
|
||||||
|
|
||||||
|
moveState.current = {
|
||||||
|
move: (e: MouseEvent) => {
|
||||||
|
e.preventDefault(); // Prevent text selection and things
|
||||||
|
if (vertical) {
|
||||||
|
const maxHeightPx = containerRect.height - minHeightPx;
|
||||||
|
const newHeightPx = clamp(
|
||||||
|
startHeight - (e.clientY - mouseStartY),
|
||||||
|
minHeightPx,
|
||||||
|
maxHeightPx,
|
||||||
|
);
|
||||||
|
setHeight(newHeightPx / containerRect.height);
|
||||||
|
} else {
|
||||||
|
const maxWidthPx = containerRect.width - minWidthPx;
|
||||||
|
const newWidthPx = clamp(
|
||||||
|
startWidth - (e.clientX - mouseStartX),
|
||||||
|
minWidthPx,
|
||||||
|
maxWidthPx,
|
||||||
|
);
|
||||||
|
setWidth(newWidthPx / containerRect.width);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
up: (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
unsub();
|
||||||
|
setIsResizing(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
document.documentElement.addEventListener('mousemove', moveState.current.move);
|
||||||
|
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
||||||
|
setIsResizing(true);
|
||||||
|
},
|
||||||
|
[width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth],
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeRequestId = useActiveRequestId();
|
||||||
|
if (activeRequestId === null) {
|
||||||
|
return <HotKeyList hotkeys={['http_request.create', 'sidebar.toggle']} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className={classNames(className, 'grid w-full h-full')} style={styles}>
|
||||||
|
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||||
|
{secondSlot && (
|
||||||
|
<>
|
||||||
|
<ResizeHandle
|
||||||
|
style={areaD}
|
||||||
|
isResizing={isResizing}
|
||||||
|
barClassName={'bg-red-300'}
|
||||||
|
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
||||||
|
onResizeStart={handleResizeStart}
|
||||||
|
onReset={handleReset}
|
||||||
|
side={vertical ? 'top' : 'left'}
|
||||||
|
justify="center"
|
||||||
|
/>
|
||||||
|
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ const gapClasses = {
|
|||||||
0: 'gap-0',
|
0: 'gap-0',
|
||||||
0.5: 'gap-0.5',
|
0.5: 'gap-0.5',
|
||||||
1: 'gap-1',
|
1: 'gap-1',
|
||||||
|
1.5: 'gap-1.5',
|
||||||
2: 'gap-2',
|
2: 'gap-2',
|
||||||
3: 'gap-3',
|
3: 'gap-3',
|
||||||
4: 'gap-4',
|
4: 'gap-4',
|
||||||
@@ -56,7 +57,7 @@ export const VStack = forwardRef(function VStack(
|
|||||||
type BaseStackProps = HTMLAttributes<HTMLElement> & {
|
type BaseStackProps = HTMLAttributes<HTMLElement> & {
|
||||||
as?: ComponentType | 'ul' | 'label' | 'form';
|
as?: ComponentType | 'ul' | 'label' | 'form';
|
||||||
space?: keyof typeof gapClasses;
|
space?: keyof typeof gapClasses;
|
||||||
alignItems?: 'start' | 'center' | 'stretch';
|
alignItems?: 'start' | 'center' | 'stretch' | 'end';
|
||||||
justifyContent?: 'start' | 'center' | 'end' | 'between';
|
justifyContent?: 'start' | 'center' | 'end' | 'between';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ const BaseStack = forwardRef(function BaseStack(
|
|||||||
alignItems === 'center' && 'items-center',
|
alignItems === 'center' && 'items-center',
|
||||||
alignItems === 'start' && 'items-start',
|
alignItems === 'start' && 'items-start',
|
||||||
alignItems === 'stretch' && 'items-stretch',
|
alignItems === 'stretch' && 'items-stretch',
|
||||||
|
alignItems === 'end' && 'items-end',
|
||||||
justifyContent === 'start' && 'justify-start',
|
justifyContent === 'start' && 'justify-start',
|
||||||
justifyContent === 'center' && 'justify-center',
|
justifyContent === 'center' && 'justify-center',
|
||||||
justifyContent === 'end' && 'justify-end',
|
justifyContent === 'end' && 'justify-end',
|
||||||
|
|||||||
25
src-web/components/responseViewers/JsonViewer.tsx
Normal file
25
src-web/components/responseViewers/JsonViewer.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||||
|
import type { HttpResponse } from '../../lib/models';
|
||||||
|
import { JsonAttributeTree } from '../core/JsonAttributeTree';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
response: HttpResponse;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JsonViewer({ response, className }: Props) {
|
||||||
|
const rawBody = useResponseBodyText(response) ?? '';
|
||||||
|
let parsed = {};
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(rawBody);
|
||||||
|
} catch (e) {
|
||||||
|
// foo
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(className, 'overflow-x-auto h-full')}>
|
||||||
|
<JsonAttributeTree attrValue={parsed} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -30,13 +30,13 @@ export function Confirm({ onHide, onResult, variant = 'confirm' }: ConfirmProps)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="end" className="mt-2 mb-4">
|
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||||
|
<Button className="focus" color={colors[variant]} onClick={handleSuccess}>
|
||||||
|
{confirmButtonTexts[variant]}
|
||||||
|
</Button>
|
||||||
<Button className="focus" color="gray" onClick={handleHide}>
|
<Button className="focus" color="gray" onClick={handleHide}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button autoFocus className="focus" color={colors[variant]} onClick={handleSuccess}>
|
|
||||||
{confirmButtonTexts[variant]}
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import type { HttpRequest } from '../lib/models';
|
import { r } from 'vitest/dist/types-94cfe4b4';
|
||||||
|
import type { GrpcRequest, HttpRequest } from '../lib/models';
|
||||||
import { useActiveRequestId } from './useActiveRequestId';
|
import { useActiveRequestId } from './useActiveRequestId';
|
||||||
import { useRequests } from './useRequests';
|
import { useGrpcRequests } from './useGrpcRequests';
|
||||||
|
import { useHttpRequests } from './useHttpRequests';
|
||||||
|
|
||||||
export function useActiveRequest(): HttpRequest | null {
|
interface TypeMap {
|
||||||
const requestId = useActiveRequestId();
|
http_request: HttpRequest;
|
||||||
const requests = useRequests();
|
grpc_request: GrpcRequest;
|
||||||
return requests.find((r) => r.id === requestId) ?? null;
|
}
|
||||||
|
|
||||||
|
export function useActiveRequest<T extends keyof TypeMap>(
|
||||||
|
model?: T | undefined,
|
||||||
|
): TypeMap[T] | null {
|
||||||
|
const requestId = useActiveRequestId();
|
||||||
|
const httpRequests = useHttpRequests();
|
||||||
|
const grpcRequests = useGrpcRequests();
|
||||||
|
|
||||||
|
if (model === 'http_request') {
|
||||||
|
return (httpRequests.find((r) => r.id === requestId) ?? null) as TypeMap[T] | null;
|
||||||
|
} else if (model === 'grpc_request') {
|
||||||
|
return (grpcRequests.find((r) => r.id === requestId) ?? null) as TypeMap[T] | null;
|
||||||
|
} else {
|
||||||
|
return (grpcRequests.find((r) => r.id === requestId) ??
|
||||||
|
httpRequests.find((r) => r.id === requestId) ??
|
||||||
|
null) as TypeMap[T] | null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import type { DialogProps } from '../components/core/Dialog';
|
import type { DialogProps } from '../components/core/Dialog';
|
||||||
import { useDialog } from '../components/DialogContext';
|
import { useDialog } from '../components/DialogContext';
|
||||||
import type { AlertProps } from './Alert';
|
import type { AlertProps } from './Alert';
|
||||||
@@ -5,20 +6,16 @@ import { Alert } from './Alert';
|
|||||||
|
|
||||||
export function useAlert() {
|
export function useAlert() {
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
return ({
|
return useCallback(
|
||||||
id,
|
({ id, title, body }: { id: string; title: DialogProps['title']; body: AlertProps['body'] }) =>
|
||||||
title,
|
dialog.show({
|
||||||
body,
|
id,
|
||||||
}: {
|
title,
|
||||||
id: string;
|
hideX: true,
|
||||||
title: DialogProps['title'];
|
size: 'sm',
|
||||||
body: AlertProps['body'];
|
render: ({ hide }) => Alert({ onHide: hide, body }),
|
||||||
}) =>
|
}),
|
||||||
dialog.show({
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
id,
|
[],
|
||||||
title,
|
);
|
||||||
hideX: true,
|
|
||||||
size: 'sm',
|
|
||||||
render: ({ hide }) => Alert({ onHide: hide, body }),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function useCookieJars() {
|
|||||||
queryKey: cookieJarsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
|
queryKey: cookieJarsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (workspaceId == null) return [];
|
if (workspaceId == null) return [];
|
||||||
return (await invoke('list_cookie_jars', { workspaceId })) as CookieJar[];
|
return (await invoke('cmd_list_cookie_jars', { workspaceId })) as CookieJar[];
|
||||||
},
|
},
|
||||||
}).data ?? []
|
}).data ?? []
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { trackEvent } from '../lib/analytics';
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { CookieJar } from '../lib/models';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
|
import { cookieJarsQueryKey } from './useCookieJars';
|
||||||
import { usePrompt } from './usePrompt';
|
import { usePrompt } from './usePrompt';
|
||||||
import { requestsQueryKey } from './useRequests';
|
|
||||||
|
|
||||||
export function useCreateCookieJar() {
|
export function useCreateCookieJar() {
|
||||||
const workspaceId = useActiveWorkspaceId();
|
const workspaceId = useActiveWorkspaceId();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
|
|
||||||
return useMutation<HttpRequest>({
|
return useMutation<CookieJar>({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (workspaceId === null) {
|
if (workspaceId === null) {
|
||||||
throw new Error("Cannot create cookie jar when there's no active workspace");
|
throw new Error("Cannot create cookie jar when there's no active workspace");
|
||||||
@@ -23,13 +23,13 @@ export function useCreateCookieJar() {
|
|||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: 'My Jar',
|
defaultValue: 'My Jar',
|
||||||
});
|
});
|
||||||
return invoke('create_cookie_jar', { workspaceId, name });
|
return invoke('cmd_create_cookie_jar', { workspaceId, name });
|
||||||
},
|
},
|
||||||
onSettled: () => trackEvent('CookieJar', 'Create'),
|
onSettled: () => trackEvent('CookieJar', 'Create'),
|
||||||
onSuccess: async (request) => {
|
onSuccess: async (cookieJar) => {
|
||||||
queryClient.setQueryData<HttpRequest[]>(
|
queryClient.setQueryData<CookieJar[]>(
|
||||||
requestsQueryKey({ workspaceId: request.workspaceId }),
|
cookieJarsQueryKey({ workspaceId: cookieJar.workspaceId }),
|
||||||
(requests) => [...(requests ?? []), request],
|
(items) => [...(items ?? []), cookieJar],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user