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:
Gregory Schier
2025-10-06 06:53:45 -07:00
parent dbc606fb53
commit 485a9ea47c
18 changed files with 203 additions and 130 deletions

View File

@@ -58,7 +58,7 @@ Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight
## 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.
## Useful Resources

View File

@@ -437,7 +437,7 @@ export type SetKeyValueRequest = { key: string, value: string, };
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,
/**

View File

@@ -1,6 +1,6 @@
// 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, };

View File

@@ -454,6 +454,8 @@ export class PluginInstance {
show: async (args) => {
await this.#sendAndWaitForReply(windowContext, {
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,
});
},

View File

@@ -271,6 +271,12 @@ export const plugin: PluginDefinition = {
label: 'Advanced',
inputs: [
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
{
type: 'text',
name: 'headerName',
label: 'Header Name',
defaultValue: 'Authorization',
},
{
type: 'text',
name: 'headerPrefix',
@@ -397,15 +403,9 @@ export const plugin: PluginDefinition = {
throw new Error('Invalid grant type ' + grantType);
}
const headerName = stringArg(values, 'headerName') || 'Authorization';
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
return {
setHeaders: [
{
name: 'Authorization',
value: headerValue,
},
],
};
return { setHeaders: [{ name: headerName, value: headerValue }] };
},
},
};

View File

@@ -1,5 +1,6 @@
import type { HttpUrlParameter } from '@yaakapp-internal/models';
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
import { resolvedModelName } from '@yaakapp/app/lib/resolvedModelName';
export const plugin: PluginDefinition = {
templateFunctions: [
@@ -96,5 +97,22 @@ export const plugin: PluginDefinition = {
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);
},
},
],
};

View File

@@ -155,7 +155,7 @@ export function formatDatetime(args: {
format?: string;
in?: ContextFn<Date>;
}): string {
const { date, format = 'yyyy-MM-dd HH:mm:ss' } = args;
const { date, format } = args;
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 });
}

View File

@@ -35,6 +35,9 @@ pub enum Error {
#[error(transparent)]
CommonError(#[from] yaak_common::error::Error),
#[error(transparent)]
ClipboardError(#[from] tauri_plugin_clipboard_manager::Error),
#[error("Updater error: {0}")]
UpdaterError(#[from] tauri_plugin_updater::Error),

View File

@@ -1,6 +1,7 @@
extern crate core;
use crate::encoding::read_response_body;
use crate::error::Error::GenericError;
use crate::error::Result;
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
use crate::http_request::{resolve_http_request, send_http_request};
use crate::import::import_data;
@@ -49,7 +50,7 @@ use yaak_plugins::plugin_meta::PluginMetadata;
use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent;
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 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
// want to block here
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;
@@ -1534,11 +1555,16 @@ async fn call_frontend<R: Runtime>(
fn get_window_from_window_context<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
) -> Option<WebviewWindow<R>> {
) -> Result<WebviewWindow<R>> {
let label = match window_context {
PluginWindowContext::Label { label, .. } => label,
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:?}");
}
window
Ok(window.ok_or(GenericError(format!("Failed to find window for {}", label)))?)
}
fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> {

View File

@@ -1,3 +1,4 @@
use crate::error::Result;
use crate::http_request::send_http_request;
use crate::render::{render_grpc_request, render_http_request, render_json_value};
use crate::window::{CreateWindowConfig, create_window};
@@ -7,10 +8,12 @@ use crate::{
};
use chrono::Utc;
use cookie::Cookie;
use log::{error, warn};
use tauri::{AppHandle, Emitter, Manager, Runtime, State};
use log::error;
use tauri::{AppHandle, Emitter, Manager, Runtime};
use tauri_plugin_clipboard_manager::ClipboardExt;
use yaak_common::window::WorkspaceWindowTrait;
use yaak_models::models::{HttpResponse, Plugin};
use yaak_models::queries::any_request::AnyRequest;
use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::UpdateSource;
use yaak_plugins::events::{
@@ -20,7 +23,6 @@ use yaak_plugins::events::{
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
TemplateRenderResponse, WindowNavigateEvent,
};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_handle::PluginHandle;
use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_templates::{RenderErrorBehavior, RenderOptions};
@@ -29,109 +31,111 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
app_handle: &AppHandle<R>,
event: &InternalEvent,
plugin_handle: &PluginHandle,
) {
) -> Result<Option<InternalEventPayload>> {
// debug!("Got event to app {event:?}");
let window_context = event.window_context.to_owned();
let response_event: Option<InternalEventPayload> = match event.clone().payload {
match event.clone().payload {
InternalEventPayload::CopyTextRequest(req) => {
app_handle
.clipboard()
.write_text(req.text.as_str())
.expect("Failed to write text to clipboard");
Some(InternalEventPayload::CopyTextResponse(EmptyPayload {}))
app_handle.clipboard().write_text(req.text.as_str())?;
Ok(Some(InternalEventPayload::CopyTextResponse(EmptyPayload {})))
}
InternalEventPayload::ShowToastRequest(req) => {
match window_context {
PluginWindowContext::Label { label, .. } => 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"),
PluginWindowContext::Label { label, .. } => {
app_handle.emit_to(label, "show_toast", req)?
}
_ => app_handle.emit("show_toast", req)?,
};
Some(InternalEventPayload::ShowToastResponse(EmptyPayload {}))
Ok(Some(InternalEventPayload::ShowToastResponse(EmptyPayload {})))
}
InternalEventPayload::PromptTextRequest(_) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for render");
call_frontend(&window, event).await
let window = get_window_from_window_context(app_handle, &window_context)?;
Ok(call_frontend(&window, event).await)
}
InternalEventPayload::FindHttpResponsesRequest(req) => {
let http_responses = app_handle
.db()
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
.unwrap_or_default();
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
Ok(Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
http_responses,
}))
})))
}
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
let http_request = app_handle.db().get_http_request(&req.id).ok();
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
Ok(Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
http_request,
}))
})))
}
InternalEventPayload::RenderGrpcRequestRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for render grpc request");
let window = get_window_from_window_context(app_handle, &window_context)?;
let workspace =
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_chain = window
.db()
.resolve_environments(&workspace.id, None, environment_id.as_deref())
.expect("Failed to resolve environments");
let environment_chain = window.db().resolve_environments(
&workspace.id,
req.grpc_request.folder_id.as_deref(),
environment_id.as_deref(),
)?;
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let opt = RenderOptions {
error_behavior: RenderErrorBehavior::Throw,
};
let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt)
.await
.expect("Failed to render grpc request");
Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
let grpc_request =
render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt).await?;
Ok(Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
grpc_request,
}))
})))
}
InternalEventPayload::RenderHttpRequestRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for render http request");
let window = get_window_from_window_context(app_handle, &window_context)?;
let workspace =
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_chain = window
.db()
.resolve_environments(&workspace.id, None, environment_id.as_deref())
.expect("Failed to resolve environments");
let environment_chain = window.db().resolve_environments(
&workspace.id,
req.http_request.folder_id.as_deref(),
environment_id.as_deref(),
)?;
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let opt = &RenderOptions {
error_behavior: RenderErrorBehavior::Throw,
};
let http_request = render_http_request(&req.http_request, environment_chain, &cb, &opt)
.await
.expect("Failed to render http request");
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
let http_request =
render_http_request(&req.http_request, environment_chain, &cb, &opt).await?;
Ok(Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
http_request,
}))
})))
}
InternalEventPayload::TemplateRenderRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for render");
let window = get_window_from_window_context(app_handle, &window_context)?;
let workspace =
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_chain = window
.db()
.resolve_environments(&workspace.id, None, environment_id.as_deref())
.expect("Failed to resolve environments");
let folder_id = if let Some(id) = window.request_id() {
match window.db().get_any_request(&id) {
Ok(AnyRequest::HttpRequest(r)) => r.folder_id,
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 opt = RenderOptions {
error_behavior: RenderErrorBehavior::Throw,
};
let data = render_json_value(req.data, environment_chain, &cb, &opt)
.await
.expect("Failed to render template");
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
let data = render_json_value(req.data, environment_chain, &cb, &opt).await?;
Ok(Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })))
}
InternalEventPayload::ErrorResponse(resp) => {
error!("Plugin error: {}: {:?}", resp.error, resp);
@@ -144,16 +148,15 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
resp.error
),
color: Some(Color::Danger),
timeout: None,
timeout: Some(30000),
..Default::default()
}),
None,
);
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
None
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
}
InternalEventPayload::ReloadResponse(req) => {
let plugins = app_handle.db().list_plugins().unwrap();
let plugins = app_handle.db().list_plugins()?;
for plugin in plugins {
if plugin.directory != plugin_handle.dir {
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
..plugin
};
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap();
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin)?;
}
if !req.silent {
@@ -178,13 +181,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
}),
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) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for sending HTTP request");
let window = get_window_from_window_context(app_handle, &window_context)?;
let mut http_request = req.http_request;
let workspace =
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() {
HttpResponse::default()
} else {
window
.db()
.upsert_http_response(
&HttpResponse {
request_id: http_request.id.clone(),
workspace_id: http_request.workspace_id.clone(),
..Default::default()
},
&UpdateSource::Plugin,
)
.unwrap()
window.db().upsert_http_response(
&HttpResponse {
request_id: http_request.id.clone(),
workspace_id: http_request.workspace_id.clone(),
..Default::default()
},
&UpdateSource::Plugin,
)?
};
let result = send_http_request(
let http_response = send_http_request(
&window,
&http_request,
&http_response,
@@ -219,16 +219,11 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
cookie_jar,
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
)
.await;
.await?;
let http_response = match result {
Ok(r) => r,
Err(_e) => return,
};
Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
Ok(Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
http_response,
}))
})))
}
InternalEventPayload::OpenWindowRequest(req) => {
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,
);
Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle)).await;
return;
return Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle))
.await;
}
{
@@ -288,32 +283,33 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
});
}
None
Ok(None)
}
InternalEventPayload::CloseWindowRequest(req) => {
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) => {
let name = plugin_handle.info().name;
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {}))
Ok(Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {})))
}
InternalEventPayload::GetKeyValueRequest(req) => {
let name = plugin_handle.info().name;
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) => {
let name = plugin_handle.info().name;
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key).unwrap();
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted }))
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key)?;
Ok(Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse {
deleted,
})))
}
InternalEventPayload::ListCookieNamesRequest(_req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for listing cookies");
let window = get_window_from_window_context(app_handle, &window_context)?;
let names = match cookie_jar_from_window(&window) {
None => Vec::new(),
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()))
.collect(),
};
Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse { names }))
Ok(Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse {
names,
})))
}
InternalEventPayload::GetCookieValueRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for listing cookies");
let window = get_window_from_window_context(app_handle, &window_context)?;
let value = match cookie_jar_from_window(&window) {
None => None,
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,
}),
};
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(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
}
_ => Ok(None),
}
}

View File

@@ -5,6 +5,7 @@ pub trait WorkspaceWindowTrait {
fn workspace_id(&self) -> Option<String>;
fn cookie_jar_id(&self) -> Option<String>;
fn environment_id(&self) -> Option<String>;
fn request_id(&self) -> Option<String>;
}
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();
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())
}
}

View 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)?))
}
}
}

View File

@@ -1,3 +1,4 @@
pub mod any_request;
mod batch;
mod cookie_jars;
mod environments;

View File

@@ -123,6 +123,9 @@ pub enum InternalEventPayload {
RenderGrpcRequestRequest(RenderGrpcRequestRequest),
RenderGrpcRequestResponse(RenderGrpcRequestResponse),
TemplateRenderRequest(TemplateRenderRequest),
TemplateRenderResponse(TemplateRenderResponse),
GetKeyValueRequest(GetKeyValueRequest),
GetKeyValueResponse(GetKeyValueResponse),
SetKeyValueRequest(SetKeyValueRequest),
@@ -135,9 +138,6 @@ pub enum InternalEventPayload {
WindowCloseEvent,
CloseWindowRequest(CloseWindowRequest),
TemplateRenderRequest(TemplateRenderRequest),
TemplateRenderResponse(TemplateRenderResponse),
ShowToastRequest(ShowToastRequest),
ShowToastResponse(EmptyPayload),

View File

@@ -67,7 +67,7 @@ export function HttpAuthenticationEditor({ model }: Props) {
</EmptyStateText>
);
} else {
return <EmptyStateText>Authentication not configured</EmptyStateText>;
return <EmptyStateText>No authentication</EmptyStateText>;
}
}

View File

@@ -351,7 +351,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
label="Request"
onChangeValue={setActiveTab}
tabs={tabs}
tabListClassName="mt-1 !mb-1.5"
tabListClassName="mt-1 mb-1.5"
>
<TabContent value={TAB_AUTH}>
<HttpAuthenticationEditor model={activeRequest} />

View File

@@ -84,7 +84,8 @@ export function Tabs({
className={classNames(
tabListClassName,
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 === 'vertical' && 'overflow-x-auto overflow-y-visible ',
// Give space for button focus states within overflow boundary.

View File

@@ -21,6 +21,8 @@ export function languageFromContentType(
} else if (justContentType.includes('javascript')) {
// Sometimes `application/javascript` returns JSON, so try detecting that
return detectFromContent(content, 'javascript');
} else if (justContentType.includes('markdown')) {
return 'markdown';
}
return detectFromContent(content, 'text');