mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-23 18:01:21 +01:00
Merge pull request #227
* Search and install plugins PoC * Checksum * Tab sidebar for settings * Fix nested tabs, and tweaks * Table for plugin results * Deep links working * Focus window during deep links * Merge branch 'master' into plugin-directory * More stuff
This commit is contained in:
@@ -372,7 +372,7 @@ export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
|
||||
5
packages/plugin-runtime-types/src/bindings/gen_search.ts
Normal file
5
packages/plugin-runtime-types/src/bindings/gen_search.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginSearchResponse = { results: Array<PluginVersion>, };
|
||||
|
||||
export type PluginVersion = { id: string, version: string, description: string | null, displayName: string, homepageUrl: string | null, repositoryUrl: string, checksum: string, readme: string | null, yanked: boolean, };
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BootRequest,
|
||||
BootResponse,
|
||||
DeleteKeyValueResponse,
|
||||
FindHttpResponsesResponse,
|
||||
FormInput,
|
||||
@@ -52,9 +53,22 @@ export class PluginInstance {
|
||||
|
||||
// Reload plugin if the JS or package.json changes
|
||||
const windowContextNone: PluginWindowContext = { type: 'none' };
|
||||
|
||||
this.#mod = {};
|
||||
this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8'));
|
||||
|
||||
const bootResponse: BootResponse = {
|
||||
name: this.#pkg.name ?? 'unknown',
|
||||
version: this.#pkg.version ?? '0.0.1',
|
||||
};
|
||||
|
||||
const fileChangeCallback = async () => {
|
||||
this.#importModule();
|
||||
return this.#sendPayload(windowContextNone, { type: 'reload_response' }, null);
|
||||
return this.#sendPayload(
|
||||
windowContextNone,
|
||||
{ type: 'reload_response', ...bootResponse },
|
||||
null,
|
||||
);
|
||||
};
|
||||
|
||||
if (this.#workerData.bootRequest.watch) {
|
||||
@@ -62,12 +76,6 @@ export class PluginInstance {
|
||||
watchFile(this.#pathPkg(), fileChangeCallback);
|
||||
}
|
||||
|
||||
this.#mod = {};
|
||||
this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8'));
|
||||
|
||||
// TODO: Re-implement this now that we're not using workers
|
||||
// prefixStdout(`[plugin][${this.#pkg.name}] %s`);
|
||||
|
||||
this.#importModule();
|
||||
}
|
||||
|
||||
|
||||
292
src-tauri/Cargo.lock
generated
292
src-tauri/Cargo.lock
generated
@@ -674,6 +674,25 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.13+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.18.5"
|
||||
@@ -926,6 +945,32 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -1080,6 +1125,12 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -1198,6 +1249,12 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
@@ -1326,6 +1383,15 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.11"
|
||||
@@ -1593,6 +1659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
@@ -2920,6 +2987,26 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66352d7a8ac12d4877b6e6ea5a9b7650ee094257dc40889955bea5bc5b08c1d0"
|
||||
dependencies = [
|
||||
"liblzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma-sys"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
@@ -2956,6 +3043,15 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.22"
|
||||
@@ -3712,6 +3808,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
@@ -3823,6 +3929,16 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@@ -4557,9 +4673,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.19"
|
||||
version = "0.12.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119"
|
||||
checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64 0.22.1",
|
||||
@@ -4577,13 +4693,11 @@ dependencies = [
|
||||
"hyper-rustls",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
@@ -4596,7 +4710,6 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tokio-socks",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-http",
|
||||
@@ -4704,6 +4817,17 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
"trim-in-place",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.37.1"
|
||||
@@ -5731,6 +5855,26 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4976ac728ebc0487515aa956cfdf200abcc52b784e441493fc544bc6ce369c8"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
"windows-result",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.2.2"
|
||||
@@ -5863,6 +6007,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin-deep-link",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -5898,7 +6043,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.59.0",
|
||||
"zip",
|
||||
"zip 2.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6137,6 +6282,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
@@ -6211,18 +6365,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
@@ -6500,6 +6642,12 @@ dependencies = [
|
||||
"petgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trim-in-place"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
@@ -7644,6 +7792,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-log",
|
||||
@@ -7678,7 +7827,10 @@ name = "yaak-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tauri",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7777,6 +7929,7 @@ dependencies = [
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"ts-rs",
|
||||
"yaak-common",
|
||||
"yaak-models",
|
||||
]
|
||||
|
||||
@@ -7823,16 +7976,21 @@ name = "yaak-plugins"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"dunce",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"log",
|
||||
"md5",
|
||||
"path-slash",
|
||||
"rand 0.9.1",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-shell",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
@@ -7842,6 +8000,7 @@ dependencies = [
|
||||
"yaak-crypto",
|
||||
"yaak-models",
|
||||
"yaak-templates",
|
||||
"zip-extract",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8037,6 +8196,20 @@ name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
@@ -8086,6 +8259,89 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "153a6fff49d264c4babdcfa6b4d534747f520e56e8f0f384f3b808c4b64cc1fd"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"getrandom 0.3.3",
|
||||
"hmac",
|
||||
"indexmap 2.9.0",
|
||||
"liblzma",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip-extract"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aed5f10c571472911e37d8f7601a8dfba52b4f7f73a344015291b82ab292faf6"
|
||||
dependencies = [
|
||||
"log",
|
||||
"thiserror 2.0.12",
|
||||
"zip 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.5.3"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"yaak-crypto",
|
||||
"yaak-fonts",
|
||||
"yaak-git",
|
||||
"yaak-grpc",
|
||||
"yaak-fonts",
|
||||
"yaak-http",
|
||||
"yaak-license",
|
||||
"yaak-mac-window",
|
||||
@@ -40,7 +40,7 @@ tauri-build = { version = "2.2.0", features = [] }
|
||||
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
encoding_rs = "0.8.35"
|
||||
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.14.0" }
|
||||
@@ -56,27 +56,28 @@ serde_json = { workspace = true, features = ["raw_value"] }
|
||||
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-clipboard-manager = "2.2.2"
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
tauri-plugin-fs = "2.2.0"
|
||||
tauri-plugin-log = { version = "2.3.1", features = ["colored"] }
|
||||
tauri-plugin-fs = "2.3.0"
|
||||
tauri-plugin-log = { version = "2.4.0", features = ["colored"] }
|
||||
tauri-plugin-opener = "2.2.6"
|
||||
tauri-plugin-os = "2.2.1"
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
tauri-plugin-single-instance = "2.2.2"
|
||||
tauri-plugin-updater = "2.6.1"
|
||||
tauri-plugin-window-state = "2.2.1"
|
||||
tauri-plugin-deep-link = "2.3.0"
|
||||
tauri-plugin-single-instance = { version = "2.2.4", features = ["deep-link"] }
|
||||
tauri-plugin-updater = "2.7.1"
|
||||
tauri-plugin-window-state = "2.2.2"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tokio-stream = "0.1.17"
|
||||
uuid = "1.12.1"
|
||||
yaak-common = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-fonts = { workspace = true }
|
||||
yaak-git = { path = "yaak-git" }
|
||||
yaak-grpc = { path = "yaak-grpc" }
|
||||
yaak-http = { workspace = true }
|
||||
yaak-license = { path = "yaak-license" }
|
||||
yaak-mac-window = { path = "yaak-mac-window" }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-fonts = { workspace = true }
|
||||
yaak-plugins = { workspace = true }
|
||||
yaak-sse = { workspace = true }
|
||||
yaak-sync = { workspace = true }
|
||||
@@ -84,7 +85,9 @@ yaak-templates = { workspace = true }
|
||||
yaak-ws = { path = "yaak-ws" }
|
||||
|
||||
[workspace.dependencies]
|
||||
reqwest = "0.12.19"
|
||||
chrono = "0.4.41"
|
||||
hex = "0.4.3"
|
||||
reqwest = "0.12.20"
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
tauri = "2.5.1"
|
||||
@@ -96,7 +99,9 @@ thiserror = "2.0.12"
|
||||
ts-rs = "11.0.1"
|
||||
rustls = { version = "0.23.27", default-features = false }
|
||||
rustls-platform-verifier = "0.6.0"
|
||||
sha2 = "0.10.9"
|
||||
yaak-common = { path = "yaak-common" }
|
||||
yaak-crypto = { path = "yaak-crypto" }
|
||||
yaak-fonts = { path = "yaak-fonts" }
|
||||
yaak-http = { path = "yaak-http" }
|
||||
yaak-models = { path = "yaak-models" }
|
||||
@@ -104,4 +109,3 @@ yaak-plugins = { path = "yaak-plugins" }
|
||||
yaak-sse = { path = "yaak-sse" }
|
||||
yaak-sync = { path = "yaak-sync" }
|
||||
yaak-templates = { path = "yaak-templates" }
|
||||
yaak-crypto = { path = "yaak-crypto" }
|
||||
|
||||
@@ -52,11 +52,12 @@
|
||||
"opener:allow-reveal-item-in-dir",
|
||||
"shell:allow-open",
|
||||
"yaak-crypto:default",
|
||||
"yaak-git:default",
|
||||
"yaak-fonts:default",
|
||||
"yaak-git:default",
|
||||
"yaak-license:default",
|
||||
"yaak-mac-window:default",
|
||||
"yaak-models:default",
|
||||
"yaak-plugins:default",
|
||||
"yaak-sync:default",
|
||||
"yaak-ws:default"
|
||||
]
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Enable for v8 execution -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
|
||||
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
|
||||
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
|
||||
|
||||
@@ -43,18 +43,6 @@ pub async fn store_launch_history<R: Runtime>(app_handle: &AppHandle<R>) -> Laun
|
||||
info
|
||||
}
|
||||
|
||||
pub fn get_os() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_num_launches<R: Runtime>(app_handle: &AppHandle<R>) -> i32 {
|
||||
app_handle.db().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0)
|
||||
}
|
||||
|
||||
105
src-tauri/src/import.rs
Normal file
105
src-tauri/src/import.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use crate::error::Result;
|
||||
use log::info;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::read_to_string;
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use yaak_models::models::{
|
||||
Environment, Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace,
|
||||
};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{BatchUpsertResult, UpdateSource, maybe_gen_id, maybe_gen_id_opt};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
|
||||
pub(crate) async fn import_data<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
file_path: &str,
|
||||
) -> Result<BatchUpsertResult> {
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
let file =
|
||||
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
let import_result = plugin_manager.import_data(window, file_contents).await?;
|
||||
|
||||
let mut id_map: BTreeMap<String, String> = BTreeMap::new();
|
||||
|
||||
let resources = import_result.resources;
|
||||
|
||||
let workspaces: Vec<Workspace> = resources
|
||||
.workspaces
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Workspace>(v.id.as_str(), &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let environments: Vec<Environment> = resources
|
||||
.environments
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Environment>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let folders: Vec<Folder> = resources
|
||||
.folders
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Folder>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let http_requests: Vec<HttpRequest> = resources
|
||||
.http_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<HttpRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let grpc_requests: Vec<GrpcRequest> = resources
|
||||
.grpc_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<GrpcRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let websocket_requests: Vec<WebsocketRequest> = resources
|
||||
.websocket_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<WebsocketRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
info!("Importing data");
|
||||
|
||||
let upserted = window.with_tx(|tx| {
|
||||
tx.batch_upsert(
|
||||
workspaces,
|
||||
environments,
|
||||
folders,
|
||||
http_requests,
|
||||
grpc_requests,
|
||||
websocket_requests,
|
||||
&UpdateSource::Import,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(upserted)
|
||||
}
|
||||
@@ -3,14 +3,15 @@ use crate::encoding::read_response_body;
|
||||
use crate::error::Error::GenericError;
|
||||
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
|
||||
use crate::http_request::send_http_request;
|
||||
use crate::import::import_data;
|
||||
use crate::notifications::YaakNotifier;
|
||||
use crate::render::{render_grpc_request, render_template};
|
||||
use crate::updates::{UpdateMode, UpdateTrigger, YaakUpdater};
|
||||
use crate::uri_scheme::handle_uri_scheme;
|
||||
use crate::uri_scheme::handle_deep_link;
|
||||
use error::Result as YaakResult;
|
||||
use eventsource_client::{EventParser, SSE};
|
||||
use log::{debug, error, info, warn};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{File, create_dir_all};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
@@ -19,24 +20,21 @@ use std::{fs, panic};
|
||||
use tauri::{AppHandle, Emitter, RunEvent, State, WebviewWindow, is_dev};
|
||||
use tauri::{Listener, Runtime};
|
||||
use tauri::{Manager, WindowEvent};
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||
use tauri_plugin_log::{Builder, Target, TargetKind};
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
use tokio::fs::read_to_string;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::block_in_place;
|
||||
use yaak_common::window::WorkspaceWindowTrait;
|
||||
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
|
||||
use yaak_grpc::{Code, ServiceDefinition, deserialize_message, serialize_message};
|
||||
use yaak_models::models::{
|
||||
CookieJar, Environment, Folder, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType,
|
||||
GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, WebsocketRequest, Workspace,
|
||||
WorkspaceMeta,
|
||||
CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType,
|
||||
GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, Workspace, WorkspaceMeta,
|
||||
};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{
|
||||
BatchUpsertResult, UpdateSource, get_workspace_export_resources, maybe_gen_id, maybe_gen_id_opt,
|
||||
};
|
||||
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
||||
use yaak_plugins::events::{
|
||||
BootResponse, CallHttpRequestActionRequest, FilterResponse,
|
||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||
@@ -55,6 +53,7 @@ mod error;
|
||||
mod grpc;
|
||||
mod history;
|
||||
mod http_request;
|
||||
mod import;
|
||||
mod notifications;
|
||||
mod plugin_events;
|
||||
mod render;
|
||||
@@ -778,98 +777,9 @@ async fn cmd_get_sse_events(file_path: &str) -> YaakResult<Vec<ServerSentEvent>>
|
||||
#[tauri::command]
|
||||
async fn cmd_import_data<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
app_handle: AppHandle<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
file_path: &str,
|
||||
) -> YaakResult<BatchUpsertResult> {
|
||||
let file = read_to_string(file_path)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
let import_result = plugin_manager.import_data(&window, file_contents).await?;
|
||||
|
||||
let mut id_map: BTreeMap<String, String> = BTreeMap::new();
|
||||
|
||||
let resources = import_result.resources;
|
||||
|
||||
let workspaces: Vec<Workspace> = resources
|
||||
.workspaces
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Workspace>(v.id.as_str(), &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let environments: Vec<Environment> = resources
|
||||
.environments
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Environment>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let folders: Vec<Folder> = resources
|
||||
.folders
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Folder>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let http_requests: Vec<HttpRequest> = resources
|
||||
.http_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<HttpRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let grpc_requests: Vec<GrpcRequest> = resources
|
||||
.grpc_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<GrpcRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
let websocket_requests: Vec<WebsocketRequest> = resources
|
||||
.websocket_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<WebsocketRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
info!("Importing data");
|
||||
|
||||
let upserted = app_handle.with_tx(|tx| {
|
||||
tx.batch_upsert(
|
||||
workspaces,
|
||||
environments,
|
||||
folders,
|
||||
http_requests,
|
||||
grpc_requests,
|
||||
websocket_requests,
|
||||
&UpdateSource::Import,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(upserted)
|
||||
import_data(&window, file_path).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1066,7 +976,7 @@ async fn cmd_install_plugin<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> YaakResult<Plugin> {
|
||||
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &directory, true).await?;
|
||||
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &directory).await?;
|
||||
|
||||
Ok(app_handle.db().upsert_plugin(
|
||||
&Plugin {
|
||||
@@ -1255,6 +1165,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_updater::Builder::default().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
@@ -1272,6 +1183,21 @@ pub fn run() {
|
||||
|
||||
builder
|
||||
.setup(|app| {
|
||||
{
|
||||
let app_handle = app.app_handle().clone();
|
||||
app.deep_link().on_open_url(move |event| {
|
||||
info!("Handling deep link open");
|
||||
let app_handle = app_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
for url in event.urls() {
|
||||
if let Err(e) = handle_deep_link(&app_handle, &url).await {
|
||||
warn!("Failed to handle deep link {}: {e:?}", url.to_string());
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let app_data_dir = app.path().app_data_dir().unwrap();
|
||||
create_dir_all(app_data_dir.clone()).expect("Problem creating App directory!");
|
||||
|
||||
@@ -1332,7 +1258,6 @@ pub fn run() {
|
||||
crate::commands::cmd_secure_template,
|
||||
crate::commands::cmd_show_workspace_key,
|
||||
])
|
||||
.register_uri_scheme_protocol("yaak", handle_uri_scheme)
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while running tauri application")
|
||||
.run(|app_handle, event| {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::history::{get_num_launches, get_os};
|
||||
use crate::history::get_num_launches;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use log::debug;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||
use yaak_common::platform::get_os;
|
||||
use yaak_license::{LicenseCheckStatus, check_license};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::UpdateSource;
|
||||
|
||||
@@ -126,7 +126,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
||||
None
|
||||
}
|
||||
InternalEventPayload::ReloadResponse(_) => {
|
||||
InternalEventPayload::ReloadResponse(r) => {
|
||||
let plugins = app_handle.db().list_plugins().unwrap();
|
||||
for plugin in plugins {
|
||||
if plugin.directory != plugin_handle.dir {
|
||||
@@ -142,7 +142,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
let toast_event = plugin_handle.build_event_to_send(
|
||||
&window_context,
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: format!("Reloaded plugin {}", plugin_handle.dir),
|
||||
message: format!("Reloaded plugin {}@{}", r.name, r.version),
|
||||
icon: Some(Icon::Info),
|
||||
..Default::default()
|
||||
}),
|
||||
|
||||
@@ -1,25 +1,74 @@
|
||||
use crate::error::Result;
|
||||
use crate::import::import_data;
|
||||
use log::{info, warn};
|
||||
use tauri::{Manager, Runtime, UriSchemeContext};
|
||||
use std::collections::HashMap;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Url};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
|
||||
use yaak_plugins::api::get_plugin;
|
||||
use yaak_plugins::events::{Color, ShowToastRequest};
|
||||
use yaak_plugins::install::download_and_install;
|
||||
|
||||
pub(crate) fn handle_uri_scheme<R: Runtime>(
|
||||
a: UriSchemeContext<R>,
|
||||
req: http::Request<Vec<u8>>,
|
||||
) -> http::Response<Vec<u8>> {
|
||||
println!("------------- Yaak URI scheme invoked!");
|
||||
let uri = req.uri();
|
||||
let window = a
|
||||
.app_handle()
|
||||
.get_webview_window(a.webview_label())
|
||||
.expect("Failed to get webview window for URI scheme event");
|
||||
info!("Yaak URI scheme invoked with {uri:?} {window:?}");
|
||||
pub(crate) async fn handle_deep_link<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
url: &Url,
|
||||
) -> Result<()> {
|
||||
let command = url.domain().unwrap_or_default();
|
||||
info!("Yaak URI scheme invoked {}?{}", command, url.query().unwrap_or_default());
|
||||
|
||||
let path = uri.path();
|
||||
if path == "/data/import" {
|
||||
warn!("TODO: import data")
|
||||
} else if path == "/plugins/install" {
|
||||
warn!("TODO: install plugin")
|
||||
let query_map: HashMap<String, String> = url.query_pairs().into_owned().collect();
|
||||
let windows = app_handle.webview_windows();
|
||||
let (_, window) = windows.iter().next().unwrap();
|
||||
|
||||
match command {
|
||||
"install-plugin" => {
|
||||
let name = query_map.get("name").unwrap();
|
||||
let version = query_map.get("version").cloned();
|
||||
let plugin_version = get_plugin(&app_handle, &name, version).await?;
|
||||
_ = window.set_focus();
|
||||
let confirmed_install = app_handle
|
||||
.dialog()
|
||||
.message(format!(
|
||||
"Install plugin {}@{}?",
|
||||
plugin_version.name, plugin_version.version
|
||||
))
|
||||
.kind(MessageDialogKind::Info)
|
||||
.buttons(MessageDialogButtons::OkCustom("Install".to_string()))
|
||||
.blocking_show();
|
||||
if !confirmed_install {
|
||||
// Cancelled installation
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
download_and_install(window, &plugin_version).await?;
|
||||
app_handle.emit(
|
||||
"show_toast",
|
||||
ShowToastRequest {
|
||||
message: format!(
|
||||
"Installed {}@{}",
|
||||
plugin_version.name, plugin_version.version
|
||||
),
|
||||
color: Some(Color::Success),
|
||||
icon: None,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
"import-data" => {
|
||||
let file_path = query_map.get("path").unwrap();
|
||||
let results = import_data(window, file_path).await?;
|
||||
_ = window.set_focus();
|
||||
window.emit(
|
||||
"show_toast",
|
||||
ShowToastRequest {
|
||||
message: format!("Imported data for {} workspaces", results.workspaces.len()),
|
||||
color: Some(Color::Success),
|
||||
icon: None,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
warn!("Unknown deep link command: {command}");
|
||||
}
|
||||
}
|
||||
|
||||
let msg = format!("No handler found for {path}");
|
||||
tauri::http::Response::builder().status(404).body(msg.as_bytes().to_vec()).unwrap()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
},
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"mobile": [],
|
||||
"desktop": {
|
||||
"schemes": [
|
||||
"yaak"
|
||||
|
||||
@@ -6,4 +6,7 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["system-proxy", "gzip"] }
|
||||
thiserror = { workspace = true }
|
||||
regex = "1.11.0"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
23
src-tauri/yaak-common/src/api_client.rs
Normal file
23
src-tauri/yaak-common/src/api_client.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::error::Result;
|
||||
use crate::platform::get_ua_platform;
|
||||
use reqwest::Client;
|
||||
use std::time::Duration;
|
||||
use tauri::http::{HeaderMap, HeaderValue};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
||||
pub fn yaak_api_client<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Client> {
|
||||
let platform = get_ua_platform();
|
||||
let version = app_handle.package_info().version.clone();
|
||||
let ua = format!("Yaak/{version} ({platform})");
|
||||
let mut default_headers = HeaderMap::new();
|
||||
default_headers.insert("Accept", HeaderValue::from_str("application/json").unwrap());
|
||||
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.timeout(Duration::from_secs(20))
|
||||
.default_headers(default_headers)
|
||||
.gzip(true)
|
||||
.user_agent(ua)
|
||||
.build()?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
19
src-tauri/yaak-common/src/error.rs
Normal file
19
src-tauri/yaak-common/src/error.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
ReqwestError(#[from] reqwest::Error),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -1 +1,4 @@
|
||||
pub mod window;
|
||||
pub mod window;
|
||||
pub mod platform;
|
||||
pub mod api_client;
|
||||
pub mod error;
|
||||
23
src-tauri/yaak-common/src/platform.rs
Normal file
23
src-tauri/yaak-common/src/platform.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
pub fn get_os() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ua_platform() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"Win"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"Mac"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"Linux"
|
||||
} else {
|
||||
"Unknown"
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ keyring = { version = "4.0.0-rc.1" }
|
||||
log = "0.4.26"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tauri = { workspace = true }
|
||||
thiserror = "2.0.12"
|
||||
thiserror = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
git2 = { version = "0.20.0", features = ["vendored-libgit2", "vendored-openssl"] }
|
||||
log = "0.4.22"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -15,6 +15,7 @@ tauri = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
ts-rs = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -16,15 +16,3 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.invoke_handler(generate_handler![check, activate, deactivate])
|
||||
.build()
|
||||
}
|
||||
|
||||
pub(crate) fn get_os() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::ops::Add;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow, is_dev};
|
||||
use ts_rs::TS;
|
||||
use yaak_common::platform::get_os;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::UpdateSource;
|
||||
|
||||
@@ -68,7 +69,7 @@ pub async fn activate_license<R: Runtime>(
|
||||
let client = reqwest::Client::new();
|
||||
let payload = ActivateLicenseRequestPayload {
|
||||
license_key: license_key.to_string(),
|
||||
app_platform: crate::get_os().to_string(),
|
||||
app_platform: get_os().to_string(),
|
||||
app_version: window.app_handle().package_info().version.to_string(),
|
||||
};
|
||||
let response = client.post(build_url("/licenses/activate")).json(&payload).send().await?;
|
||||
@@ -107,7 +108,7 @@ pub async fn deactivate_license<R: Runtime>(window: &WebviewWindow<R>) -> Result
|
||||
let client = reqwest::Client::new();
|
||||
let path = format!("/licenses/activations/{}/deactivate", activation_id);
|
||||
let payload = DeactivateLicenseRequestPayload {
|
||||
app_platform: crate::get_os().to_string(),
|
||||
app_platform: get_os().to_string(),
|
||||
app_version: window.app_handle().package_info().version.to_string(),
|
||||
};
|
||||
let response = client.post(build_url(&path)).json(&payload).send().await?;
|
||||
@@ -149,7 +150,7 @@ pub enum LicenseCheckStatus {
|
||||
|
||||
pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<LicenseCheckStatus> {
|
||||
let payload = CheckActivationRequestPayload {
|
||||
app_platform: crate::get_os().to_string(),
|
||||
app_platform: get_os().to_string(),
|
||||
app_version: window.package_info().version.to_string(),
|
||||
};
|
||||
let activation_id = get_activation_id(window.app_handle()).await;
|
||||
|
||||
@@ -7,7 +7,7 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
hex = "0.4.3"
|
||||
hex = { workspace = true }
|
||||
include_dir = "0.7"
|
||||
log = "0.4.22"
|
||||
nanoid = "0.4.0"
|
||||
@@ -18,10 +18,10 @@ sea-query = { version = "0.32.1", features = ["with-chrono", "attr"] }
|
||||
sea-query-rusqlite = { version = "0.7.0", features = ["with-chrono"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = "0.10.9"
|
||||
tauri = { workspace = true}
|
||||
sha2 = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
thiserror = "2.0.11"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt
|
||||
|
||||
export type PluginKeyValue = { model: "plugin_key_value", createdAt: string, updatedAt: string, pluginName: string, key: string, value: string, };
|
||||
|
||||
export type ProxySetting = { "type": "enabled", disabled: boolean, http: string, https: string, auth: ProxySettingAuth | null, bypass: string, } | { "type": "disabled" };
|
||||
export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" };
|
||||
|
||||
export type ProxySettingAuth = { user: string, password: string, };
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
[package]
|
||||
name = "yaak-plugins"
|
||||
links = "yaak-plugins"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
dunce = "1.0.4"
|
||||
futures-util = "0.3.30"
|
||||
log = "0.4.21"
|
||||
@@ -12,16 +14,23 @@ md5 = "0.7.0"
|
||||
path-slash = "0.2.1"
|
||||
rand = "0.9.0"
|
||||
regex = "1.10.6"
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
thiserror = "2.0.7"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] }
|
||||
tokio-tungstenite = "0.26.1"
|
||||
ts-rs = { workspace = true, features = ["import-esm"] }
|
||||
sha2 = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-templates = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
base64 = "0.22.1"
|
||||
zip-extract = "0.4.0"
|
||||
chrono = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -372,7 +372,7 @@ export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
|
||||
5
src-tauri/yaak-plugins/bindings/gen_search.ts
Normal file
5
src-tauri/yaak-plugins/bindings/gen_search.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginSearchResponse = { results: Array<PluginVersion>, };
|
||||
|
||||
export type PluginVersion = { id: string, version: string, description: string | null, displayName: string, homepageUrl: string | null, repositoryUrl: string, checksum: string, readme: string | null, yanked: boolean, };
|
||||
5
src-tauri/yaak-plugins/build.rs
Normal file
5
src-tauri/yaak-plugins/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
const COMMANDS: &[&str] = &["search", "install"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
@@ -1,2 +1,14 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { PluginSearchResponse, PluginVersion } from './bindings/gen_search';
|
||||
|
||||
export * from './bindings/gen_models';
|
||||
export * from './bindings/gen_events';
|
||||
export * from './bindings/gen_search';
|
||||
|
||||
export async function searchPlugins(query: string) {
|
||||
return invoke<PluginSearchResponse>('plugin:yaak-plugins|search', { query });
|
||||
}
|
||||
|
||||
export async function installPlugin(plugin: PluginVersion) {
|
||||
return invoke<string>('plugin:yaak-plugins|install', { plugin });
|
||||
}
|
||||
|
||||
3
src-tauri/yaak-plugins/permissions/default.toml
Normal file
3
src-tauri/yaak-plugins/permissions/default.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = ["allow-search", "allow-install"]
|
||||
67
src-tauri/yaak-plugins/src/api.rs
Normal file
67
src-tauri/yaak-plugins/src/api.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use crate::commands::{PluginSearchResponse, PluginVersion};
|
||||
use crate::error::Result;
|
||||
use reqwest::{Response, Url};
|
||||
use std::str::FromStr;
|
||||
use log::info;
|
||||
use tauri::{AppHandle, Runtime, is_dev};
|
||||
use yaak_common::api_client::yaak_api_client;
|
||||
use crate::error::Error::ApiErr;
|
||||
|
||||
pub async fn get_plugin<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
name: &str,
|
||||
version: Option<String>,
|
||||
) -> Result<PluginVersion> {
|
||||
info!("Getting plugin: {name} {version:?}");
|
||||
let mut url = base_url(&format!("/{name}"));
|
||||
if let Some(version) = version {
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
query_pairs.append_pair("version", &version);
|
||||
};
|
||||
let resp = yaak_api_client(app_handle)?.get(url.clone()).send().await?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(ApiErr(format!("{} response to {}", resp.status(), url.to_string())));
|
||||
}
|
||||
Ok(resp.json().await?)
|
||||
}
|
||||
|
||||
pub async fn download_plugin_archive<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
plugin_version: &PluginVersion,
|
||||
) -> Result<Response> {
|
||||
let name = plugin_version.name.clone();
|
||||
let version = plugin_version.version.clone();
|
||||
info!("Downloading plugin: {name} {version}");
|
||||
let mut url = base_url(&format!("/{}/download", name));
|
||||
{
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
query_pairs.append_pair("version", &version);
|
||||
};
|
||||
let resp = yaak_api_client(app_handle)?.get(url.clone()).send().await?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(ApiErr(format!("{} response to {}", resp.status(), url.to_string())));
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn search_plugins<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
query: &str,
|
||||
) -> Result<PluginSearchResponse> {
|
||||
let mut url = base_url("/search");
|
||||
{
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
query_pairs.append_pair("query", query);
|
||||
};
|
||||
let resp = yaak_api_client(app_handle)?.get(url).send().await?;
|
||||
Ok(resp.json().await?)
|
||||
}
|
||||
|
||||
fn base_url(path: &str) -> Url {
|
||||
let base_url = if is_dev() {
|
||||
"http://localhost:9444/api/v1/plugins"
|
||||
} else {
|
||||
"https://api.yaak.app/api/v1/plugins"
|
||||
};
|
||||
Url::from_str(&format!("{base_url}{path}")).unwrap()
|
||||
}
|
||||
8
src-tauri/yaak-plugins/src/checksum.rs
Normal file
8
src-tauri/yaak-plugins/src/checksum.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub(crate) fn compute_checksum(bytes: impl AsRef<[u8]>) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&bytes);
|
||||
let hash = hasher.finalize();
|
||||
hex::encode(hash)
|
||||
}
|
||||
45
src-tauri/yaak-plugins/src/commands.rs
Normal file
45
src-tauri/yaak-plugins/src/commands.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::api::search_plugins;
|
||||
use crate::error::Result;
|
||||
use crate::install::download_and_install;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Runtime, WebviewWindow, command};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn search<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
query: &str,
|
||||
) -> Result<PluginSearchResponse> {
|
||||
search_plugins(&app_handle, query).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn install<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin: PluginVersion,
|
||||
) -> Result<String> {
|
||||
download_and_install(&window, &plugin).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginSearchResponse {
|
||||
pub results: Vec<PluginVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginVersion {
|
||||
pub id: String,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub homepage_url: Option<String>,
|
||||
pub repository_url: Option<String>,
|
||||
pub checksum: String,
|
||||
pub readme: Option<String>,
|
||||
pub yanked: bool,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::events::InternalEvent;
|
||||
use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
use tokio::io;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
@@ -8,9 +9,12 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
CryptoErr(#[from] yaak_crypto::error::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
DbErr(#[from] yaak_models::error::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
TemplateErr(#[from] yaak_templates::error::Error),
|
||||
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IoErr(#[from] io::Error),
|
||||
|
||||
@@ -23,8 +27,17 @@ pub enum Error {
|
||||
#[error("Grpc send error: {0}")]
|
||||
GrpcSendErr(#[from] SendError<InternalEvent>),
|
||||
|
||||
#[error("Failed to send request: {0}")]
|
||||
RequestError(#[from] reqwest::Error),
|
||||
|
||||
#[error("JSON error: {0}")]
|
||||
JsonErr(#[from] serde_json::Error),
|
||||
|
||||
#[error("API Error: {0}")]
|
||||
ApiErr(String),
|
||||
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] yaak_common::error::Error),
|
||||
|
||||
#[error("Timeout elapsed: {0}")]
|
||||
TimeoutElapsed(#[from] tokio::time::error::Elapsed),
|
||||
@@ -38,6 +51,9 @@ pub enum Error {
|
||||
#[error("Plugin error: {0}")]
|
||||
PluginErr(String),
|
||||
|
||||
#[error("zip error: {0}")]
|
||||
ZipError(#[from] zip_extract::ZipExtractError),
|
||||
|
||||
#[error("Client not initialized error")]
|
||||
ClientNotInitializedErr,
|
||||
|
||||
@@ -45,4 +61,13 @@ pub enum Error {
|
||||
UnknownEventErr,
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -67,7 +67,7 @@ pub enum InternalEventPayload {
|
||||
BootResponse(BootResponse),
|
||||
|
||||
ReloadRequest(EmptyPayload),
|
||||
ReloadResponse(EmptyPayload),
|
||||
ReloadResponse(BootResponse),
|
||||
|
||||
TerminateRequest,
|
||||
TerminateResponse,
|
||||
|
||||
60
src-tauri/yaak-plugins/src/install.rs
Normal file
60
src-tauri/yaak-plugins/src/install.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::api::download_plugin_archive;
|
||||
use crate::checksum::compute_checksum;
|
||||
use crate::commands::PluginVersion;
|
||||
use crate::error::Error::PluginErr;
|
||||
use crate::error::Result;
|
||||
use crate::events::PluginWindowContext;
|
||||
use crate::manager::PluginManager;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
use std::fs::create_dir_all;
|
||||
use std::io::Cursor;
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use yaak_models::models::Plugin;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{UpdateSource, generate_id};
|
||||
|
||||
pub async fn download_and_install<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
plugin_version: &PluginVersion,
|
||||
) -> Result<String> {
|
||||
let resp = download_plugin_archive(window.app_handle(), &plugin_version).await?;
|
||||
let bytes = resp.bytes().await?;
|
||||
|
||||
let checksum = compute_checksum(&bytes);
|
||||
if checksum != plugin_version.checksum {
|
||||
return Err(PluginErr(format!(
|
||||
"Checksum mismatch {}b {checksum} != {}",
|
||||
bytes.len(),
|
||||
plugin_version.checksum
|
||||
)));
|
||||
}
|
||||
|
||||
info!("Checksum matched {}", checksum);
|
||||
|
||||
let plugin_dir = window.path().app_data_dir()?.join("plugins").join(generate_id());
|
||||
let plugin_dir_str = plugin_dir.to_str().unwrap().to_string();
|
||||
create_dir_all(&plugin_dir)?;
|
||||
|
||||
zip_extract::extract(Cursor::new(&bytes), &plugin_dir, true)?;
|
||||
info!("Extracted plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &plugin_dir_str).await?;
|
||||
|
||||
let p = window.db().upsert_plugin(
|
||||
&Plugin {
|
||||
id: plugin_version.id.clone(),
|
||||
checked_at: Some(Utc::now().naive_utc()),
|
||||
directory: plugin_dir_str.clone(),
|
||||
enabled: true,
|
||||
url: None,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
)?;
|
||||
|
||||
info!("Installed plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||
|
||||
Ok(p.id)
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::commands::{install, search};
|
||||
use crate::manager::PluginManager;
|
||||
use log::info;
|
||||
use std::process::exit;
|
||||
use tauri::plugin::{Builder, TauriPlugin};
|
||||
use tauri::{Manager, RunEvent, Runtime, State};
|
||||
use tauri::{Manager, RunEvent, Runtime, State, generate_handler};
|
||||
|
||||
mod commands;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod manager;
|
||||
@@ -13,9 +15,13 @@ pub mod plugin_handle;
|
||||
mod server_ws;
|
||||
pub mod template_callback;
|
||||
mod util;
|
||||
mod checksum;
|
||||
pub mod api;
|
||||
pub mod install;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("yaak-plugins")
|
||||
.invoke_handler(generate_handler![search, install])
|
||||
.setup(|app_handle, _| {
|
||||
let manager = PluginManager::new(app_handle.clone());
|
||||
app_handle.manage(manager.clone());
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::server_ws::PluginRuntimeServerWebsocket;
|
||||
use log::{error, info, warn};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::path::BaseDirectory;
|
||||
@@ -38,12 +38,13 @@ pub struct PluginManager {
|
||||
plugins: Arc<Mutex<Vec<PluginHandle>>>,
|
||||
kill_tx: tokio::sync::watch::Sender<bool>,
|
||||
ws_service: Arc<PluginRuntimeServerWebsocket>,
|
||||
vendored_plugin_dir: PathBuf,
|
||||
installed_plugin_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PluginCandidate {
|
||||
dir: String,
|
||||
watch: bool,
|
||||
}
|
||||
|
||||
impl PluginManager {
|
||||
@@ -56,11 +57,21 @@ impl PluginManager {
|
||||
let ws_service =
|
||||
PluginRuntimeServerWebsocket::new(events_tx, client_disconnect_tx, client_connect_tx);
|
||||
|
||||
let vendored_plugin_dir = app_handle
|
||||
.path()
|
||||
.resolve("vendored/plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let installed_plugin_dir =
|
||||
app_handle.path().app_data_dir().expect("failed to get app data dir");
|
||||
|
||||
let plugin_manager = PluginManager {
|
||||
plugins: Default::default(),
|
||||
subscribers: Default::default(),
|
||||
ws_service: Arc::new(ws_service.clone()),
|
||||
kill_tx: kill_server_tx,
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
};
|
||||
|
||||
// Forward events to subscribers
|
||||
@@ -135,18 +146,13 @@ impl PluginManager {
|
||||
&self,
|
||||
app_handle: &AppHandle<R>,
|
||||
) -> Vec<PluginCandidate> {
|
||||
let bundled_plugins_dir = &app_handle
|
||||
.path()
|
||||
.resolve("vendored/plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let plugins_dir = if is_dev() {
|
||||
// Use plugins directly for easy development
|
||||
env::current_dir()
|
||||
.map(|cwd| cwd.join("../plugins").canonicalize().unwrap())
|
||||
.unwrap_or_else(|_| bundled_plugins_dir.clone())
|
||||
.unwrap_or_else(|_| self.vendored_plugin_dir.to_path_buf())
|
||||
} else {
|
||||
bundled_plugins_dir.clone()
|
||||
self.vendored_plugin_dir.to_path_buf()
|
||||
};
|
||||
|
||||
info!("Loading bundled plugins from {plugins_dir:?}");
|
||||
@@ -155,13 +161,7 @@ impl PluginManager {
|
||||
.await
|
||||
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str())
|
||||
.iter()
|
||||
.map(|d| {
|
||||
let is_vendored = plugins_dir.starts_with(bundled_plugins_dir);
|
||||
PluginCandidate {
|
||||
dir: d.into(),
|
||||
watch: !is_vendored,
|
||||
}
|
||||
})
|
||||
.map(|d| PluginCandidate { dir: d.into() })
|
||||
.collect();
|
||||
|
||||
let plugins = app_handle.db().list_plugins().unwrap_or_default();
|
||||
@@ -169,7 +169,6 @@ impl PluginManager {
|
||||
.iter()
|
||||
.map(|p| PluginCandidate {
|
||||
dir: p.directory.to_owned(),
|
||||
watch: true,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -203,7 +202,6 @@ impl PluginManager {
|
||||
&self,
|
||||
window_context: &PluginWindowContext,
|
||||
dir: &str,
|
||||
watch: bool,
|
||||
) -> Result<()> {
|
||||
info!("Adding plugin by dir {dir}");
|
||||
let maybe_tx = self.ws_service.app_to_plugin_events_tx.lock().await;
|
||||
@@ -212,6 +210,9 @@ impl PluginManager {
|
||||
Some(tx) => tx,
|
||||
};
|
||||
let plugin_handle = PluginHandle::new(dir, tx.clone());
|
||||
let dir_path = Path::new(dir);
|
||||
let is_vendored = dir_path.starts_with(self.vendored_plugin_dir.as_path());
|
||||
let is_installed = dir_path.starts_with(self.installed_plugin_dir.as_path());
|
||||
|
||||
// Boot the plugin
|
||||
let event = timeout(
|
||||
@@ -221,7 +222,7 @@ impl PluginManager {
|
||||
&plugin_handle,
|
||||
&InternalEventPayload::BootRequest(BootRequest {
|
||||
dir: dir.to_string(),
|
||||
watch,
|
||||
watch: !is_vendored && !is_installed,
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -256,10 +257,7 @@ impl PluginManager {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Err(e) = self
|
||||
.add_plugin_by_dir(window_context, candidate.dir.as_str(), candidate.watch)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = self.add_plugin_by_dir(window_context, candidate.dir.as_str()).await {
|
||||
warn!("Failed to add plugin {} {e:?}", candidate.dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
hex = "0.4.3"
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
hex = { workspace = true }
|
||||
log = "0.4.22"
|
||||
notify = "8.0.0"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -12,7 +12,7 @@ md5 = "0.7.0"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
thiserror = "2.0.11"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "time", "test-util"] }
|
||||
tokio-tungstenite = { version = "0.26.2", default-features = false, features = ["rustls-tls-native-roots", "connect"] }
|
||||
yaak-http = { workspace = true }
|
||||
|
||||
@@ -269,7 +269,7 @@ export function GrpcRequestPane({
|
||||
label="Request"
|
||||
onChangeValue={setActiveTab}
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
tabListClassName="mt-1 !mb-1.5"
|
||||
>
|
||||
<TabContent value="message">
|
||||
<GrpcEditor
|
||||
|
||||
@@ -350,7 +350,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
label="Request"
|
||||
onChangeValue={setActiveTab}
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
tabListClassName="mt-1 !mb-1.5"
|
||||
>
|
||||
<TabContent value={TAB_AUTH}>
|
||||
<HttpAuthenticationEditor model={activeRequest} />
|
||||
|
||||
@@ -153,7 +153,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
tabs={tabs}
|
||||
label="Response"
|
||||
className="ml-3 mr-3 mb-3"
|
||||
tabListClassName="mt-1.5"
|
||||
tabListClassName="mt-0.5"
|
||||
>
|
||||
<TabContent value={TAB_BODY}>
|
||||
<ErrorBoundary name="Http Response Viewer">
|
||||
|
||||
@@ -11,6 +11,7 @@ interface Props {
|
||||
export function ImportDataDialog({ importData }: Props) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [filePath, setFilePath] = useLocalStorage<string | null>('importFilePath', null);
|
||||
|
||||
return (
|
||||
<VStack space={5} className="pb-4">
|
||||
<VStack space={1}>
|
||||
|
||||
@@ -66,8 +66,10 @@ export default function Settings({ hide }: Props) {
|
||||
</HeaderSize>
|
||||
)}
|
||||
<Tabs
|
||||
layout="horizontal"
|
||||
value={tab}
|
||||
addBorders
|
||||
tabListClassName="min-w-[10rem] bg-surface x-theme-sidebar border-r border-border"
|
||||
label="Settings"
|
||||
onChangeValue={setTab}
|
||||
tabs={tabs.map((value) => ({ value, label: capitalize(value) }))}
|
||||
@@ -81,7 +83,7 @@ export default function Settings({ hide }: Props) {
|
||||
<TabContent value={TAB_THEME} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<SettingsTheme />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_PLUGINS} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<TabContent value={TAB_PLUGINS} className="pt-3 h-full px-4 grid grid-rows-1">
|
||||
<SettingsPlugins />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_PROXY} className="pt-3 overflow-y-auto h-full px-4">
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { Plugin} from '@yaakapp-internal/models';
|
||||
import type { Plugin } from '@yaakapp-internal/models';
|
||||
import { pluginsAtom } from '@yaakapp-internal/models';
|
||||
import { installPlugin, PluginVersion, searchPlugins } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useDebouncedValue } from '../../hooks/useDebouncedValue';
|
||||
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
|
||||
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
||||
import { useRefreshPlugins } from '../../hooks/usePlugins';
|
||||
@@ -10,86 +13,86 @@ import { useUninstallPlugin } from '../../hooks/useUninstallPlugin';
|
||||
import { Button } from '../core/Button';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { LoadingIcon } from '../core/LoadingIcon';
|
||||
import { PlainInput } from '../core/PlainInput';
|
||||
import { HStack } from '../core/Stacks';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '../core/Table';
|
||||
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
||||
import { EmptyStateText } from '../EmptyStateText';
|
||||
import { SelectFile } from '../SelectFile';
|
||||
|
||||
export function SettingsPlugins() {
|
||||
const [directory, setDirectory] = React.useState<string | null>(null);
|
||||
const plugins = useAtomValue(pluginsAtom);
|
||||
const createPlugin = useInstallPlugin();
|
||||
const refreshPlugins = useRefreshPlugins();
|
||||
const [tab, setTab] = useState<string>();
|
||||
return (
|
||||
<div className="grid grid-rows-[minmax(0,1fr)_auto] h-full">
|
||||
{plugins.length === 0 ? (
|
||||
<div className="pb-4">
|
||||
<EmptyStateText className="text-center">
|
||||
Plugins extend the functionality of Yaak.
|
||||
<br />
|
||||
Add your first plugin to get started.
|
||||
</EmptyStateText>
|
||||
</div>
|
||||
) : (
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-2 text-left">Plugin</th>
|
||||
<th className="py-2 text-right">Version</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{plugins.map((p) => (
|
||||
<PluginInfo key={p.id} plugin={p} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (directory == null) return;
|
||||
createPlugin.mutate(directory);
|
||||
setDirectory(null);
|
||||
}}
|
||||
<div className="h-full">
|
||||
<Tabs
|
||||
value={tab}
|
||||
label="Plugins"
|
||||
onChangeValue={setTab}
|
||||
addBorders
|
||||
tabListClassName="!-ml-3"
|
||||
tabs={[
|
||||
{ label: 'Marketplace', value: 'search' },
|
||||
{ label: 'Installed', value: 'installed' },
|
||||
]}
|
||||
>
|
||||
<footer className="grid grid-cols-[minmax(0,1fr)_auto] -mx-4 py-2 px-4 border-t bg-surface-highlight border-border-subtle min-w-0">
|
||||
<SelectFile
|
||||
size="xs"
|
||||
noun="Plugin"
|
||||
directory
|
||||
onChange={({ filePath }) => setDirectory(filePath)}
|
||||
filePath={directory}
|
||||
/>
|
||||
<HStack>
|
||||
{directory && (
|
||||
<Button size="xs" type="submit" color="primary" className="ml-auto">
|
||||
Add Plugin
|
||||
</Button>
|
||||
)}
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="refresh"
|
||||
title="Reload plugins"
|
||||
spin={refreshPlugins.isPending}
|
||||
onClick={() => refreshPlugins.mutate()}
|
||||
/>
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="help"
|
||||
title="View documentation"
|
||||
onClick={() => openUrl('https://feedback.yaak.app/help/articles/6911763-quick-start')}
|
||||
/>
|
||||
</HStack>
|
||||
</footer>
|
||||
</form>
|
||||
<TabContent value="search">
|
||||
<PluginSearch />
|
||||
</TabContent>
|
||||
<TabContent value="installed">
|
||||
<InstalledPlugins />
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (directory == null) return;
|
||||
createPlugin.mutate(directory);
|
||||
setDirectory(null);
|
||||
}}
|
||||
>
|
||||
<footer className="grid grid-cols-[minmax(0,1fr)_auto] -mx-4 py-2 px-4 border-t bg-surface-highlight border-border-subtle min-w-0">
|
||||
<SelectFile
|
||||
size="xs"
|
||||
noun="Plugin"
|
||||
directory
|
||||
onChange={({ filePath }) => setDirectory(filePath)}
|
||||
filePath={directory}
|
||||
/>
|
||||
<HStack>
|
||||
{directory && (
|
||||
<Button size="xs" type="submit" color="primary" className="ml-auto">
|
||||
Add Plugin
|
||||
</Button>
|
||||
)}
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="refresh"
|
||||
title="Reload plugins"
|
||||
spin={refreshPlugins.isPending}
|
||||
onClick={() => refreshPlugins.mutate()}
|
||||
/>
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="help"
|
||||
title="View documentation"
|
||||
onClick={() =>
|
||||
openUrl('https://feedback.yaak.app/help/articles/6911763-quick-start')
|
||||
}
|
||||
/>
|
||||
</HStack>
|
||||
</footer>
|
||||
</form>
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PluginInfo({ plugin }: { plugin: Plugin }) {
|
||||
const pluginInfo = usePluginInfo(plugin.id);
|
||||
const deletePlugin = useUninstallPlugin(plugin.id);
|
||||
const deletePlugin = useUninstallPlugin();
|
||||
return (
|
||||
<tr className="group">
|
||||
<td className="py-2 select-text cursor-text w-full">{pluginInfo.data?.name}</td>
|
||||
@@ -101,9 +104,127 @@ function PluginInfo({ plugin }: { plugin: Plugin }) {
|
||||
size="sm"
|
||||
icon="trash"
|
||||
title="Uninstall plugin"
|
||||
onClick={() => deletePlugin.mutate()}
|
||||
onClick={() => deletePlugin.mutate(plugin.id)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function PluginSearch() {
|
||||
const [query, setQuery] = useState<string>('');
|
||||
const debouncedQuery = useDebouncedValue(query);
|
||||
const results = useQuery({
|
||||
queryKey: ['plugins', debouncedQuery],
|
||||
queryFn: () => searchPlugins(query),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)] gap-3">
|
||||
<HStack space={1.5}>
|
||||
<PlainInput
|
||||
hideLabel
|
||||
label="Search"
|
||||
placeholder="Search plugins..."
|
||||
onChange={setQuery}
|
||||
defaultValue={query}
|
||||
/>
|
||||
</HStack>
|
||||
<div className="w-full h-full overflow-auto">
|
||||
{results.data == null ? (
|
||||
<EmptyStateText>
|
||||
<LoadingIcon size="xl" className="text-text-subtlest" />
|
||||
</EmptyStateText>
|
||||
) : (results.data.results ?? []).length === 0 ? (
|
||||
<EmptyStateText>No plugins found</EmptyStateText>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Name</TableHeaderCell>
|
||||
<TableHeaderCell>Version</TableHeaderCell>
|
||||
<TableHeaderCell>Description</TableHeaderCell>
|
||||
<TableHeaderCell children="" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{results.data.results.map((plugin) => {
|
||||
return (
|
||||
<TableRow key={plugin.id}>
|
||||
<TableCell className="font-semibold">{plugin.displayName}</TableCell>
|
||||
<TableCell className="text-text-subtle">
|
||||
<InlineCode>{plugin.version}</InlineCode>
|
||||
</TableCell>
|
||||
<TableCell className="w-full text-text-subtle">
|
||||
{plugin.description ?? 'n/a'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<InstallPluginButton plugin={plugin} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InstallPluginButton({ plugin }: { plugin: PluginVersion }) {
|
||||
const plugins = useAtomValue(pluginsAtom);
|
||||
const deletePlugin = useUninstallPlugin();
|
||||
const installed = plugins?.some((p) => p.id === plugin.id);
|
||||
const installPluginMutation = useMutation({
|
||||
mutationKey: ['install_plugin', plugin.id],
|
||||
mutationFn: installPlugin,
|
||||
});
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
variant={installed ? 'solid' : 'border'}
|
||||
color={installed ? 'primary' : 'secondary'}
|
||||
className="ml-auto"
|
||||
isLoading={installPluginMutation.isPending}
|
||||
onClick={async () => {
|
||||
if (installed) {
|
||||
deletePlugin.mutate(plugin.id);
|
||||
} else {
|
||||
installPluginMutation.mutate(plugin);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{installed ? 'Uninstall' : 'Install'}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function InstalledPlugins() {
|
||||
const plugins = useAtomValue(pluginsAtom);
|
||||
return plugins.length === 0 ? (
|
||||
<div className="pb-4">
|
||||
<EmptyStateText className="text-center">
|
||||
Plugins extend the functionality of Yaak.
|
||||
<br />
|
||||
Add your first plugin to get started.
|
||||
</EmptyStateText>
|
||||
</div>
|
||||
) : (
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-2 text-left">Plugin</th>
|
||||
<th className="py-2 text-right">Version</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{plugins.map((p) => (
|
||||
<PluginInfo key={p.id} plugin={p} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { useLicense } from '@yaakapp-internal/license';
|
||||
import { useRef } from 'react';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { appInfo } from '../lib/appInfo';
|
||||
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
|
||||
import { useExportData } from '../hooks/useExportData';
|
||||
import { useImportData } from '../hooks/useImportData';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { appInfo } from '../lib/appInfo';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { importData } from '../lib/importData';
|
||||
import type { DropdownRef } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
@@ -15,7 +15,6 @@ import { IconButton } from './core/IconButton';
|
||||
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
|
||||
|
||||
export function SettingsDropdown() {
|
||||
const importData = useImportData();
|
||||
const exportData = useExportData();
|
||||
const dropdownRef = useRef<DropdownRef>(null);
|
||||
const checkForUpdates = useCheckForUpdates();
|
||||
|
||||
@@ -234,7 +234,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
label="Request"
|
||||
onChangeValue={setActiveTab}
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
tabListClassName="mt-1 !mb-1.5"
|
||||
>
|
||||
<TabContent value={TAB_AUTH}>
|
||||
<HttpAuthenticationEditor model={activeRequest} />
|
||||
|
||||
@@ -4,14 +4,19 @@ import { useAtomValue } from 'jotai';
|
||||
import * as m from 'motion/react-m';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useEnsureActiveCookieJar, useSubscribeActiveCookieJarId } from '../hooks/useActiveCookieJar';
|
||||
import { activeEnvironmentAtom, useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
||||
import {
|
||||
useEnsureActiveCookieJar,
|
||||
useSubscribeActiveCookieJarId,
|
||||
} from '../hooks/useActiveCookieJar';
|
||||
import {
|
||||
activeEnvironmentAtom,
|
||||
useSubscribeActiveEnvironmentId,
|
||||
} from '../hooks/useActiveEnvironment';
|
||||
import { activeRequestAtom } from '../hooks/useActiveRequest';
|
||||
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useImportData } from '../hooks/useImportData';
|
||||
import { useSubscribeRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||
import { useSubscribeRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useSubscribeRecentRequests } from '../hooks/useRecentRequests';
|
||||
@@ -22,6 +27,7 @@ import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
||||
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
||||
import { duplicateRequestAndNavigate } from '../lib/duplicateRequestAndNavigate';
|
||||
import { importData } from '../lib/importData';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
@@ -204,7 +210,6 @@ export function Workspace() {
|
||||
function WorkspaceBody() {
|
||||
const activeRequest = useAtomValue(activeRequestAtom);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const importData = useImportData();
|
||||
|
||||
if (activeWorkspace == null) {
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { Icon } from '../Icon';
|
||||
import type { RadioDropdownProps } from '../RadioDropdown';
|
||||
import { RadioDropdown } from '../RadioDropdown';
|
||||
import { HStack } from '../Stacks';
|
||||
import { ErrorBoundary } from '../../ErrorBoundary';
|
||||
import { Icon } from '../Icon';
|
||||
import { RadioDropdown, RadioDropdownProps } from '../RadioDropdown';
|
||||
|
||||
export type TabItem =
|
||||
| {
|
||||
@@ -28,6 +26,7 @@ interface Props {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
addBorders?: boolean;
|
||||
layout?: 'horizontal' | 'vertical';
|
||||
}
|
||||
|
||||
export function Tabs({
|
||||
@@ -39,6 +38,7 @@ export function Tabs({
|
||||
className,
|
||||
tabListClassName,
|
||||
addBorders,
|
||||
layout = 'vertical',
|
||||
}: Props) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -49,7 +49,10 @@ export function Tabs({
|
||||
const tabs = ref.current?.querySelectorAll<HTMLDivElement>(`[data-tab]`);
|
||||
for (const tab of tabs ?? []) {
|
||||
const v = tab.getAttribute('data-tab');
|
||||
if (v === value) {
|
||||
let parent = tab.closest('.tabs-container');
|
||||
if (parent !== ref.current) {
|
||||
// Tab is part of a nested tab container, so ignore it
|
||||
} else if (v === value) {
|
||||
tab.setAttribute('tabindex', '-1');
|
||||
tab.setAttribute('data-state', 'active');
|
||||
tab.setAttribute('aria-hidden', 'false');
|
||||
@@ -67,29 +70,41 @@ export function Tabs({
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
className,
|
||||
'tabs-container',
|
||||
'transform-gpu',
|
||||
'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1',
|
||||
'h-full grid',
|
||||
layout === 'horizontal' && 'grid-rows-1 grid-cols-[auto_minmax(0,1fr)]',
|
||||
layout === 'vertical' && 'grid-rows-[auto_minmax(0,1fr)] grid-cols-1',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
aria-label={label}
|
||||
className={classNames(
|
||||
tabListClassName,
|
||||
addBorders && '!-ml-1 h-md mt-2',
|
||||
'flex items-center overflow-x-auto overflow-y-visible hide-scrollbars mt-1 mb-2',
|
||||
addBorders && '!-ml-1',
|
||||
'flex items-center hide-scrollbars mb-2',
|
||||
layout === 'horizontal' && 'h-full overflow-auto pt-1 px-2',
|
||||
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
|
||||
// Give space for button focus states within overflow boundary.
|
||||
'-ml-5 pl-3 pr-1 py-1',
|
||||
layout === 'vertical' && 'py-1 -ml-5 pl-3 pr-1',
|
||||
)}
|
||||
>
|
||||
<HStack space={2} className="h-full flex-shrink-0">
|
||||
<div
|
||||
className={classNames(
|
||||
layout === 'horizontal' && 'flex flex-col gap-1 w-full mt-1 pb-3 mb-auto',
|
||||
layout === 'vertical' && 'flex flex-row flex-shrink-0 gap-2 w-full',
|
||||
)}
|
||||
>
|
||||
{tabs.map((t) => {
|
||||
const isActive = t.value === value;
|
||||
const btnClassName = classNames(
|
||||
'h-full flex items-center rounded',
|
||||
'h-sm flex items-center rounded',
|
||||
'!px-2 ml-[1px]',
|
||||
addBorders && 'border',
|
||||
isActive ? 'text-text' : 'text-text-subtle hover:text-text',
|
||||
isActive && addBorders ? 'border-border-subtle' : 'border-transparent',
|
||||
isActive && addBorders
|
||||
? 'border-border-subtle bg-surface-active'
|
||||
: 'border-transparent',
|
||||
);
|
||||
|
||||
if ('options' in t) {
|
||||
@@ -135,7 +150,7 @@ export function Tabs({
|
||||
);
|
||||
}
|
||||
})}
|
||||
</HStack>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import type { BatchUpsertResult } from '@yaakapp-internal/models';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { FormattedError } from '../components/core/FormattedError';
|
||||
import { VStack } from '../components/core/Stacks';
|
||||
import { ImportDataDialog } from '../components/ImportDataDialog';
|
||||
import { showAlert } from '../lib/alert';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { activeWorkspaceAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useImportData() {
|
||||
const importData = async (filePath: string): Promise<boolean> => {
|
||||
const activeWorkspace = jotaiStore.get(activeWorkspaceAtom);
|
||||
const imported = await invokeCmd<BatchUpsertResult>('cmd_import_data', {
|
||||
filePath,
|
||||
workspaceId: activeWorkspace?.id,
|
||||
});
|
||||
|
||||
const importedWorkspace = imported.workspaces[0];
|
||||
|
||||
showDialog({
|
||||
id: 'import-complete',
|
||||
title: 'Import Complete',
|
||||
size: 'sm',
|
||||
hideX: true,
|
||||
render: ({ hide }) => {
|
||||
return (
|
||||
<VStack space={3} className="pb-4">
|
||||
<ul className="list-disc pl-6">
|
||||
<li>{pluralizeCount('Workspace', imported.workspaces.length)}</li>
|
||||
{imported.environments.length > 0 && (
|
||||
<li>{pluralizeCount('Environment', imported.environments.length)}</li>
|
||||
)}
|
||||
{imported.folders.length > 0 && (
|
||||
<li>{pluralizeCount('Folder', imported.folders.length)}</li>
|
||||
)}
|
||||
{imported.httpRequests.length > 0 && (
|
||||
<li>{pluralizeCount('HTTP Request', imported.httpRequests.length)}</li>
|
||||
)}
|
||||
{imported.grpcRequests.length > 0 && (
|
||||
<li>{pluralizeCount('GRPC Request', imported.grpcRequests.length)}</li>
|
||||
)}
|
||||
{imported.websocketRequests.length > 0 && (
|
||||
<li>{pluralizeCount('Websocket Request', imported.websocketRequests.length)}</li>
|
||||
)}
|
||||
</ul>
|
||||
<div>
|
||||
<Button className="ml-auto" onClick={hide} color="primary">
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</VStack>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (importedWorkspace != null) {
|
||||
const environmentId = imported.environments[0]?.id ?? null;
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: importedWorkspace.id },
|
||||
search: { environment_id: environmentId },
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return useFastMutation({
|
||||
mutationKey: ['import_data'],
|
||||
onError: (err: string) => {
|
||||
showAlert({
|
||||
id: 'import-failed',
|
||||
title: 'Import Failed',
|
||||
size: 'md',
|
||||
body: <FormattedError>{err}</FormattedError>,
|
||||
});
|
||||
},
|
||||
mutationFn: async () => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
showDialog({
|
||||
id: 'import',
|
||||
title: 'Import Data',
|
||||
size: 'sm',
|
||||
render: ({ hide }) => {
|
||||
const importAndHide = async (filePath: string) => {
|
||||
try {
|
||||
const didImport = await importData(filePath);
|
||||
if (!didImport) {
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
return <ImportDataDialog importData={importAndHide} />;
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -10,10 +10,7 @@ function pluginInfoKey(id: string) {
|
||||
export function usePluginInfo(id: string) {
|
||||
return useQuery({
|
||||
queryKey: pluginInfoKey(id),
|
||||
queryFn: async () => {
|
||||
const info = (await invokeCmd('cmd_plugin_info', { id })) as BootResponse;
|
||||
return info;
|
||||
},
|
||||
queryFn: () => invokeCmd<BootResponse>('cmd_plugin_info', { id }),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import type { Plugin } from '@yaakapp-internal/models';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useUninstallPlugin(pluginId: string) {
|
||||
return useFastMutation<Plugin | null, string>({
|
||||
export function useUninstallPlugin() {
|
||||
return useFastMutation({
|
||||
mutationKey: ['uninstall_plugin'],
|
||||
mutationFn: async () => {
|
||||
mutationFn: async (pluginId: string) => {
|
||||
return invokeCmd('cmd_uninstall_plugin', { pluginId });
|
||||
},
|
||||
});
|
||||
|
||||
107
src-web/lib/importData.tsx
Normal file
107
src-web/lib/importData.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { BatchUpsertResult } from '@yaakapp-internal/models';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { FormattedError } from '../components/core/FormattedError';
|
||||
import { VStack } from '../components/core/Stacks';
|
||||
import { ImportDataDialog } from '../components/ImportDataDialog';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { showAlert } from './alert';
|
||||
import { showDialog } from './dialog';
|
||||
import { jotaiStore } from './jotai';
|
||||
import { pluralizeCount } from './pluralize';
|
||||
import { router } from './router';
|
||||
import { invokeCmd } from './tauri';
|
||||
|
||||
export const importData = createFastMutation({
|
||||
mutationKey: ['import_data'],
|
||||
onError: (err: string) => {
|
||||
showAlert({
|
||||
id: 'import-failed',
|
||||
title: 'Import Failed',
|
||||
size: 'md',
|
||||
body: <FormattedError>{err}</FormattedError>,
|
||||
});
|
||||
},
|
||||
mutationFn: async () => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
showDialog({
|
||||
id: 'import',
|
||||
title: 'Import Data',
|
||||
size: 'sm',
|
||||
render: ({ hide }) => {
|
||||
const importAndHide = async (filePath: string) => {
|
||||
try {
|
||||
const didImport = await performImport(filePath);
|
||||
if (!didImport) {
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
return <ImportDataDialog importData={importAndHide} />;
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
async function performImport(filePath: string): Promise<boolean> {
|
||||
const activeWorkspace = jotaiStore.get(activeWorkspaceAtom);
|
||||
const imported = await invokeCmd<BatchUpsertResult>('cmd_import_data', {
|
||||
filePath,
|
||||
workspaceId: activeWorkspace?.id,
|
||||
});
|
||||
|
||||
const importedWorkspace = imported.workspaces[0];
|
||||
|
||||
showDialog({
|
||||
id: 'import-complete',
|
||||
title: 'Import Complete',
|
||||
size: 'sm',
|
||||
hideX: true,
|
||||
render: ({ hide }) => {
|
||||
return (
|
||||
<VStack space={3} className="pb-4">
|
||||
<ul className="list-disc pl-6">
|
||||
<li>{pluralizeCount('Workspace', imported.workspaces.length)}</li>
|
||||
{imported.environments.length > 0 && (
|
||||
<li>{pluralizeCount('Environment', imported.environments.length)}</li>
|
||||
)}
|
||||
{imported.folders.length > 0 && (
|
||||
<li>{pluralizeCount('Folder', imported.folders.length)}</li>
|
||||
)}
|
||||
{imported.httpRequests.length > 0 && (
|
||||
<li>{pluralizeCount('HTTP Request', imported.httpRequests.length)}</li>
|
||||
)}
|
||||
{imported.grpcRequests.length > 0 && (
|
||||
<li>{pluralizeCount('GRPC Request', imported.grpcRequests.length)}</li>
|
||||
)}
|
||||
{imported.websocketRequests.length > 0 && (
|
||||
<li>{pluralizeCount('Websocket Request', imported.websocketRequests.length)}</li>
|
||||
)}
|
||||
</ul>
|
||||
<div>
|
||||
<Button className="ml-auto" onClick={hide} color="primary">
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</VStack>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (importedWorkspace != null) {
|
||||
const environmentId = imported.environments[0]?.id ?? null;
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: importedWorkspace.id },
|
||||
search: { environment_id: environmentId },
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user