mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-25 00:55:03 +01:00
Variables under Environment, and render all props
This commit is contained in:
2
src-tauri/migrations/20231028161007_variables.sql
Normal file
2
src-tauri/migrations/20231028161007_variables.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE environments DROP COLUMN data;
|
||||
ALTER TABLE environments ADD COLUMN variables DEFAULT '[]' NOT NULL;
|
||||
@@ -160,6 +160,16 @@
|
||||
},
|
||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE workspace_id = ?\n ORDER BY created_at DESC\n "
|
||||
},
|
||||
"3ec4710d28a7f38608c96798d971217ac97788bcb639089d0c5750c0d339bc9a": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE environments\n SET (name, variables, updated_at) = (?, ?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n "
|
||||
},
|
||||
"448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -282,6 +292,60 @@
|
||||
},
|
||||
"query": "\n UPDATE http_responses SET (\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n error,\n headers,\n updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
||||
},
|
||||
"689bcc92b914f50c14921faa796c07a256deb84c832fc3d90200b393fb159417": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>",
|
||||
"ordinal": 6,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM environments\n WHERE id = ?\n "
|
||||
},
|
||||
"6f0cb5a6d1e8dbc8cdfcc3c7e7944b2c83c22cb795b9d6b98fe067dabec9680b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -378,16 +442,6 @@
|
||||
},
|
||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n "
|
||||
},
|
||||
"6f12b56113b09966b472431b6cb95c354bea51b4dfb22a96517655c0fca0ab05": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE environments\n SET (name, data, updated_at) = (?, ?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n "
|
||||
},
|
||||
"84be2b954870ab181738656ecd4d03fca2ff21012947014c79626abfce8e999b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -398,6 +452,16 @@
|
||||
},
|
||||
"query": "\n DELETE FROM workspaces\n WHERE id = ?\n "
|
||||
},
|
||||
"86e32d6a6fadf35436f19b577a659c203a8d143cb3a8d6122951c5bf54a0888d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO environments (id, workspace_id, name, variables)\n VALUES (?, ?, ?, ?)\n "
|
||||
},
|
||||
"8947a2a90478277c42fe9b06bc1fa98197642a4d281a3dbc101be2c9c1fec36c": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -408,7 +472,27 @@
|
||||
},
|
||||
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
|
||||
},
|
||||
"986763e31599881f287ef378002fc35d8e983af10a30a9aa4ade606dacf83260": {
|
||||
"aeb0712785a9964d516dc8939bc54aa8206ad852e608b362d014b67a0f21b0ed": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n DELETE FROM environments\n WHERE id = ?\n "
|
||||
},
|
||||
"b19c275180909a39342b13c3cdcf993781636913ae590967f5508c46a56dc961": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 11
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n authentication,\n authentication_type,\n headers,\n sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n sort_priority = excluded.sort_priority\n "
|
||||
},
|
||||
"ba2b34a77723f24f86e4c3c45274dbfec6ca130e16e592f948844c037bdc0593": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -442,9 +526,9 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "data!: Json<HashMap<String, JsonValue>>",
|
||||
"name": "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -460,37 +544,7 @@
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT id, workspace_id, model, created_at, updated_at, name,\n data AS \"data!: Json<HashMap<String, JsonValue>>\"\n FROM environments\n WHERE workspace_id = ?\n "
|
||||
},
|
||||
"ab7294b681f1202ef06aaa26885147ead2db6ac740023793cda1e1c92665d996": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO environments (\n id,\n workspace_id,\n name,\n data\n )\n VALUES (?, ?, ?, ?)\n "
|
||||
},
|
||||
"aeb0712785a9964d516dc8939bc54aa8206ad852e608b362d014b67a0f21b0ed": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n DELETE FROM environments\n WHERE id = ?\n "
|
||||
},
|
||||
"b19c275180909a39342b13c3cdcf993781636913ae590967f5508c46a56dc961": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 11
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n authentication,\n authentication_type,\n headers,\n sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n sort_priority = excluded.sort_priority\n "
|
||||
"query": "\n SELECT id, workspace_id, model, created_at, updated_at, name,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM environments\n WHERE workspace_id = ?\n "
|
||||
},
|
||||
"c23c61b05a4c9e04ab0c1fc2c579d6f2a82a37aeed8addf9861b4985f2a5422e": {
|
||||
"describe": {
|
||||
@@ -815,59 +869,5 @@
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n "
|
||||
},
|
||||
"fb89f653780b3f3ab0dd0bb2af30c8d3945203819cb9df7bdd331df56a6ae690": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "data!: Json<HashMap<String, JsonValue>>",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n data AS \"data!: Json<HashMap<String, JsonValue>>\"\n FROM environments\n WHERE id = ?\n "
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ use reqwest::redirect::Policy;
|
||||
use serde::Serialize;
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::sqlite::SqlitePoolOptions;
|
||||
use sqlx::types::{Json, JsonValue};
|
||||
use sqlx::types::Json;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::collections::HashMap;
|
||||
use std::env::current_dir;
|
||||
@@ -81,14 +81,10 @@ async fn actually_send_ephemeral_request(
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<models::HttpResponse, String> {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let environment = models::get_environment(environment_id, pool).await.ok();
|
||||
let environment_ref = environment.as_ref();
|
||||
|
||||
// TODO: Use active environment
|
||||
let mut url_string = match environment {
|
||||
Some(e) => render::render(&request.url, e.clone()),
|
||||
None => request.url.to_string(),
|
||||
};
|
||||
let mut url_string = render::render(&request.url, environment.as_ref());
|
||||
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
url_string = format!("http://{}", url_string);
|
||||
@@ -110,57 +106,54 @@ async fn actually_send_ephemeral_request(
|
||||
if h.name.is_empty() && h.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !h.enabled {
|
||||
continue;
|
||||
}
|
||||
let header_name = match HeaderName::from_bytes(h.name.as_bytes()) {
|
||||
|
||||
let name = render::render(&h.name, environment_ref);
|
||||
let value = render::render(&h.value, environment_ref);
|
||||
|
||||
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create header name: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let header_value = match HeaderValue::from_str(h.value.as_str()) {
|
||||
let header_value = match HeaderValue::from_str(value.as_str()) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create header value: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
headers.insert(header_name, header_value);
|
||||
}
|
||||
|
||||
if let Some(b) = &request.authentication_type {
|
||||
let empty_value = &serde_json::to_value("").unwrap();
|
||||
let a = request.authentication.0;
|
||||
|
||||
if b == "basic" {
|
||||
let a = request.authentication.0;
|
||||
let auth = format!(
|
||||
"{}:{}",
|
||||
a.get("username")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or(""),
|
||||
a.get("password")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or(""),
|
||||
);
|
||||
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, environment_ref);
|
||||
let password = render::render(raw_password, environment_ref);
|
||||
|
||||
let auth = format!( "{username}:{password}");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
|
||||
);
|
||||
} else if b == "bearer" {
|
||||
let token = request
|
||||
.authentication
|
||||
.0
|
||||
.get("token")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
||||
let token = render::render(raw_token, environment_ref);
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
|
||||
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -170,7 +163,10 @@ async fn actually_send_ephemeral_request(
|
||||
let builder = client.request(m, url_string.to_string()).headers(headers);
|
||||
|
||||
let sendable_req_result = match (request.body, request.body_type) {
|
||||
(Some(b), Some(_)) => builder.body(b).build(),
|
||||
(Some(raw_body), Some(_)) => {
|
||||
let body = render::render(&raw_body, environment_ref);
|
||||
builder.body(body).build()
|
||||
},
|
||||
_ => builder.build(),
|
||||
};
|
||||
|
||||
@@ -341,8 +337,8 @@ async fn create_environment(
|
||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||
) -> Result<models::Environment, String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
let data: HashMap<String, JsonValue> = HashMap::new();
|
||||
let created_environment = models::create_environment(workspace_id, name, data, pool)
|
||||
let variables = Vec::new();
|
||||
let created_environment = models::create_environment(workspace_id, name, variables, pool)
|
||||
.await
|
||||
.expect("Failed to create environment");
|
||||
|
||||
@@ -418,7 +414,7 @@ async fn update_environment(
|
||||
let updated_environment = models::update_environment(
|
||||
environment.id.as_str(),
|
||||
environment.name.as_str(),
|
||||
environment.data.0,
|
||||
environment.variables.0,
|
||||
pool,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -27,7 +27,30 @@ pub struct Environment {
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub name: String,
|
||||
pub data: Json<HashMap<String, JsonValue>>,
|
||||
pub variables: Json<Vec<EnvironmentVariable>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EnvironmentVariable {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Variable {
|
||||
pub id: String,
|
||||
pub workspace_id: String,
|
||||
pub environment_id: String,
|
||||
pub model: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub sort_priority: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -213,7 +236,7 @@ pub async fn find_environments(
|
||||
Environment,
|
||||
r#"
|
||||
SELECT id, workspace_id, model, created_at, updated_at, name,
|
||||
data AS "data!: Json<HashMap<String, JsonValue>>"
|
||||
variables AS "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>"
|
||||
FROM environments
|
||||
WHERE workspace_id = ?
|
||||
"#,
|
||||
@@ -226,26 +249,21 @@ pub async fn find_environments(
|
||||
pub async fn create_environment(
|
||||
workspace_id: &str,
|
||||
name: &str,
|
||||
data: HashMap<String, JsonValue>,
|
||||
variables: Vec<EnvironmentVariable>,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<Environment, sqlx::Error> {
|
||||
let id = generate_id(Some("en"));
|
||||
let data_json = Json(data);
|
||||
let trimmed_name = name.trim();
|
||||
let variables_json = Json(variables);
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO environments (
|
||||
id,
|
||||
workspace_id,
|
||||
name,
|
||||
data
|
||||
)
|
||||
INSERT INTO environments (id, workspace_id, name, variables)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"#,
|
||||
id,
|
||||
workspace_id,
|
||||
trimmed_name,
|
||||
data_json,
|
||||
variables_json,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
@@ -270,18 +288,18 @@ pub async fn delete_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environ
|
||||
pub async fn update_environment(
|
||||
id: &str,
|
||||
name: &str,
|
||||
data: HashMap<String, JsonValue>,
|
||||
variables: Vec<EnvironmentVariable>,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<Environment, sqlx::Error> {
|
||||
let json_data = Json(data);
|
||||
let variables_json = Json(variables);
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE environments
|
||||
SET (name, data, updated_at) = (?, ?, CURRENT_TIMESTAMP)
|
||||
SET (name, variables, updated_at) = (?, ?, CURRENT_TIMESTAMP)
|
||||
WHERE id = ?;
|
||||
"#,
|
||||
name,
|
||||
json_data,
|
||||
variables_json,
|
||||
id,
|
||||
)
|
||||
.execute(pool)
|
||||
@@ -300,7 +318,7 @@ pub async fn get_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environmen
|
||||
created_at,
|
||||
updated_at,
|
||||
name,
|
||||
data AS "data!: Json<HashMap<String, JsonValue>>"
|
||||
variables AS "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>"
|
||||
FROM environments
|
||||
WHERE id = ?
|
||||
"#,
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
use crate::models::Environment;
|
||||
use std::collections::HashMap;
|
||||
use tauri::regex::Regex;
|
||||
|
||||
use crate::models::Environment;
|
||||
pub fn render(template: &str, environment: Option<&Environment>) -> String {
|
||||
match environment {
|
||||
Some(environment) => render_with_environment(template, environment),
|
||||
None => template.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_with_environment(template: &str, environment: &Environment) -> String {
|
||||
let mut map = HashMap::new();
|
||||
let variables = &environment.variables.0;
|
||||
for variable in variables {
|
||||
map.insert(variable.name.as_str(), variable.value.as_str());
|
||||
}
|
||||
|
||||
pub fn render(template: &str, environment: Environment) -> String {
|
||||
let variables = environment.data;
|
||||
Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}")
|
||||
.expect("Failed to create regex")
|
||||
.replace(template, |caps: &tauri::regex::Captures| {
|
||||
let key = caps.get(1).unwrap().as_str();
|
||||
match variables.get(key) {
|
||||
Some(v) => {
|
||||
if v.is_string() {
|
||||
v.as_str().expect("Should be string").to_string()
|
||||
} else {
|
||||
v.to_string()
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
}
|
||||
map.get(key).unwrap_or(&"")
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export function BasicAuth({ requestId, authentication }: Props) {
|
||||
return (
|
||||
<VStack className="my-2" space={2}>
|
||||
<Input
|
||||
useTemplating
|
||||
label="Username"
|
||||
name="username"
|
||||
size="sm"
|
||||
@@ -26,6 +27,7 @@ export function BasicAuth({ requestId, authentication }: Props) {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
useTemplating
|
||||
label="Password"
|
||||
name="password"
|
||||
size="sm"
|
||||
|
||||
@@ -14,6 +14,7 @@ export function BearerAuth({ requestId, authentication }: Props) {
|
||||
return (
|
||||
<VStack className="my-2" space={2}>
|
||||
<Input
|
||||
useTemplating
|
||||
label="Token"
|
||||
name="token"
|
||||
size="sm"
|
||||
|
||||
@@ -4,16 +4,11 @@ import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
@@ -24,62 +19,23 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
}: Props) {
|
||||
const environments = useEnvironments();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
||||
const deleteEnvironment = useDeleteEnvironment(activeEnvironment);
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const prompt = usePrompt();
|
||||
const dialog = useDialog();
|
||||
const routes = useAppRoutes();
|
||||
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
const environmentItems: DropdownItem[] = environments.map(
|
||||
(e) => ({
|
||||
key: e.id,
|
||||
label: e.name,
|
||||
rightSlot: e.id === activeEnvironment?.id ? <Icon icon="check" /> : undefined,
|
||||
onSelect: async () => {
|
||||
routes.setEnvironment(e);
|
||||
},
|
||||
}),
|
||||
[activeEnvironment?.id],
|
||||
);
|
||||
|
||||
return [
|
||||
...environmentItems,
|
||||
...((environmentItems.length > 0
|
||||
? [{ type: 'separator', label: activeEnvironment?.name }]
|
||||
: []) as DropdownItem[]),
|
||||
...((activeEnvironment != null
|
||||
? [
|
||||
{
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
onSelect: async () => {
|
||||
const name = await prompt({
|
||||
title: 'Rename Environment',
|
||||
description: (
|
||||
<>
|
||||
Enter a new name for <InlineCode>{activeEnvironment?.name}</InlineCode>
|
||||
</>
|
||||
),
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: activeEnvironment?.name,
|
||||
});
|
||||
updateEnvironment.mutate({ name });
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: deleteEnvironment.mutate,
|
||||
variant: 'danger',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
]
|
||||
: []) as DropdownItem[]),
|
||||
const items: DropdownItem[] = useMemo(
|
||||
() => [
|
||||
...environments.map(
|
||||
(e) => ({
|
||||
key: e.id,
|
||||
label: e.name,
|
||||
rightSlot: e.id === activeEnvironment?.id ? <Icon icon="check" /> : undefined,
|
||||
onSelect: async () => {
|
||||
routes.setEnvironment(e);
|
||||
},
|
||||
}),
|
||||
[activeEnvironment?.id],
|
||||
),
|
||||
{ type: 'separator', label: 'Environments' },
|
||||
...((environments.length > 0
|
||||
? [
|
||||
{
|
||||
@@ -95,32 +51,9 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
},
|
||||
]
|
||||
: []) as DropdownItem[]),
|
||||
{
|
||||
key: 'create-environment',
|
||||
label: 'Create Environment',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: async () => {
|
||||
const name = await prompt({
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: 'My Environment',
|
||||
description: 'Enter a name for the new environment',
|
||||
title: 'Create Environment',
|
||||
});
|
||||
createEnvironment.mutate({ name });
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [
|
||||
activeEnvironment,
|
||||
createEnvironment,
|
||||
deleteEnvironment,
|
||||
dialog,
|
||||
environments,
|
||||
prompt,
|
||||
routes,
|
||||
updateEnvironment,
|
||||
]);
|
||||
],
|
||||
[activeEnvironment, dialog, environments, routes],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown items={items}>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||
import type { Environment } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Editor } from './core/Editor';
|
||||
import classNames from 'classnames';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
import type { PairEditorProps } from './core/PairEditor';
|
||||
import { useCallback } from 'react';
|
||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||
|
||||
export const EnvironmentEditDialog = function() {
|
||||
export const EnvironmentEditDialog = function () {
|
||||
const routes = useAppRoutes();
|
||||
const prompt = usePrompt();
|
||||
const environments = useEnvironments();
|
||||
@@ -58,21 +59,21 @@ export const EnvironmentEditDialog = function() {
|
||||
);
|
||||
};
|
||||
|
||||
const EnvironmentEditor = function({ environment }: { environment: Environment }) {
|
||||
const EnvironmentEditor = function ({ environment }: { environment: Environment }) {
|
||||
const updateEnvironment = useUpdateEnvironment(environment.id);
|
||||
const handleChange = useCallback<PairEditorProps['onChange']>(
|
||||
(variables) => {
|
||||
updateEnvironment.mutate({ variables });
|
||||
},
|
||||
[updateEnvironment],
|
||||
);
|
||||
return (
|
||||
<Editor
|
||||
contentType="application/json"
|
||||
className="w-full min-h-[40px] !bg-gray-50"
|
||||
defaultValue={JSON.stringify(environment.data, null, 2)}
|
||||
forceUpdateKey={environment.id}
|
||||
onChange={(data) => {
|
||||
try {
|
||||
updateEnvironment.mutate({ data: JSON.parse(data) });
|
||||
} catch (err) {
|
||||
// That's okay
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<PairEditor
|
||||
forceUpdateKey={environment.id}
|
||||
pairs={environment.variables}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -63,5 +63,7 @@ const validateHttpHeader = (v: string) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hi = v.replace(/\$\{\[\s*[^\]\s]+\s*]}/gi, 'fo');
|
||||
console.log('V', v, '-->', hi);
|
||||
return v.match(/^[a-zA-Z0-9-_]+$/) !== null;
|
||||
};
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
|
||||
/* Bring above on hover */
|
||||
@apply hover:z-10 relative;
|
||||
|
||||
-webkit-text-security: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import './Editor.css';
|
||||
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||
import type { GenericCompletionConfig } from './genericCompletion';
|
||||
import { singleLineExt } from './singleLine';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useActiveEnvironment } from '../../../hooks/useActiveEnvironment';
|
||||
|
||||
// Export some things so all the code-split parts are in this file
|
||||
|
||||
@@ -48,7 +48,7 @@ const placeholderMatcher = new BetterMatchDecorator({
|
||||
if (groupMatch == null) {
|
||||
// Should never happen, but make TS happy
|
||||
console.warn('Group match was empty', match);
|
||||
return Decoration.replace({});;
|
||||
return Decoration.replace({});
|
||||
}
|
||||
|
||||
return Decoration.replace({
|
||||
|
||||
@@ -9,11 +9,13 @@ import { twigCompletion } from './completion';
|
||||
import { parser as twigParser } from './twig';
|
||||
import type { Environment } from '../../../../lib/models';
|
||||
|
||||
export function twig(base: LanguageSupport, environment: Environment | null, autocomplete?: GenericCompletionConfig) {
|
||||
// TODO: fill variables here
|
||||
const data = environment?.data ?? {};
|
||||
const options = Object.keys(data).map(key => ({ name: key }));
|
||||
const completions = twigCompletion({ options });
|
||||
export function twig(
|
||||
base: LanguageSupport,
|
||||
environment: Environment | null,
|
||||
autocomplete?: GenericCompletionConfig,
|
||||
) {
|
||||
const variables = environment?.variables ?? [];
|
||||
const completions = twigCompletion({ options: variables });
|
||||
|
||||
const language = mixLanguage(base);
|
||||
const completion = language.data.of({ autocomplete: completions });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -24,7 +24,8 @@ export type PairEditorProps = {
|
||||
valueValidate?: InputProps['validate'];
|
||||
};
|
||||
|
||||
type Pair = {
|
||||
export type Pair = {
|
||||
id?: string;
|
||||
enabled?: boolean;
|
||||
name: string;
|
||||
value: string;
|
||||
@@ -342,6 +343,8 @@ const FormRow = memo(function FormRow({
|
||||
);
|
||||
});
|
||||
|
||||
const newPairContainer = (pair?: Pair): PairContainer => {
|
||||
return { pair: pair ?? { name: '', value: '', enabled: true }, id: uuid() };
|
||||
const newPairContainer = (initialPair?: Pair): PairContainer => {
|
||||
const id = initialPair?.id ?? uuid();
|
||||
const pair = initialPair ?? { name: '', value: '', enabled: true };
|
||||
return { id, pair };
|
||||
};
|
||||
|
||||
18
src-web/hooks/useVariables.ts
Normal file
18
src-web/hooks/useVariables.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { Variable } from '../lib/models';
|
||||
|
||||
export function variablesQueryKey({ environmentId }: { environmentId: string }) {
|
||||
return ['variables', { environmentId }];
|
||||
}
|
||||
|
||||
export function useVariables({ environmentId }: { environmentId: string }) {
|
||||
return (
|
||||
useQuery({
|
||||
queryKey: variablesQueryKey({ environmentId }),
|
||||
queryFn: async () => {
|
||||
return (await invoke('list_variables', { environmentId })) as Variable[];
|
||||
},
|
||||
}).data ?? []
|
||||
);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export interface Workspace extends BaseModel {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface HttpHeader {
|
||||
export interface EnvironmentVariable {
|
||||
name: string;
|
||||
value: string;
|
||||
enabled?: boolean;
|
||||
@@ -32,7 +32,13 @@ export interface Environment extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly model: 'environment';
|
||||
name: string;
|
||||
data: Record<string, string | number | boolean | null | undefined>;
|
||||
variables: EnvironmentVariable[];
|
||||
}
|
||||
|
||||
export interface HttpHeader {
|
||||
name: string;
|
||||
value: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface HttpRequest extends BaseModel {
|
||||
|
||||
Reference in New Issue
Block a user