mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-19 07:26:59 +01:00
gRPC authentication
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 ",
|
||||
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message, authentication_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -64,9 +64,19 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
|
||||
"name": "authentication_type",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -85,8 +95,10 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7398403d3de2dc5c5b4b6392f083041d9a55194bb97819225a2612fdeb60ad42"
|
||||
"hash": "0d9e685f878fc2a0e1803c6aaae3828deebd684fc9f78e9f8595a550f90749fe"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 ",
|
||||
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message, authentication_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE workspace_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -64,9 +64,19 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
|
||||
"name": "authentication_type",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -85,8 +95,10 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "761d27c3ec425c37ad9abe9c732a9c1746c81ca50d2c413e540b74c8c8e908b7"
|
||||
"hash": "545d21ff21bd02468be86746325dc9f1b50f3fe53d7735194d91927b5d14a436"
|
||||
}
|
||||
12
src-tauri/.sqlx/query-c554305252cb21e34aa1e3c1f204c6060c3d2a209689a879824dea4d26e5497e.json
generated
Normal file
12
src-tauri/.sqlx/query-c554305252cb21e34aa1e3c1f204c6060c3d2a209689a879824dea4d26e5497e.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, authentication_type, authentication\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 authentication_type = excluded.authentication_type,\n authentication = excluded.authentication\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c554305252cb21e34aa1e3c1f204c6060c3d2a209689a879824dea4d26e5497e"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"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,5 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::Client;
|
||||
@@ -10,8 +11,9 @@ use serde_json::Deserializer;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_stream::StreamExt;
|
||||
use tonic::body::BoxBody;
|
||||
use tonic::metadata::{MetadataKey, MetadataValue};
|
||||
use tonic::transport::Uri;
|
||||
use tonic::{IntoRequest, IntoStreamingRequest, Response, Status, Streaming};
|
||||
use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming};
|
||||
|
||||
use crate::codec::DynamicCodec;
|
||||
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
|
||||
@@ -47,6 +49,7 @@ impl GrpcConnection {
|
||||
service: &str,
|
||||
method: &str,
|
||||
message: &str,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<DynamicMessage, String> {
|
||||
let method = &self.method(&service, &method)?;
|
||||
let input_message = method.input();
|
||||
@@ -58,7 +61,9 @@ impl GrpcConnection {
|
||||
|
||||
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||
|
||||
let req = req_message.into_request();
|
||||
let mut req = req_message.into_request();
|
||||
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
|
||||
|
||||
let path = method_desc_to_path(method);
|
||||
let codec = DynamicCodec::new(method.clone());
|
||||
client.ready().await.unwrap();
|
||||
@@ -75,12 +80,13 @@ impl GrpcConnection {
|
||||
service: &str,
|
||||
method: &str,
|
||||
stream: ReceiverStream<String>,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
|
||||
let method = &self.method(&service, &method)?;
|
||||
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||
|
||||
let method2 = method.clone();
|
||||
let req = stream
|
||||
let mut req = stream
|
||||
.map(move |s| {
|
||||
let mut deserializer = Deserializer::from_str(&s);
|
||||
let req_message = DynamicMessage::deserialize(method2.input(), &mut deserializer)
|
||||
@@ -90,6 +96,9 @@ impl GrpcConnection {
|
||||
req_message
|
||||
})
|
||||
.into_streaming_request();
|
||||
|
||||
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
|
||||
|
||||
let path = method_desc_to_path(method);
|
||||
let codec = DynamicCodec::new(method.clone());
|
||||
client.ready().await.map_err(|e| e.to_string())?;
|
||||
@@ -101,11 +110,12 @@ impl GrpcConnection {
|
||||
service: &str,
|
||||
method: &str,
|
||||
stream: ReceiverStream<String>,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<DynamicMessage, String> {
|
||||
let method = &self.method(&service, &method)?;
|
||||
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||
|
||||
let req = {
|
||||
let mut req = {
|
||||
let method = method.clone();
|
||||
stream
|
||||
.map(move |s| {
|
||||
@@ -119,6 +129,9 @@ impl GrpcConnection {
|
||||
})
|
||||
.into_streaming_request()
|
||||
};
|
||||
|
||||
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
|
||||
|
||||
let path = method_desc_to_path(method);
|
||||
let codec = DynamicCodec::new(method.clone());
|
||||
client.ready().await.unwrap();
|
||||
@@ -134,6 +147,7 @@ impl GrpcConnection {
|
||||
service: &str,
|
||||
method: &str,
|
||||
message: &str,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
|
||||
let method = &self.method(&service, &method)?;
|
||||
let input_message = method.input();
|
||||
@@ -145,7 +159,9 @@ impl GrpcConnection {
|
||||
|
||||
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
|
||||
|
||||
let req = req_message.into_request();
|
||||
let mut req = req_message.into_request();
|
||||
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
|
||||
|
||||
let path = method_desc_to_path(method);
|
||||
let codec = DynamicCodec::new(method.clone());
|
||||
client.ready().await.map_err(|e| e.to_string())?;
|
||||
@@ -222,10 +238,11 @@ impl GrpcHandle {
|
||||
service: &str,
|
||||
method: &str,
|
||||
message: &str,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
|
||||
self.connect(id, uri, proto_files)
|
||||
.await?
|
||||
.server_streaming(service, method, message)
|
||||
.server_streaming(service, method, message, metadata)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -237,10 +254,11 @@ impl GrpcHandle {
|
||||
service: &str,
|
||||
method: &str,
|
||||
stream: ReceiverStream<String>,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<DynamicMessage, String> {
|
||||
self.connect(id, uri, proto_files)
|
||||
.await?
|
||||
.client_streaming(service, method, stream)
|
||||
.client_streaming(service, method, stream, metadata)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -252,10 +270,11 @@ impl GrpcHandle {
|
||||
service: &str,
|
||||
method: &str,
|
||||
stream: ReceiverStream<String>,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
|
||||
self.connect(id, uri, proto_files)
|
||||
.await?
|
||||
.streaming(service, method, stream)
|
||||
.streaming(service, method, stream, metadata)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -282,3 +301,13 @@ impl GrpcHandle {
|
||||
Ok(connection)
|
||||
}
|
||||
}
|
||||
|
||||
fn decorate_req<T>(metadata: HashMap<String, String>, req: &mut Request<T>) -> Result<(), String> {
|
||||
for (k, v) in metadata {
|
||||
req.metadata_mut().insert(
|
||||
MetadataKey::from_str(k.as_str()).map_err(|e| e.to_string())?,
|
||||
MetadataValue::from_str(v.as_str()).map_err(|e| e.to_string())?,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE grpc_requests ADD COLUMN authentication TEXT NOT NULL DEFAULT '{}';
|
||||
ALTER TABLE grpc_requests ADD COLUMN authentication_type TEXT;
|
||||
@@ -17,6 +17,7 @@ use std::str::FromStr;
|
||||
|
||||
use ::http::uri::InvalidUri;
|
||||
use ::http::Uri;
|
||||
use base64::Engine;
|
||||
use fern::colors::ColoredLevelConfig;
|
||||
use log::{debug, error, info, warn};
|
||||
use rand::random;
|
||||
@@ -128,12 +129,20 @@ async fn cmd_grpc_reflect(
|
||||
#[tauri::command]
|
||||
async fn cmd_grpc_go(
|
||||
request_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
w: Window,
|
||||
grpc_handle: State<'_, Mutex<GrpcHandle>>,
|
||||
) -> Result<String, String> {
|
||||
let req = get_grpc_request(&w, request_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let environment = match environment_id {
|
||||
Some(id) => Some(get_environment(&w, id).await.map_err(|e| e.to_string())?),
|
||||
None => None,
|
||||
};
|
||||
let workspace = get_workspace(&w, &req.workspace_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let conn = {
|
||||
let req = req.clone();
|
||||
upsert_grpc_connection(
|
||||
@@ -261,6 +270,37 @@ async fn cmd_grpc_go(
|
||||
}
|
||||
};
|
||||
let event_handler = w.listen_global(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
|
||||
let mut metadata = HashMap::new();
|
||||
if let Some(b) = &req.authentication_type {
|
||||
let req = req.clone();
|
||||
let environment_ref = environment.as_ref();
|
||||
let empty_value = &serde_json::to_value("").unwrap();
|
||||
let a = req.authentication.0;
|
||||
|
||||
if b == "basic" {
|
||||
let raw_username = a
|
||||
.get("username")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let raw_password = a
|
||||
.get("password")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let username = render::render(raw_username, &workspace, environment_ref);
|
||||
let password = render::render(raw_password, &workspace, environment_ref);
|
||||
|
||||
let auth = format!("{username}:{password}");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
|
||||
} else if b == "bearer" {
|
||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
||||
let token = render::render(raw_token, &workspace, environment_ref);
|
||||
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
|
||||
}
|
||||
}
|
||||
println!("METADATA: {:?}", metadata);
|
||||
|
||||
let grpc_listen = {
|
||||
let w = w.clone();
|
||||
@@ -272,28 +312,36 @@ async fn cmd_grpc_go(
|
||||
method_desc.is_server_streaming(),
|
||||
) {
|
||||
(true, true) => (
|
||||
Some(connection.streaming(&service, &method, in_msg_stream).await),
|
||||
Some(
|
||||
connection
|
||||
.streaming(&service, &method, in_msg_stream, metadata)
|
||||
.await,
|
||||
),
|
||||
None,
|
||||
),
|
||||
(true, false) => (
|
||||
None,
|
||||
Some(
|
||||
connection
|
||||
.client_streaming(&service, &method, in_msg_stream)
|
||||
.client_streaming(&service, &method, in_msg_stream, metadata)
|
||||
.await,
|
||||
),
|
||||
),
|
||||
(false, true) => (
|
||||
Some(
|
||||
connection
|
||||
.server_streaming(&service, &method, &req.message)
|
||||
.server_streaming(&service, &method, &req.message, metadata)
|
||||
.await,
|
||||
),
|
||||
None,
|
||||
),
|
||||
(false, false) => (
|
||||
None,
|
||||
Some(connection.unary(&service, &method, &req.message).await),
|
||||
Some(
|
||||
connection
|
||||
.unary(&service, &method, &req.message, metadata)
|
||||
.await,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -205,6 +205,8 @@ pub struct GrpcRequest {
|
||||
pub method: Option<String>,
|
||||
pub message: String,
|
||||
pub proto_files: Json<Vec<String>>,
|
||||
pub authentication_type: Option<String>,
|
||||
pub authentication: Json<HashMap<String, JsonValue>>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@@ -502,9 +504,9 @@ pub async fn upsert_grpc_request(
|
||||
r#"
|
||||
INSERT INTO grpc_requests (
|
||||
id, name, workspace_id, folder_id, sort_priority, url, service, method, message,
|
||||
proto_files
|
||||
proto_files, authentication_type, authentication
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
name = excluded.name,
|
||||
@@ -514,7 +516,9 @@ pub async fn upsert_grpc_request(
|
||||
service = excluded.service,
|
||||
method = excluded.method,
|
||||
message = excluded.message,
|
||||
proto_files = excluded.proto_files
|
||||
proto_files = excluded.proto_files,
|
||||
authentication_type = excluded.authentication_type,
|
||||
authentication = excluded.authentication
|
||||
"#,
|
||||
id,
|
||||
trimmed_name,
|
||||
@@ -526,6 +530,8 @@ pub async fn upsert_grpc_request(
|
||||
request.method,
|
||||
request.message,
|
||||
request.proto_files,
|
||||
request.authentication_type,
|
||||
request.authentication,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -546,7 +552,8 @@ pub async fn get_grpc_request(
|
||||
r#"
|
||||
SELECT
|
||||
id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,
|
||||
url, service, method, message,
|
||||
url, service, method, message, authentication_type,
|
||||
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
|
||||
proto_files AS "proto_files!: sqlx::types::Json<Vec<String>>"
|
||||
FROM grpc_requests
|
||||
WHERE id = ?
|
||||
@@ -567,7 +574,8 @@ pub async fn list_grpc_requests(
|
||||
r#"
|
||||
SELECT
|
||||
id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,
|
||||
url, service, method, message,
|
||||
url, service, method, message, authentication_type,
|
||||
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
|
||||
proto_files AS "proto_files!: sqlx::types::Json<Vec<String>>"
|
||||
FROM grpc_requests
|
||||
WHERE workspace_id = ?
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import type { GrpcRequest, HttpRequest } from '../lib/models';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
|
||||
interface Props {
|
||||
requestId: string;
|
||||
authentication: HttpRequest['authentication'];
|
||||
interface Props<T> {
|
||||
request: T;
|
||||
}
|
||||
|
||||
export function BasicAuth({ requestId, authentication }: Props) {
|
||||
const updateRequest = useUpdateHttpRequest(requestId);
|
||||
export function BasicAuth<T extends HttpRequest | GrpcRequest>({ request }: Props<T>) {
|
||||
const updateHttpRequest = useUpdateHttpRequest(request.id);
|
||||
const updateGrpcRequest = useUpdateGrpcRequest(request.id);
|
||||
|
||||
return (
|
||||
<VStack className="my-2" space={2}>
|
||||
<Input
|
||||
useTemplating
|
||||
autocompleteVariables
|
||||
forceUpdateKey={requestId}
|
||||
forceUpdateKey={request.id}
|
||||
placeholder="username"
|
||||
label="Username"
|
||||
name="username"
|
||||
size="sm"
|
||||
defaultValue={`${authentication.username}`}
|
||||
defaultValue={`${request.authentication.username}`}
|
||||
onChange={(username: string) => {
|
||||
updateRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { password: r.authentication.password, username },
|
||||
}));
|
||||
if (request.model === 'http_request') {
|
||||
updateHttpRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { password: r.authentication.password, username },
|
||||
}));
|
||||
} else {
|
||||
updateGrpcRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { password: r.authentication.password, username },
|
||||
}));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
useTemplating
|
||||
autocompleteVariables
|
||||
forceUpdateKey={requestId}
|
||||
forceUpdateKey={request?.id}
|
||||
placeholder="password"
|
||||
label="Password"
|
||||
name="password"
|
||||
size="sm"
|
||||
type="password"
|
||||
defaultValue={`${authentication.password}`}
|
||||
defaultValue={`${request.authentication.password}`}
|
||||
onChange={(password: string) => {
|
||||
updateRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { username: r.authentication.username, password },
|
||||
}));
|
||||
if (request.model === 'http_request') {
|
||||
updateHttpRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { username: r.authentication.username, password },
|
||||
}));
|
||||
} else {
|
||||
updateGrpcRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { username: r.authentication.username, password },
|
||||
}));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import type { GrpcRequest, HttpRequest } from '../lib/models';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
|
||||
interface Props {
|
||||
requestId: string;
|
||||
authentication: HttpRequest['authentication'];
|
||||
interface Props<T> {
|
||||
request: T;
|
||||
}
|
||||
|
||||
export function BearerAuth({ requestId, authentication }: Props) {
|
||||
const updateRequest = useUpdateHttpRequest(requestId);
|
||||
export function BearerAuth<T extends HttpRequest | GrpcRequest>({ request }: Props<T>) {
|
||||
const updateHttpRequest = useUpdateHttpRequest(request?.id ?? null);
|
||||
const updateGrpcRequest = useUpdateGrpcRequest(request?.id ?? null);
|
||||
|
||||
return (
|
||||
<VStack className="my-2" space={2}>
|
||||
@@ -21,12 +22,19 @@ export function BearerAuth({ requestId, authentication }: Props) {
|
||||
label="Token"
|
||||
name="token"
|
||||
size="sm"
|
||||
defaultValue={`${authentication.token}`}
|
||||
defaultValue={`${request.authentication.token}`}
|
||||
onChange={(token: string) => {
|
||||
updateRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { token },
|
||||
}));
|
||||
if (request.model === 'http_request') {
|
||||
updateHttpRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { token },
|
||||
}));
|
||||
} else {
|
||||
updateGrpcRequest.mutate((r) => ({
|
||||
...r,
|
||||
authentication: { token },
|
||||
}));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
@@ -80,10 +80,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
style={style}
|
||||
activeRequest={activeRequest}
|
||||
methodType={methodType}
|
||||
onUnary={grpc.unary.mutate}
|
||||
onServerStreaming={grpc.serverStreaming.mutate}
|
||||
onClientStreaming={grpc.clientStreaming.mutate}
|
||||
onStreaming={grpc.streaming.mutate}
|
||||
onGo={grpc.go.mutate}
|
||||
onCommit={grpc.commit.mutate}
|
||||
onCancel={grpc.cancel.mutate}
|
||||
onSend={grpc.send.mutate}
|
||||
@@ -93,7 +90,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
/>
|
||||
)}
|
||||
secondSlot={({ style }) =>
|
||||
!grpc.unary.isLoading && (
|
||||
!grpc.go.isLoading && (
|
||||
<div
|
||||
style={style}
|
||||
className={classNames(
|
||||
@@ -102,9 +99,9 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
|
||||
)}
|
||||
>
|
||||
{grpc.unary.error ? (
|
||||
{grpc.go.error ? (
|
||||
<Banner color="danger" className="m-2">
|
||||
{grpc.unary.error}
|
||||
{grpc.go.error}
|
||||
</Banner>
|
||||
) : messages.length >= 0 ? (
|
||||
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
|
||||
|
||||
@@ -5,9 +5,12 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||
import type { GrpcRequest } from '../lib/models';
|
||||
import { AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE } from '../lib/models';
|
||||
import { BasicAuth } from './BasicAuth';
|
||||
import { BearerAuth } from './BearerAuth';
|
||||
import { Button } from './core/Button';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -15,6 +18,7 @@ import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { GrpcEditor } from './GrpcEditor';
|
||||
import { UrlBar } from './UrlBar';
|
||||
|
||||
@@ -31,13 +35,10 @@ interface Props {
|
||||
| 'streaming'
|
||||
| 'no-schema'
|
||||
| 'no-method';
|
||||
onUnary: () => void;
|
||||
onCommit: () => void;
|
||||
onCancel: () => void;
|
||||
onSend: (v: { message: string }) => void;
|
||||
onClientStreaming: () => void;
|
||||
onServerStreaming: () => void;
|
||||
onStreaming: () => void;
|
||||
onGo: () => void;
|
||||
services: ReflectResponseService[] | null;
|
||||
}
|
||||
|
||||
@@ -50,19 +51,17 @@ export function GrpcConnectionSetupPane({
|
||||
activeRequest,
|
||||
reflectionError,
|
||||
reflectionLoading,
|
||||
onStreaming,
|
||||
onClientStreaming,
|
||||
onServerStreaming,
|
||||
onGo,
|
||||
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 [activeTab, setActiveTab] = useActiveTab();
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
|
||||
const [paneSize, setPaneSize] = useState(99999);
|
||||
const urlContainerEl = useRef<HTMLDivElement>(null);
|
||||
@@ -116,17 +115,9 @@ export function GrpcConnectionSetupPane({
|
||||
body: 'Service or method not selected',
|
||||
});
|
||||
}
|
||||
if (methodType === 'streaming') {
|
||||
onStreaming();
|
||||
} else if (methodType === 'server_streaming') {
|
||||
onServerStreaming();
|
||||
} else if (methodType === 'client_streaming') {
|
||||
onClientStreaming();
|
||||
} else {
|
||||
onUnary();
|
||||
}
|
||||
onGo();
|
||||
},
|
||||
[activeRequest, methodType, onStreaming, onServerStreaming, onClientStreaming, onUnary],
|
||||
[activeRequest, onGo],
|
||||
);
|
||||
|
||||
const tabs: TabItem[] = useMemo(
|
||||
@@ -136,7 +127,7 @@ export function GrpcConnectionSetupPane({
|
||||
value: 'auth',
|
||||
label: 'Auth',
|
||||
options: {
|
||||
value: AUTH_TYPE_NONE, // TODO
|
||||
value: activeRequest.authenticationType,
|
||||
items: [
|
||||
{ label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC },
|
||||
{ label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER },
|
||||
@@ -144,13 +135,24 @@ export function GrpcConnectionSetupPane({
|
||||
{ label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE },
|
||||
],
|
||||
onChange: async (authenticationType) => {
|
||||
// TODO
|
||||
let authentication: GrpcRequest['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 ?? '',
|
||||
};
|
||||
}
|
||||
await updateRequest.mutateAsync({ authenticationType, authentication });
|
||||
},
|
||||
},
|
||||
},
|
||||
{ value: 'metadata', label: 'Metadata' },
|
||||
],
|
||||
[],
|
||||
[activeRequest.authentication, activeRequest.authenticationType, updateRequest],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -166,7 +168,7 @@ export function GrpcConnectionSetupPane({
|
||||
url={activeRequest.url ?? ''}
|
||||
method={null}
|
||||
submitIcon={null}
|
||||
forceUpdateKey={activeRequest?.id ?? ''}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
placeholder="localhost:50051"
|
||||
onSubmit={handleConnect}
|
||||
onUrlChange={handleChangeUrl}
|
||||
@@ -270,6 +272,15 @@ export function GrpcConnectionSetupPane({
|
||||
request={activeRequest}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value="auth">
|
||||
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? (
|
||||
<BasicAuth key={forceUpdateKey} request={activeRequest} />
|
||||
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
|
||||
<BearerAuth key={forceUpdateKey} request={activeRequest} />
|
||||
) : (
|
||||
<EmptyStateText>No Authentication {activeRequest.authenticationType}</EmptyStateText>
|
||||
)}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -154,7 +154,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
token: authentication.token ?? '',
|
||||
};
|
||||
}
|
||||
updateRequest.mutate({ authenticationType, authentication });
|
||||
await updateRequest.mutateAsync({ authenticationType, authentication });
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -226,17 +226,9 @@ export const RequestPane = memo(function RequestPane({
|
||||
>
|
||||
<TabContent value="auth">
|
||||
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? (
|
||||
<BasicAuth
|
||||
key={forceUpdateKey}
|
||||
requestId={activeRequest.id}
|
||||
authentication={activeRequest.authentication}
|
||||
/>
|
||||
<BasicAuth key={forceUpdateKey} request={activeRequest} />
|
||||
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
|
||||
<BearerAuth
|
||||
key={forceUpdateKey}
|
||||
requestId={activeRequest.id}
|
||||
authentication={activeRequest.authentication}
|
||||
/>
|
||||
<BearerAuth key={forceUpdateKey} request={activeRequest} />
|
||||
) : (
|
||||
<EmptyStateText>
|
||||
No Authentication {activeRequest.authenticationType}
|
||||
|
||||
@@ -82,7 +82,7 @@ export function Tabs({
|
||||
{tabs.map((t) => {
|
||||
const isActive = t.value === value;
|
||||
const btnClassName = classNames(
|
||||
isActive ? '' : 'text-gray-600 hover:text-gray-800',
|
||||
isActive ? 'text-gray-800' : 'text-gray-600 hover:text-gray-700',
|
||||
'!px-2 ml-[1px]',
|
||||
);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
import type { GrpcConnection, GrpcRequest } from '../lib/models';
|
||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||
import { useDebouncedValue } from './useDebouncedValue';
|
||||
|
||||
export interface ReflectResponseService {
|
||||
@@ -12,21 +13,10 @@ export interface ReflectResponseService {
|
||||
|
||||
export function useGrpc(req: GrpcRequest | null, conn: GrpcConnection | null) {
|
||||
const requestId = req?.id ?? 'n/a';
|
||||
const environmentId = useActiveEnvironmentId();
|
||||
|
||||
const unary = useMutation<void, string>({
|
||||
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
|
||||
});
|
||||
|
||||
const clientStreaming = useMutation<void, string>({
|
||||
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
|
||||
});
|
||||
|
||||
const serverStreaming = useMutation<void, string>({
|
||||
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
|
||||
});
|
||||
|
||||
const streaming = useMutation<void, string>({
|
||||
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
|
||||
const go = useMutation<void, string>({
|
||||
mutationFn: async () => await invoke('cmd_grpc_go', { requestId, environmentId }),
|
||||
});
|
||||
|
||||
const send = useMutation({
|
||||
@@ -58,10 +48,7 @@ export function useGrpc(req: GrpcRequest | null, conn: GrpcConnection | null) {
|
||||
});
|
||||
|
||||
return {
|
||||
unary,
|
||||
clientStreaming,
|
||||
serverStreaming,
|
||||
streaming,
|
||||
go,
|
||||
reflect,
|
||||
cancel,
|
||||
commit,
|
||||
|
||||
@@ -115,6 +115,8 @@ export interface GrpcRequest extends BaseModel {
|
||||
method: string | null;
|
||||
message: string;
|
||||
protoFiles: string[];
|
||||
authentication: Record<string, string | number | boolean | null | undefined>;
|
||||
authenticationType: string | null;
|
||||
}
|
||||
|
||||
export interface GrpcMessage extends BaseModel {
|
||||
|
||||
Reference in New Issue
Block a user