mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Show toast on plugin event handling errors instead of crashing
Also set folder context on template render and fix timestamp function
This commit is contained in:
@@ -58,7 +58,7 @@ Built with [Tauri](https://tauri.app), Rust, and React, it’s fast, lightweight
|
|||||||
|
|
||||||
## Contribution Policy
|
## Contribution Policy
|
||||||
|
|
||||||
Yaak is open source, but only accepting contributions for bug fixes. To get started,
|
Yaak is open source but only accepting contributions for bug fixes. To get started,
|
||||||
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
|
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
|
||||||
|
|
||||||
## Useful Resources
|
## Useful Resources
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ export type SetKeyValueRequest = { key: string, value: string, };
|
|||||||
|
|
||||||
export type SetKeyValueResponse = {};
|
export type SetKeyValueResponse = {};
|
||||||
|
|
||||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
|
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, timeout?: number, };
|
||||||
|
|
||||||
export type TemplateFunction = { name: string, description?: string,
|
export type TemplateFunction = { name: string, description?: string,
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array<EnvironmentVariable>, color: string | null, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -454,6 +454,8 @@ export class PluginInstance {
|
|||||||
show: async (args) => {
|
show: async (args) => {
|
||||||
await this.#sendAndWaitForReply(windowContext, {
|
await this.#sendAndWaitForReply(windowContext, {
|
||||||
type: 'show_toast_request',
|
type: 'show_toast_request',
|
||||||
|
// Handle default here because null/undefined both convert to None in Rust translation
|
||||||
|
timeout: args.timeout === undefined ? 5000 : args.timeout,
|
||||||
...args,
|
...args,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -271,6 +271,12 @@ export const plugin: PluginDefinition = {
|
|||||||
label: 'Advanced',
|
label: 'Advanced',
|
||||||
inputs: [
|
inputs: [
|
||||||
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'headerName',
|
||||||
|
label: 'Header Name',
|
||||||
|
defaultValue: 'Authorization',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'headerPrefix',
|
name: 'headerPrefix',
|
||||||
@@ -397,15 +403,9 @@ export const plugin: PluginDefinition = {
|
|||||||
throw new Error('Invalid grant type ' + grantType);
|
throw new Error('Invalid grant type ' + grantType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headerName = stringArg(values, 'headerName') || 'Authorization';
|
||||||
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
|
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
|
||||||
return {
|
return { setHeaders: [{ name: headerName, value: headerValue }] };
|
||||||
setHeaders: [
|
|
||||||
{
|
|
||||||
name: 'Authorization',
|
|
||||||
value: headerValue,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { HttpUrlParameter } from '@yaakapp-internal/models';
|
import type { HttpUrlParameter } from '@yaakapp-internal/models';
|
||||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
|
import { resolvedModelName } from '@yaakapp/app/lib/resolvedModelName';
|
||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
templateFunctions: [
|
templateFunctions: [
|
||||||
@@ -96,5 +97,22 @@ export const plugin: PluginDefinition = {
|
|||||||
return renderedValue;
|
return renderedValue;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'request.name',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: 'requestId',
|
||||||
|
label: 'Http Request',
|
||||||
|
type: 'http_request',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
|
const requestId = String(args.values.requestId ?? 'n/a');
|
||||||
|
const httpRequest = await ctx.httpRequest.getById({ id: requestId });
|
||||||
|
if (httpRequest == null) return null;
|
||||||
|
|
||||||
|
return resolvedModelName(httpRequest);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function formatDatetime(args: {
|
|||||||
format?: string;
|
format?: string;
|
||||||
in?: ContextFn<Date>;
|
in?: ContextFn<Date>;
|
||||||
}): string {
|
}): string {
|
||||||
const { date, format = 'yyyy-MM-dd HH:mm:ss' } = args;
|
const { date, format } = args;
|
||||||
const d = parseDateString(date ?? '');
|
const d = parseDateString(date ?? '');
|
||||||
return formatDate(d, String(format), { in: args.in });
|
return formatDate(d, String(format || 'yyyy-MM-dd HH:mm:ss'), { in: args.in });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ pub enum Error {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CommonError(#[from] yaak_common::error::Error),
|
CommonError(#[from] yaak_common::error::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
ClipboardError(#[from] tauri_plugin_clipboard_manager::Error),
|
||||||
|
|
||||||
#[error("Updater error: {0}")]
|
#[error("Updater error: {0}")]
|
||||||
UpdaterError(#[from] tauri_plugin_updater::Error),
|
UpdaterError(#[from] tauri_plugin_updater::Error),
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
extern crate core;
|
extern crate core;
|
||||||
use crate::encoding::read_response_body;
|
use crate::encoding::read_response_body;
|
||||||
use crate::error::Error::GenericError;
|
use crate::error::Error::GenericError;
|
||||||
|
use crate::error::Result;
|
||||||
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
|
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
|
||||||
use crate::http_request::{resolve_http_request, send_http_request};
|
use crate::http_request::{resolve_http_request, send_http_request};
|
||||||
use crate::import::import_data;
|
use crate::import::import_data;
|
||||||
@@ -49,7 +50,7 @@ use yaak_plugins::plugin_meta::PluginMetadata;
|
|||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
use yaak_sse::sse::ServerSentEvent;
|
use yaak_sse::sse::ServerSentEvent;
|
||||||
use yaak_templates::format::format_json;
|
use yaak_templates::format::format_json;
|
||||||
use yaak_templates::{Tokens, transform_args, RenderOptions, RenderErrorBehavior};
|
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod encoding;
|
mod encoding;
|
||||||
@@ -1499,7 +1500,27 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
|
|||||||
// We might have recursive back-and-forth calls between app and plugin, so we don't
|
// We might have recursive back-and-forth calls between app and plugin, so we don't
|
||||||
// want to block here
|
// want to block here
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
|
let ev = plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
|
||||||
|
|
||||||
|
let ev = match ev {
|
||||||
|
Ok(Some(ev)) => ev,
|
||||||
|
Ok(None) => return,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to handle plugin event: {e:?}");
|
||||||
|
let _ = app_handle.emit("show_toast", InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||||
|
message: e.to_string(),
|
||||||
|
color: Some(Color::Danger),
|
||||||
|
icon: None,
|
||||||
|
timeout: Some(30000),
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
||||||
|
if let Err(e) = plugin_manager.reply(&event, &ev).await {
|
||||||
|
warn!("Failed to reply to plugin manager: {:?}", e)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
plugin_manager.unsubscribe(rx_id.as_str()).await;
|
plugin_manager.unsubscribe(rx_id.as_str()).await;
|
||||||
@@ -1534,11 +1555,16 @@ async fn call_frontend<R: Runtime>(
|
|||||||
fn get_window_from_window_context<R: Runtime>(
|
fn get_window_from_window_context<R: Runtime>(
|
||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
window_context: &PluginWindowContext,
|
window_context: &PluginWindowContext,
|
||||||
) -> Option<WebviewWindow<R>> {
|
) -> Result<WebviewWindow<R>> {
|
||||||
let label = match window_context {
|
let label = match window_context {
|
||||||
PluginWindowContext::Label { label, .. } => label,
|
PluginWindowContext::Label { label, .. } => label,
|
||||||
PluginWindowContext::None => {
|
PluginWindowContext::None => {
|
||||||
return app_handle.webview_windows().iter().next().map(|(_, w)| w.to_owned());
|
return app_handle
|
||||||
|
.webview_windows()
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(|(_, w)| w.to_owned())
|
||||||
|
.ok_or(GenericError("No windows open".to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1551,7 +1577,7 @@ fn get_window_from_window_context<R: Runtime>(
|
|||||||
error!("Failed to find window by {window_context:?}");
|
error!("Failed to find window by {window_context:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
window
|
Ok(window.ok_or(GenericError(format!("Failed to find window for {}", label)))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> {
|
fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::error::Result;
|
||||||
use crate::http_request::send_http_request;
|
use crate::http_request::send_http_request;
|
||||||
use crate::render::{render_grpc_request, render_http_request, render_json_value};
|
use crate::render::{render_grpc_request, render_http_request, render_json_value};
|
||||||
use crate::window::{CreateWindowConfig, create_window};
|
use crate::window::{CreateWindowConfig, create_window};
|
||||||
@@ -7,10 +8,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use log::{error, warn};
|
use log::error;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, State};
|
use tauri::{AppHandle, Emitter, Manager, Runtime};
|
||||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||||
|
use yaak_common::window::WorkspaceWindowTrait;
|
||||||
use yaak_models::models::{HttpResponse, Plugin};
|
use yaak_models::models::{HttpResponse, Plugin};
|
||||||
|
use yaak_models::queries::any_request::AnyRequest;
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{
|
||||||
@@ -20,7 +23,6 @@ use yaak_plugins::events::{
|
|||||||
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
|
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
|
||||||
TemplateRenderResponse, WindowNavigateEvent,
|
TemplateRenderResponse, WindowNavigateEvent,
|
||||||
};
|
};
|
||||||
use yaak_plugins::manager::PluginManager;
|
|
||||||
use yaak_plugins::plugin_handle::PluginHandle;
|
use yaak_plugins::plugin_handle::PluginHandle;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
use yaak_templates::{RenderErrorBehavior, RenderOptions};
|
use yaak_templates::{RenderErrorBehavior, RenderOptions};
|
||||||
@@ -29,109 +31,111 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
event: &InternalEvent,
|
event: &InternalEvent,
|
||||||
plugin_handle: &PluginHandle,
|
plugin_handle: &PluginHandle,
|
||||||
) {
|
) -> Result<Option<InternalEventPayload>> {
|
||||||
// debug!("Got event to app {event:?}");
|
// debug!("Got event to app {event:?}");
|
||||||
let window_context = event.window_context.to_owned();
|
let window_context = event.window_context.to_owned();
|
||||||
let response_event: Option<InternalEventPayload> = match event.clone().payload {
|
match event.clone().payload {
|
||||||
InternalEventPayload::CopyTextRequest(req) => {
|
InternalEventPayload::CopyTextRequest(req) => {
|
||||||
app_handle
|
app_handle.clipboard().write_text(req.text.as_str())?;
|
||||||
.clipboard()
|
Ok(Some(InternalEventPayload::CopyTextResponse(EmptyPayload {})))
|
||||||
.write_text(req.text.as_str())
|
|
||||||
.expect("Failed to write text to clipboard");
|
|
||||||
Some(InternalEventPayload::CopyTextResponse(EmptyPayload {}))
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::ShowToastRequest(req) => {
|
InternalEventPayload::ShowToastRequest(req) => {
|
||||||
match window_context {
|
match window_context {
|
||||||
PluginWindowContext::Label { label, .. } => app_handle
|
PluginWindowContext::Label { label, .. } => {
|
||||||
.emit_to(label, "show_toast", req)
|
app_handle.emit_to(label, "show_toast", req)?
|
||||||
.expect("Failed to emit show_toast to window"),
|
}
|
||||||
_ => app_handle.emit("show_toast", req).expect("Failed to emit show_toast"),
|
_ => app_handle.emit("show_toast", req)?,
|
||||||
};
|
};
|
||||||
Some(InternalEventPayload::ShowToastResponse(EmptyPayload {}))
|
Ok(Some(InternalEventPayload::ShowToastResponse(EmptyPayload {})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::PromptTextRequest(_) => {
|
InternalEventPayload::PromptTextRequest(_) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for render");
|
Ok(call_frontend(&window, event).await)
|
||||||
call_frontend(&window, event).await
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||||
let http_responses = app_handle
|
let http_responses = app_handle
|
||||||
.db()
|
.db()
|
||||||
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
|
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
Ok(Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
||||||
http_responses,
|
http_responses,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
|
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
|
||||||
let http_request = app_handle.db().get_http_request(&req.id).ok();
|
let http_request = app_handle.db().get_http_request(&req.id).ok();
|
||||||
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
|
Ok(Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
|
||||||
http_request,
|
http_request,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::RenderGrpcRequestRequest(req) => {
|
InternalEventPayload::RenderGrpcRequestRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for render grpc request");
|
|
||||||
|
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
let environment_id = environment_from_window(&window).map(|e| e.id);
|
let environment_id = environment_from_window(&window).map(|e| e.id);
|
||||||
let environment_chain = window
|
let environment_chain = window.db().resolve_environments(
|
||||||
.db()
|
&workspace.id,
|
||||||
.resolve_environments(&workspace.id, None, environment_id.as_deref())
|
req.grpc_request.folder_id.as_deref(),
|
||||||
.expect("Failed to resolve environments");
|
environment_id.as_deref(),
|
||||||
|
)?;
|
||||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||||
let opt = RenderOptions {
|
let opt = RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
};
|
};
|
||||||
let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt)
|
let grpc_request =
|
||||||
.await
|
render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt).await?;
|
||||||
.expect("Failed to render grpc request");
|
Ok(Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
|
||||||
Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
|
|
||||||
grpc_request,
|
grpc_request,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for render http request");
|
|
||||||
|
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
let environment_id = environment_from_window(&window).map(|e| e.id);
|
let environment_id = environment_from_window(&window).map(|e| e.id);
|
||||||
let environment_chain = window
|
let environment_chain = window.db().resolve_environments(
|
||||||
.db()
|
&workspace.id,
|
||||||
.resolve_environments(&workspace.id, None, environment_id.as_deref())
|
req.http_request.folder_id.as_deref(),
|
||||||
.expect("Failed to resolve environments");
|
environment_id.as_deref(),
|
||||||
|
)?;
|
||||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||||
let opt = &RenderOptions {
|
let opt = &RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
};
|
};
|
||||||
let http_request = render_http_request(&req.http_request, environment_chain, &cb, &opt)
|
let http_request =
|
||||||
.await
|
render_http_request(&req.http_request, environment_chain, &cb, &opt).await?;
|
||||||
.expect("Failed to render http request");
|
Ok(Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
||||||
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
|
||||||
http_request,
|
http_request,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::TemplateRenderRequest(req) => {
|
InternalEventPayload::TemplateRenderRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for render");
|
|
||||||
|
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
let environment_id = environment_from_window(&window).map(|e| e.id);
|
let environment_id = environment_from_window(&window).map(|e| e.id);
|
||||||
let environment_chain = window
|
let folder_id = if let Some(id) = window.request_id() {
|
||||||
.db()
|
match window.db().get_any_request(&id) {
|
||||||
.resolve_environments(&workspace.id, None, environment_id.as_deref())
|
Ok(AnyRequest::HttpRequest(r)) => r.folder_id,
|
||||||
.expect("Failed to resolve environments");
|
Ok(AnyRequest::GrpcRequest(r)) => r.folder_id,
|
||||||
|
Ok(AnyRequest::WebsocketRequest(r)) => r.folder_id,
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let environment_chain = window.db().resolve_environments(
|
||||||
|
&workspace.id,
|
||||||
|
folder_id.as_deref(),
|
||||||
|
environment_id.as_deref(),
|
||||||
|
)?;
|
||||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||||
let opt = RenderOptions {
|
let opt = RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
};
|
};
|
||||||
let data = render_json_value(req.data, environment_chain, &cb, &opt)
|
let data = render_json_value(req.data, environment_chain, &cb, &opt).await?;
|
||||||
.await
|
Ok(Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })))
|
||||||
.expect("Failed to render template");
|
|
||||||
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::ErrorResponse(resp) => {
|
InternalEventPayload::ErrorResponse(resp) => {
|
||||||
error!("Plugin error: {}: {:?}", resp.error, resp);
|
error!("Plugin error: {}: {:?}", resp.error, resp);
|
||||||
@@ -144,16 +148,15 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
resp.error
|
resp.error
|
||||||
),
|
),
|
||||||
color: Some(Color::Danger),
|
color: Some(Color::Danger),
|
||||||
timeout: None,
|
timeout: Some(30000),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
|
||||||
None
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::ReloadResponse(req) => {
|
InternalEventPayload::ReloadResponse(req) => {
|
||||||
let plugins = app_handle.db().list_plugins().unwrap();
|
let plugins = app_handle.db().list_plugins()?;
|
||||||
for plugin in plugins {
|
for plugin in plugins {
|
||||||
if plugin.directory != plugin_handle.dir {
|
if plugin.directory != plugin_handle.dir {
|
||||||
continue;
|
continue;
|
||||||
@@ -163,7 +166,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
|
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
|
||||||
..plugin
|
..plugin
|
||||||
};
|
};
|
||||||
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap();
|
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !req.silent {
|
if !req.silent {
|
||||||
@@ -178,13 +181,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::SendHttpRequestRequest(req) => {
|
InternalEventPayload::SendHttpRequestRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for sending HTTP request");
|
|
||||||
let mut http_request = req.http_request;
|
let mut http_request = req.http_request;
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
@@ -198,20 +201,17 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
let http_response = if http_request.id.is_empty() {
|
let http_response = if http_request.id.is_empty() {
|
||||||
HttpResponse::default()
|
HttpResponse::default()
|
||||||
} else {
|
} else {
|
||||||
window
|
window.db().upsert_http_response(
|
||||||
.db()
|
&HttpResponse {
|
||||||
.upsert_http_response(
|
request_id: http_request.id.clone(),
|
||||||
&HttpResponse {
|
workspace_id: http_request.workspace_id.clone(),
|
||||||
request_id: http_request.id.clone(),
|
..Default::default()
|
||||||
workspace_id: http_request.workspace_id.clone(),
|
},
|
||||||
..Default::default()
|
&UpdateSource::Plugin,
|
||||||
},
|
)?
|
||||||
&UpdateSource::Plugin,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = send_http_request(
|
let http_response = send_http_request(
|
||||||
&window,
|
&window,
|
||||||
&http_request,
|
&http_request,
|
||||||
&http_response,
|
&http_response,
|
||||||
@@ -219,16 +219,11 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
cookie_jar,
|
cookie_jar,
|
||||||
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
let http_response = match result {
|
Ok(Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
||||||
Ok(r) => r,
|
|
||||||
Err(_e) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
|
||||||
http_response,
|
http_response,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::OpenWindowRequest(req) => {
|
InternalEventPayload::OpenWindowRequest(req) => {
|
||||||
let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128);
|
let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128);
|
||||||
@@ -251,8 +246,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle)).await;
|
return Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle))
|
||||||
return;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -288,32 +283,33 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
InternalEventPayload::CloseWindowRequest(req) => {
|
InternalEventPayload::CloseWindowRequest(req) => {
|
||||||
if let Some(window) = app_handle.webview_windows().get(&req.label) {
|
if let Some(window) = app_handle.webview_windows().get(&req.label) {
|
||||||
window.close().expect("Failed to close window");
|
window.close()?;
|
||||||
}
|
}
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
InternalEventPayload::SetKeyValueRequest(req) => {
|
InternalEventPayload::SetKeyValueRequest(req) => {
|
||||||
let name = plugin_handle.info().name;
|
let name = plugin_handle.info().name;
|
||||||
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
|
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
|
||||||
Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {}))
|
Ok(Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::GetKeyValueRequest(req) => {
|
InternalEventPayload::GetKeyValueRequest(req) => {
|
||||||
let name = plugin_handle.info().name;
|
let name = plugin_handle.info().name;
|
||||||
let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value);
|
let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value);
|
||||||
Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value }))
|
Ok(Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value })))
|
||||||
}
|
}
|
||||||
InternalEventPayload::DeleteKeyValueRequest(req) => {
|
InternalEventPayload::DeleteKeyValueRequest(req) => {
|
||||||
let name = plugin_handle.info().name;
|
let name = plugin_handle.info().name;
|
||||||
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key).unwrap();
|
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key)?;
|
||||||
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted }))
|
Ok(Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse {
|
||||||
|
deleted,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::ListCookieNamesRequest(_req) => {
|
InternalEventPayload::ListCookieNamesRequest(_req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for listing cookies");
|
|
||||||
let names = match cookie_jar_from_window(&window) {
|
let names = match cookie_jar_from_window(&window) {
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
Some(j) => j
|
Some(j) => j
|
||||||
@@ -322,11 +318,12 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
.filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string()))
|
.filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string()))
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse { names }))
|
Ok(Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse {
|
||||||
|
names,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::GetCookieValueRequest(req) => {
|
InternalEventPayload::GetCookieValueRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_window_context(app_handle, &window_context)?;
|
||||||
.expect("Failed to find window for listing cookies");
|
|
||||||
let value = match cookie_jar_from_window(&window) {
|
let value = match cookie_jar_from_window(&window) {
|
||||||
None => None,
|
None => None,
|
||||||
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
|
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
|
||||||
@@ -336,15 +333,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))
|
Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(e) = response_event {
|
|
||||||
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
|
||||||
if let Err(e) = plugin_manager.reply(&event, &e).await {
|
|
||||||
warn!("Failed to reply to plugin manager: {:?}", e)
|
|
||||||
}
|
}
|
||||||
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub trait WorkspaceWindowTrait {
|
|||||||
fn workspace_id(&self) -> Option<String>;
|
fn workspace_id(&self) -> Option<String>;
|
||||||
fn cookie_jar_id(&self) -> Option<String>;
|
fn cookie_jar_id(&self) -> Option<String>;
|
||||||
fn environment_id(&self) -> Option<String>;
|
fn environment_id(&self) -> Option<String>;
|
||||||
|
fn request_id(&self) -> Option<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
|
impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
|
||||||
@@ -28,4 +29,10 @@ impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
|
|||||||
let mut query_pairs = url.query_pairs();
|
let mut query_pairs = url.query_pairs();
|
||||||
query_pairs.find(|(k, _v)| k == "environment_id").map(|(_k, v)| v.to_string())
|
query_pairs.find(|(k, _v)| k == "environment_id").map(|(_k, v)| v.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn request_id(&self) -> Option<String> {
|
||||||
|
let url = self.url().unwrap();
|
||||||
|
let mut query_pairs = url.query_pairs();
|
||||||
|
query_pairs.find(|(k, _v)| k == "request_id").map(|(_k, v)| v.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src-tauri/yaak-models/src/queries/any_request.rs
Normal file
23
src-tauri/yaak-models/src/queries/any_request.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use crate::db_context::DbContext;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::models::{
|
||||||
|
GrpcRequest, HttpRequest, WebsocketRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum AnyRequest {
|
||||||
|
HttpRequest(HttpRequest),
|
||||||
|
GrpcRequest(GrpcRequest),
|
||||||
|
WebsocketRequest(WebsocketRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DbContext<'a> {
|
||||||
|
pub fn get_any_request(&self, id: &str) -> Result<AnyRequest> {
|
||||||
|
if let Ok(http_request) = self.get_http_request(id) {
|
||||||
|
Ok(AnyRequest::HttpRequest(http_request))
|
||||||
|
} else if let Ok(grpc_request) = self.get_grpc_request(id) {
|
||||||
|
Ok(AnyRequest::GrpcRequest(grpc_request))
|
||||||
|
} else {
|
||||||
|
Ok(AnyRequest::WebsocketRequest(self.get_websocket_request(id)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod any_request;
|
||||||
mod batch;
|
mod batch;
|
||||||
mod cookie_jars;
|
mod cookie_jars;
|
||||||
mod environments;
|
mod environments;
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ pub enum InternalEventPayload {
|
|||||||
RenderGrpcRequestRequest(RenderGrpcRequestRequest),
|
RenderGrpcRequestRequest(RenderGrpcRequestRequest),
|
||||||
RenderGrpcRequestResponse(RenderGrpcRequestResponse),
|
RenderGrpcRequestResponse(RenderGrpcRequestResponse),
|
||||||
|
|
||||||
|
TemplateRenderRequest(TemplateRenderRequest),
|
||||||
|
TemplateRenderResponse(TemplateRenderResponse),
|
||||||
|
|
||||||
GetKeyValueRequest(GetKeyValueRequest),
|
GetKeyValueRequest(GetKeyValueRequest),
|
||||||
GetKeyValueResponse(GetKeyValueResponse),
|
GetKeyValueResponse(GetKeyValueResponse),
|
||||||
SetKeyValueRequest(SetKeyValueRequest),
|
SetKeyValueRequest(SetKeyValueRequest),
|
||||||
@@ -135,9 +138,6 @@ pub enum InternalEventPayload {
|
|||||||
WindowCloseEvent,
|
WindowCloseEvent,
|
||||||
CloseWindowRequest(CloseWindowRequest),
|
CloseWindowRequest(CloseWindowRequest),
|
||||||
|
|
||||||
TemplateRenderRequest(TemplateRenderRequest),
|
|
||||||
TemplateRenderResponse(TemplateRenderResponse),
|
|
||||||
|
|
||||||
ShowToastRequest(ShowToastRequest),
|
ShowToastRequest(ShowToastRequest),
|
||||||
ShowToastResponse(EmptyPayload),
|
ShowToastResponse(EmptyPayload),
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
|||||||
</EmptyStateText>
|
</EmptyStateText>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <EmptyStateText>Authentication not configured</EmptyStateText>;
|
return <EmptyStateText>No authentication</EmptyStateText>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
label="Request"
|
label="Request"
|
||||||
onChangeValue={setActiveTab}
|
onChangeValue={setActiveTab}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
tabListClassName="mt-1 !mb-1.5"
|
tabListClassName="mt-1 mb-1.5"
|
||||||
>
|
>
|
||||||
<TabContent value={TAB_AUTH}>
|
<TabContent value={TAB_AUTH}>
|
||||||
<HttpAuthenticationEditor model={activeRequest} />
|
<HttpAuthenticationEditor model={activeRequest} />
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ export function Tabs({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
tabListClassName,
|
tabListClassName,
|
||||||
addBorders && '!-ml-1',
|
addBorders && '!-ml-1',
|
||||||
'flex items-center hide-scrollbars mb-2',
|
addBorders && layout === 'vertical' && 'mb-2',
|
||||||
|
'flex items-center hide-scrollbars',
|
||||||
layout === 'horizontal' && 'h-full overflow-auto p-2 -mr-2',
|
layout === 'horizontal' && 'h-full overflow-auto p-2 -mr-2',
|
||||||
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
|
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
|
||||||
// Give space for button focus states within overflow boundary.
|
// Give space for button focus states within overflow boundary.
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export function languageFromContentType(
|
|||||||
} else if (justContentType.includes('javascript')) {
|
} else if (justContentType.includes('javascript')) {
|
||||||
// Sometimes `application/javascript` returns JSON, so try detecting that
|
// Sometimes `application/javascript` returns JSON, so try detecting that
|
||||||
return detectFromContent(content, 'javascript');
|
return detectFromContent(content, 'javascript');
|
||||||
|
} else if (justContentType.includes('markdown')) {
|
||||||
|
return 'markdown';
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectFromContent(content, 'text');
|
return detectFromContent(content, 'text');
|
||||||
|
|||||||
Reference in New Issue
Block a user