mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 17:18:32 +02:00
Use req/conn/msg models in unary/server
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, connection_id, created_at, message\n FROM grpc_messages\n WHERE id = ?\n ",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -37,6 +37,16 @@
|
|||||||
"name": "message",
|
"name": "message",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_server",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_info",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -49,8 +59,10 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "f055a2b6ab6bc3fea115dbea3f287df833c9bb73cdf2fe38da21fe5f5f6ae639"
|
"hash": "196ed792c8d96425d428cb9609b0c1b18e8f1ba3c1fdfb38c91ffd7bada97f59"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, connection_id, created_at, message\n FROM grpc_messages\n WHERE workspace_id = ?\n ",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -37,6 +37,16 @@
|
|||||||
"name": "message",
|
"name": "message",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_server",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_info",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Bool"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -49,8 +59,10 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "a86d3e86c5638d5f666c0cbe26dd5b3e88c632c56c7c4f3f116a29867e41e6a2"
|
"hash": "3c52c0fa3372cdd2657a775c3b93fb65f42d3226cec27220469558e14973328c"
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n INSERT INTO grpc_messages (\n id, workspace_id, request_id, connection_id, message\n )\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n message = excluded.message\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 5
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "4abfe0884ba046534dc6c255409c41ce287d7b0137726b8ca09870382a8b8300"
|
|
||||||
}
|
|
||||||
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 id, model, workspace_id, request_id, created_at, updated_at, service, method\n FROM grpc_connections\n WHERE workspace_id = ?\n ",
|
"query": "\n SELECT id, model, workspace_id, request_id, created_at, updated_at, service, method\n FROM grpc_connections\n WHERE request_id = ?\n ORDER BY created_at DESC\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "29d3bee45c4fca4c63231e1205edc708818737fe37137dbba6af2d784c3c0221"
|
"hash": "a7b969f33ed0424188b429227d6e3fac2bef52f2e1b0eb1d3846d1293d41f86c"
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use prost_reflect::{DescriptorPool, MessageDescriptor};
|
use prost_reflect::{DescriptorPool, MessageDescriptor};
|
||||||
use prost_types::field_descriptor_proto;
|
use prost_types::field_descriptor_proto;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
@@ -50,8 +50,8 @@ impl Default for JsonType {
|
|||||||
|
|
||||||
impl serde::Serialize for JsonType {
|
impl serde::Serialize for JsonType {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
JsonType::String => serializer.serialize_str("string"),
|
JsonType::String => serializer.serialize_str("string"),
|
||||||
@@ -67,8 +67,8 @@ impl serde::Serialize for JsonType {
|
|||||||
|
|
||||||
impl<'de> serde::Deserialize<'de> for JsonType {
|
impl<'de> serde::Deserialize<'de> for JsonType {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<JsonType, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<JsonType, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let s = String::deserialize(deserializer)?;
|
let s = String::deserialize(deserializer)?;
|
||||||
match s.as_str() {
|
match s.as_str() {
|
||||||
@@ -83,7 +83,10 @@ impl<'de> serde::Deserialize<'de> for JsonType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_to_json_schema(pool: &DescriptorPool, message: MessageDescriptor) -> JsonSchemaEntry {
|
pub fn message_to_json_schema(
|
||||||
|
pool: &DescriptorPool,
|
||||||
|
message: MessageDescriptor,
|
||||||
|
) -> JsonSchemaEntry {
|
||||||
let mut schema = JsonSchemaEntry {
|
let mut schema = JsonSchemaEntry {
|
||||||
title: Some(message.name().to_string()),
|
title: Some(message.name().to_string()),
|
||||||
type_: JsonType::Object, // Messages are objects
|
type_: JsonType::Object, // Messages are objects
|
||||||
|
|||||||
@@ -52,5 +52,7 @@ CREATE TABLE grpc_messages
|
|||||||
ON DELETE CASCADE,
|
ON DELETE CASCADE,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
updated_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
|
message TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,14 +44,15 @@ use crate::http::send_http_request;
|
|||||||
use crate::models::{
|
use crate::models::{
|
||||||
cancel_pending_responses, create_response, delete_all_responses, delete_cookie_jar,
|
cancel_pending_responses, create_response, delete_all_responses, delete_cookie_jar,
|
||||||
delete_environment, delete_folder, delete_request, delete_response, delete_workspace,
|
delete_environment, delete_folder, delete_request, delete_response, delete_workspace,
|
||||||
duplicate_grpc_request, duplicate_http_request, list_cookie_jars, list_folders, list_requests,
|
duplicate_grpc_request, duplicate_http_request, generate_id, get_cookie_jar, get_environment,
|
||||||
list_responses, list_workspaces, generate_id, get_cookie_jar, get_environment, get_folder,
|
get_folder, get_grpc_request, get_http_request, get_key_value_raw, get_or_create_settings,
|
||||||
get_grpc_request, get_http_request, get_key_value_raw, get_or_create_settings, get_response,
|
get_response, get_workspace, get_workspace_export_resources, list_cookie_jars,
|
||||||
get_workspace, get_workspace_export_resources, list_environments, list_grpc_requests,
|
list_environments, list_folders, list_grpc_connections, list_grpc_messages, list_grpc_requests,
|
||||||
set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar,
|
list_requests, list_responses, list_workspaces, set_key_value_raw, update_response_if_id,
|
||||||
upsert_environment, upsert_folder, upsert_grpc_request, upsert_http_request, upsert_workspace,
|
update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
|
||||||
CookieJar, Environment, EnvironmentVariable, Folder, GrpcRequest, HttpRequest, HttpResponse,
|
upsert_grpc_message, upsert_grpc_request, upsert_http_request, upsert_workspace, CookieJar,
|
||||||
KeyValue, Settings, Workspace,
|
Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcMessage, GrpcRequest,
|
||||||
|
HttpRequest, HttpResponse, KeyValue, Settings, Workspace,
|
||||||
};
|
};
|
||||||
use crate::plugin::{ImportResources, ImportResult};
|
use crate::plugin::{ImportResources, ImportResult};
|
||||||
use crate::updates::{update_mode_from_str, UpdateMode, YaakUpdater};
|
use crate::updates::{update_mode_from_str, UpdateMode, YaakUpdater};
|
||||||
@@ -98,20 +99,80 @@ async fn cmd_grpc_reflect(endpoint: &str) -> Result<Vec<ServiceDefinition>, Stri
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_grpc_call_unary(
|
async fn cmd_grpc_call_unary(
|
||||||
endpoint: &str,
|
request_id: &str,
|
||||||
service: &str,
|
app_handle: AppHandle<Wry>,
|
||||||
method: &str,
|
|
||||||
message: &str,
|
|
||||||
grpc_handle: State<'_, Mutex<GrpcManager>>,
|
grpc_handle: State<'_, Mutex<GrpcManager>>,
|
||||||
) -> Result<String, String> {
|
db_state: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
let uri = safe_uri(endpoint).map_err(|e| e.to_string())?;
|
) -> Result<GrpcMessage, String> {
|
||||||
grpc_handle
|
let db = &*db_state.lock().await;
|
||||||
|
let req = get_grpc_request(db, request_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let conn = {
|
||||||
|
let req = req.clone();
|
||||||
|
upsert_grpc_connection(
|
||||||
|
db,
|
||||||
|
&GrpcConnection {
|
||||||
|
workspace_id: req.workspace_id,
|
||||||
|
request_id: req.id,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
};
|
||||||
|
emit_side_effect(app_handle.clone(), "created_model", conn.clone());
|
||||||
|
|
||||||
|
{
|
||||||
|
let req = req.clone();
|
||||||
|
let conn = conn.clone();
|
||||||
|
upsert_grpc_message(
|
||||||
|
db,
|
||||||
|
&GrpcMessage {
|
||||||
|
workspace_id: req.workspace_id,
|
||||||
|
request_id: req.id,
|
||||||
|
connection_id: conn.id,
|
||||||
|
is_info: true,
|
||||||
|
message: format!("Initiating connection to {}", req.url),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
|
||||||
|
let conn_id = generate_id(Some("grpc"));
|
||||||
|
let msg = match grpc_handle
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.connect("default", uri)
|
.connect(&conn_id, uri)
|
||||||
.await
|
.await
|
||||||
.unary(service, method, message)
|
.unary(
|
||||||
|
&req.service.unwrap_or_default(),
|
||||||
|
&req.method.unwrap_or_default(),
|
||||||
|
&req.message,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
|
{
|
||||||
|
Ok(msg) => {
|
||||||
|
upsert_grpc_message(
|
||||||
|
db,
|
||||||
|
&GrpcMessage {
|
||||||
|
message: msg,
|
||||||
|
workspace_id: req.workspace_id,
|
||||||
|
request_id: req.id,
|
||||||
|
connection_id: conn.id,
|
||||||
|
is_server: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
msg.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -235,27 +296,47 @@ async fn cmd_grpc_bidi_streaming(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_grpc_server_streaming(
|
async fn cmd_grpc_server_streaming(
|
||||||
endpoint: &str,
|
request_id: &str,
|
||||||
service: &str,
|
|
||||||
method: &str,
|
|
||||||
message: &str,
|
|
||||||
app_handle: AppHandle<Wry>,
|
app_handle: AppHandle<Wry>,
|
||||||
grpc_handle: State<'_, Mutex<GrpcManager>>,
|
grpc_handle: State<'_, Mutex<GrpcManager>>,
|
||||||
) -> Result<String, String> {
|
db_state: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
) -> Result<GrpcConnection, String> {
|
||||||
|
let db = &*db_state.lock().await;
|
||||||
|
let req = get_grpc_request(db, request_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let conn = {
|
||||||
|
let req = req.clone();
|
||||||
|
upsert_grpc_connection(
|
||||||
|
db,
|
||||||
|
&GrpcConnection {
|
||||||
|
workspace_id: req.workspace_id,
|
||||||
|
request_id: req.id,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
};
|
||||||
|
emit_side_effect(app_handle.clone(), "created_model", conn.clone());
|
||||||
|
|
||||||
let (cancelled_tx, mut cancelled_rx) = tokio::sync::watch::channel(false);
|
let (cancelled_tx, mut cancelled_rx) = tokio::sync::watch::channel(false);
|
||||||
|
|
||||||
let uri = safe_uri(endpoint).map_err(|e| e.to_string())?;
|
let (service, method) = match (&req.service, &req.method) {
|
||||||
let conn_id = generate_id(Some("grpc"));
|
(Some(service), Some(method)) => (service, method),
|
||||||
|
_ => return Err("Service and method are required".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
|
||||||
let mut stream = grpc_handle
|
let mut stream = grpc_handle
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.server_streaming(&conn_id, uri, service, method, message)
|
.server_streaming(&conn.id, uri, &service, &method, &req.message)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
enum GrpcMessage {
|
enum IncomingMsg {
|
||||||
Message(String),
|
Message(String),
|
||||||
Commit,
|
Commit,
|
||||||
Cancel,
|
Cancel,
|
||||||
@@ -270,15 +351,15 @@ async fn cmd_grpc_server_streaming(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match serde_json::from_str::<GrpcMessage>(ev.payload().unwrap()) {
|
match serde_json::from_str::<IncomingMsg>(ev.payload().unwrap()) {
|
||||||
Ok(GrpcMessage::Message(msg)) => {
|
Ok(IncomingMsg::Message(msg)) => {
|
||||||
println!("Received message: {}", msg);
|
println!("Received message: {}", msg);
|
||||||
}
|
}
|
||||||
Ok(GrpcMessage::Commit) => {
|
Ok(IncomingMsg::Commit) => {
|
||||||
println!("Received commit");
|
println!("Received commit");
|
||||||
// TODO: Commit client streaming stream
|
// TODO: Commit client streaming stream
|
||||||
}
|
}
|
||||||
Ok(GrpcMessage::Cancel) => {
|
Ok(IncomingMsg::Cancel) => {
|
||||||
println!("Received cancel");
|
println!("Received cancel");
|
||||||
cancelled_tx.send_replace(true);
|
cancelled_tx.send_replace(true);
|
||||||
}
|
}
|
||||||
@@ -289,19 +370,34 @@ async fn cmd_grpc_server_streaming(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let event_handler =
|
let event_handler =
|
||||||
app_handle.listen_global(format!("grpc_client_msg_{}", conn_id).as_str(), cb);
|
app_handle.listen_global(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
|
||||||
|
|
||||||
let grpc_listen = {
|
let grpc_listen = {
|
||||||
|
let db = db.clone();
|
||||||
|
let conn_id = conn.clone().id;
|
||||||
let app_handle = app_handle.clone();
|
let app_handle = app_handle.clone();
|
||||||
let conn_id = conn_id.clone();
|
|
||||||
async move {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
|
let req = req.clone();
|
||||||
|
let conn_id = conn_id.clone();
|
||||||
match stream.next().await {
|
match stream.next().await {
|
||||||
Some(Ok(item)) => {
|
Some(Ok(item)) => {
|
||||||
let item = serde_json::to_string_pretty(&item).unwrap();
|
let item = serde_json::to_string_pretty(&item).unwrap();
|
||||||
app_handle
|
let msg = upsert_grpc_message(
|
||||||
.emit_all(format!("grpc_server_msg_{}", &conn_id).as_str(), item)
|
&db,
|
||||||
.expect("Failed to emit");
|
&GrpcMessage {
|
||||||
|
message: item,
|
||||||
|
workspace_id: req.workspace_id,
|
||||||
|
request_id: req.id,
|
||||||
|
connection_id: conn_id,
|
||||||
|
is_server: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.expect("Failed to upsert message");
|
||||||
|
emit_side_effect(app_handle.clone(), "created_model", msg);
|
||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
error!("gRPC stream error: {:?}", e);
|
error!("gRPC stream error: {:?}", e);
|
||||||
@@ -328,7 +424,7 @@ async fn cmd_grpc_server_streaming(
|
|||||||
app_handle.unlisten(event_handler);
|
app_handle.unlisten(event_handler);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(conn_id)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -783,7 +879,7 @@ async fn cmd_duplicate_grpc_request(
|
|||||||
let request = duplicate_grpc_request(db, id)
|
let request = duplicate_grpc_request(db, id)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to duplicate grpc request");
|
.expect("Failed to duplicate grpc request");
|
||||||
emit_and_return(&window, "updated_model", request)
|
emit_and_return(&window, "created_model", request)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -823,7 +919,7 @@ async fn cmd_duplicate_http_request(
|
|||||||
let request = duplicate_http_request(db, id)
|
let request = duplicate_http_request(db, id)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to duplicate http request");
|
.expect("Failed to duplicate http request");
|
||||||
emit_and_return(&window, "updated_model", request)
|
emit_and_return(&window, "created_model", request)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -982,6 +1078,28 @@ async fn cmd_delete_environment(
|
|||||||
emit_and_return(&window, "deleted_model", req)
|
emit_and_return(&window, "deleted_model", req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_list_grpc_connections(
|
||||||
|
request_id: &str,
|
||||||
|
db_state: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
) -> Result<Vec<GrpcConnection>, String> {
|
||||||
|
let db = &*db_state.lock().await;
|
||||||
|
list_grpc_connections(db, request_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_list_grpc_messages(
|
||||||
|
connection_id: &str,
|
||||||
|
db_state: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
) -> Result<Vec<GrpcMessage>, String> {
|
||||||
|
let db = &*db_state.lock().await;
|
||||||
|
list_grpc_messages(db, connection_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_list_grpc_requests(
|
async fn cmd_list_grpc_requests(
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
@@ -990,8 +1108,7 @@ async fn cmd_list_grpc_requests(
|
|||||||
let db = &*db_state.lock().await;
|
let db = &*db_state.lock().await;
|
||||||
let requests = list_grpc_requests(db, workspace_id)
|
let requests = list_grpc_requests(db, workspace_id)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to find grpc requests");
|
.map_err(|e| e.to_string())?;
|
||||||
// .map_err(|e| e.to_string())
|
|
||||||
Ok(requests)
|
Ok(requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1123,7 +1240,7 @@ async fn cmd_get_workspace(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_list_responses(
|
async fn cmd_list_http_responses(
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
db_state: State<'_, Mutex<Pool<Sqlite>>>,
|
db_state: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
@@ -1303,6 +1420,7 @@ fn main() {
|
|||||||
cmd_delete_response,
|
cmd_delete_response,
|
||||||
cmd_delete_workspace,
|
cmd_delete_workspace,
|
||||||
cmd_duplicate_http_request,
|
cmd_duplicate_http_request,
|
||||||
|
cmd_duplicate_grpc_request,
|
||||||
cmd_export_data,
|
cmd_export_data,
|
||||||
cmd_filter_response,
|
cmd_filter_response,
|
||||||
cmd_get_cookie_jar,
|
cmd_get_cookie_jar,
|
||||||
@@ -1324,7 +1442,9 @@ fn main() {
|
|||||||
cmd_list_folders,
|
cmd_list_folders,
|
||||||
cmd_list_http_requests,
|
cmd_list_http_requests,
|
||||||
cmd_list_grpc_requests,
|
cmd_list_grpc_requests,
|
||||||
cmd_list_responses,
|
cmd_list_grpc_connections,
|
||||||
|
cmd_list_grpc_messages,
|
||||||
|
cmd_list_http_responses,
|
||||||
cmd_list_workspaces,
|
cmd_list_workspaces,
|
||||||
cmd_new_window,
|
cmd_new_window,
|
||||||
cmd_send_ephemeral_request,
|
cmd_send_ephemeral_request,
|
||||||
|
|||||||
@@ -228,6 +228,8 @@ pub struct GrpcMessage {
|
|||||||
pub connection_id: String,
|
pub connection_id: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
pub is_server: bool,
|
||||||
|
pub is_info: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
@@ -589,16 +591,17 @@ pub async fn get_grpc_connection(
|
|||||||
|
|
||||||
pub async fn list_grpc_connections(
|
pub async fn list_grpc_connections(
|
||||||
db: &Pool<Sqlite>,
|
db: &Pool<Sqlite>,
|
||||||
workspace_id: &str,
|
request_id: &str,
|
||||||
) -> Result<Vec<GrpcConnection>, sqlx::Error> {
|
) -> Result<Vec<GrpcConnection>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
GrpcConnection,
|
GrpcConnection,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, created_at, updated_at, service, method
|
SELECT id, model, workspace_id, request_id, created_at, updated_at, service, method
|
||||||
FROM grpc_connections
|
FROM grpc_connections
|
||||||
WHERE workspace_id = ?
|
WHERE request_id = ?
|
||||||
|
ORDER BY created_at DESC
|
||||||
"#,
|
"#,
|
||||||
workspace_id,
|
request_id,
|
||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
@@ -615,30 +618,36 @@ pub async fn upsert_grpc_message(
|
|||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO grpc_messages (
|
INSERT INTO grpc_messages (
|
||||||
id, workspace_id, request_id, connection_id, message
|
id, workspace_id, request_id, connection_id, message, is_server, is_info
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (id) DO UPDATE SET
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
updated_at = CURRENT_TIMESTAMP,
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
message = excluded.message
|
message = excluded.message,
|
||||||
|
is_server = excluded.is_server,
|
||||||
|
is_info = excluded.is_info
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
message.workspace_id,
|
message.workspace_id,
|
||||||
message.request_id,
|
message.request_id,
|
||||||
message.connection_id,
|
message.connection_id,
|
||||||
message.message,
|
message.message,
|
||||||
|
message.is_server,
|
||||||
|
message.is_info,
|
||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::models::get_grpc_message(db, &id).await
|
get_grpc_message(db, &id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_grpc_message(db: &Pool<Sqlite>, id: &str) -> Result<GrpcMessage, sqlx::Error> {
|
pub async fn get_grpc_message(db: &Pool<Sqlite>, id: &str) -> Result<GrpcMessage, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
GrpcMessage,
|
GrpcMessage,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, connection_id, created_at, message
|
SELECT
|
||||||
|
id, model, workspace_id, request_id, connection_id, created_at, message,
|
||||||
|
is_server, is_info
|
||||||
FROM grpc_messages
|
FROM grpc_messages
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
"#,
|
"#,
|
||||||
@@ -650,16 +659,18 @@ pub async fn get_grpc_message(db: &Pool<Sqlite>, id: &str) -> Result<GrpcMessage
|
|||||||
|
|
||||||
pub async fn list_grpc_messages(
|
pub async fn list_grpc_messages(
|
||||||
db: &Pool<Sqlite>,
|
db: &Pool<Sqlite>,
|
||||||
workspace_id: &str,
|
connection_id: &str,
|
||||||
) -> Result<Vec<GrpcMessage>, sqlx::Error> {
|
) -> Result<Vec<GrpcMessage>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
GrpcMessage,
|
GrpcMessage,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, connection_id, created_at, message
|
SELECT
|
||||||
|
id, model, workspace_id, request_id, connection_id, created_at, message,
|
||||||
|
is_server, is_info
|
||||||
FROM grpc_messages
|
FROM grpc_messages
|
||||||
WHERE workspace_id = ?
|
WHERE connection_id = ?
|
||||||
"#,
|
"#,
|
||||||
workspace_id,
|
connection_id,
|
||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -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 { httpRequestsQueryKey } from '../hooks/useHttpRequests';
|
|
||||||
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 { useSyncAppearance } from '../hooks/useSyncAppearance';
|
import { useSyncAppearance } from '../hooks/useSyncAppearance';
|
||||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
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';
|
||||||
|
|
||||||
@@ -49,7 +52,13 @@ export function GlobalHooks() {
|
|||||||
payload.model === 'http_request'
|
payload.model === 'http_request'
|
||||||
? httpRequestsQueryKey(payload)
|
? httpRequestsQueryKey(payload)
|
||||||
: payload.model === 'http_response'
|
: payload.model === 'http_response'
|
||||||
? responsesQueryKey(payload)
|
? httpResponsesQueryKey(payload)
|
||||||
|
: payload.model === 'grpc_connection'
|
||||||
|
? grpcConnectionsQueryKey(payload)
|
||||||
|
: payload.model === 'grpc_message'
|
||||||
|
? grpcMessagesQueryKey(payload)
|
||||||
|
: payload.model === 'grpc_request'
|
||||||
|
? grpcRequestsQueryKey(payload)
|
||||||
: payload.model === 'workspace'
|
: payload.model === 'workspace'
|
||||||
? workspacesQueryKey(payload)
|
? workspacesQueryKey(payload)
|
||||||
: payload.model === 'key_value'
|
: payload.model === 'key_value'
|
||||||
@@ -78,7 +87,13 @@ export function GlobalHooks() {
|
|||||||
payload.model === 'http_request'
|
payload.model === 'http_request'
|
||||||
? httpRequestsQueryKey(payload)
|
? httpRequestsQueryKey(payload)
|
||||||
: payload.model === 'http_response'
|
: payload.model === 'http_response'
|
||||||
? responsesQueryKey(payload)
|
? httpResponsesQueryKey(payload)
|
||||||
|
: payload.model === 'grpc_connection'
|
||||||
|
? grpcConnectionsQueryKey(payload)
|
||||||
|
: payload.model === 'grpc_message'
|
||||||
|
? grpcMessagesQueryKey(payload)
|
||||||
|
: payload.model === 'grpc_request'
|
||||||
|
? grpcRequestsQueryKey(payload)
|
||||||
: payload.model === 'workspace'
|
: payload.model === 'workspace'
|
||||||
? workspacesQueryKey(payload)
|
? workspacesQueryKey(payload)
|
||||||
: payload.model === 'key_value'
|
: payload.model === 'key_value'
|
||||||
@@ -113,11 +128,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[]>(httpRequestsQueryKey(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') {
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import type { CSSProperties, FormEvent } from 'react';
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useAlert } from '../hooks/useAlert';
|
import { useAlert } from '../hooks/useAlert';
|
||||||
import type { GrpcMessage } from '../hooks/useGrpc';
|
|
||||||
import { useGrpc } from '../hooks/useGrpc';
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
|
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||||
|
import { useGrpcMessages } from '../hooks/useGrpcMessages';
|
||||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { Editor } from './core/Editor';
|
|
||||||
import { HotKeyList } from './core/HotKeyList';
|
import { HotKeyList } from './core/HotKeyList';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
@@ -30,9 +30,11 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
const activeRequest = useActiveRequest('grpc_request');
|
const activeRequest = useActiveRequest('grpc_request');
|
||||||
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
|
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
|
||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
const [activeMessage, setActiveMessage] = useState<GrpcMessage | null>(null);
|
const [activeMessageId, setActiveMessageId] = useState<string | null>(null);
|
||||||
const [resp, setResp] = useState<string>('');
|
|
||||||
const grpc = useGrpc(activeRequest?.url ?? null, activeRequest?.id ?? null);
|
const grpc = useGrpc(activeRequest?.url ?? null, activeRequest?.id ?? null);
|
||||||
|
const connections = useGrpcConnections(activeRequest?.id ?? null);
|
||||||
|
const activeConnection = connections[0] ?? null;
|
||||||
|
const messages = useGrpcMessages(activeConnection?.id ?? null);
|
||||||
|
|
||||||
const activeMethod = useMemo(() => {
|
const activeMethod = useMemo(() => {
|
||||||
if (grpc.services == null || activeRequest == null) return null;
|
if (grpc.services == null || activeRequest == null) return null;
|
||||||
@@ -61,9 +63,9 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
if (activeMethod.clientStreaming && activeMethod.serverStreaming) {
|
if (activeMethod.clientStreaming && activeMethod.serverStreaming) {
|
||||||
await grpc.bidiStreaming.mutateAsync(activeRequest);
|
await grpc.bidiStreaming.mutateAsync(activeRequest);
|
||||||
} else if (activeMethod.serverStreaming && !activeMethod.clientStreaming) {
|
} else if (activeMethod.serverStreaming && !activeMethod.clientStreaming) {
|
||||||
await grpc.serverStreaming.mutateAsync(activeRequest);
|
await grpc.serverStreaming.mutateAsync(activeRequest.id);
|
||||||
} else {
|
} else {
|
||||||
setResp(await grpc.unary.mutateAsync(activeRequest));
|
await grpc.unary.mutateAsync(activeRequest.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeMethod, activeRequest, alert, grpc.bidiStreaming, grpc.serverStreaming, grpc.unary],
|
[activeMethod, activeRequest, alert, grpc.bidiStreaming, grpc.serverStreaming, grpc.unary],
|
||||||
@@ -127,8 +129,13 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
setPaneSize(entry.contentRect.width);
|
setPaneSize(entry.contentRect.width);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const activeMessage = useMemo(
|
||||||
|
() => messages.find((m) => m.id === activeMessageId) ?? null,
|
||||||
|
[activeMessageId, messages],
|
||||||
|
);
|
||||||
|
|
||||||
if (activeRequest == null) {
|
if (activeRequest == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -136,7 +143,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
name="grpc_layout"
|
name="grpc_layout"
|
||||||
className="p-3 gap-1.5"
|
className="p-3 gap-1.5"
|
||||||
style={style}
|
style={style}
|
||||||
leftSlot={() => (
|
firstSlot={() => (
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<div
|
<div
|
||||||
ref={urlContainerEl}
|
ref={urlContainerEl}
|
||||||
@@ -218,7 +225,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
)}
|
)}
|
||||||
rightSlot={() =>
|
secondSlot={() =>
|
||||||
!grpc.unary.isLoading && (
|
!grpc.unary.isLoading && (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -231,20 +238,20 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
<Banner color="danger" className="m-2">
|
<Banner color="danger" className="m-2">
|
||||||
{grpc.unary.error}
|
{grpc.unary.error}
|
||||||
</Banner>
|
</Banner>
|
||||||
) : (grpc.messages.value ?? []).length > 0 ? (
|
) : messages.length >= 0 ? (
|
||||||
<SplitLayout
|
<SplitLayout
|
||||||
name="grpc_messages2"
|
name="grpc_messages"
|
||||||
minHeightPx={20}
|
minHeightPx={20}
|
||||||
defaultRatio={0.25}
|
defaultRatio={0.25}
|
||||||
leftSlot={() => (
|
firstSlot={() => (
|
||||||
<div className="overflow-y-auto">
|
<div className="overflow-y-auto">
|
||||||
{...(grpc.messages.value ?? []).map((m, i) => (
|
{...messages.map((m) => (
|
||||||
<HStack
|
<HStack
|
||||||
key={`${m.timestamp}::${m.message}::${i}`}
|
key={m.id}
|
||||||
space={2}
|
space={2}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (m === activeMessage) setActiveMessage(null);
|
if (m.id === activeMessageId) setActiveMessageId(null);
|
||||||
else setActiveMessage(m);
|
else setActiveMessageId(m.id);
|
||||||
}}
|
}}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -254,29 +261,25 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
className={
|
className={
|
||||||
m.type === 'server'
|
m.isInfo
|
||||||
|
? 'text-gray-600'
|
||||||
|
: m.isServer
|
||||||
? 'text-blue-600'
|
? 'text-blue-600'
|
||||||
: m.type === 'client'
|
: 'text-green-600'
|
||||||
? 'text-green-600'
|
|
||||||
: 'text-gray-600'
|
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
m.type === 'server'
|
m.isInfo ? 'info' : m.isServer ? 'arrowBigDownDash' : 'arrowBigUpDash'
|
||||||
? 'arrowBigDownDash'
|
|
||||||
: m.type === 'client'
|
|
||||||
? 'arrowBigUpDash'
|
|
||||||
: 'info'
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="w-full truncate text-gray-800 text-2xs">{m.message}</div>
|
<div className="w-full truncate text-gray-800 text-2xs">{m.message}</div>
|
||||||
<div className="text-gray-600 text-2xs">
|
<div className="text-gray-600 text-2xs">
|
||||||
{format(m.timestamp, 'HH:mm:ss')}
|
{format(m.createdAt, 'HH:mm:ss')}
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
rightSlot={
|
secondSlot={
|
||||||
!activeMessage
|
!activeMessage
|
||||||
? null
|
? null
|
||||||
: () => (
|
: () => (
|
||||||
@@ -293,15 +296,15 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : resp ? (
|
|
||||||
<Editor
|
|
||||||
className="bg-gray-50 dark:bg-gray-100"
|
|
||||||
contentType="application/json"
|
|
||||||
defaultValue={resp}
|
|
||||||
readOnly
|
|
||||||
forceUpdateKey={resp}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
|
// ) : ? (
|
||||||
|
// <Editor
|
||||||
|
// readOnly
|
||||||
|
// className="bg-gray-50 dark:bg-gray-100"
|
||||||
|
// contentType="application/json"
|
||||||
|
// defaultValue={resp.message}
|
||||||
|
// forceUpdateKey={resp.id}
|
||||||
|
// />
|
||||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
|
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export function HttpRequestLayout({ style }: Props) {
|
|||||||
name="http_layout"
|
name="http_layout"
|
||||||
className="p-3 gap-1.5"
|
className="p-3 gap-1.5"
|
||||||
style={style}
|
style={style}
|
||||||
leftSlot={({ orientation, style }) => (
|
firstSlot={({ orientation, style }) => (
|
||||||
<RequestPane style={style} fullHeight={orientation === 'horizontal'} />
|
<RequestPane style={style} fullHeight={orientation === 'horizontal'} />
|
||||||
)}
|
)}
|
||||||
rightSlot={({ style }) => <ResponsePane style={style} />}
|
secondSlot={({ style }) => <ResponsePane style={style} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createGlobalState } from 'react-use';
|
|||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||||
import { useResponseContentType } from '../hooks/useResponseContentType';
|
import { useResponseContentType } from '../hooks/useResponseContentType';
|
||||||
import { useResponses } from '../hooks/useResponses';
|
import { useHttpResponses } from '../hooks/useHttpResponses';
|
||||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { isResponseLoading } from '../lib/models';
|
import { isResponseLoading } from '../lib/models';
|
||||||
@@ -39,7 +39,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const latestResponse = useLatestResponse(activeRequest?.id ?? null);
|
const latestResponse = useLatestResponse(activeRequest?.id ?? null);
|
||||||
const responses = useResponses(activeRequest?.id ?? null);
|
const responses = useHttpResponses(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;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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';
|
||||||
@@ -15,7 +14,8 @@ import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
|||||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
||||||
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 { useGrpcRequests } from '../hooks/useGrpcRequests';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
@@ -29,6 +29,7 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
|||||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||||
|
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||||
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
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';
|
||||||
@@ -58,14 +59,21 @@ 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 httpRequests = useHttpRequests();
|
const httpRequests = useHttpRequests();
|
||||||
const grpcRequests = useGrpcRequests();
|
const grpcRequests = useGrpcRequests();
|
||||||
const folders = useFolders();
|
const folders = useFolders();
|
||||||
const deleteAnyRequest = useDeleteAnyRequest();
|
const deleteAnyRequest = useDeleteAnyRequest();
|
||||||
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);
|
||||||
@@ -82,8 +90,12 @@ export function Sidebar({ className }: Props) {
|
|||||||
namespace: NAMESPACE_NO_SYNC,
|
namespace: NAMESPACE_NO_SYNC,
|
||||||
});
|
});
|
||||||
|
|
||||||
useHotKey('http_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(
|
||||||
@@ -146,9 +158,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;
|
||||||
}
|
}
|
||||||
@@ -160,7 +173,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
sidebarRef.current?.focus();
|
sidebarRef.current?.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeRequestId, treeParentMap],
|
[activeRequest, treeParentMap],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
@@ -230,7 +243,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,11 +554,13 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
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 sendManyRequests = useSendManyRequests();
|
const sendManyRequests = useSendManyRequests();
|
||||||
const latestResponse = useLatestResponse(itemId);
|
const latestResponse = useLatestResponse(itemId);
|
||||||
const updateRequest = useUpdateHttpRequest(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);
|
||||||
@@ -553,10 +568,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) => {
|
||||||
@@ -677,7 +697,9 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ 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 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);
|
||||||
@@ -47,7 +46,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);
|
||||||
}
|
}
|
||||||
@@ -72,10 +71,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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ interface SlotProps {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
leftSlot: (props: SlotProps) => ReactNode;
|
firstSlot: (props: SlotProps) => ReactNode;
|
||||||
rightSlot: null | ((props: SlotProps) => ReactNode);
|
secondSlot: null | ((props: SlotProps) => ReactNode);
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
defaultRatio?: number;
|
defaultRatio?: number;
|
||||||
@@ -33,8 +33,8 @@ const STACK_VERTICAL_WIDTH = 700;
|
|||||||
|
|
||||||
export function SplitLayout({
|
export function SplitLayout({
|
||||||
style,
|
style,
|
||||||
leftSlot,
|
firstSlot,
|
||||||
rightSlot,
|
secondSlot,
|
||||||
className,
|
className,
|
||||||
name,
|
name,
|
||||||
defaultRatio = 0.5,
|
defaultRatio = 0.5,
|
||||||
@@ -54,7 +54,7 @@ export function SplitLayout({
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!rightSlot) {
|
if (!secondSlot) {
|
||||||
height = 0;
|
height = 0;
|
||||||
minHeightPx = 0;
|
minHeightPx = 0;
|
||||||
}
|
}
|
||||||
@@ -145,8 +145,8 @@ export function SplitLayout({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className={classNames(className, 'grid w-full h-full')} style={styles}>
|
<div ref={containerRef} className={classNames(className, 'grid w-full h-full')} style={styles}>
|
||||||
{leftSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||||
{rightSlot && (
|
{secondSlot && (
|
||||||
<>
|
<>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
style={areaD}
|
style={areaD}
|
||||||
@@ -158,7 +158,7 @@ export function SplitLayout({
|
|||||||
side={vertical ? 'top' : 'left'}
|
side={vertical ? 'top' : 'left'}
|
||||||
justify="center"
|
justify="center"
|
||||||
/>
|
/>
|
||||||
{rightSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { HttpRequest } from '../lib/models';
|
|||||||
import { getHttpRequest } from '../lib/store';
|
import { getHttpRequest } from '../lib/store';
|
||||||
import { useConfirm } from './useConfirm';
|
import { useConfirm } from './useConfirm';
|
||||||
import { httpRequestsQueryKey } from './useHttpRequests';
|
import { httpRequestsQueryKey } from './useHttpRequests';
|
||||||
import { responsesQueryKey } from './useResponses';
|
import { httpResponsesQueryKey } from './useHttpResponses';
|
||||||
|
|
||||||
export function useDeleteAnyRequest() {
|
export function useDeleteAnyRequest() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -35,7 +35,7 @@ export function useDeleteAnyRequest() {
|
|||||||
if (request === null) return;
|
if (request === null) return;
|
||||||
|
|
||||||
const { workspaceId, id: requestId } = request;
|
const { workspaceId, id: requestId } = request;
|
||||||
queryClient.setQueryData(responsesQueryKey({ requestId }), []); // Responses were deleted
|
queryClient.setQueryData(httpResponsesQueryKey({ requestId }), []); // Responses were deleted
|
||||||
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey({ workspaceId }), (requests) =>
|
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey({ workspaceId }), (requests) =>
|
||||||
(requests ?? []).filter((r) => r.id !== requestId),
|
(requests ?? []).filter((r) => r.id !== requestId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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 { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { responsesQueryKey } from './useResponses';
|
import { httpResponsesQueryKey } from './useHttpResponses';
|
||||||
|
|
||||||
export function useDeleteResponse(id: string | null) {
|
export function useDeleteResponse(id: string | null) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -12,7 +12,7 @@ export function useDeleteResponse(id: string | null) {
|
|||||||
},
|
},
|
||||||
onSettled: () => trackEvent('HttpResponse', 'Delete'),
|
onSettled: () => trackEvent('HttpResponse', 'Delete'),
|
||||||
onSuccess: ({ requestId, id: responseId }) => {
|
onSuccess: ({ requestId, id: responseId }) => {
|
||||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey({ requestId }), (responses) =>
|
queryClient.setQueryData<HttpResponse[]>(httpResponsesQueryKey({ requestId }), (responses) =>
|
||||||
(responses ?? []).filter((response) => response.id !== responseId),
|
(responses ?? []).filter((response) => response.id !== responseId),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 { responsesQueryKey } from './useResponses';
|
import { httpResponsesQueryKey } from './useHttpResponses';
|
||||||
|
|
||||||
export function useDeleteResponses(requestId?: string) {
|
export function useDeleteResponses(requestId?: string) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -13,7 +13,7 @@ export function useDeleteResponses(requestId?: string) {
|
|||||||
onSettled: () => trackEvent('HttpResponse', 'DeleteMany'),
|
onSettled: () => trackEvent('HttpResponse', 'DeleteMany'),
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
if (requestId === undefined) return;
|
if (requestId === undefined) return;
|
||||||
queryClient.setQueryData(responsesQueryKey({ requestId }), []);
|
queryClient.setQueryData(httpResponsesQueryKey({ requestId }), []);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
41
src-web/hooks/useDuplicateGrpcRequest.ts
Normal file
41
src-web/hooks/useDuplicateGrpcRequest.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
|
import type { GrpcRequest } from '../lib/models';
|
||||||
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
|
import { grpcRequestsQueryKey } from './useGrpcRequests';
|
||||||
|
|
||||||
|
export function useDuplicateGrpcRequest({
|
||||||
|
id,
|
||||||
|
navigateAfter,
|
||||||
|
}: {
|
||||||
|
id: string | null;
|
||||||
|
navigateAfter: boolean;
|
||||||
|
}) {
|
||||||
|
const activeWorkspaceId = useActiveWorkspaceId();
|
||||||
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
|
const routes = useAppRoutes();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<GrpcRequest, string>({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (id === null) throw new Error("Can't duplicate a null grpc request");
|
||||||
|
return invoke('cmd_duplicate_grpc_request', { id });
|
||||||
|
},
|
||||||
|
onSettled: () => trackEvent('GrpcRequest', 'Duplicate'),
|
||||||
|
onSuccess: async (request) => {
|
||||||
|
queryClient.setQueryData<GrpcRequest[]>(
|
||||||
|
grpcRequestsQueryKey({ workspaceId: request.workspaceId }),
|
||||||
|
(requests) => [...(requests ?? []), request],
|
||||||
|
);
|
||||||
|
if (navigateAfter && activeWorkspaceId !== null) {
|
||||||
|
routes.navigate('request', {
|
||||||
|
workspaceId: activeWorkspaceId,
|
||||||
|
requestId: request.id,
|
||||||
|
environmentId: activeEnvironmentId ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
|||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
import { httpRequestsQueryKey } from './useHttpRequests';
|
import { httpRequestsQueryKey } from './useHttpRequests';
|
||||||
|
|
||||||
export function useDuplicateRequest({
|
export function useDuplicateHttpRequest({
|
||||||
id,
|
id,
|
||||||
navigateAfter,
|
navigateAfter,
|
||||||
}: {
|
}: {
|
||||||
@@ -3,8 +3,7 @@ import { invoke } from '@tauri-apps/api';
|
|||||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||||
import { emit, listen } from '@tauri-apps/api/event';
|
import { emit, listen } from '@tauri-apps/api/event';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import type { GrpcConnection, GrpcMessage, GrpcRequest } from '../lib/models';
|
||||||
import type { GrpcRequest } from '../lib/models';
|
|
||||||
import { useKeyValue } from './useKeyValue';
|
import { useKeyValue } from './useKeyValue';
|
||||||
|
|
||||||
interface ReflectResponseService {
|
interface ReflectResponseService {
|
||||||
@@ -12,12 +11,6 @@ interface ReflectResponseService {
|
|||||||
methods: { name: string; schema: string; serverStreaming: boolean; clientStreaming: boolean }[];
|
methods: { name: string; schema: string; serverStreaming: boolean; clientStreaming: boolean }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrpcMessage {
|
|
||||||
message: string;
|
|
||||||
timestamp: string;
|
|
||||||
type: 'server' | 'client' | 'info';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGrpc(url: string | null, requestId: string | null) {
|
export function useGrpc(url: string | null, requestId: string | null) {
|
||||||
const messages = useKeyValue<GrpcMessage[]>({
|
const messages = useKeyValue<GrpcMessage[]>({
|
||||||
namespace: 'debug',
|
namespace: 'debug',
|
||||||
@@ -25,54 +18,30 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
|||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
});
|
});
|
||||||
const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null);
|
const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null);
|
||||||
const unlisten = useRef<UnlistenFn | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveConnectionId(null);
|
setActiveConnectionId(null);
|
||||||
unlisten.current?.();
|
|
||||||
}, [requestId]);
|
}, [requestId]);
|
||||||
|
|
||||||
const unary = useMutation<string, string, GrpcRequest>({
|
const unary = useMutation<GrpcMessage, string, string>({
|
||||||
mutationKey: ['grpc_unary', url],
|
mutationKey: ['grpc_unary', url],
|
||||||
mutationFn: async ({ service, method, message, url }) => {
|
mutationFn: async (id) => {
|
||||||
if (url === null) throw new Error('No URL provided');
|
const message = (await invoke('cmd_grpc_call_unary', {
|
||||||
return (await invoke('cmd_grpc_call_unary', {
|
requestId: id,
|
||||||
endpoint: url,
|
})) as GrpcMessage;
|
||||||
service,
|
await messages.set([message]);
|
||||||
method,
|
console.log('MESSAGE', message);
|
||||||
message,
|
return message;
|
||||||
})) as string;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverStreaming = useMutation<void, string, GrpcRequest>({
|
const serverStreaming = useMutation<void, string, string>({
|
||||||
mutationKey: ['grpc_server_streaming', url],
|
mutationKey: ['grpc_server_streaming', url],
|
||||||
mutationFn: async ({ service, method, message, url }) => {
|
mutationFn: async (requestId) => {
|
||||||
if (url === null) throw new Error('No URL provided');
|
if (url === null) throw new Error('No URL provided');
|
||||||
await messages.set([
|
await messages.set([]);
|
||||||
{
|
const c = (await invoke('cmd_grpc_server_streaming', { requestId })) as GrpcConnection;
|
||||||
type: 'client',
|
setActiveConnectionId(c.id);
|
||||||
message: JSON.stringify(JSON.parse(message)),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const id: string = await invoke('cmd_grpc_server_streaming', {
|
|
||||||
endpoint: url,
|
|
||||||
service,
|
|
||||||
method,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
unlisten.current = await listen(`grpc_server_msg_${id}`, async (event) => {
|
|
||||||
await messages.set((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
message: tryFormatJson(event.payload as string, false),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
type: 'server',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
setActiveConnectionId(id);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,20 +55,8 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
|||||||
method,
|
method,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
await messages.set([
|
await messages.set([]);
|
||||||
{ type: 'info', message: `Started connection ${id}`, timestamp: new Date().toISOString() },
|
|
||||||
]);
|
|
||||||
setActiveConnectionId(id);
|
setActiveConnectionId(id);
|
||||||
unlisten.current = await listen(`grpc_server_msg_${id}`, (event) => {
|
|
||||||
messages.set((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
message: tryFormatJson(event.payload as string, false),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
type: 'server',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,9 +64,10 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
|||||||
mutationKey: ['grpc_send', url],
|
mutationKey: ['grpc_send', url],
|
||||||
mutationFn: async ({ message }: { message: string }) => {
|
mutationFn: async ({ message }: { message: string }) => {
|
||||||
if (activeConnectionId == null) throw new Error('No active connection');
|
if (activeConnectionId == null) throw new Error('No active connection');
|
||||||
await messages.set((m) => {
|
await messages.set([]);
|
||||||
return [...m, { type: 'client', message, timestamp: new Date().toISOString() }];
|
// await messages.set((m) => {
|
||||||
});
|
// return [...m, { type: 'client', message, timestamp: new Date().toISOString() }];
|
||||||
|
// });
|
||||||
await emit(`grpc_client_msg_${activeConnectionId}`, { Message: message });
|
await emit(`grpc_client_msg_${activeConnectionId}`, { Message: message });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -118,12 +76,7 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
|||||||
mutationKey: ['grpc_cancel', url],
|
mutationKey: ['grpc_cancel', url],
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
setActiveConnectionId(null);
|
setActiveConnectionId(null);
|
||||||
unlisten.current?.();
|
await emit(`grpc_client_msg_${activeConnectionId}`, 'Cancel');
|
||||||
await emit('grpc_message_in', 'Cancel');
|
|
||||||
await messages.set((m) => [
|
|
||||||
...m,
|
|
||||||
{ type: 'info', message: 'Cancelled by client', timestamp: new Date().toISOString() },
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,7 +94,6 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
|||||||
bidiStreaming,
|
bidiStreaming,
|
||||||
services: reflect.data,
|
services: reflect.data,
|
||||||
cancel,
|
cancel,
|
||||||
messages,
|
|
||||||
isStreaming: activeConnectionId !== null,
|
isStreaming: activeConnectionId !== null,
|
||||||
send,
|
send,
|
||||||
};
|
};
|
||||||
|
|||||||
23
src-web/hooks/useGrpcConnections.ts
Normal file
23
src-web/hooks/useGrpcConnections.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { GrpcConnection } from '../lib/models';
|
||||||
|
|
||||||
|
export function grpcConnectionsQueryKey({ requestId }: { requestId: string }) {
|
||||||
|
return ['grpc_connections', { requestId }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGrpcConnections(requestId: string | null) {
|
||||||
|
return (
|
||||||
|
useQuery<GrpcConnection[]>({
|
||||||
|
enabled: requestId !== null,
|
||||||
|
initialData: [],
|
||||||
|
queryKey: grpcConnectionsQueryKey({ requestId: requestId ?? 'n/a' }),
|
||||||
|
queryFn: async () => {
|
||||||
|
return (await invoke('cmd_list_grpc_connections', {
|
||||||
|
requestId,
|
||||||
|
limit: 200,
|
||||||
|
})) as GrpcConnection[];
|
||||||
|
},
|
||||||
|
}).data ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src-web/hooks/useGrpcMessages.ts
Normal file
23
src-web/hooks/useGrpcMessages.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { GrpcMessage } from '../lib/models';
|
||||||
|
|
||||||
|
export function grpcMessagesQueryKey({ connectionId }: { connectionId: string }) {
|
||||||
|
return ['grpc_messages', { connectionId }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGrpcMessages(connectionId: string | null) {
|
||||||
|
return (
|
||||||
|
useQuery<GrpcMessage[]>({
|
||||||
|
enabled: connectionId !== null,
|
||||||
|
initialData: [],
|
||||||
|
queryKey: grpcMessagesQueryKey({ connectionId: connectionId ?? 'n/a' }),
|
||||||
|
queryFn: async () => {
|
||||||
|
return (await invoke('cmd_list_grpc_messages', {
|
||||||
|
connectionId,
|
||||||
|
limit: 200,
|
||||||
|
})) as GrpcMessage[];
|
||||||
|
},
|
||||||
|
}).data ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,18 +2,18 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
|
|
||||||
export function responsesQueryKey({ requestId }: { requestId: string }) {
|
export function httpResponsesQueryKey({ requestId }: { requestId: string }) {
|
||||||
return ['http_responses', { requestId }];
|
return ['http_responses', { requestId }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useResponses(requestId: string | null) {
|
export function useHttpResponses(requestId: string | null) {
|
||||||
return (
|
return (
|
||||||
useQuery<HttpResponse[]>({
|
useQuery<HttpResponse[]>({
|
||||||
enabled: requestId !== null,
|
enabled: requestId !== null,
|
||||||
initialData: [],
|
initialData: [],
|
||||||
queryKey: responsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
queryKey: httpResponsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return (await invoke('cmd_list_responses', { requestId, limit: 200 })) as HttpResponse[];
|
return (await invoke('cmd_list_http_responses', { requestId, limit: 200 })) as HttpResponse[];
|
||||||
},
|
},
|
||||||
}).data ?? []
|
}).data ?? []
|
||||||
);
|
);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { useResponses } from './useResponses';
|
import { useHttpResponses } from './useHttpResponses';
|
||||||
|
|
||||||
export function useLatestResponse(requestId: string | null): HttpResponse | null {
|
export function useLatestResponse(requestId: string | null): HttpResponse | null {
|
||||||
const responses = useResponses(requestId);
|
const responses = useHttpResponses(requestId);
|
||||||
return responses[0] ?? null;
|
return responses[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export const AUTH_TYPE_BEARER = 'bearer';
|
|||||||
export type Model =
|
export type Model =
|
||||||
| Settings
|
| Settings
|
||||||
| Workspace
|
| Workspace
|
||||||
|
| GrpcConnection
|
||||||
| GrpcRequest
|
| GrpcRequest
|
||||||
|
| GrpcMessage
|
||||||
| HttpRequest
|
| HttpRequest
|
||||||
| HttpResponse
|
| HttpResponse
|
||||||
| KeyValue
|
| KeyValue
|
||||||
@@ -114,6 +116,24 @@ export interface GrpcRequest extends BaseModel {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GrpcMessage extends BaseModel {
|
||||||
|
readonly workspaceId: string;
|
||||||
|
readonly requestId: string;
|
||||||
|
readonly connectionId: string;
|
||||||
|
readonly model: 'grpc_message';
|
||||||
|
message: string;
|
||||||
|
isServer: boolean;
|
||||||
|
isInfo: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrpcConnection extends BaseModel {
|
||||||
|
readonly workspaceId: string;
|
||||||
|
readonly requestId: string;
|
||||||
|
readonly model: 'grpc_connection';
|
||||||
|
service: string;
|
||||||
|
method: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HttpRequest extends BaseModel {
|
export interface HttpRequest extends BaseModel {
|
||||||
readonly workspaceId: string;
|
readonly workspaceId: string;
|
||||||
readonly model: 'http_request';
|
readonly model: 'http_request';
|
||||||
|
|||||||
Reference in New Issue
Block a user