mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:18:30 +02:00
Initial "plugin" system with importer (#7)
This commit is contained in:
4
Makefile
4
Makefile
@@ -5,3 +5,7 @@ sqlx-prepare:
|
|||||||
|
|
||||||
dev:
|
dev:
|
||||||
npm run tauri-dev
|
npm run tauri-dev
|
||||||
|
|
||||||
|
|
||||||
|
build:
|
||||||
|
./node_modules/.bin/tauri build
|
||||||
|
|||||||
4
plugins/hello-world/greet.js
Normal file
4
plugins/hello-world/greet.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export function greet() {
|
||||||
|
// Call Rust-provided fn!
|
||||||
|
sayHello('Plugin');
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { hello } from './hello.js';
|
import { greet } from './greet.js';
|
||||||
|
|
||||||
export function entrypoint() {
|
export function hello() {
|
||||||
hello();
|
greet();
|
||||||
console.log('Try JSON parse', JSON.parse(`{ "hello": 123 }`).hello);
|
console.log('Try JSON parse', JSON.parse(`{ "hello": 123 }`).hello);
|
||||||
console.log('Try RegExp', '123'.match(/[\d]+/));
|
console.log('Try RegExp', '123'.match(/[\d]+/));
|
||||||
}
|
}
|
||||||
100
plugins/insomnia-importer/Insomnia_hello-world.json
Normal file
100
plugins/insomnia-importer/Insomnia_hello-world.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"_type": "export",
|
||||||
|
"__export_format": 4,
|
||||||
|
"__export_date": "2023-11-01T23:41:02.844Z",
|
||||||
|
"__export_source": "insomnia.desktop.app:v8.3.0",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"_id": "req_c8ae891b0fe549a4a530a75da59b6e34",
|
||||||
|
"parentId": "wrk_ea69a78d6a0540f583d2ec80666a1724",
|
||||||
|
"modified": 1698767088880,
|
||||||
|
"created": 1698767077168,
|
||||||
|
"url": "https://schier.co",
|
||||||
|
"name": "My Request",
|
||||||
|
"description": "",
|
||||||
|
"method": "GET",
|
||||||
|
"body": {},
|
||||||
|
"parameters": [],
|
||||||
|
"headers": [{ "name": "User-Agent", "value": "insomnia/8.3.0" }],
|
||||||
|
"authentication": {},
|
||||||
|
"metaSortKey": -1698767077168,
|
||||||
|
"isPrivate": false,
|
||||||
|
"settingStoreCookies": true,
|
||||||
|
"settingSendCookies": true,
|
||||||
|
"settingDisableRenderRequestBody": false,
|
||||||
|
"settingEncodeUrl": true,
|
||||||
|
"settingRebuildPath": true,
|
||||||
|
"settingFollowRedirects": "global",
|
||||||
|
"_type": "request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "wrk_ea69a78d6a0540f583d2ec80666a1724",
|
||||||
|
"parentId": null,
|
||||||
|
"modified": 1698767073768,
|
||||||
|
"created": 1698767068649,
|
||||||
|
"name": "Hello World",
|
||||||
|
"description": "",
|
||||||
|
"scope": "collection",
|
||||||
|
"_type": "workspace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "env_90b3abd7ed857fd535396167018da33932100672",
|
||||||
|
"parentId": "wrk_ea69a78d6a0540f583d2ec80666a1724",
|
||||||
|
"modified": 1698881852559,
|
||||||
|
"created": 1698767068650,
|
||||||
|
"name": "Base Environment",
|
||||||
|
"data": { "base": true },
|
||||||
|
"dataPropertyOrder": { "&": ["base"] },
|
||||||
|
"color": null,
|
||||||
|
"isPrivate": false,
|
||||||
|
"metaSortKey": 1698767068650,
|
||||||
|
"_type": "environment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "jar_90b3abd7ed857fd535396167018da33932100672",
|
||||||
|
"parentId": "wrk_ea69a78d6a0540f583d2ec80666a1724",
|
||||||
|
"modified": 1698767090390,
|
||||||
|
"created": 1698767068651,
|
||||||
|
"name": "Default Jar",
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"key": "_gorilla_csrf",
|
||||||
|
"value": "MTY5ODc2NzA5MHxJa1Z1U0RCVVMzcDJhbEJFWkd0Q09WVkllbXBMVlhSd1VtaGFkVlpsVVhobVNVNDVTV2hDWmpFd1JtTTlJZ289fPkab2rsnQwWmJi-pCbg5Wz4O_6csc29ZcYOdB0tOLtD",
|
||||||
|
"expires": "2023-11-07T15:44:50.000Z",
|
||||||
|
"maxAge": 604800,
|
||||||
|
"domain": "schier.co",
|
||||||
|
"path": "/",
|
||||||
|
"httpOnly": true,
|
||||||
|
"hostOnly": true,
|
||||||
|
"creation": "2023-10-31T15:44:50.390Z",
|
||||||
|
"lastAccessed": "2023-10-31T15:44:50.390Z",
|
||||||
|
"sameSite": "lax",
|
||||||
|
"id": "672286917061701"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_type": "cookie_jar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "env_d04deba50c2f44b0b9bd01c53efebff4",
|
||||||
|
"parentId": "env_90b3abd7ed857fd535396167018da33932100672",
|
||||||
|
"modified": 1698882026143,
|
||||||
|
"created": 1698881855600,
|
||||||
|
"name": "Sub Environment",
|
||||||
|
"data": {
|
||||||
|
"string": "string",
|
||||||
|
"bool": true,
|
||||||
|
"number": 123,
|
||||||
|
"object": { "foo": "bar" },
|
||||||
|
"array": [1, 2, 3]
|
||||||
|
},
|
||||||
|
"dataPropertyOrder": {
|
||||||
|
"&": ["string", "bool", "number", "object", "array"],
|
||||||
|
"&~|object": ["foo"]
|
||||||
|
},
|
||||||
|
"color": null,
|
||||||
|
"isPrivate": false,
|
||||||
|
"metaSortKey": 1698881855600,
|
||||||
|
"_type": "environment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
plugins/insomnia-importer/importers/environment.js
Normal file
23
plugins/insomnia-importer/importers/environment.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Import an Insomnia environment object.
|
||||||
|
* @param {Object} e - The environment object to import.
|
||||||
|
*/
|
||||||
|
export function importEnvironment(e) {
|
||||||
|
if (e.parentId.startsWith('env_')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log('IMPORTING Environment', e._id, e.name, JSON.stringify(e, null, 2));
|
||||||
|
return {
|
||||||
|
id: e._id,
|
||||||
|
createdAt: new Date(e.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
workspaceId: e.parentId,
|
||||||
|
model: 'environment',
|
||||||
|
name: e.name,
|
||||||
|
variables: Object.entries(e.data).map(([name, value]) => ({
|
||||||
|
enabled: true,
|
||||||
|
name,
|
||||||
|
value: `${value}`,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
28
plugins/insomnia-importer/importers/request.js
Normal file
28
plugins/insomnia-importer/importers/request.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Import an Insomnia request object.
|
||||||
|
* @param {Object} r - The request object to import.
|
||||||
|
* @param {number} sortPriority - The sort priority to use for the request.
|
||||||
|
*/
|
||||||
|
export function importRequest(r, sortPriority = 0) {
|
||||||
|
console.log('IMPORTING REQUEST', r._id, r.name, JSON.stringify(r, null, 2));
|
||||||
|
return {
|
||||||
|
id: r._id,
|
||||||
|
createdAt: new Date(r.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(r.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
workspaceId: r.parentId,
|
||||||
|
model: 'http_request',
|
||||||
|
sortPriority,
|
||||||
|
name: r.name,
|
||||||
|
url: r.url,
|
||||||
|
body: null, // TODO: Import body
|
||||||
|
bodyType: null,
|
||||||
|
authentication: {}, // TODO: Import authentication
|
||||||
|
authenticationType: null,
|
||||||
|
method: r.method,
|
||||||
|
headers: (r.headers ?? []).map(({ name, value, disabled }) => ({
|
||||||
|
enabled: !disabled,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
14
plugins/insomnia-importer/importers/workspace.js
Normal file
14
plugins/insomnia-importer/importers/workspace.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Import an Insomnia workspace object.
|
||||||
|
* @param {Object} w - The workspace object to import.
|
||||||
|
*/
|
||||||
|
export function importWorkspace(w) {
|
||||||
|
console.log('IMPORTING Workpace', w._id, w.name, JSON.stringify(w, null, 2));
|
||||||
|
return {
|
||||||
|
id: w._id,
|
||||||
|
createdAt: new Date(w.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(w.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
model: 'workspace',
|
||||||
|
name: w.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
50
plugins/insomnia-importer/index.js
Normal file
50
plugins/insomnia-importer/index.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { importEnvironment } from './importers/environment.js';
|
||||||
|
import { importRequest } from './importers/request.js';
|
||||||
|
import { importWorkspace } from './importers/workspace.js';
|
||||||
|
|
||||||
|
const TYPES = {
|
||||||
|
workspace: 'workspace',
|
||||||
|
request: 'request',
|
||||||
|
environment: 'environment',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pluginHookImport(contents) {
|
||||||
|
const parsed = JSON.parse(contents);
|
||||||
|
if (!isObject(parsed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { _type, __export_format } = parsed;
|
||||||
|
if (_type !== 'export' || __export_format !== 4 || !Array.isArray(parsed.resources)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resources = {
|
||||||
|
workspaces: [],
|
||||||
|
requests: [],
|
||||||
|
environments: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const v of parsed.resources) {
|
||||||
|
if (v._type === TYPES.workspace) {
|
||||||
|
resources.workspaces.push(importWorkspace(v));
|
||||||
|
} else if (v._type === TYPES.environment) {
|
||||||
|
resources.environments.push(importEnvironment(v));
|
||||||
|
} else if (v._type === TYPES.request) {
|
||||||
|
resources.requests.push(importRequest(v));
|
||||||
|
} else {
|
||||||
|
console.log('UNKNOWN TYPE', v._type, JSON.stringify(v, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out any `null` values
|
||||||
|
resources.requests = resources.requests.filter(Boolean);
|
||||||
|
resources.environments = resources.environments.filter(Boolean);
|
||||||
|
resources.workspaces = resources.workspaces.filter(Boolean);
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(obj) {
|
||||||
|
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||||
|
}
|
||||||
68
src-tauri/Cargo.lock
generated
68
src-tauri/Cargo.lock
generated
@@ -133,6 +133,17 @@ dependencies = [
|
|||||||
"critical-section",
|
"critical-section",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.1.19",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -509,6 +520,30 @@ dependencies = [
|
|||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"clap_lex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cobs"
|
name = "cobs"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -1577,6 +1612,15 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -2392,7 +2436,7 @@ version = "1.16.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.3.3",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2561,6 +2605,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -4012,6 +4062,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"clap",
|
||||||
"cocoa 0.24.1",
|
"cocoa 0.24.1",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
@@ -4238,6 +4289,21 @@ dependencies = [
|
|||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thin-slice"
|
name = "thin-slice"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|||||||
@@ -29,7 +29,19 @@ reqwest = { version = "0.11.14", features = ["json"] }
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||||
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time", "offline"] }
|
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time", "offline"] }
|
||||||
tauri = { version = "1.3", features = ["config-toml", "devtools", "fs-read-file", "os-all", "protocol-asset", "shell-open", "system-tray", "updater", "window-start-dragging"] }
|
tauri = { version = "1.3", features = [
|
||||||
|
"cli",
|
||||||
|
"config-toml",
|
||||||
|
"devtools",
|
||||||
|
"fs-read-file",
|
||||||
|
"os-all",
|
||||||
|
"protocol-asset",
|
||||||
|
"shell-open",
|
||||||
|
"system-tray",
|
||||||
|
"updater",
|
||||||
|
"window-start-dragging",
|
||||||
|
"dialog-open",
|
||||||
|
] }
|
||||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tokio = { version = "1.25.0", features = ["sync"] }
|
tokio = { version = "1.25.0", features = ["sync"] }
|
||||||
uuid = "1.3.0"
|
uuid = "1.3.0"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE workspaces ADD COLUMN variables DEFAULT '[]' NOT NULL;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export function hello() {
|
|
||||||
sayHello('Plugin');
|
|
||||||
}
|
|
||||||
@@ -160,16 +160,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE workspace_id = ?\n ORDER BY created_at DESC\n "
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE workspace_id = ?\n ORDER BY created_at DESC\n "
|
||||||
},
|
},
|
||||||
"3ec4710d28a7f38608c96798d971217ac97788bcb639089d0c5750c0d339bc9a": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n UPDATE environments\n SET (name, variables, updated_at) = (?, ?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n "
|
|
||||||
},
|
|
||||||
"448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": {
|
"448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -180,6 +170,60 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
|
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
|
"5588db23df7f30dc75857e05395ebbcf2384e2ac0d7cb87f76d74c6d50781d7b": {
|
||||||
|
"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": "name",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id, model, created_at, updated_at, name, description,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM workspaces\n "
|
||||||
|
},
|
||||||
"5aa070e61995f8b1724efaa94c5f0cef5a4be6efda5d70354ad449d7d4b5aee4": {
|
"5aa070e61995f8b1724efaa94c5f0cef5a4be6efda5d70354ad449d7d4b5aee4": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -282,6 +326,16 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at DESC\n "
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at DESC\n "
|
||||||
},
|
},
|
||||||
|
"610223ad10b6e25926d486ba775a74b55625fcc4e6637d8a805d44ec3f3b9532": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO workspaces (id, name, description, variables)\n VALUES (?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n description = excluded.description,\n variables = excluded.variables\n "
|
||||||
|
},
|
||||||
"62475fd9483fb5eda01c937949da2ef66ac7005b4be06b87aa6210d462348aca": {
|
"62475fd9483fb5eda01c937949da2ef66ac7005b4be06b87aa6210d462348aca": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -452,16 +506,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM workspaces\n WHERE id = ?\n "
|
"query": "\n DELETE FROM workspaces\n WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
"86e32d6a6fadf35436f19b577a659c203a8d143cb3a8d6122951c5bf54a0888d": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n INSERT INTO environments (id, workspace_id, name, variables)\n VALUES (?, ?, ?, ?)\n "
|
|
||||||
},
|
|
||||||
"8947a2a90478277c42fe9b06bc1fa98197642a4d281a3dbc101be2c9c1fec36c": {
|
"8947a2a90478277c42fe9b06bc1fa98197642a4d281a3dbc101be2c9c1fec36c": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -648,102 +692,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
"caf3f21bf291dfbd36446592066e96c1f83abe96f6ea9211a3e049eb9c58a8c8": {
|
|
||||||
"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": "name",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "description",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces WHERE id = ?\n "
|
|
||||||
},
|
|
||||||
"cea4cae52f16ec78aca9a47b17117422d4f165e5a3b308c70fd1a180382475ea": {
|
|
||||||
"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": "name",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "description",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id, model, created_at, updated_at, name, description\n FROM workspaces\n "
|
|
||||||
},
|
|
||||||
"ced098adb79c0ee64e223b6e02371ef253920a2c342275de0fa9c181529a4adc": {
|
"ced098adb79c0ee64e223b6e02371ef253920a2c342275de0fa9c181529a4adc": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -850,24 +798,68 @@
|
|||||||
},
|
},
|
||||||
"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 INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n "
|
||||||
},
|
},
|
||||||
"e0f41023d877d94b7609ce910a71bd89c4827a558654b8ae14d85e6ba86990cf": {
|
"dbe457087a7bccbca4c1d673aa8e547df04530a7f860a6ccd4e20126a7cdfa4f": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [
|
||||||
"nullable": [],
|
{
|
||||||
|
"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": "name",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 2
|
"Right": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n UPDATE workspaces SET (name, updated_at) =\n (?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
"query": "\n SELECT id, model, created_at, updated_at, name, description,\n variables AS \"variables!: sqlx::types::Json<Vec<EnvironmentVariable>>\"\n FROM workspaces WHERE id = ?\n "
|
||||||
},
|
},
|
||||||
"f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": {
|
"dcc2f405f8e29d0599d86bcde509187e9cc5fc647067eaa5c738cb24e2f081e5": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"nullable": [],
|
"nullable": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 3
|
"Right": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n "
|
"query": "\n INSERT INTO environments (\n id,\n workspace_id,\n name,\n variables\n )\n VALUES (?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n variables = excluded.variables\n "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ use std::collections::HashMap;
|
|||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::process::exit;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tauri::TitleBarStyle;
|
use tauri::TitleBarStyle;
|
||||||
use tauri::{AppHandle, Menu, MenuItem, RunEvent, State, Submenu, Window, WindowUrl, Wry};
|
use tauri::{AppHandle, Menu, MenuItem, RunEvent, State, Submenu, Window, WindowUrl, Wry};
|
||||||
@@ -30,9 +31,9 @@ use tokio::sync::Mutex;
|
|||||||
use window_ext::TrafficLightWindowExt;
|
use window_ext::TrafficLightWindowExt;
|
||||||
|
|
||||||
mod models;
|
mod models;
|
||||||
|
mod plugin;
|
||||||
mod render;
|
mod render;
|
||||||
mod window_ext;
|
mod window_ext;
|
||||||
mod plugin;
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct CustomResponse {
|
pub struct CustomResponse {
|
||||||
@@ -72,8 +73,14 @@ async fn send_ephemeral_request(
|
|||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let response = models::HttpResponse::default();
|
let response = models::HttpResponse::default();
|
||||||
let environment_id2 = environment_id.unwrap_or("n/a").to_string();
|
let environment_id2 = environment_id.unwrap_or("n/a").to_string();
|
||||||
return actually_send_ephemeral_request(request, &response, &environment_id2, &app_handle, pool)
|
return actually_send_ephemeral_request(
|
||||||
.await;
|
request,
|
||||||
|
&response,
|
||||||
|
&environment_id2,
|
||||||
|
&app_handle,
|
||||||
|
pool,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn actually_send_ephemeral_request(
|
async fn actually_send_ephemeral_request(
|
||||||
@@ -248,6 +255,25 @@ async fn actually_send_ephemeral_request(
|
|||||||
Err(e) => response_err(response, e.to_string(), app_handle, pool).await,
|
Err(e) => response_err(response, e.to_string(), app_handle, pool).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[tauri::command]
|
||||||
|
async fn import_data(
|
||||||
|
window: Window<Wry>,
|
||||||
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
file_paths: Vec<&str>,
|
||||||
|
workspace_id: Option<&str>,
|
||||||
|
) -> Result<plugin::ImportedResources, String> {
|
||||||
|
let pool = &*db_instance.lock().await;
|
||||||
|
let workspace_id2 = workspace_id.unwrap_or_default();
|
||||||
|
let imported = plugin::run_plugin_import(
|
||||||
|
&window.app_handle(),
|
||||||
|
pool,
|
||||||
|
"insomnia-importer",
|
||||||
|
file_paths.first().unwrap(),
|
||||||
|
workspace_id2,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Ok(imported)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn send_request(
|
async fn send_request(
|
||||||
@@ -331,9 +357,15 @@ async fn create_workspace(
|
|||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<models::Workspace, String> {
|
) -> Result<models::Workspace, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let created_workspace = models::create_workspace(name, "", pool)
|
let created_workspace = models::upsert_workspace(
|
||||||
.await
|
pool,
|
||||||
.expect("Failed to create workspace");
|
models::Workspace {
|
||||||
|
name: name.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create workspace");
|
||||||
|
|
||||||
emit_and_return(&window, "created_model", created_workspace)
|
emit_and_return(&window, "created_model", created_workspace)
|
||||||
}
|
}
|
||||||
@@ -347,9 +379,17 @@ async fn create_environment(
|
|||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<models::Environment, String> {
|
) -> Result<models::Environment, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let created_environment = models::create_environment(workspace_id, name, variables, pool)
|
let created_environment = models::upsert_environment(
|
||||||
.await
|
pool,
|
||||||
.expect("Failed to create environment");
|
models::Environment {
|
||||||
|
workspace_id: workspace_id.to_string(),
|
||||||
|
name: name.to_string(),
|
||||||
|
variables: Json(variables),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create environment");
|
||||||
|
|
||||||
emit_and_return(&window, "created_model", created_environment)
|
emit_and_return(&window, "created_model", created_environment)
|
||||||
}
|
}
|
||||||
@@ -363,20 +403,15 @@ async fn create_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;
|
||||||
let headers = Vec::new();
|
|
||||||
let created_request = models::upsert_request(
|
let created_request = models::upsert_request(
|
||||||
None,
|
|
||||||
workspace_id,
|
|
||||||
name,
|
|
||||||
"GET",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
HashMap::new(),
|
|
||||||
None,
|
|
||||||
"",
|
|
||||||
headers,
|
|
||||||
sort_priority,
|
|
||||||
pool,
|
pool,
|
||||||
|
models::HttpRequest {
|
||||||
|
workspace_id: workspace_id.to_string(),
|
||||||
|
name: name.to_string(),
|
||||||
|
method: "GET".to_string(),
|
||||||
|
sort_priority,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create request");
|
.expect("Failed to create request");
|
||||||
@@ -405,7 +440,7 @@ async fn update_workspace(
|
|||||||
) -> Result<models::Workspace, String> {
|
) -> Result<models::Workspace, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
|
|
||||||
let updated_workspace = models::update_workspace(workspace, pool)
|
let updated_workspace = models::upsert_workspace(pool, workspace)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to update request");
|
.expect("Failed to update request");
|
||||||
|
|
||||||
@@ -420,14 +455,9 @@ async fn update_environment(
|
|||||||
) -> Result<models::Environment, String> {
|
) -> Result<models::Environment, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
|
|
||||||
let updated_environment = models::update_environment(
|
let updated_environment = models::upsert_environment(pool, environment)
|
||||||
environment.id.as_str(),
|
.await
|
||||||
environment.name.as_str(),
|
.expect("Failed to update environment");
|
||||||
environment.variables.0,
|
|
||||||
pool,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to update request");
|
|
||||||
|
|
||||||
emit_and_return(&window, "updated_model", updated_environment)
|
emit_and_return(&window, "updated_model", updated_environment)
|
||||||
}
|
}
|
||||||
@@ -439,35 +469,9 @@ async fn update_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;
|
||||||
|
let updated_request = models::upsert_request(pool, request)
|
||||||
// TODO: Figure out how to make this better
|
.await
|
||||||
let b2;
|
.expect("Failed to update request");
|
||||||
let body = match request.body {
|
|
||||||
Some(b) => {
|
|
||||||
b2 = b;
|
|
||||||
Some(b2.as_str())
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Figure out how to make this better
|
|
||||||
let updated_request = models::upsert_request(
|
|
||||||
Some(request.id.as_str()),
|
|
||||||
request.workspace_id.as_str(),
|
|
||||||
request.name.as_str(),
|
|
||||||
request.method.as_str(),
|
|
||||||
body,
|
|
||||||
request.body_type,
|
|
||||||
request.authentication.0,
|
|
||||||
request.authentication_type,
|
|
||||||
request.url.as_str(),
|
|
||||||
request.headers.0,
|
|
||||||
request.sort_priority,
|
|
||||||
pool,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to update request");
|
|
||||||
|
|
||||||
emit_and_return(&window, "updated_model", updated_request)
|
emit_and_return(&window, "updated_model", updated_request)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,10 +602,15 @@ async fn list_workspaces(
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to find workspaces");
|
.expect("Failed to find workspaces");
|
||||||
if workspaces.is_empty() {
|
if workspaces.is_empty() {
|
||||||
let workspace =
|
let workspace = models::upsert_workspace(
|
||||||
models::create_workspace("My Project", "This is the default workspace", pool)
|
pool,
|
||||||
.await
|
models::Workspace {
|
||||||
.expect("Failed to create workspace");
|
name: "My Project".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create workspace");
|
||||||
Ok(vec![workspace])
|
Ok(vec![workspace])
|
||||||
} else {
|
} else {
|
||||||
Ok(workspaces)
|
Ok(workspaces)
|
||||||
@@ -641,6 +650,7 @@ fn main() {
|
|||||||
let p_string = p.to_string_lossy().replace(' ', "%20");
|
let p_string = p.to_string_lossy().replace(' ', "%20");
|
||||||
let url = format!("sqlite://{}?mode=rwc", p_string);
|
let url = format!("sqlite://{}?mode=rwc", p_string);
|
||||||
println!("Connecting to database at {}", url);
|
println!("Connecting to database at {}", url);
|
||||||
|
|
||||||
tauri::async_runtime::block_on(async move {
|
tauri::async_runtime::block_on(async move {
|
||||||
let pool = SqlitePoolOptions::new()
|
let pool = SqlitePoolOptions::new()
|
||||||
.connect(url.as_str())
|
.connect(url.as_str())
|
||||||
@@ -648,12 +658,44 @@ fn main() {
|
|||||||
.expect("Failed to connect to database");
|
.expect("Failed to connect to database");
|
||||||
|
|
||||||
// Setup the DB handle
|
// Setup the DB handle
|
||||||
let m = Mutex::new(pool);
|
let m = Mutex::new(pool.clone());
|
||||||
migrate_db(app.handle(), &m)
|
migrate_db(app.handle(), &m)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to migrate database");
|
.expect("Failed to migrate database");
|
||||||
app.manage(m);
|
app.manage(m);
|
||||||
|
|
||||||
|
// TODO: Move this somewhere better
|
||||||
|
match app.get_cli_matches() {
|
||||||
|
Ok(matches) => {
|
||||||
|
let cmd = matches.subcommand.unwrap_or_default();
|
||||||
|
if cmd.name == "import" {
|
||||||
|
let arg_file = cmd
|
||||||
|
.matches
|
||||||
|
.args
|
||||||
|
.get("file")
|
||||||
|
.unwrap()
|
||||||
|
.value
|
||||||
|
.as_str()
|
||||||
|
.unwrap();
|
||||||
|
plugin::run_plugin_import(
|
||||||
|
&app.handle(),
|
||||||
|
&pool,
|
||||||
|
"insomnia-importer",
|
||||||
|
arg_file,
|
||||||
|
"wk_WN8Nrm2Awm",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
exit(0);
|
||||||
|
} else if cmd.name == "hello" {
|
||||||
|
plugin::run_plugin_hello(&app.handle(), "hello-world");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Nothing found: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -671,6 +713,7 @@ fn main() {
|
|||||||
get_environment,
|
get_environment,
|
||||||
get_request,
|
get_request,
|
||||||
get_workspace,
|
get_workspace,
|
||||||
|
import_data,
|
||||||
list_environments,
|
list_environments,
|
||||||
list_requests,
|
list_requests,
|
||||||
list_responses,
|
list_responses,
|
||||||
@@ -690,7 +733,6 @@ fn main() {
|
|||||||
let w = create_window(app_handle, None);
|
let w = create_window(app_handle, None);
|
||||||
w.restore_state(StateFlags::all())
|
w.restore_state(StateFlags::all())
|
||||||
.expect("Failed to restore window state");
|
.expect("Failed to restore window state");
|
||||||
plugin::test_plugins(&app_handle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitRequested { api, .. } => {
|
// ExitRequested { api, .. } => {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use sqlx::types::chrono::NaiveDateTime;
|
|||||||
use sqlx::types::{Json, JsonValue};
|
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, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub model: String,
|
pub model: String,
|
||||||
@@ -16,10 +16,11 @@ pub struct Workspace {
|
|||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
pub variables: Json<Vec<EnvironmentVariable>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
@@ -30,35 +31,44 @@ pub struct Environment {
|
|||||||
pub variables: Json<Vec<EnvironmentVariable>>,
|
pub variables: Json<Vec<EnvironmentVariable>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
fn default_enabled() -> bool {
|
||||||
#[serde(rename_all = "camelCase")]
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct EnvironmentVariable {
|
pub struct EnvironmentVariable {
|
||||||
#[serde(default)]
|
#[serde(default = "default_enabled")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct HttpRequestHeader {
|
pub struct HttpRequestHeader {
|
||||||
#[serde(default)]
|
#[serde(default = "default_enabled")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
|
fn default_http_request_method() -> String {
|
||||||
#[serde(rename_all = "camelCase")]
|
"GET".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct HttpRequest {
|
pub struct HttpRequest {
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
pub model: String,
|
pub model: String,
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub updated_at: NaiveDateTime,
|
|
||||||
pub sort_priority: f64,
|
pub sort_priority: f64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
#[serde(default = "default_http_request_method")]
|
||||||
pub method: String,
|
pub method: String,
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub body_type: Option<String>,
|
pub body_type: Option<String>,
|
||||||
@@ -67,15 +77,15 @@ pub struct HttpRequest {
|
|||||||
pub headers: Json<Vec<HttpRequestHeader>>,
|
pub headers: Json<Vec<HttpRequestHeader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct HttpResponseHeader {
|
pub struct HttpResponseHeader {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct HttpResponse {
|
pub struct HttpResponse {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub model: String,
|
pub model: String,
|
||||||
@@ -94,8 +104,8 @@ pub struct HttpResponse {
|
|||||||
pub headers: Json<Vec<HttpResponseHeader>>,
|
pub headers: Json<Vec<HttpResponseHeader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct KeyValue {
|
pub struct KeyValue {
|
||||||
pub model: String,
|
pub model: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
@@ -153,7 +163,8 @@ pub async fn find_workspaces(pool: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx
|
|||||||
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, name, description,
|
||||||
|
variables AS "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>"
|
||||||
FROM workspaces
|
FROM workspaces
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
@@ -165,7 +176,8 @@ 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, name, description,
|
||||||
|
variables AS "variables!: sqlx::types::Json<Vec<EnvironmentVariable>>"
|
||||||
FROM workspaces WHERE id = ?
|
FROM workspaces WHERE id = ?
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
@@ -193,27 +205,6 @@ pub async fn delete_workspace(id: &str, pool: &Pool<Sqlite>) -> Result<Workspace
|
|||||||
Ok(workspace)
|
Ok(workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_workspace(
|
|
||||||
name: &str,
|
|
||||||
description: &str,
|
|
||||||
pool: &Pool<Sqlite>,
|
|
||||||
) -> Result<Workspace, sqlx::Error> {
|
|
||||||
let id = generate_id(Some("wk"));
|
|
||||||
sqlx::query!(
|
|
||||||
r#"
|
|
||||||
INSERT INTO workspaces (id, name, description)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
"#,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
get_workspace(&id, pool).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find_environments(
|
pub async fn find_environments(
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
@@ -232,30 +223,6 @@ pub async fn find_environments(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_environment(
|
|
||||||
workspace_id: &str,
|
|
||||||
name: &str,
|
|
||||||
variables: Vec<EnvironmentVariable>,
|
|
||||||
pool: &Pool<Sqlite>,
|
|
||||||
) -> Result<Environment, sqlx::Error> {
|
|
||||||
let id = generate_id(Some("en"));
|
|
||||||
let trimmed_name = name.trim();
|
|
||||||
let variables_json = Json(variables);
|
|
||||||
sqlx::query!(
|
|
||||||
r#"
|
|
||||||
INSERT INTO environments (id, workspace_id, name, variables)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
"#,
|
|
||||||
id,
|
|
||||||
workspace_id,
|
|
||||||
trimmed_name,
|
|
||||||
variables_json,
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
get_environment(&id, pool).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environment, sqlx::Error> {
|
pub async fn delete_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environment, sqlx::Error> {
|
||||||
let env = get_environment(id, pool).await?;
|
let env = get_environment(id, pool).await?;
|
||||||
let _ = sqlx::query!(
|
let _ = sqlx::query!(
|
||||||
@@ -271,26 +238,37 @@ pub async fn delete_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environ
|
|||||||
Ok(env)
|
Ok(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_environment(
|
pub async fn upsert_environment(
|
||||||
id: &str,
|
|
||||||
name: &str,
|
|
||||||
variables: Vec<EnvironmentVariable>,
|
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
|
environment: Environment,
|
||||||
) -> Result<Environment, sqlx::Error> {
|
) -> Result<Environment, sqlx::Error> {
|
||||||
let variables_json = Json(variables);
|
let id = match environment.id.as_str() {
|
||||||
|
"" => generate_id(Some("ev")),
|
||||||
|
_ => environment.id.to_string(),
|
||||||
|
};
|
||||||
|
let trimmed_name = environment.name.trim();
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE environments
|
INSERT INTO environments (
|
||||||
SET (name, variables, updated_at) = (?, ?, CURRENT_TIMESTAMP)
|
id,
|
||||||
WHERE id = ?;
|
workspace_id,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
|
name = excluded.name,
|
||||||
|
variables = excluded.variables
|
||||||
"#,
|
"#,
|
||||||
name,
|
|
||||||
variables_json,
|
|
||||||
id,
|
id,
|
||||||
|
environment.workspace_id,
|
||||||
|
trimmed_name,
|
||||||
|
environment.variables,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
get_environment(id, pool).await
|
get_environment(&id, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environment, sqlx::Error> {
|
pub async fn get_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environment, sqlx::Error> {
|
||||||
@@ -315,60 +293,23 @@ pub async fn get_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environmen
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn duplicate_request(id: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, sqlx::Error> {
|
pub async fn duplicate_request(id: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, sqlx::Error> {
|
||||||
let existing = get_request(id, pool).await?;
|
let mut request = get_request(id, pool).await?.clone();
|
||||||
|
request.id = "".to_string();
|
||||||
// TODO: Figure out how to make this better
|
upsert_request(pool, request).await
|
||||||
let b2;
|
|
||||||
let body = match existing.body {
|
|
||||||
Some(b) => {
|
|
||||||
b2 = b;
|
|
||||||
Some(b2.as_str())
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
upsert_request(
|
|
||||||
None,
|
|
||||||
existing.workspace_id.as_str(),
|
|
||||||
existing.name.as_str(),
|
|
||||||
existing.method.as_str(),
|
|
||||||
body,
|
|
||||||
existing.body_type,
|
|
||||||
existing.authentication.0,
|
|
||||||
existing.authentication_type,
|
|
||||||
existing.url.as_str(),
|
|
||||||
existing.headers.0,
|
|
||||||
existing.sort_priority + 0.001,
|
|
||||||
pool,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upsert_request(
|
pub async fn upsert_request(
|
||||||
id: Option<&str>,
|
|
||||||
workspace_id: &str,
|
|
||||||
name: &str,
|
|
||||||
method: &str,
|
|
||||||
body: Option<&str>,
|
|
||||||
body_type: Option<String>,
|
|
||||||
authentication: HashMap<String, JsonValue>,
|
|
||||||
authentication_type: Option<String>,
|
|
||||||
url: &str,
|
|
||||||
headers: Vec<HttpRequestHeader>,
|
|
||||||
sort_priority: f64,
|
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
|
r: HttpRequest,
|
||||||
) -> Result<HttpRequest, sqlx::Error> {
|
) -> Result<HttpRequest, sqlx::Error> {
|
||||||
let generated_id;
|
let id = match r.id.as_str() {
|
||||||
let id = match id {
|
"" => generate_id(Some("rq")),
|
||||||
Some(v) => v,
|
_ => r.id.to_string(),
|
||||||
None => {
|
|
||||||
generated_id = generate_id(Some("rq"));
|
|
||||||
generated_id.as_str()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let headers_json = Json(headers);
|
let headers_json = Json(r.headers);
|
||||||
let auth_json = Json(authentication);
|
let auth_json = Json(r.authentication);
|
||||||
let trimmed_name = name.trim();
|
let trimmed_name = r.name.trim();
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO http_requests (
|
INSERT INTO http_requests (
|
||||||
@@ -398,20 +339,21 @@ pub async fn upsert_request(
|
|||||||
sort_priority = excluded.sort_priority
|
sort_priority = excluded.sort_priority
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
workspace_id,
|
r.workspace_id,
|
||||||
trimmed_name,
|
trimmed_name,
|
||||||
url,
|
r.url,
|
||||||
method,
|
r.method,
|
||||||
body,
|
r.body,
|
||||||
body_type,
|
r.body_type,
|
||||||
auth_json,
|
auth_json,
|
||||||
authentication_type,
|
r.authentication_type,
|
||||||
headers_json,
|
headers_json,
|
||||||
sort_priority,
|
r.sort_priority,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
get_request(id, pool).await
|
|
||||||
|
get_request(&id, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_requests(
|
pub async fn find_requests(
|
||||||
@@ -552,18 +494,29 @@ pub async fn update_response_if_id(
|
|||||||
return update_response(response, pool).await;
|
return update_response(response, pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_workspace(
|
pub async fn upsert_workspace(
|
||||||
workspace: Workspace,
|
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
|
workspace: Workspace,
|
||||||
) -> Result<Workspace, sqlx::Error> {
|
) -> Result<Workspace, sqlx::Error> {
|
||||||
|
let id = match workspace.id.as_str() {
|
||||||
|
"" => generate_id(Some("wk")),
|
||||||
|
_ => workspace.id.to_string(),
|
||||||
|
};
|
||||||
let trimmed_name = workspace.name.trim();
|
let trimmed_name = workspace.name.trim();
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE workspaces SET (name, updated_at) =
|
INSERT INTO workspaces (id, name, description, variables)
|
||||||
(?, CURRENT_TIMESTAMP) WHERE id = ?;
|
VALUES (?, ?, ?, ?)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
|
name = excluded.name,
|
||||||
|
description = excluded.description,
|
||||||
|
variables = excluded.variables
|
||||||
"#,
|
"#,
|
||||||
|
id,
|
||||||
trimmed_name,
|
trimmed_name,
|
||||||
workspace.id,
|
workspace.description,
|
||||||
|
workspace.variables,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::fs;
|
||||||
|
|
||||||
use boa_engine::{
|
use boa_engine::{
|
||||||
js_string,
|
js_string,
|
||||||
module::{ModuleLoader, SimpleModuleLoader},
|
module::{ModuleLoader, SimpleModuleLoader},
|
||||||
@@ -5,17 +7,96 @@ use boa_engine::{
|
|||||||
Context, JsArgs, JsNativeError, JsValue, Module, NativeFunction, Source,
|
Context, JsArgs, JsNativeError, JsValue, Module, NativeFunction, Source,
|
||||||
};
|
};
|
||||||
use boa_runtime::Console;
|
use boa_runtime::Console;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
pub fn test_plugins(app_handle: &AppHandle) {
|
use crate::models::{self, Environment, HttpRequest, Workspace};
|
||||||
|
|
||||||
|
pub fn run_plugin_hello(app_handle: &AppHandle, plugin_name: &str) {
|
||||||
|
run_plugin(app_handle, plugin_name, "hello", &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ImportedResources {
|
||||||
|
requests: Vec<HttpRequest>,
|
||||||
|
environments: Vec<Environment>,
|
||||||
|
workspaces: Vec<Workspace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_plugin_import(
|
||||||
|
app_handle: &AppHandle,
|
||||||
|
pool: &Pool<Sqlite>,
|
||||||
|
plugin_name: &str,
|
||||||
|
file_path: &str,
|
||||||
|
workspace_id: &str,
|
||||||
|
) -> ImportedResources {
|
||||||
|
let file = fs::read_to_string(file_path).expect("Unable to read file");
|
||||||
|
let file_contents = file.as_str();
|
||||||
|
let result_json = run_plugin(
|
||||||
|
app_handle,
|
||||||
|
plugin_name,
|
||||||
|
"pluginHookImport",
|
||||||
|
&[js_string!(file_contents).into()],
|
||||||
|
);
|
||||||
|
let resources: ImportedResources =
|
||||||
|
serde_json::from_value(result_json).expect("failed to parse result json");
|
||||||
|
let mut imported_resources = ImportedResources::default();
|
||||||
|
|
||||||
|
println!("Importing resources: {}", workspace_id.is_empty());
|
||||||
|
if workspace_id.is_empty() {
|
||||||
|
for w in resources.workspaces {
|
||||||
|
println!("Importing workspace: {:?}", w);
|
||||||
|
let x = models::upsert_workspace(&pool, w)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create workspace");
|
||||||
|
imported_resources.workspaces.push(x.clone());
|
||||||
|
println!("Imported workspace: {}", x.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut e in resources.environments {
|
||||||
|
if !workspace_id.is_empty() {
|
||||||
|
e.workspace_id = workspace_id.to_string();
|
||||||
|
}
|
||||||
|
println!("Importing environment: {:?}", e);
|
||||||
|
let x = models::upsert_environment(&pool, e)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create environment");
|
||||||
|
imported_resources.environments.push(x.clone());
|
||||||
|
println!("Imported environment: {}", x.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut r in resources.requests {
|
||||||
|
if !workspace_id.is_empty() {
|
||||||
|
r.workspace_id = workspace_id.to_string();
|
||||||
|
}
|
||||||
|
println!("Importing request: {:?}", r);
|
||||||
|
let x = models::upsert_request(&pool, r)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create request");
|
||||||
|
imported_resources.requests.push(x.clone());
|
||||||
|
println!("Imported request: {}", x.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
imported_resources
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_plugin(
|
||||||
|
app_handle: &AppHandle,
|
||||||
|
plugin_name: &str,
|
||||||
|
entrypoint: &str,
|
||||||
|
js_args: &[JsValue],
|
||||||
|
) -> serde_json::Value {
|
||||||
let plugin_dir = app_handle
|
let plugin_dir = app_handle
|
||||||
.path_resolver()
|
.path_resolver()
|
||||||
.resolve_resource("plugins/hello-world")
|
.resolve_resource("../plugins")
|
||||||
.expect("failed to resolve plugin directory resource");
|
.expect("failed to resolve plugin directory resource")
|
||||||
let plugin_entry_file = app_handle
|
.join(plugin_name);
|
||||||
.path_resolver()
|
let plugin_index_file = plugin_dir.join("index.js");
|
||||||
.resolve_resource("plugins/hello-world/index.js")
|
|
||||||
.expect("failed to resolve plugin entry point resource");
|
println!("Plugin dir: {:?}", plugin_dir);
|
||||||
|
|
||||||
// Module loader for the specific plugin
|
// Module loader for the specific plugin
|
||||||
let loader = &SimpleModuleLoader::new(plugin_dir).expect("failed to create module loader");
|
let loader = &SimpleModuleLoader::new(plugin_dir).expect("failed to create module loader");
|
||||||
@@ -29,14 +110,14 @@ pub fn test_plugins(app_handle: &AppHandle) {
|
|||||||
add_runtime(context);
|
add_runtime(context);
|
||||||
add_globals(context);
|
add_globals(context);
|
||||||
|
|
||||||
let source = Source::from_filepath(&plugin_entry_file).expect("Error opening file");
|
let source = Source::from_filepath(&plugin_index_file).expect("Error opening file");
|
||||||
|
|
||||||
// Can also pass a `Some(realm)` if you need to execute the module in another realm.
|
// Can also pass a `Some(realm)` if you need to execute the module in another realm.
|
||||||
let module = Module::parse(source, None, context).expect("failed to parse module");
|
let module = Module::parse(source, None, context).expect("failed to parse module");
|
||||||
|
|
||||||
// Insert parsed entrypoint into the module loader
|
// Insert parsed entrypoint into the module loader
|
||||||
// TODO: Is this needed if loaded from file already?
|
// TODO: Is this needed if loaded from file already?
|
||||||
loader.insert(plugin_entry_file, module.clone());
|
loader.insert(plugin_index_file, module.clone());
|
||||||
|
|
||||||
let _promise_result = module
|
let _promise_result = module
|
||||||
.load_link_evaluate(context)
|
.load_link_evaluate(context)
|
||||||
@@ -58,18 +139,22 @@ pub fn test_plugins(app_handle: &AppHandle) {
|
|||||||
|
|
||||||
let namespace = module.namespace(context);
|
let namespace = module.namespace(context);
|
||||||
|
|
||||||
let entrypoint_fn = namespace
|
let result = namespace
|
||||||
.get(js_string!("entrypoint"), context)
|
.get(js_string!(entrypoint), context)
|
||||||
.expect("failed to get entrypoint")
|
.expect("failed to get entrypoint")
|
||||||
.as_callable()
|
.as_callable()
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| JsNativeError::typ().with_message("export wasn't a function!"))
|
.ok_or_else(|| JsNativeError::typ().with_message("export wasn't a function!"))
|
||||||
.expect("Failed to get entrypoint");
|
.expect("Failed to get entrypoint")
|
||||||
|
.call(&JsValue::undefined(), js_args, context)
|
||||||
// Actually call the entrypoint function
|
|
||||||
let _result = entrypoint_fn
|
|
||||||
.call(&JsValue::undefined(), &[], context)
|
|
||||||
.expect("Failed to call entrypoint");
|
.expect("Failed to call entrypoint");
|
||||||
|
|
||||||
|
match result.is_undefined() {
|
||||||
|
true => json!(null), // to_json doesn't work with undefined (yet)
|
||||||
|
false => result
|
||||||
|
.to_json(context)
|
||||||
|
.expect("failed to convert result to json"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_runtime(context: &mut Context<'_>) {
|
fn add_runtime(context: &mut Context<'_>) {
|
||||||
|
|||||||
@@ -12,6 +12,23 @@
|
|||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"windows": [],
|
"windows": [],
|
||||||
|
"cli": {
|
||||||
|
"description": "Yaak CLI",
|
||||||
|
"longDescription": "This is the Yaak CLI, yo",
|
||||||
|
"beforeHelp": "u can use it to build, develop and manage your Yaak application.",
|
||||||
|
"afterHelp": "Have fun!",
|
||||||
|
"args": [],
|
||||||
|
"subcommands": {
|
||||||
|
"import": {
|
||||||
|
"args": [{
|
||||||
|
"name": "file",
|
||||||
|
"short": "f",
|
||||||
|
"takesValue": true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"hello": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
"all": false,
|
"all": false,
|
||||||
"os": {
|
"os": {
|
||||||
@@ -36,6 +53,10 @@
|
|||||||
},
|
},
|
||||||
"window": {
|
"window": {
|
||||||
"startDragging": true
|
"startDragging": true
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"all": false,
|
||||||
|
"open": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
@@ -54,7 +75,7 @@
|
|||||||
"longDescription": "The best cross-platform visual API client",
|
"longDescription": "The best cross-platform visual API client",
|
||||||
"resources": [
|
"resources": [
|
||||||
"migrations/*",
|
"migrations/*",
|
||||||
"plugins/*"
|
"../plugins/*"
|
||||||
],
|
],
|
||||||
"shortDescription": "The best API client",
|
"shortDescription": "The best API client",
|
||||||
"targets": [
|
"targets": [
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { useDialog } from './DialogContext';
|
|||||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -23,15 +22,14 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
const createEnvironment = useCreateEnvironment();
|
const createEnvironment = useCreateEnvironment();
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const prompt = usePrompt();
|
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
const showEnvironmentDialog = useCallback(() => {
|
const showEnvironmentDialog = useCallback(() => {
|
||||||
dialog.show({
|
dialog.show({
|
||||||
title: 'Manage Environments',
|
title: 'Manage Environments',
|
||||||
render: () => <EnvironmentEditDialog />,
|
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
|
||||||
});
|
});
|
||||||
}, [dialog]);
|
}, [dialog, activeEnvironment]);
|
||||||
|
|
||||||
const items: DropdownItem[] = useMemo(
|
const items: DropdownItem[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { useEnvironments } from '../hooks/useEnvironments';
|
import { useEnvironments } from '../hooks/useEnvironments';
|
||||||
import type { Environment } from '../lib/models';
|
import type { Environment, Workspace } from '../lib/models';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
|
||||||
import { PairEditor } from './core/PairEditor';
|
import { PairEditor } from './core/PairEditor';
|
||||||
import type { PairEditorProps } from './core/PairEditor';
|
import type { PairEditorProps } from './core/PairEditor';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||||
import { HStack, VStack } from './core/Stacks';
|
import { HStack, VStack } from './core/Stacks';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
@@ -19,12 +17,20 @@ import { usePrompt } from '../hooks/usePrompt';
|
|||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||||
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
|
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||||
|
|
||||||
export const EnvironmentEditDialog = function () {
|
interface Props {
|
||||||
const routes = useAppRoutes();
|
initialEnvironment: Environment | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||||
|
const [selectedEnvironment, setSelectedEnvironment] = useState<Environment | null>(
|
||||||
|
initialEnvironment,
|
||||||
|
);
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const createEnvironment = useCreateEnvironment();
|
const createEnvironment = useCreateEnvironment();
|
||||||
const activeEnvironment = useActiveEnvironment();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
|
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const showSidebar = windowSize.width > 500;
|
const showSidebar = windowSize.width > 500;
|
||||||
@@ -39,19 +45,34 @@ export const EnvironmentEditDialog = function () {
|
|||||||
{showSidebar && (
|
{showSidebar && (
|
||||||
<aside className="grid grid-rows-[minmax(0,1fr)_auto] gap-y-0.5 h-full max-w-[250px] pr-4 border-r border-gray-100">
|
<aside className="grid grid-rows-[minmax(0,1fr)_auto] gap-y-0.5 h-full max-w-[250px] pr-4 border-r border-gray-100">
|
||||||
<div className="min-w-0 h-full w-full overflow-y-scroll">
|
<div className="min-w-0 h-full w-full overflow-y-scroll">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
color="custom"
|
||||||
|
justify="start"
|
||||||
|
className={classNames(
|
||||||
|
'w-full',
|
||||||
|
'text-gray-600 hocus:text-gray-800',
|
||||||
|
selectedEnvironment == null && 'bg-highlightSecondary !text-gray-900',
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedEnvironment(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Base Environment
|
||||||
|
</Button>
|
||||||
{environments.map((e) => (
|
{environments.map((e) => (
|
||||||
<Button
|
<Button
|
||||||
|
key={e.id}
|
||||||
|
justify="start"
|
||||||
size="xs"
|
size="xs"
|
||||||
color="custom"
|
color="custom"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full',
|
'w-full',
|
||||||
'text-gray-600 hocus:text-gray-800',
|
'text-gray-600 hocus:text-gray-800',
|
||||||
activeEnvironment?.id === e.id && 'bg-highlightSecondary !text-gray-900',
|
selectedEnvironment?.id === e.id && 'bg-highlightSecondary !text-gray-900',
|
||||||
)}
|
)}
|
||||||
justify="start"
|
|
||||||
key={e.id}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
routes.setEnvironment(e);
|
setSelectedEnvironment(e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{e.name}
|
{e.name}
|
||||||
@@ -68,8 +89,8 @@ export const EnvironmentEditDialog = function () {
|
|||||||
</Button>
|
</Button>
|
||||||
</aside>
|
</aside>
|
||||||
)}
|
)}
|
||||||
{activeEnvironment != null ? (
|
{activeWorkspace != null ? (
|
||||||
<EnvironmentEditor environment={activeEnvironment} />
|
<EnvironmentEditor environment={selectedEnvironment} workspace={activeWorkspace} />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex w-full h-full items-center justify-center text-gray-400 italic">
|
<div className="flex w-full h-full items-center justify-center text-gray-400 italic">
|
||||||
select an environment
|
select an environment
|
||||||
@@ -79,57 +100,72 @@ export const EnvironmentEditDialog = function () {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EnvironmentEditor = function ({ environment }: { environment: Environment }) {
|
const EnvironmentEditor = function ({
|
||||||
|
environment,
|
||||||
|
workspace,
|
||||||
|
}: {
|
||||||
|
environment: Environment | null;
|
||||||
|
workspace: Workspace;
|
||||||
|
}) {
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const updateEnvironment = useUpdateEnvironment(environment.id);
|
const updateEnvironment = useUpdateEnvironment(environment?.id ?? 'n/a');
|
||||||
|
const updateWorkspace = useUpdateWorkspace(workspace.id);
|
||||||
const deleteEnvironment = useDeleteEnvironment(environment);
|
const deleteEnvironment = useDeleteEnvironment(environment);
|
||||||
|
const variables = environment == null ? workspace.variables : environment.variables;
|
||||||
const handleChange = useCallback<PairEditorProps['onChange']>(
|
const handleChange = useCallback<PairEditorProps['onChange']>(
|
||||||
(variables) => {
|
(variables) => {
|
||||||
updateEnvironment.mutate({ variables });
|
if (environment != null) {
|
||||||
|
updateEnvironment.mutate({ variables });
|
||||||
|
} else {
|
||||||
|
updateWorkspace.mutate({ variables });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[updateEnvironment],
|
[updateWorkspace, updateEnvironment, environment],
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
|
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
|
||||||
const allVariableNames = environments.flatMap((e) => e.variables.map((v) => v.name));
|
const allVariableNames = environments.flatMap((e) => e.variables.map((v) => v.name));
|
||||||
// Filter out empty strings and variables that already exist in the active environment
|
// Filter out empty strings and variables that already exist in the active environment
|
||||||
const variableNames = allVariableNames.filter(
|
const variableNames = allVariableNames.filter(
|
||||||
(name) => name != '' && !environment.variables.find((v) => v.name === name),
|
(name) => name != '' && !variables.find((v) => v.name === name),
|
||||||
);
|
);
|
||||||
return { options: variableNames.map((name) => ({ label: name, type: 'constant' })) };
|
return { options: variableNames.map((name) => ({ label: name, type: 'constant' })) };
|
||||||
}, [environments, environment.variables]);
|
}, [environments, variables]);
|
||||||
|
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const items = useMemo<DropdownItem[]>(
|
const items = useMemo<DropdownItem[] | null>(
|
||||||
() => [
|
() =>
|
||||||
{
|
environment == null
|
||||||
key: 'rename',
|
? null
|
||||||
label: 'Rename',
|
: [
|
||||||
leftSlot: <Icon icon="pencil" size="sm" />,
|
{
|
||||||
onSelect: async () => {
|
key: 'rename',
|
||||||
const name = await prompt({
|
label: 'Rename',
|
||||||
title: 'Rename Environment',
|
leftSlot: <Icon icon="pencil" size="sm" />,
|
||||||
description: (
|
onSelect: async () => {
|
||||||
<>
|
const name = await prompt({
|
||||||
Enter a new name for <InlineCode>{environment.name}</InlineCode>
|
title: 'Rename Environment',
|
||||||
</>
|
description: (
|
||||||
),
|
<>
|
||||||
name: 'name',
|
Enter a new name for <InlineCode>{environment.name}</InlineCode>
|
||||||
label: 'Name',
|
</>
|
||||||
defaultValue: environment.name,
|
),
|
||||||
});
|
name: 'name',
|
||||||
updateEnvironment.mutate({ name });
|
label: 'Name',
|
||||||
},
|
defaultValue: environment.name,
|
||||||
},
|
});
|
||||||
{
|
updateEnvironment.mutate({ name });
|
||||||
key: 'delete',
|
},
|
||||||
variant: 'danger',
|
},
|
||||||
label: 'Delete',
|
{
|
||||||
leftSlot: <Icon icon="trash" size="sm" />,
|
key: 'delete',
|
||||||
onSelect: () => deleteEnvironment.mutate(),
|
variant: 'danger',
|
||||||
},
|
label: 'Delete',
|
||||||
],
|
leftSlot: <Icon icon="trash" size="sm" />,
|
||||||
[deleteEnvironment, updateEnvironment, environment.name, prompt],
|
onSelect: () => deleteEnvironment.mutate(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[deleteEnvironment, updateEnvironment, prompt, environment],
|
||||||
);
|
);
|
||||||
|
|
||||||
const validateName = useCallback((name: string) => {
|
const validateName = useCallback((name: string) => {
|
||||||
@@ -141,10 +177,12 @@ const EnvironmentEditor = function ({ environment }: { environment: Environment
|
|||||||
return (
|
return (
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<HStack space={2} className="justify-between">
|
<HStack space={2} className="justify-between">
|
||||||
<h1 className="text-xl">{environment.name}</h1>
|
<h1 className="text-xl">{environment?.name ?? 'Base Environment'}</h1>
|
||||||
<Dropdown items={items}>
|
{items != null && (
|
||||||
<IconButton icon="gear" title="Environment Actions" size="sm" className="!h-auto w-8" />
|
<Dropdown items={items}>
|
||||||
</Dropdown>
|
<IconButton icon="gear" title="Environment Actions" size="sm" className="!h-auto w-8" />
|
||||||
|
</Dropdown>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<PairEditor
|
<PairEditor
|
||||||
nameAutocomplete={nameAutocomplete}
|
nameAutocomplete={nameAutocomplete}
|
||||||
@@ -153,8 +191,8 @@ const EnvironmentEditor = function ({ environment }: { environment: Environment
|
|||||||
valuePlaceholder="variable value"
|
valuePlaceholder="variable value"
|
||||||
nameValidate={validateName}
|
nameValidate={validateName}
|
||||||
valueAutocompleteVariables={false}
|
valueAutocompleteVariables={false}
|
||||||
forceUpdateKey={environment.id}
|
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||||
pairs={environment.variables}
|
pairs={variables}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -85,9 +85,10 @@ export function RecentRequestsDropdown() {
|
|||||||
return (
|
return (
|
||||||
<Dropdown ref={dropdownRef} items={items}>
|
<Dropdown ref={dropdownRef} items={items}>
|
||||||
<Button
|
<Button
|
||||||
|
data-tauri-drag-region
|
||||||
size="sm"
|
size="sm"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none',
|
'flex-[2] text-center text-gray-800 text-sm truncate pointer-events-auto',
|
||||||
activeRequest === null && 'text-opacity-disabled italic',
|
activeRequest === null && 'text-opacity-disabled italic',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
import type { HTMLAttributes, ReactElement } from 'react';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import React, { useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { open } from '@tauri-apps/api/dialog';
|
||||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
import type { DropdownRef } from './core/Dropdown';
|
import type { DropdownItem, DropdownProps, DropdownRef } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { HotKey } from './core/HotKey';
|
import { HotKey } from './core/HotKey';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
import type { Environment, HttpRequest, Workspace } from '../lib/models';
|
||||||
|
import { useDialog } from './DialogContext';
|
||||||
|
import { pluralize } from '../lib/pluralize';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
requestId: string;
|
requestId: string | null;
|
||||||
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
children: DropdownProps['children'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RequestActionsDropdown({ requestId, children }: Props) {
|
export function RequestActionsDropdown({ requestId, children }: Props) {
|
||||||
const deleteRequest = useDeleteRequest(requestId);
|
const deleteRequest = useDeleteRequest(requestId);
|
||||||
const duplicateRequest = useDuplicateRequest({ id: requestId, navigateAfter: true });
|
const duplicateRequest = useDuplicateRequest({ id: requestId, navigateAfter: true });
|
||||||
const dropdownRef = useRef<DropdownRef>(null);
|
const dropdownRef = useRef<DropdownRef>(null);
|
||||||
|
const routes = useAppRoutes();
|
||||||
|
const dialog = useDialog();
|
||||||
const { appearance, toggleAppearance } = useTheme();
|
const { appearance, toggleAppearance } = useTheme();
|
||||||
|
|
||||||
useListenToTauriEvent('toggle_settings', () => {
|
useListenToTauriEvent('toggle_settings', () => {
|
||||||
@@ -29,25 +36,88 @@ export function RequestActionsDropdown({ requestId, children }: Props) {
|
|||||||
duplicateRequest.mutate();
|
duplicateRequest.mutate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const importData = useCallback(async () => {
|
||||||
|
const selected = await open({
|
||||||
|
multiple: true,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'Export File',
|
||||||
|
extensions: ['json'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (selected == null || selected.length === 0) return;
|
||||||
|
const imported: {
|
||||||
|
workspaces: Workspace[];
|
||||||
|
environments: Environment[];
|
||||||
|
requests: HttpRequest[];
|
||||||
|
} = await invoke('import_data', {
|
||||||
|
filePaths: selected,
|
||||||
|
workspaceId: null,
|
||||||
|
});
|
||||||
|
const importedWorkspace = imported.workspaces[0];
|
||||||
|
|
||||||
|
dialog.show({
|
||||||
|
title: 'Import Complete',
|
||||||
|
description: 'Imported the following:',
|
||||||
|
size: 'dynamic',
|
||||||
|
render: () => {
|
||||||
|
const { workspaces, environments, requests } = imported;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ul className="list-disc pl-6">
|
||||||
|
<li>
|
||||||
|
{workspaces.length} {pluralize('Workspace', workspaces.length)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{environments.length} {pluralize('Environment', environments.length)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{requests.length} {pluralize('Request', requests.length)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (importedWorkspace != null) {
|
||||||
|
routes.navigate('workspace', {
|
||||||
|
workspaceId: importedWorkspace.id,
|
||||||
|
environmentId: imported.environments[0]?.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [routes, dialog]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
ref={dropdownRef}
|
ref={dropdownRef}
|
||||||
items={[
|
items={[
|
||||||
|
...(requestId != null
|
||||||
|
? ([
|
||||||
|
{
|
||||||
|
key: 'duplicate',
|
||||||
|
label: 'Duplicate',
|
||||||
|
onSelect: duplicateRequest.mutate,
|
||||||
|
leftSlot: <Icon icon="copy" />,
|
||||||
|
rightSlot: <HotKey modifier="Meta" keyName="D" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
label: 'Delete',
|
||||||
|
onSelect: deleteRequest.mutate,
|
||||||
|
variant: 'danger',
|
||||||
|
leftSlot: <Icon icon="trash" />,
|
||||||
|
},
|
||||||
|
{ type: 'separator', label: 'Yaak Settings' },
|
||||||
|
] as DropdownItem[])
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
key: 'duplicate',
|
key: 'import',
|
||||||
label: 'Duplicate',
|
label: 'Import',
|
||||||
onSelect: duplicateRequest.mutate,
|
onSelect: importData,
|
||||||
leftSlot: <Icon icon="copy" />,
|
leftSlot: <Icon icon="download" />,
|
||||||
rightSlot: <HotKey modifier="Meta" keyName="D" />,
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'delete',
|
|
||||||
label: 'Delete',
|
|
||||||
onSelect: deleteRequest.mutate,
|
|
||||||
variant: 'danger',
|
|
||||||
leftSlot: <Icon icon="trash" />,
|
|
||||||
},
|
|
||||||
{ type: 'separator', label: 'Yaak Settings' },
|
|
||||||
{
|
{
|
||||||
key: 'appearance',
|
key: 'appearance',
|
||||||
label: appearance === 'dark' ? 'Light Theme' : 'Dark Theme',
|
label: appearance === 'dark' ? 'Light Theme' : 'Dark Theme',
|
||||||
|
|||||||
@@ -31,16 +31,14 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
<RecentRequestsDropdown />
|
<RecentRequestsDropdown />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex justify-end -mr-2 pointer-events-none">
|
<div className="flex-1 flex justify-end -mr-2 pointer-events-none">
|
||||||
{activeRequest && (
|
<RequestActionsDropdown requestId={activeRequest?.id ?? null}>
|
||||||
<RequestActionsDropdown requestId={activeRequest?.id}>
|
<IconButton
|
||||||
<IconButton
|
size="sm"
|
||||||
size="sm"
|
title="Request Options"
|
||||||
title="Request Options"
|
icon="gear"
|
||||||
icon="gear"
|
className="pointer-events-auto"
|
||||||
className="pointer-events-auto"
|
/>
|
||||||
/>
|
</RequestActionsDropdown>
|
||||||
</RequestActionsDropdown>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
TriangleDownIcon,
|
TriangleDownIcon,
|
||||||
TriangleLeftIcon,
|
TriangleLeftIcon,
|
||||||
TriangleRightIcon,
|
TriangleRightIcon,
|
||||||
|
DownloadIcon,
|
||||||
UpdateIcon,
|
UpdateIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -55,6 +56,7 @@ const icons = {
|
|||||||
dividerH: DividerHorizontalIcon,
|
dividerH: DividerHorizontalIcon,
|
||||||
dotsH: DotsHorizontalIcon,
|
dotsH: DotsHorizontalIcon,
|
||||||
dotsV: DotsVerticalIcon,
|
dotsV: DotsVerticalIcon,
|
||||||
|
download: DownloadIcon,
|
||||||
drag: DragHandleDots2Icon,
|
drag: DragHandleDots2Icon,
|
||||||
eye: EyeOpenIcon,
|
eye: EyeOpenIcon,
|
||||||
eyeClosed: EyeClosedIcon,
|
eyeClosed: EyeClosedIcon,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface Workspace extends BaseModel {
|
|||||||
readonly model: 'workspace';
|
readonly model: 'workspace';
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
variables: EnvironmentVariable[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnvironmentVariable {
|
export interface EnvironmentVariable {
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ export const appThemeVariants: AppThemeColorVariant[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export type AppThemeLayer = 'root' | 'sidebar' | 'titlebar' | 'content' | 'above';
|
export type AppThemeLayer = 'root' | 'sidebar' | 'titlebar' | 'content' | 'above';
|
||||||
|
export type AppThemeColors = Record<AppThemeColor, string>;
|
||||||
|
|
||||||
export interface AppThemeLayerStyle {
|
export interface AppThemeLayerStyle {
|
||||||
colors: Record<AppThemeColor, string>;
|
colors: AppThemeColors;
|
||||||
blackPoint?: number;
|
blackPoint?: number;
|
||||||
whitePoint?: number;
|
whitePoint?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
import type { AppTheme } from './theme';
|
import type { AppTheme, AppThemeColors } from './theme';
|
||||||
import { generateCSS, toTailwindVariable } from './theme';
|
import { generateCSS, toTailwindVariable } from './theme';
|
||||||
|
|
||||||
export type Appearance = 'dark' | 'light';
|
export type Appearance = 'dark' | 'light';
|
||||||
|
|
||||||
|
enum Theme {
|
||||||
|
yaak = 'yaak',
|
||||||
|
catppuccin = 'catppuccin',
|
||||||
|
}
|
||||||
|
|
||||||
|
const themes: Record<Theme, AppThemeColors> = {
|
||||||
|
yaak: {
|
||||||
|
gray: '#6b5b98',
|
||||||
|
red: '#ff417b',
|
||||||
|
orange: '#fd9014',
|
||||||
|
yellow: '#e8d13f',
|
||||||
|
green: '#3fd265',
|
||||||
|
blue: '#219dff',
|
||||||
|
pink: '#ff6dff',
|
||||||
|
violet: '#b176ff',
|
||||||
|
},
|
||||||
|
catppuccin: {
|
||||||
|
gray: 'hsl(240, 23%, 47%)',
|
||||||
|
red: 'hsl(343, 91%, 74%)',
|
||||||
|
orange: 'hsl(23, 92%, 74%)',
|
||||||
|
yellow: 'hsl(41, 86%, 72%)',
|
||||||
|
green: 'hsl(115, 54%, 65%)',
|
||||||
|
blue: 'hsl(217, 92%, 65%)',
|
||||||
|
pink: 'hsl(316, 72%, 75%)',
|
||||||
|
violet: 'hsl(267, 84%, 70%)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const darkTheme: AppTheme = {
|
const darkTheme: AppTheme = {
|
||||||
name: 'Default Dark',
|
name: 'Default Dark',
|
||||||
appearance: 'dark',
|
appearance: 'dark',
|
||||||
layers: {
|
layers: {
|
||||||
root: {
|
root: {
|
||||||
blackPoint: 0.2,
|
blackPoint: 0.2,
|
||||||
colors: {
|
colors: themes.catppuccin,
|
||||||
gray: '#6b5b98',
|
|
||||||
red: '#ff417b',
|
|
||||||
orange: '#fd9014',
|
|
||||||
yellow: '#e8d13f',
|
|
||||||
green: '#3fd265',
|
|
||||||
blue: '#219dff',
|
|
||||||
pink: '#ff6dff',
|
|
||||||
violet: '#b176ff',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user