mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-29 21:51:59 +02:00
Good start to multi-window
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Yaak App</title>
|
<title>Yaak App</title>
|
||||||
<!-- <script src="http://localhost:8097"></script>-->
|
<script src="http://localhost:8097"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|||||||
7
src-tauri/migrations/20230329000257_updated-by.sql
Normal file
7
src-tauri/migrations/20230329000257_updated-by.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE http_requests ADD COLUMN updated_by TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE http_responses ADD COLUMN updated_by TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE workspaces ADD COLUMN updated_by TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE key_values ADD COLUMN updated_by TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE http_requests ADD COLUMN authentication TEXT NOT NULL DEFAULT '{}';
|
||||||
|
ALTER TABLE http_requests ADD COLUMN authentication_type TEXT;
|
||||||
@@ -1,53 +1,5 @@
|
|||||||
{
|
{
|
||||||
"db": "SQLite",
|
"db": "SQLite",
|
||||||
"06aaf8f4a17566f1d25da2a60f0baf4b5fc28c3cf0c001a84e25edf9eab3c7e3": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "model",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Datetime"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Datetime"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "namespace",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "key",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "value",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT model, created_at, updated_at, namespace, key, value\n FROM key_values\n WHERE namespace = ? AND key = ?\n "
|
|
||||||
},
|
|
||||||
"07d1a1c7b4f3d9625a766e60fd57bb779b71dae30e5bbce34885a911a5a42428": {
|
"07d1a1c7b4f3d9625a766e60fd57bb779b71dae30e5bbce34885a911a5a42428": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -68,6 +20,70 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM http_responses\n WHERE request_id = ?\n "
|
"query": "\n DELETE FROM http_responses\n WHERE request_id = ?\n "
|
||||||
},
|
},
|
||||||
|
"19e0076c3cd13b73a46619b5c0ee5bf304fe27245db3d19a648f625bf5231cb0": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO workspaces (id, updated_by, name, description)\n VALUES (?, ?, ?, ?)\n "
|
||||||
|
},
|
||||||
|
"2f93d7bd211af59c7cc15765a746216632fbe4f02301acf8382f1cf3f8d24c8d": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_by",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id, model, created_at, updated_at, updated_by, name, description\n FROM workspaces WHERE id = ?\n "
|
||||||
|
},
|
||||||
"448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": {
|
"448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -78,7 +94,7 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
|
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
"539bb11d635c0295f969d32c6bf1e3d78f2686521a5ef2a4af661b7e645f58c1": {
|
"51ee4652a889417dce585e4da457a629dd9e064b8866c3a916bc3bd2c933e14f": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -97,9 +113,9 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
"name": "request_id",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Datetime"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
"name": "updated_at",
|
||||||
@@ -107,39 +123,49 @@
|
|||||||
"type_info": "Datetime"
|
"type_info": "Datetime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "updated_by",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "url",
|
"name": "created_at",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Datetime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "method",
|
"name": "status",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Text"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "status_reason",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body_type",
|
"name": "body",
|
||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sort_priority",
|
"name": "elapsed",
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"type_info": "Float"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
"name": "url",
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "error",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||||
|
"ordinal": 13,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nullable": [
|
"nullable": [
|
||||||
@@ -152,15 +178,17 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
false
|
false
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Right": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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 sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n "
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, updated_by,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n "
|
||||||
},
|
},
|
||||||
"84be2b954870ab181738656ecd4d03fca2ff21012947014c79626abfce8e999b": {
|
"84be2b954870ab181738656ecd4d03fca2ff21012947014c79626abfce8e999b": {
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -172,48 +200,43 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM workspaces\n WHERE id = ?\n "
|
"query": "\n DELETE FROM workspaces\n WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
"a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": {
|
"8d71216aa3902af45acc36bb4671e36bea6da2d30b42cfe8b70cff00cd00f256": {
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
|
||||||
},
|
|
||||||
"caf3f21bf291dfbd36446592066e96c1f83abe96f6ea9211a3e049eb9c58a8c8": {
|
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "model",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "model",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"ordinal": 2,
|
"ordinal": 1,
|
||||||
"type_info": "Datetime"
|
"type_info": "Datetime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
"name": "updated_at",
|
||||||
"ordinal": 3,
|
"ordinal": 2,
|
||||||
"type_info": "Datetime"
|
"type_info": "Datetime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "updated_by",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "description",
|
"name": "key",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nullable": [
|
"nullable": [
|
||||||
@@ -222,15 +245,16 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Right": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces WHERE id = ?\n "
|
"query": "\n SELECT model, created_at, updated_at, updated_by, namespace, key, value\n FROM key_values\n WHERE namespace = ? AND key = ?\n "
|
||||||
},
|
},
|
||||||
"cea4cae52f16ec78aca9a47b17117422d4f165e5a3b308c70fd1a180382475ea": {
|
"9f09f300e04d9b77d408bea52069b7c812dcad163d144df4b7b02a9ba7969345": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -254,14 +278,19 @@
|
|||||||
"type_info": "Datetime"
|
"type_info": "Datetime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "updated_by",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "description",
|
"name": "name",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nullable": [
|
"nullable": [
|
||||||
@@ -270,205 +299,26 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 0
|
"Right": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces\n "
|
"query": "\n SELECT id, model, created_at, updated_at, updated_by, name, description\n FROM workspaces\n "
|
||||||
},
|
},
|
||||||
"d5ad6d5f82fe837fa9215bd4619ec18a7c95b3088d4fbf9825f2d1d28069d1ce": {
|
"afe2d3a2b2198582d2a4360a0947785d435528eb77f67c420e830a997b5ad101": {
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "model",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "workspace_id",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "request_id",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Datetime"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Datetime"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status_reason",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "elapsed",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "url",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "error",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
|
||||||
"ordinal": 12,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n "
|
|
||||||
},
|
|
||||||
"d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": {
|
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"nullable": [],
|
"nullable": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 3
|
"Right": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n "
|
"query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_by, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
||||||
},
|
},
|
||||||
"e3ade0a69348d512e47e964bded9d7d890b92fdc1e01c6c22fa5e91f943639f2": {
|
"bc1b3220c104567176ff741b5648a14054485603ebb04222a7b9f60fc54f0970": {
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "model",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "workspace_id",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "request_id",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Datetime"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Datetime"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status_reason",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "elapsed",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "url",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "error",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
|
||||||
"ordinal": 12,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
|
|
||||||
},
|
|
||||||
"e523dc91256b4409a734850eae59ac73b951177ce88d35e2ab708871f3067ace": {
|
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -522,13 +372,28 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sort_priority",
|
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authentication_type",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort_priority",
|
||||||
|
"ordinal": 12,
|
||||||
"type_info": "Float"
|
"type_info": "Float"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_by",
|
||||||
|
"ordinal": 13,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
||||||
"ordinal": 11,
|
"ordinal": 14,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -543,6 +408,9 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
],
|
],
|
||||||
@@ -550,19 +418,125 @@
|
|||||||
"Right": 1
|
"Right": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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 sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n "
|
"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 updated_by,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
"e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": {
|
"ce62a799babc731dc53e43144751006c3905367c47c511b8abee832c58f8111d": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"nullable": [],
|
"nullable": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 9
|
"Right": 12
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
|
"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 updated_by\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 updated_by = excluded.updated_by\n "
|
||||||
},
|
},
|
||||||
"f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": {
|
"d56b00aeaca0edd9a9fea4c7fdce0229a6d6500c8294854974dd4fc30af8bda8": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n body,\n headers,\n updated_by\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
|
||||||
|
},
|
||||||
|
"d68f12980ff00de36f02a82a626f99e86abf5a26ebdf74c95832d3be396206da": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "model",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace_id",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "request_id",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_by",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status_reason",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "elapsed",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "url",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "error",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||||
|
"ordinal": 13,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, updated_by, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
|
||||||
|
},
|
||||||
|
"d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"nullable": [],
|
"nullable": [],
|
||||||
@@ -570,16 +544,108 @@
|
|||||||
"Right": 3
|
"Right": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n "
|
"query": "\n INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n "
|
||||||
},
|
},
|
||||||
"f506d6b1451d95489cf41fea2d1cd3fae4f0773e16ae11ded6fd5923f015c8d5": {
|
"dc749b8ed41ac55776e4f9dae36a82b3b880eb781eaa6fb53382e6db10f5a741": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [
|
||||||
"nullable": [],
|
{
|
||||||
|
"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": "url",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "method",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body_type",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authentication!: Json<HashMap<String, JsonValue>>",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authentication_type",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort_priority",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_by",
|
||||||
|
"ordinal": 13,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 9
|
"Right": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_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 url = excluded.url,\n sort_priority = excluded.sort_priority\n "
|
"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 updated_by,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,14 +18,16 @@ use reqwest::redirect::Policy;
|
|||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
use sqlx::migrate::Migrator;
|
use sqlx::migrate::Migrator;
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
use sqlx::types::Json;
|
use sqlx::types::{Json};
|
||||||
use tauri::{AppHandle, Menu, MenuItem, State, Submenu, Wry};
|
use tauri::{AppHandle, Menu, MenuItem, State, Submenu, TitleBarStyle, Window, Wry};
|
||||||
use tauri::{CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowEvent};
|
use tauri::{CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowEvent};
|
||||||
use tauri::regex::Regex;
|
use tauri::regex::Regex;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use window_ext::WindowExt;
|
use window_ext::WindowExt;
|
||||||
|
|
||||||
|
use crate::models::generate_id;
|
||||||
|
|
||||||
mod models;
|
mod models;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod window_ext;
|
mod window_ext;
|
||||||
@@ -60,18 +62,18 @@ async fn migrate_db(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn send_ephemeral_request(
|
async fn send_ephemeral_request(
|
||||||
request: models::HttpRequest,
|
request: models::HttpRequest,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<models::HttpResponse, String> {
|
) -> Result<models::HttpResponse, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let response = models::HttpResponse::default();
|
let response = models::HttpResponse::default();
|
||||||
return actually_send_ephemeral_request(request, response, app_handle, pool).await;
|
return actually_send_ephemeral_request(request, response, window, pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn actually_send_ephemeral_request(
|
async fn actually_send_ephemeral_request(
|
||||||
request: models::HttpRequest,
|
request: models::HttpRequest,
|
||||||
mut response: models::HttpResponse,
|
mut response: models::HttpResponse,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<models::HttpResponse, String> {
|
) -> Result<models::HttpResponse, String> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
@@ -131,8 +133,8 @@ async fn actually_send_ephemeral_request(
|
|||||||
headers.insert(header_name, header_value);
|
headers.insert(header_name, header_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let m =
|
let m = Method::from_bytes(request.method.to_uppercase().as_bytes())
|
||||||
Method::from_bytes(request.method.to_uppercase().as_bytes()).expect("Failed to create method");
|
.expect("Failed to create method");
|
||||||
let builder = client.request(m, url_string.to_string()).headers(headers);
|
let builder = client.request(m, url_string.to_string()).headers(headers);
|
||||||
|
|
||||||
let sendable_req_result = match (request.body, request.body_type) {
|
let sendable_req_result = match (request.body, request.body_type) {
|
||||||
@@ -143,13 +145,13 @@ async fn actually_send_ephemeral_request(
|
|||||||
let sendable_req = match sendable_req_result {
|
let sendable_req = match sendable_req_result {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return response_err(response, e.to_string(), app_handle, pool).await;
|
return response_err(response, e.to_string(), window, pool).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let resp = client.execute(sendable_req).await;
|
let resp = client.execute(sendable_req).await;
|
||||||
|
|
||||||
let p = app_handle
|
let p = window.app_handle()
|
||||||
.path_resolver()
|
.path_resolver()
|
||||||
.resolve_resource("plugins/plugin.ts")
|
.resolve_resource("plugins/plugin.ts")
|
||||||
.expect("failed to resolve resource");
|
.expect("failed to resolve resource");
|
||||||
@@ -172,19 +174,19 @@ async fn actually_send_ephemeral_request(
|
|||||||
response.url = v.url().to_string();
|
response.url = v.url().to_string();
|
||||||
response.body = v.text().await.expect("Failed to get body");
|
response.body = v.text().await.expect("Failed to get body");
|
||||||
response.elapsed = start.elapsed().as_millis() as i64;
|
response.elapsed = start.elapsed().as_millis() as i64;
|
||||||
response = models::update_response_if_id(response, pool)
|
response = models::update_response_if_id(response, window.label(), pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to update response");
|
.expect("Failed to update response");
|
||||||
app_handle.emit_all("updated_response", &response).unwrap();
|
window.app_handle().emit_all("updated_response", &response).unwrap();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
Err(e) => response_err(response, e.to_string(), app_handle, pool).await,
|
Err(e) => response_err(response, e.to_string(), window, pool).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn send_request(
|
async fn send_request(
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
@@ -194,26 +196,26 @@ async fn send_request(
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to get request");
|
.expect("Failed to get request");
|
||||||
|
|
||||||
let response = models::create_response(&req.id, 0, "", 0, None, "", vec![], pool)
|
let response = models::create_response(&req.id, 0, "", 0, None, "", vec![], window.label(), pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create response");
|
.expect("Failed to create response");
|
||||||
app_handle.emit_all("updated_response", &response).unwrap();
|
window.app_handle().emit_all("updated_response", &response).unwrap();
|
||||||
|
|
||||||
actually_send_ephemeral_request(req, response, app_handle, pool).await?;
|
actually_send_ephemeral_request(req, response, window, pool).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn response_err(
|
async fn response_err(
|
||||||
mut response: models::HttpResponse,
|
mut response: models::HttpResponse,
|
||||||
error: String,
|
error: String,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<models::HttpResponse, String> {
|
) -> Result<models::HttpResponse, String> {
|
||||||
response.error = Some(error.clone());
|
response.error = Some(error.clone());
|
||||||
response = models::update_response_if_id(response, pool)
|
response = models::update_response_if_id(response, window.label(), pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to update response");
|
.expect("Failed to update response");
|
||||||
app_handle.emit_all("updated_response", &response).unwrap();
|
window.app_handle().emit_all("updated_response", &response).unwrap();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,10 +239,9 @@ async fn set_key_value(
|
|||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let created_key_value =
|
let created_key_value = models::set_key_value(namespace, key, value, pool)
|
||||||
models::set_key_value(namespace, key, value, pool)
|
.await
|
||||||
.await
|
.expect("Failed to create key value");
|
||||||
.expect("Failed to create key value");
|
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.emit_all("updated_key_value", &created_key_value)
|
.emit_all("updated_key_value", &created_key_value)
|
||||||
@@ -252,14 +253,16 @@ async fn set_key_value(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn create_workspace(
|
async fn create_workspace(
|
||||||
name: &str,
|
name: &str,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let created_workspace =
|
let created_workspace = models::create_workspace(name, "", window.label(), pool)
|
||||||
models::create_workspace(name, "", pool).await.expect("Failed to create workspace");
|
.await
|
||||||
|
.expect("Failed to create workspace");
|
||||||
|
|
||||||
app_handle
|
window
|
||||||
|
.app_handle()
|
||||||
.emit_all("updated_workspace", &created_workspace)
|
.emit_all("updated_workspace", &created_workspace)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -271,17 +274,30 @@ async fn create_request(
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
sort_priority: f64,
|
sort_priority: f64,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let headers = Vec::new();
|
let headers = Vec::new();
|
||||||
let created_request =
|
let created_request = models::upsert_request(
|
||||||
models::upsert_request(None, workspace_id, name, "GET", None, None, "", headers, sort_priority, pool)
|
None,
|
||||||
.await
|
workspace_id,
|
||||||
.expect("Failed to create request");
|
name,
|
||||||
|
"GET",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
HashMap::new(),
|
||||||
|
None,
|
||||||
|
"",
|
||||||
|
headers,
|
||||||
|
sort_priority,
|
||||||
|
window.label(),
|
||||||
|
pool,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create request");
|
||||||
|
|
||||||
app_handle
|
window.app_handle()
|
||||||
.emit_all("updated_request", &created_request)
|
.emit_all("updated_request", &created_request)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -291,21 +307,21 @@ async fn create_request(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn duplicate_request(
|
async fn duplicate_request(
|
||||||
id: &str,
|
id: &str,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let request = models::duplicate_request(id, pool).await.expect("Failed to duplicate request");
|
let request = models::duplicate_request(id, window.label(), pool)
|
||||||
app_handle
|
.await
|
||||||
.emit_all("updated_request", &request)
|
.expect("Failed to duplicate request");
|
||||||
.unwrap();
|
window.app_handle().emit_all("updated_request", &request).unwrap();
|
||||||
Ok(request.id)
|
Ok(request.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn update_request(
|
async fn update_request(
|
||||||
request: models::HttpRequest,
|
request: models::HttpRequest,
|
||||||
app_handle: AppHandle<Wry>,
|
window: Window<Wry>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
@@ -320,6 +336,7 @@ async fn update_request(
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Figure out how to make this better
|
||||||
let updated_request = models::upsert_request(
|
let updated_request = models::upsert_request(
|
||||||
Some(request.id.as_str()),
|
Some(request.id.as_str()),
|
||||||
request.workspace_id.as_str(),
|
request.workspace_id.as_str(),
|
||||||
@@ -327,15 +344,18 @@ async fn update_request(
|
|||||||
request.method.as_str(),
|
request.method.as_str(),
|
||||||
body,
|
body,
|
||||||
request.body_type,
|
request.body_type,
|
||||||
|
request.authentication.0,
|
||||||
|
request.authentication_type,
|
||||||
request.url.as_str(),
|
request.url.as_str(),
|
||||||
request.headers.0,
|
request.headers.0,
|
||||||
request.sort_priority,
|
request.sort_priority,
|
||||||
|
window.label(),
|
||||||
pool,
|
pool,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to update request");
|
.expect("Failed to update request");
|
||||||
|
|
||||||
app_handle
|
window.app_handle()
|
||||||
.emit_all("updated_request", updated_request)
|
.emit_all("updated_request", updated_request)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -373,7 +393,9 @@ async fn get_request(
|
|||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<models::HttpRequest, String> {
|
) -> Result<models::HttpRequest, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
models::get_request(id, pool).await.map_err(|e| e.to_string())
|
models::get_request(id, pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -415,15 +437,17 @@ async fn delete_all_responses(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn workspaces(
|
async fn workspaces(
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
window: Window<Wry>,
|
||||||
) -> Result<Vec<models::Workspace>, String> {
|
) -> Result<Vec<models::Workspace>, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let workspaces = models::find_workspaces(pool)
|
let workspaces = models::find_workspaces(pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to find workspaces");
|
.expect("Failed to find workspaces");
|
||||||
if workspaces.is_empty() {
|
if workspaces.is_empty() {
|
||||||
let workspace = models::create_workspace("My Project", "This is the default workspace", pool)
|
let workspace =
|
||||||
.await
|
models::create_workspace("My Project", "This is the default workspace", window.label(), pool)
|
||||||
.expect("Failed to create workspace");
|
.await
|
||||||
|
.expect("Failed to create workspace");
|
||||||
Ok(vec![workspace])
|
Ok(vec![workspace])
|
||||||
} else {
|
} else {
|
||||||
Ok(workspaces)
|
Ok(workspaces)
|
||||||
@@ -454,36 +478,16 @@ fn main() {
|
|||||||
let tray_menu = SystemTrayMenu::new().add_item(quit);
|
let tray_menu = SystemTrayMenu::new().add_item(quit);
|
||||||
let system_tray = SystemTray::new().with_menu(tray_menu);
|
let system_tray = SystemTray::new().with_menu(tray_menu);
|
||||||
|
|
||||||
let default_menu = Menu::os_default("Yaak".to_string().as_str());
|
|
||||||
let mut test_menu = Menu::new()
|
|
||||||
.add_item(CustomMenuItem::new("send_request".to_string(), "Send Request").accelerator("CmdOrCtrl+r"))
|
|
||||||
.add_item(CustomMenuItem::new("zoom_reset".to_string(), "Zoom to Actual Size").accelerator("CmdOrCtrl+0"))
|
|
||||||
.add_item(CustomMenuItem::new("zoom_in".to_string(), "Zoom In").accelerator("CmdOrCtrl+Plus"))
|
|
||||||
.add_item(CustomMenuItem::new("zoom_out".to_string(), "Zoom Out").accelerator("CmdOrCtrl+-"))
|
|
||||||
.add_item(CustomMenuItem::new("toggle_sidebar".to_string(), "Toggle Sidebar").accelerator("CmdOrCtrl+b"));
|
|
||||||
if is_dev() {
|
|
||||||
test_menu = test_menu
|
|
||||||
.add_native_item(MenuItem::Separator)
|
|
||||||
.add_item(CustomMenuItem::new("refresh".to_string(), "Refresh").accelerator("CmdOrCtrl + Shift + r"))
|
|
||||||
.add_item(CustomMenuItem::new("toggle_devtools".to_string(), "Open Devtools").accelerator("CmdOrCtrl + Option + i"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let submenu = Submenu::new("Test Menu", test_menu);
|
|
||||||
|
|
||||||
let menu = default_menu.add_submenu(submenu);
|
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.menu(menu)
|
|
||||||
.system_tray(system_tray)
|
.system_tray(system_tray)
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let win = app.get_window("main").unwrap();
|
let handle = app.handle();
|
||||||
|
std::thread::spawn(move || {
|
||||||
#[cfg(target_os = "macos")]
|
let win = create_window(handle);
|
||||||
win.position_traffic_lights();
|
if let Err(e) = win.show() {
|
||||||
|
println!("Failed to show window {}", e)
|
||||||
Ok(())
|
}
|
||||||
})
|
});
|
||||||
.setup(|app| {
|
|
||||||
let dir = match is_dev() {
|
let dir = match is_dev() {
|
||||||
true => current_dir().unwrap(),
|
true => current_dir().unwrap(),
|
||||||
false => app.path_resolver().app_data_dir().unwrap(),
|
false => app.path_resolver().app_data_dir().unwrap(),
|
||||||
@@ -521,40 +525,6 @@ fn main() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_menu_event(|event| {
|
|
||||||
match event.menu_item_id() {
|
|
||||||
"quit" => std::process::exit(0),
|
|
||||||
"close" => event.window().close().unwrap(),
|
|
||||||
"zoom_reset" => event.window().emit("zoom", 0).unwrap(),
|
|
||||||
"zoom_in" => event.window().emit("zoom", 1).unwrap(),
|
|
||||||
"zoom_out" => event.window().emit("zoom", -1).unwrap(),
|
|
||||||
"toggle_sidebar" => event.window().emit("toggle_sidebar", true).unwrap(),
|
|
||||||
"refresh" => event.window().emit("refresh", true).unwrap(),
|
|
||||||
"send_request" => event.window().emit("send_request", true).unwrap(),
|
|
||||||
"toggle_devtools" => {
|
|
||||||
if event.window().is_devtools_open() {
|
|
||||||
event.window().close_devtools();
|
|
||||||
} else {
|
|
||||||
event.window().open_devtools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.on_window_event(|e| {
|
|
||||||
let apply_offset = || {
|
|
||||||
let win = e.window();
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
win.position_traffic_lights();
|
|
||||||
};
|
|
||||||
|
|
||||||
match e.event() {
|
|
||||||
WindowEvent::Resized(..) => apply_offset(),
|
|
||||||
WindowEvent::ThemeChanged(..) => apply_offset(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
greet,
|
greet,
|
||||||
workspaces,
|
workspaces,
|
||||||
@@ -582,3 +552,102 @@ fn is_dev() -> bool {
|
|||||||
let env = option_env!("YAAK_ENV");
|
let env = option_env!("YAAK_ENV");
|
||||||
env.unwrap_or("production") != "production"
|
env.unwrap_or("production") != "production"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_window(handle: AppHandle<Wry>) -> Window<Wry> {
|
||||||
|
let default_menu = Menu::os_default("Yaak".to_string().as_str());
|
||||||
|
let mut test_menu = Menu::new()
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("send_request".to_string(), "Send Request")
|
||||||
|
.accelerator("CmdOrCtrl+r"),
|
||||||
|
)
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("zoom_reset".to_string(), "Zoom to Actual Size")
|
||||||
|
.accelerator("CmdOrCtrl+0"),
|
||||||
|
)
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("zoom_in".to_string(), "Zoom In").accelerator("CmdOrCtrl+Plus"),
|
||||||
|
)
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("zoom_out".to_string(), "Zoom Out").accelerator("CmdOrCtrl+-"),
|
||||||
|
)
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("toggle_sidebar".to_string(), "Toggle Sidebar")
|
||||||
|
.accelerator("CmdOrCtrl+b"),
|
||||||
|
);
|
||||||
|
if is_dev() {
|
||||||
|
test_menu = test_menu
|
||||||
|
.add_native_item(MenuItem::Separator)
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("refresh".to_string(), "Refresh")
|
||||||
|
.accelerator("CmdOrCtrl + Shift + r"),
|
||||||
|
)
|
||||||
|
.add_item(
|
||||||
|
CustomMenuItem::new("toggle_devtools".to_string(), "Open Devtools")
|
||||||
|
.accelerator("CmdOrCtrl + Option + i"),
|
||||||
|
)
|
||||||
|
.add_item(CustomMenuItem::new("new_window".to_string(), "New Window"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let submenu = Submenu::new("Test Menu", test_menu);
|
||||||
|
|
||||||
|
let menu = default_menu.add_submenu(submenu);
|
||||||
|
let window_id = generate_id("win");
|
||||||
|
let win = tauri::WindowBuilder::new(
|
||||||
|
&handle,
|
||||||
|
window_id,
|
||||||
|
tauri::WindowUrl::App("workspaces".into()),
|
||||||
|
)
|
||||||
|
.menu(menu)
|
||||||
|
.fullscreen(false)
|
||||||
|
.resizable(true)
|
||||||
|
.inner_size(1100.0, 600.0)
|
||||||
|
.hidden_title(true)
|
||||||
|
.title("Yaak")
|
||||||
|
.title_bar_style(TitleBarStyle::Overlay)
|
||||||
|
.build()
|
||||||
|
.expect("failed to build window");
|
||||||
|
|
||||||
|
let win2 = win.clone();
|
||||||
|
win.on_menu_event(move |event| {
|
||||||
|
match event.menu_item_id() {
|
||||||
|
"quit" => std::process::exit(0),
|
||||||
|
"close" => win2.close().unwrap(),
|
||||||
|
"zoom_reset" => win2.emit("zoom", 0).unwrap(),
|
||||||
|
"zoom_in" => win2.emit("zoom", 1).unwrap(),
|
||||||
|
"zoom_out" => win2.emit("zoom", -1).unwrap(),
|
||||||
|
"toggle_sidebar" => win2.emit("toggle_sidebar", true).unwrap(),
|
||||||
|
"refresh" => win2.emit("refresh", true).unwrap(),
|
||||||
|
"send_request" => win2.emit("send_request", true).unwrap(),
|
||||||
|
"new_window" => {
|
||||||
|
create_window(handle.clone()).show().unwrap();
|
||||||
|
}
|
||||||
|
"toggle_devtools" => {
|
||||||
|
if win2.is_devtools_open() {
|
||||||
|
win2.close_devtools();
|
||||||
|
} else {
|
||||||
|
win2.open_devtools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let win3 = win.clone();
|
||||||
|
win.on_window_event(move |e| {
|
||||||
|
let apply_offset = || {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
win3.position_traffic_lights();
|
||||||
|
};
|
||||||
|
|
||||||
|
match e {
|
||||||
|
WindowEvent::Resized(..) => apply_offset(),
|
||||||
|
WindowEvent::ThemeChanged(..) => apply_offset(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
win.position_traffic_lights();
|
||||||
|
|
||||||
|
win
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::types::chrono::NaiveDateTime;
|
use sqlx::types::chrono::NaiveDateTime;
|
||||||
use sqlx::types::Json;
|
use sqlx::types::{Json, JsonValue};
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -11,6 +12,7 @@ pub struct Workspace {
|
|||||||
pub model: String,
|
pub model: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
pub updated_by: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
@@ -31,6 +33,7 @@ pub struct HttpRequest {
|
|||||||
pub model: String,
|
pub model: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
pub updated_by: String,
|
||||||
pub sort_priority: f64,
|
pub sort_priority: f64,
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -38,6 +41,8 @@ pub struct HttpRequest {
|
|||||||
pub method: String,
|
pub method: String,
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub body_type: Option<String>,
|
pub body_type: Option<String>,
|
||||||
|
pub authentication: Json<HashMap<String, JsonValue>>,
|
||||||
|
pub authentication_type: Option<String>,
|
||||||
pub headers: Json<Vec<HttpRequestHeader>>,
|
pub headers: Json<Vec<HttpRequestHeader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +62,7 @@ pub struct HttpResponse {
|
|||||||
pub request_id: String,
|
pub request_id: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
pub updated_by: String,
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub elapsed: i64,
|
pub elapsed: i64,
|
||||||
@@ -72,6 +78,7 @@ pub struct KeyValue {
|
|||||||
pub model: String,
|
pub model: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
pub updated_by: String,
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
@@ -94,9 +101,9 @@ pub async fn set_key_value(
|
|||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to insert key value");
|
.expect("Failed to insert key value");
|
||||||
|
|
||||||
get_key_value(namespace, key, pool).await
|
get_key_value(namespace, key, pool).await
|
||||||
}
|
}
|
||||||
@@ -105,23 +112,23 @@ pub async fn get_key_value(namespace: &str, key: &str, pool: &Pool<Sqlite>) -> O
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
KeyValue,
|
KeyValue,
|
||||||
r#"
|
r#"
|
||||||
SELECT model, created_at, updated_at, namespace, key, value
|
SELECT model, created_at, updated_at, updated_by, namespace, key, value
|
||||||
FROM key_values
|
FROM key_values
|
||||||
WHERE namespace = ? AND key = ?
|
WHERE namespace = ? AND key = ?
|
||||||
"#,
|
"#,
|
||||||
namespace,
|
namespace,
|
||||||
key,
|
key,
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_workspaces(pool: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx::Error> {
|
pub async fn find_workspaces(pool: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Workspace,
|
Workspace,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, created_at, updated_at, name, description
|
SELECT id, model, created_at, updated_at, updated_by, name, description
|
||||||
FROM workspaces
|
FROM workspaces
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
@@ -133,7 +140,7 @@ pub async fn get_workspace(id: &str, pool: &Pool<Sqlite>) -> Result<Workspace, s
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Workspace,
|
Workspace,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, created_at, updated_at, name, description
|
SELECT id, model, created_at, updated_at, updated_by, name, description
|
||||||
FROM workspaces WHERE id = ?
|
FROM workspaces WHERE id = ?
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
@@ -153,25 +160,27 @@ pub async fn delete_workspace(id: &str, pool: &Pool<Sqlite>) -> Result<Workspace
|
|||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await;
|
.await;
|
||||||
Ok(workspace)
|
Ok(workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_workspace(
|
pub async fn create_workspace(
|
||||||
name: &str,
|
name: &str,
|
||||||
description: &str,
|
description: &str,
|
||||||
|
updated_by: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<Workspace, sqlx::Error> {
|
) -> Result<Workspace, sqlx::Error> {
|
||||||
let id = generate_id("wk");
|
let id = generate_id("wk");
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO workspaces (id, name, description)
|
INSERT INTO workspaces (id, updated_by, name, description)
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
updated_by,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await
|
||||||
@@ -180,11 +189,10 @@ pub async fn create_workspace(
|
|||||||
get_workspace(&id, pool).await
|
get_workspace(&id, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn duplicate_request(
|
pub async fn duplicate_request(id: &str, updated_by: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, sqlx::Error> {
|
||||||
id: &str,
|
let existing = get_request(id, pool)
|
||||||
pool: &Pool<Sqlite>,
|
.await
|
||||||
) -> Result<HttpRequest, sqlx::Error> {
|
.expect("Failed to get request to duplicate");
|
||||||
let existing = get_request(id, pool).await.expect("Failed to get request to duplicate");
|
|
||||||
// TODO: Figure out how to make this better
|
// TODO: Figure out how to make this better
|
||||||
let b2;
|
let b2;
|
||||||
let body = match existing.body {
|
let body = match existing.body {
|
||||||
@@ -194,6 +202,7 @@ pub async fn duplicate_request(
|
|||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
upsert_request(
|
upsert_request(
|
||||||
None,
|
None,
|
||||||
existing.workspace_id.as_str(),
|
existing.workspace_id.as_str(),
|
||||||
@@ -201,14 +210,17 @@ pub async fn duplicate_request(
|
|||||||
existing.method.as_str(),
|
existing.method.as_str(),
|
||||||
body,
|
body,
|
||||||
existing.body_type,
|
existing.body_type,
|
||||||
|
existing.authentication.0,
|
||||||
|
existing.authentication_type,
|
||||||
existing.url.as_str(),
|
existing.url.as_str(),
|
||||||
existing.headers.0,
|
existing.headers.0,
|
||||||
existing.sort_priority,
|
existing.sort_priority,
|
||||||
|
updated_by,
|
||||||
pool,
|
pool,
|
||||||
).await
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn upsert_request(
|
pub async fn upsert_request(
|
||||||
id: Option<&str>,
|
id: Option<&str>,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
@@ -216,9 +228,12 @@ pub async fn upsert_request(
|
|||||||
method: &str,
|
method: &str,
|
||||||
body: Option<&str>,
|
body: Option<&str>,
|
||||||
body_type: Option<String>,
|
body_type: Option<String>,
|
||||||
|
authentication: HashMap<String, JsonValue>,
|
||||||
|
authentication_type: Option<String>,
|
||||||
url: &str,
|
url: &str,
|
||||||
headers: Vec<HttpRequestHeader>,
|
headers: Vec<HttpRequestHeader>,
|
||||||
sort_priority: f64,
|
sort_priority: f64,
|
||||||
|
updated_by: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<HttpRequest, sqlx::Error> {
|
) -> Result<HttpRequest, sqlx::Error> {
|
||||||
let generated_id;
|
let generated_id;
|
||||||
@@ -230,6 +245,7 @@ pub async fn upsert_request(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let headers_json = Json(headers);
|
let headers_json = Json(headers);
|
||||||
|
let auth_json = Json(authentication);
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO http_requests (
|
INSERT INTO http_requests (
|
||||||
@@ -240,10 +256,13 @@ pub async fn upsert_request(
|
|||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
body_type,
|
body_type,
|
||||||
|
authentication,
|
||||||
|
authentication_type,
|
||||||
headers,
|
headers,
|
||||||
sort_priority
|
sort_priority,
|
||||||
|
updated_by
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (id) DO UPDATE SET
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
updated_at = CURRENT_TIMESTAMP,
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
name = excluded.name,
|
name = excluded.name,
|
||||||
@@ -251,8 +270,11 @@ pub async fn upsert_request(
|
|||||||
headers = excluded.headers,
|
headers = excluded.headers,
|
||||||
body = excluded.body,
|
body = excluded.body,
|
||||||
body_type = excluded.body_type,
|
body_type = excluded.body_type,
|
||||||
|
authentication = excluded.authentication,
|
||||||
|
authentication_type = excluded.authentication_type,
|
||||||
url = excluded.url,
|
url = excluded.url,
|
||||||
sort_priority = excluded.sort_priority
|
sort_priority = excluded.sort_priority,
|
||||||
|
updated_by = excluded.updated_by
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
workspace_id,
|
workspace_id,
|
||||||
@@ -261,8 +283,11 @@ pub async fn upsert_request(
|
|||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
body_type,
|
body_type,
|
||||||
|
auth_json,
|
||||||
|
authentication_type,
|
||||||
headers_json,
|
headers_json,
|
||||||
sort_priority,
|
sort_priority,
|
||||||
|
updated_by,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await
|
||||||
@@ -288,7 +313,10 @@ pub async fn find_requests(
|
|||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
body_type,
|
body_type,
|
||||||
|
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
|
||||||
|
authentication_type,
|
||||||
sort_priority,
|
sort_priority,
|
||||||
|
updated_by,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>"
|
||||||
FROM http_requests
|
FROM http_requests
|
||||||
WHERE workspace_id = ?
|
WHERE workspace_id = ?
|
||||||
@@ -314,7 +342,10 @@ pub async fn get_request(id: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, s
|
|||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
body_type,
|
body_type,
|
||||||
|
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
|
||||||
|
authentication_type,
|
||||||
sort_priority,
|
sort_priority,
|
||||||
|
updated_by,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpRequestHeader>>"
|
||||||
FROM http_requests
|
FROM http_requests
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@@ -350,6 +381,7 @@ pub async fn create_response(
|
|||||||
status_reason: Option<&str>,
|
status_reason: Option<&str>,
|
||||||
body: &str,
|
body: &str,
|
||||||
headers: Vec<HttpResponseHeader>,
|
headers: Vec<HttpResponseHeader>,
|
||||||
|
updated_by: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<HttpResponse, sqlx::Error> {
|
) -> Result<HttpResponse, sqlx::Error> {
|
||||||
let req = get_request(request_id, pool)
|
let req = get_request(request_id, pool)
|
||||||
@@ -359,8 +391,19 @@ pub async fn create_response(
|
|||||||
let headers_json = Json(headers);
|
let headers_json = Json(headers);
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)
|
INSERT INTO http_responses (
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
id,
|
||||||
|
request_id,
|
||||||
|
workspace_id,
|
||||||
|
elapsed,
|
||||||
|
url,
|
||||||
|
status,
|
||||||
|
status_reason,
|
||||||
|
body,
|
||||||
|
headers,
|
||||||
|
updated_by
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
request_id,
|
request_id,
|
||||||
@@ -371,6 +414,7 @@ pub async fn create_response(
|
|||||||
status_reason,
|
status_reason,
|
||||||
body,
|
body,
|
||||||
headers_json,
|
headers_json,
|
||||||
|
updated_by,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await
|
||||||
@@ -381,23 +425,25 @@ pub async fn create_response(
|
|||||||
|
|
||||||
pub async fn update_response_if_id(
|
pub async fn update_response_if_id(
|
||||||
response: HttpResponse,
|
response: HttpResponse,
|
||||||
|
updated_by: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<HttpResponse, sqlx::Error> {
|
) -> Result<HttpResponse, sqlx::Error> {
|
||||||
if response.id == "" {
|
if response.id == "" {
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
}
|
}
|
||||||
return update_response(response, pool).await;
|
return update_response(response, updated_by, pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_response(
|
pub async fn update_response(
|
||||||
response: HttpResponse,
|
response: HttpResponse,
|
||||||
|
updated_by: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<HttpResponse, sqlx::Error> {
|
) -> Result<HttpResponse, sqlx::Error> {
|
||||||
let headers_json = Json(response.headers);
|
let headers_json = Json(response.headers);
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =
|
UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_by, updated_at) =
|
||||||
(?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;
|
(?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;
|
||||||
"#,
|
"#,
|
||||||
response.elapsed,
|
response.elapsed,
|
||||||
response.url,
|
response.url,
|
||||||
@@ -406,6 +452,7 @@ pub async fn update_response(
|
|||||||
response.body,
|
response.body,
|
||||||
response.error,
|
response.error,
|
||||||
headers_json,
|
headers_json,
|
||||||
|
updated_by,
|
||||||
response.id,
|
response.id,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
@@ -418,7 +465,7 @@ pub async fn get_response(id: &str, pool: &Pool<Sqlite>) -> Result<HttpResponse,
|
|||||||
sqlx::query_as_unchecked!(
|
sqlx::query_as_unchecked!(
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, updated_at, created_at,
|
SELECT id, model, workspace_id, request_id, updated_at, updated_by, created_at,
|
||||||
status, status_reason, body, elapsed, url, error,
|
status, status_reason, body, elapsed, url, error,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||||
FROM http_responses
|
FROM http_responses
|
||||||
@@ -437,7 +484,7 @@ pub async fn find_responses(
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, updated_at,
|
SELECT id, model, workspace_id, request_id, updated_at, updated_by,
|
||||||
created_at, status, status_reason, body, elapsed, url, error,
|
created_at, status, status_reason, body, elapsed, url, error,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||||
FROM http_responses
|
FROM http_responses
|
||||||
|
|||||||
@@ -11,19 +11,7 @@
|
|||||||
"version": "0.0.2"
|
"version": "0.0.2"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"windows": [
|
"windows": [],
|
||||||
{
|
|
||||||
"fullscreen": false,
|
|
||||||
"hiddenTitle": true,
|
|
||||||
"resizable": true,
|
|
||||||
"title": "Yaak",
|
|
||||||
"titleBarStyle": "Overlay",
|
|
||||||
"height": 600,
|
|
||||||
"width": 1100,
|
|
||||||
"minWidth": 400,
|
|
||||||
"minHeight": 400
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
"all": false,
|
"all": false,
|
||||||
"fs": {
|
"fs": {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
|
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
||||||
import { persistQueryClient } from '@tanstack/react-query-persist-client';
|
import { persistQueryClient } from '@tanstack/react-query-persist-client';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
@@ -21,6 +20,7 @@ import { extractKeyValue, getKeyValue, setKeyValue } from '../lib/keyValueStore'
|
|||||||
import type { HttpRequest, HttpResponse, KeyValue, Workspace } from '../lib/models';
|
import type { HttpRequest, HttpResponse, KeyValue, Workspace } from '../lib/models';
|
||||||
import { AppRouter } from './AppRouter';
|
import { AppRouter } from './AppRouter';
|
||||||
import { DialogProvider } from './DialogContext';
|
import { DialogProvider } from './DialogContext';
|
||||||
|
import { appWindow, WebviewWindow } from '@tauri-apps/api/window';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@@ -43,10 +43,13 @@ persistQueryClient({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await listen('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => {
|
await listen('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => {
|
||||||
|
if (keyValue.updatedBy === appWindow.label) return;
|
||||||
queryClient.setQueryData(keyValueQueryKey(keyValue), extractKeyValue(keyValue));
|
queryClient.setQueryData(keyValueQueryKey(keyValue), extractKeyValue(keyValue));
|
||||||
});
|
});
|
||||||
|
|
||||||
await listen('updated_request', ({ payload: request }: { payload: HttpRequest }) => {
|
await listen('updated_request', ({ payload: request }: { payload: HttpRequest }) => {
|
||||||
|
if (request.updatedBy === appWindow.label) return;
|
||||||
|
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
requestsQueryKey(request.workspaceId),
|
requestsQueryKey(request.workspaceId),
|
||||||
(requests: HttpRequest[] = []) => {
|
(requests: HttpRequest[] = []) => {
|
||||||
@@ -72,6 +75,8 @@ await listen('updated_response', ({ payload: response }: { payload: HttpResponse
|
|||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
responsesQueryKey(response.requestId),
|
responsesQueryKey(response.requestId),
|
||||||
(responses: HttpResponse[] = []) => {
|
(responses: HttpResponse[] = []) => {
|
||||||
|
if (response.updatedBy === appWindow.label) return;
|
||||||
|
|
||||||
const newResponses = [];
|
const newResponses = [];
|
||||||
let found = false;
|
let found = false;
|
||||||
for (const r of responses) {
|
for (const r of responses) {
|
||||||
@@ -92,6 +97,8 @@ await listen('updated_response', ({ payload: response }: { payload: HttpResponse
|
|||||||
|
|
||||||
await listen('updated_workspace', ({ payload: workspace }: { payload: Workspace }) => {
|
await listen('updated_workspace', ({ payload: workspace }: { payload: Workspace }) => {
|
||||||
queryClient.setQueryData(workspacesQueryKey(), (workspaces: Workspace[] = []) => {
|
queryClient.setQueryData(workspacesQueryKey(), (workspaces: Workspace[] = []) => {
|
||||||
|
if (workspace.updatedBy === appWindow.label) return;
|
||||||
|
|
||||||
const newWorkspaces = [];
|
const newWorkspaces = [];
|
||||||
let found = false;
|
let found = false;
|
||||||
for (const w of workspaces) {
|
for (const w of workspaces) {
|
||||||
@@ -175,7 +182,7 @@ export function App() {
|
|||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<DialogProvider>
|
<DialogProvider>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
{/*<ReactQueryDevtools initialIsOpen={false} />*/}
|
||||||
</DialogProvider>
|
</DialogProvider>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
|
|||||||
@@ -62,9 +62,14 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
});
|
});
|
||||||
const req: HttpRequest = { ...baseRequest, body, id: '' };
|
const req: HttpRequest = { ...baseRequest, body, id: '' };
|
||||||
sendEphemeralRequest(req).then((response) => {
|
sendEphemeralRequest(req).then((response) => {
|
||||||
const { data } = JSON.parse(response.body);
|
try {
|
||||||
const schema = buildClientSchema(data);
|
const { data } = JSON.parse(response.body);
|
||||||
setGraphqlExtension(graphql(schema, {}));
|
const schema = buildClientSchema(data);
|
||||||
|
setGraphqlExtension(graphql(schema, {}));
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Failed to parse introspection query', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [baseRequest.url]);
|
}, [baseRequest.url]);
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export function Overlay({ zIndex = 30, open, onClose, portalName, children }: Pr
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="absolute inset-0 bg-gray-600/60 dark:bg-black/50"
|
className="absolute inset-0 bg-gray-600/60 dark:bg-black/50"
|
||||||
/>
|
/>
|
||||||
|
{/* Add region to still be able to drag the window */}
|
||||||
|
<div data-tauri-drag-region className="absolute top-0 left-0 right-0 h-md" />
|
||||||
{children}
|
{children}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
@@ -6,7 +7,7 @@ import { useKeyValue } from '../hooks/useKeyValue';
|
|||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import type { HttpHeader, HttpRequest } from '../lib/models';
|
import type { HttpHeader, HttpRequest } from '../lib/models';
|
||||||
import { HttpRequestBodyType } from '../lib/models';
|
import { BODY_TYPE_GRAPHQL, BODY_TYPE_JSON, BODY_TYPE_NONE, BODY_TYPE_XML } from '../lib/models';
|
||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
import type { TabItem } from './core/Tabs/Tabs';
|
import type { TabItem } from './core/Tabs/Tabs';
|
||||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||||
@@ -32,45 +33,64 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
defaultValue: 'body',
|
defaultValue: 'body',
|
||||||
});
|
});
|
||||||
|
|
||||||
const tabs: TabItem<HttpRequest['bodyType']>[] = useMemo(
|
const tabs: TabItem[] = useMemo(
|
||||||
() => [
|
() =>
|
||||||
{
|
activeRequest === null
|
||||||
value: 'body',
|
? []
|
||||||
label: activeRequest?.bodyType ?? 'No Body',
|
: [
|
||||||
options: {
|
{
|
||||||
onChange: async (bodyType: HttpRequest['bodyType']) => {
|
value: 'body',
|
||||||
const patch: Partial<HttpRequest> = { bodyType };
|
options: {
|
||||||
if (bodyType == HttpRequestBodyType.GraphQL) {
|
value: activeRequest.bodyType,
|
||||||
patch.method = 'POST';
|
items: [
|
||||||
patch.headers = [
|
{ label: 'No Body', shortLabel: 'Body', value: BODY_TYPE_NONE },
|
||||||
...(activeRequest?.headers.filter((h) => h.name.toLowerCase() !== 'content-type') ??
|
{ label: 'JSON', value: BODY_TYPE_JSON },
|
||||||
[]),
|
{ label: 'XML', value: BODY_TYPE_XML },
|
||||||
{
|
{ label: 'GraphQL', value: BODY_TYPE_GRAPHQL },
|
||||||
name: 'Content-Type',
|
],
|
||||||
value: 'application/json',
|
onChange: async (bodyType) => {
|
||||||
enabled: true,
|
const patch: Partial<HttpRequest> = { bodyType };
|
||||||
|
if (bodyType == BODY_TYPE_GRAPHQL) {
|
||||||
|
patch.method = 'POST';
|
||||||
|
patch.headers = [
|
||||||
|
...(activeRequest?.headers.filter(
|
||||||
|
(h) => h.name.toLowerCase() !== 'content-type',
|
||||||
|
) ?? []),
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'application/json',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
setTimeout(() => {
|
||||||
|
setForceUpdateHeaderEditorKey((u) => u + 1);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
await updateRequest.mutate(patch);
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
setTimeout(() => {
|
},
|
||||||
setForceUpdateHeaderEditorKey((u) => u + 1);
|
{
|
||||||
}, 100);
|
value: 'auth',
|
||||||
}
|
label: 'Auth',
|
||||||
await updateRequest.mutate(patch);
|
options: {
|
||||||
},
|
value: activeRequest.authenticationType,
|
||||||
value: activeRequest?.bodyType ?? null,
|
items: [
|
||||||
items: [
|
{ label: 'No Auth', shortLabel: 'Auth', value: null },
|
||||||
{ label: 'No Body', value: null },
|
{ label: 'Basic', value: 'basic' },
|
||||||
{ label: 'JSON', value: HttpRequestBodyType.JSON },
|
],
|
||||||
{ label: 'XML', value: HttpRequestBodyType.XML },
|
onChange: async (a) => {
|
||||||
{ label: 'GraphQL', value: HttpRequestBodyType.GraphQL },
|
await updateRequest.mutate({
|
||||||
|
authenticationType: a,
|
||||||
|
authentication: { username: '', password: '' },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ value: 'params', label: 'URL Params' },
|
||||||
|
{ value: 'headers', label: 'Headers' },
|
||||||
],
|
],
|
||||||
},
|
[activeRequest?.bodyType, activeRequest?.headers, activeRequest?.authenticationType],
|
||||||
},
|
|
||||||
{ value: 'params', label: 'URL Params' },
|
|
||||||
{ value: 'headers', label: 'Headers' },
|
|
||||||
{ value: 'auth', label: 'Auth' },
|
|
||||||
],
|
|
||||||
[activeRequest?.bodyType, activeRequest?.headers],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBodyChange = useCallback((body: string) => updateRequest.mutate({ body }), []);
|
const handleBodyChange = useCallback((body: string) => updateRequest.mutate({ body }), []);
|
||||||
@@ -79,6 +99,9 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const forceUpdateKey =
|
||||||
|
activeRequest?.updatedBy === appWindow.label ? undefined : activeRequest?.updatedAt;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
@@ -86,7 +109,12 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
>
|
>
|
||||||
{activeRequest && (
|
{activeRequest && (
|
||||||
<>
|
<>
|
||||||
<UrlBar id={activeRequest.id} url={activeRequest.url} method={activeRequest.method} />
|
<UrlBar
|
||||||
|
key={forceUpdateKey}
|
||||||
|
id={activeRequest.id}
|
||||||
|
url={activeRequest.url}
|
||||||
|
method={activeRequest.method}
|
||||||
|
/>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab.value}
|
value={activeTab.value}
|
||||||
onChangeValue={activeTab.set}
|
onChangeValue={activeTab.set}
|
||||||
@@ -110,7 +138,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
<ParametersEditor key={activeRequestId} parameters={[]} onChange={() => null} />
|
<ParametersEditor key={activeRequestId} parameters={[]} onChange={() => null} />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value="body" className="pl-3 mt-1">
|
<TabContent value="body" className="pl-3 mt-1">
|
||||||
{activeRequest.bodyType === HttpRequestBodyType.JSON ? (
|
{activeRequest.bodyType === BODY_TYPE_JSON ? (
|
||||||
<Editor
|
<Editor
|
||||||
key={activeRequest.id}
|
key={activeRequest.id}
|
||||||
useTemplating
|
useTemplating
|
||||||
@@ -122,7 +150,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
onChange={handleBodyChange}
|
onChange={handleBodyChange}
|
||||||
format={(v) => tryFormatJson(v)}
|
format={(v) => tryFormatJson(v)}
|
||||||
/>
|
/>
|
||||||
) : activeRequest.bodyType === HttpRequestBodyType.XML ? (
|
) : activeRequest.bodyType === BODY_TYPE_XML ? (
|
||||||
<Editor
|
<Editor
|
||||||
key={activeRequest.id}
|
key={activeRequest.id}
|
||||||
useTemplating
|
useTemplating
|
||||||
@@ -133,7 +161,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
contentType="text/xml"
|
contentType="text/xml"
|
||||||
onChange={handleBodyChange}
|
onChange={handleBodyChange}
|
||||||
/>
|
/>
|
||||||
) : activeRequest.bodyType === HttpRequestBodyType.GraphQL ? (
|
) : activeRequest.bodyType === BODY_TYPE_GRAPHQL ? (
|
||||||
<GraphQLEditor
|
<GraphQLEditor
|
||||||
key={activeRequest.id}
|
key={activeRequest.id}
|
||||||
baseRequest={activeRequest}
|
baseRequest={activeRequest}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
import { useSidebarDisplay } from '../hooks/useSidebarDisplay';
|
import { useSidebarDisplay } from '../hooks/useSidebarDisplay';
|
||||||
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
|
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
|
||||||
|
import { Button } from './core/Button';
|
||||||
import { Overlay } from './Overlay';
|
import { Overlay } from './Overlay';
|
||||||
import { RequestResponse } from './RequestResponse';
|
import { RequestResponse } from './RequestResponse';
|
||||||
import { ResizeHandle } from './ResizeHandle';
|
import { ResizeHandle } from './ResizeHandle';
|
||||||
@@ -90,6 +91,14 @@ export default function Workspace() {
|
|||||||
[sideWidth, floating],
|
[sideWidth, floating],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (windowSize.width <= 100) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button>Send</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={styles}
|
style={styles}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
|||||||
className,
|
className,
|
||||||
'outline-none whitespace-nowrap',
|
'outline-none whitespace-nowrap',
|
||||||
// 'border border-transparent focus-visible:border-focus',
|
// 'border border-transparent focus-visible:border-focus',
|
||||||
'focus-visible:ring ring-blue-300',
|
'focus-visible-or-class:ring ring-blue-300',
|
||||||
'rounded-md flex items-center',
|
'rounded-md flex items-center',
|
||||||
colorStyles[color || 'default'],
|
colorStyles[color || 'default'],
|
||||||
justify === 'start' && 'justify-start',
|
justify === 'start' && 'justify-start',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import FocusTrap from 'focus-trap-react';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
|
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||||
import { Children, cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { Children, cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useKeyPressEvent, useMount } from 'react-use';
|
import { useKeyPressEvent } from 'react-use';
|
||||||
import { Portal } from '../Portal';
|
import { Portal } from '../Portal';
|
||||||
import { Separator } from './Separator';
|
import { Separator } from './Separator';
|
||||||
import { VStack } from './Stacks';
|
import { VStack } from './Stacks';
|
||||||
@@ -83,10 +83,6 @@ interface MenuProps {
|
|||||||
function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||||
if (triggerRect === undefined) return null;
|
if (triggerRect === undefined) return null;
|
||||||
|
|
||||||
useMount(() => {
|
|
||||||
console.log(document.activeElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [menuStyles, setMenuStyles] = useState<CSSProperties>({});
|
const [menuStyles, setMenuStyles] = useState<CSSProperties>({});
|
||||||
|
|
||||||
@@ -98,6 +94,10 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
|||||||
setMenuStyles({ maxHeight: windowBox.height - menuBox.top - 5 });
|
setMenuStyles({ maxHeight: windowBox.height - menuBox.top - 5 });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useKeyPressEvent('Escape', () => {
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
|
||||||
useKeyPressEvent('ArrowUp', () => {
|
useKeyPressEvent('ArrowUp', () => {
|
||||||
setSelectedIndex((currIndex) => {
|
setSelectedIndex((currIndex) => {
|
||||||
let nextIndex = (currIndex ?? 0) - 1;
|
let nextIndex = (currIndex ?? 0) - 1;
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { Icon } from './Icon';
|
|||||||
|
|
||||||
export interface RadioDropdownItem<T> {
|
export interface RadioDropdownItem<T> {
|
||||||
label: string;
|
label: string;
|
||||||
|
shortLabel?: string;
|
||||||
value: T;
|
value: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RadioDropdownProps<T> {
|
export interface RadioDropdownProps<T = string | null> {
|
||||||
value: T;
|
value: T;
|
||||||
onChange: (value: T) => void;
|
onChange: (value: T) => void;
|
||||||
items: RadioDropdownItem<T>[];
|
items: RadioDropdownItem<T>[];
|
||||||
@@ -18,8 +19,9 @@ export interface RadioDropdownProps<T> {
|
|||||||
export function RadioDropdown<T>({ value, items, onChange, children }: RadioDropdownProps<T>) {
|
export function RadioDropdown<T>({ value, items, onChange, children }: RadioDropdownProps<T>) {
|
||||||
const dropdownItems = useMemo(
|
const dropdownItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
items.map(({ label, value: v }) => ({
|
items.map(({ label, shortLabel, value: v }) => ({
|
||||||
label,
|
label,
|
||||||
|
shortLabel,
|
||||||
onSelect: () => onChange(v),
|
onSelect: () => onChange(v),
|
||||||
leftSlot: <Icon icon={value === v ? 'check' : 'empty'} />,
|
leftSlot: <Icon icon={value === v ? 'check' : 'empty'} />,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -7,23 +7,27 @@ import type { RadioDropdownProps } from '../RadioDropdown';
|
|||||||
import { RadioDropdown } from '../RadioDropdown';
|
import { RadioDropdown } from '../RadioDropdown';
|
||||||
import { HStack } from '../Stacks';
|
import { HStack } from '../Stacks';
|
||||||
|
|
||||||
export type TabItem<T = string> = {
|
export type TabItem =
|
||||||
value: string;
|
| {
|
||||||
label: string;
|
value: string;
|
||||||
options?: Omit<RadioDropdownProps<T>, 'children'>;
|
label: string;
|
||||||
};
|
}
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
options: Omit<RadioDropdownProps, 'children'>;
|
||||||
|
};
|
||||||
|
|
||||||
interface Props<T = unknown> {
|
interface Props {
|
||||||
label: string;
|
label: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
onChangeValue: (value: string) => void;
|
onChangeValue: (value: string) => void;
|
||||||
tabs: TabItem<T>[];
|
tabs: TabItem[];
|
||||||
tabListClassName?: string;
|
tabListClassName?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tabs<T>({
|
export function Tabs({
|
||||||
value,
|
value,
|
||||||
onChangeValue,
|
onChangeValue,
|
||||||
label,
|
label,
|
||||||
@@ -31,7 +35,7 @@ export function Tabs<T>({
|
|||||||
tabs,
|
tabs,
|
||||||
className,
|
className,
|
||||||
tabListClassName,
|
tabListClassName,
|
||||||
}: Props<T>) {
|
}: Props) {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const handleTabChange = (value: string) => {
|
const handleTabChange = (value: string) => {
|
||||||
@@ -77,7 +81,8 @@ export function Tabs<T>({
|
|||||||
const btnClassName = classnames(
|
const btnClassName = classnames(
|
||||||
isActive ? 'bg-gray-100 text-gray-800' : 'text-gray-600 hover:text-gray-900',
|
isActive ? 'bg-gray-100 text-gray-800' : 'text-gray-600 hover:text-gray-900',
|
||||||
);
|
);
|
||||||
if (t.options) {
|
if ('options' in t) {
|
||||||
|
const option = t.options.items.find((i) => i.value === t.options?.value);
|
||||||
return (
|
return (
|
||||||
<RadioDropdown
|
<RadioDropdown
|
||||||
key={t.value}
|
key={t.value}
|
||||||
@@ -91,7 +96,7 @@ export function Tabs<T>({
|
|||||||
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
||||||
className={btnClassName}
|
className={btnClassName}
|
||||||
>
|
>
|
||||||
{t.options.items.find((i) => i.value === t.options?.value)?.label ?? ''}
|
{option?.shortLabel ?? option?.label ?? 'Unknown'}
|
||||||
<Icon icon="triangleDown" className="-mr-1.5" />
|
<Icon icon="triangleDown" className="-mr-1.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</RadioDropdown>
|
</RadioDropdown>
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ export function Confirm({ hide }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="end">
|
<HStack space={2} justifyContent="end">
|
||||||
<Button color="gray" onClick={hide}>
|
<Button className="focus" color="gray" onClick={hide}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button ref={focusRef} color="primary">
|
<Button className="focus" ref={focusRef} color="primary">
|
||||||
Confirm
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import type { Workspace } from '../lib/models';
|
import type { Workspace } from '../lib/models';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useWorkspaces } from './useWorkspaces';
|
import { useWorkspaces } from './useWorkspaces';
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { getRequest } from '../lib/store';
|
import { getRequest } from '../lib/store';
|
||||||
|
import { requestsQueryKey } from './useRequests';
|
||||||
|
|
||||||
export function useUpdateRequest(id: string | null) {
|
export function useUpdateRequest(id: string | null) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation<void, unknown, Partial<HttpRequest>>({
|
return useMutation<void, unknown, Partial<HttpRequest>>({
|
||||||
mutationFn: async (patch) => {
|
mutationFn: async (patch) => {
|
||||||
const request = await getRequest(id);
|
const request = await getRequest(id);
|
||||||
@@ -13,9 +15,19 @@ export function useUpdateRequest(id: string | null) {
|
|||||||
|
|
||||||
const updatedRequest = { ...request, ...patch };
|
const updatedRequest = { ...request, ...patch };
|
||||||
|
|
||||||
|
console.log('UPDATING REQUEST', patch);
|
||||||
await invoke('update_request', {
|
await invoke('update_request', {
|
||||||
request: updatedRequest,
|
request: updatedRequest,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onMutate: async (patch) => {
|
||||||
|
const request = await getRequest(id);
|
||||||
|
if (request === null) return;
|
||||||
|
queryClient.setQueryData(
|
||||||
|
requestsQueryKey(request?.workspaceId),
|
||||||
|
(requests: HttpRequest[] | undefined) =>
|
||||||
|
requests?.map((r) => (r.id === request.id ? { ...r, ...patch } : r)),
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
export interface BaseModel {
|
export interface BaseModel {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly createdAt: Date;
|
readonly createdAt: string;
|
||||||
readonly updatedAt: Date;
|
readonly updatedAt: string;
|
||||||
|
readonly updatedBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Workspace extends BaseModel {
|
export interface Workspace extends BaseModel {
|
||||||
@@ -16,11 +17,13 @@ export interface HttpHeader {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum HttpRequestBodyType {
|
export const BODY_TYPE_NONE = null;
|
||||||
GraphQL = 'graphql',
|
export const BODY_TYPE_GRAPHQL = 'graphql';
|
||||||
JSON = 'application/json',
|
export const BODY_TYPE_JSON = 'application/json';
|
||||||
XML = 'text/xml',
|
export const BODY_TYPE_XML = 'text/xml';
|
||||||
}
|
|
||||||
|
export const AUTH_TYPE_NONE = null;
|
||||||
|
export const AUTH_TYPE_BASIC = 'basic';
|
||||||
|
|
||||||
export interface HttpRequest extends BaseModel {
|
export interface HttpRequest extends BaseModel {
|
||||||
readonly workspaceId: string;
|
readonly workspaceId: string;
|
||||||
@@ -29,7 +32,11 @@ export interface HttpRequest extends BaseModel {
|
|||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
body: string | null;
|
body: string | null;
|
||||||
bodyType: HttpRequestBodyType | null;
|
bodyType: string | null;
|
||||||
|
authentication: any | null;
|
||||||
|
authenticationType: string | null;
|
||||||
|
auth: Record<string, string | number | null>;
|
||||||
|
authType: string | null;
|
||||||
method: string;
|
method: string;
|
||||||
headers: HttpHeader[];
|
headers: HttpHeader[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
require("@tailwindcss/container-queries"),
|
require("@tailwindcss/container-queries"),
|
||||||
plugin(function({ addVariant }) {
|
plugin(function({ addVariant }) {
|
||||||
addVariant('hocus', ['&:hover', '&:focus'])
|
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus'])
|
||||||
|
addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus'])
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user