mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 17:58:27 +02:00
JWT auth plugin and necessary updates
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@yaakapp/api",
|
"name": "@yaakapp/api",
|
||||||
"version": "0.2.26",
|
"version": "0.2.27",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"typings": "./lib/index.d.ts",
|
"typings": "./lib/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "s
|
|||||||
|
|
||||||
export type CopyTextRequest = { text: string, };
|
export type CopyTextRequest = { text: string, };
|
||||||
|
|
||||||
|
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||||
|
|
||||||
export type EmptyPayload = {};
|
export type EmptyPayload = {};
|
||||||
|
|
||||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||||
@@ -49,7 +51,7 @@ export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
|||||||
|
|
||||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||||
|
|
||||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
|
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
|
||||||
|
|
||||||
export type FormInputBase = { name: string,
|
export type FormInputBase = { name: string,
|
||||||
/**
|
/**
|
||||||
@@ -79,6 +81,24 @@ label?: string,
|
|||||||
*/
|
*/
|
||||||
defaultValue?: string, };
|
defaultValue?: string, };
|
||||||
|
|
||||||
|
export type FormInputEditor = {
|
||||||
|
/**
|
||||||
|
* Placeholder for the text input
|
||||||
|
*/
|
||||||
|
placeholder?: string | null, language: EditorLanguage, name: string,
|
||||||
|
/**
|
||||||
|
* Whether the user must fill in the argument
|
||||||
|
*/
|
||||||
|
optional?: boolean,
|
||||||
|
/**
|
||||||
|
* The label of the input
|
||||||
|
*/
|
||||||
|
label?: string,
|
||||||
|
/**
|
||||||
|
* The default value
|
||||||
|
*/
|
||||||
|
defaultValue?: string, };
|
||||||
|
|
||||||
export type FormInputFile = {
|
export type FormInputFile = {
|
||||||
/**
|
/**
|
||||||
* The title of the file selection window
|
* The title of the file selection window
|
||||||
@@ -139,7 +159,11 @@ export type FormInputText = {
|
|||||||
/**
|
/**
|
||||||
* Placeholder for the text input
|
* Placeholder for the text input
|
||||||
*/
|
*/
|
||||||
placeholder?: string | null, name: string,
|
placeholder?: string | null,
|
||||||
|
/**
|
||||||
|
* Placeholder for the text input
|
||||||
|
*/
|
||||||
|
password?: boolean, name: string,
|
||||||
/**
|
/**
|
||||||
* Whether the user must fill in the argument
|
* Whether the user must fill in the argument
|
||||||
*/
|
*/
|
||||||
@@ -153,7 +177,7 @@ label?: string,
|
|||||||
*/
|
*/
|
||||||
defaultValue?: string, };
|
defaultValue?: string, };
|
||||||
|
|
||||||
export type GetHttpAuthenticationResponse = { name: string, pluginName: string, config: Array<FormInput>, };
|
export type GetHttpAuthenticationResponse = { name: string, label: string, shortLabel: string, config: Array<FormInput>, };
|
||||||
|
|
||||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||||
|
|
||||||
|
|||||||
@@ -304,11 +304,13 @@ async function initialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === 'get_http_authentication_request' && mod.plugin?.authentication) {
|
if (payload.type === 'get_http_authentication_request' && mod.plugin?.authentication) {
|
||||||
|
const auth = mod.plugin.authentication;
|
||||||
const replyPayload: InternalEventPayload = {
|
const replyPayload: InternalEventPayload = {
|
||||||
|
...auth,
|
||||||
type: 'get_http_authentication_response',
|
type: 'get_http_authentication_response',
|
||||||
name: mod.plugin.authentication.name,
|
|
||||||
pluginName: pkg.name,
|
// Remove unneeded attrs
|
||||||
config: mod.plugin.authentication.config,
|
onApply: undefined,
|
||||||
};
|
};
|
||||||
sendPayload(windowContext, replyPayload, replyId);
|
sendPayload(windowContext, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -369,14 +369,8 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Apply authentication
|
// Apply authentication
|
||||||
|
|
||||||
// Map legacy auth name values from before they were plugins
|
if let Some(auth_name) = request.authentication_type.to_owned() {
|
||||||
let auth_plugin_name = match request.authentication_type.clone() {
|
|
||||||
Some(s) if s == "basic" => Some("@yaakapp/auth-basic".to_string()),
|
|
||||||
Some(s) if s == "bearer" => Some("@yaakapp/auth-bearer".to_string()),
|
|
||||||
_ => request.authentication_type.to_owned(),
|
|
||||||
};
|
|
||||||
if let Some(plugin_name) = auth_plugin_name {
|
|
||||||
let req = CallHttpAuthenticationRequest {
|
let req = CallHttpAuthenticationRequest {
|
||||||
config: serde_json::to_value(&request.authentication)
|
config: serde_json::to_value(&request.authentication)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -395,13 +389,13 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
let plugin_result =
|
let plugin_result =
|
||||||
match plugin_manager.call_http_authentication(window, &plugin_name, req).await {
|
match plugin_manager.call_http_authentication(window, &auth_name, req).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
|
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let url = sendable_req.url_mut();
|
let url = sendable_req.url_mut();
|
||||||
*url = Url::parse(&plugin_result.url).unwrap();
|
*url = Url::parse(&plugin_result.url).unwrap();
|
||||||
|
|||||||
@@ -235,19 +235,10 @@ async fn cmd_grpc_go<R: Runtime>(
|
|||||||
metadata.insert(h.name, h.value);
|
metadata.insert(h.name, h.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map legacy auth name values from before they were plugins
|
if let Some(auth_name) = req.authentication_type.clone() {
|
||||||
let auth_plugin_name = match req.authentication_type.clone() {
|
let auth = req.authentication.clone();
|
||||||
Some(s) if s == "basic" => Some("@yaakapp/auth-basic".to_string()),
|
|
||||||
Some(s) if s == "bearer" => Some("@yaakapp/auth-bearer".to_string()),
|
|
||||||
_ => req.authentication_type.to_owned(),
|
|
||||||
};
|
|
||||||
if let Some(plugin_name) = auth_plugin_name {
|
|
||||||
let plugin_req = CallHttpAuthenticationRequest {
|
let plugin_req = CallHttpAuthenticationRequest {
|
||||||
config: serde_json::to_value(&req.authentication)
|
config: serde_json::to_value(&auth).unwrap().as_object().unwrap().to_owned(),
|
||||||
.unwrap()
|
|
||||||
.as_object()
|
|
||||||
.unwrap()
|
|
||||||
.to_owned(),
|
|
||||||
method: "POST".to_string(),
|
method: "POST".to_string(),
|
||||||
url: req.url.clone(),
|
url: req.url.clone(),
|
||||||
headers: metadata
|
headers: metadata
|
||||||
@@ -259,7 +250,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
|||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
let plugin_result = plugin_manager
|
let plugin_result = plugin_manager
|
||||||
.call_http_authentication(&window, &plugin_name, plugin_req)
|
.call_http_authentication(&window, &auth_name, plugin_req)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
@@ -980,7 +971,9 @@ async fn cmd_get_http_authentication<R: Runtime>(
|
|||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
) -> Result<Vec<GetHttpAuthenticationResponse>, String> {
|
) -> Result<Vec<GetHttpAuthenticationResponse>, String> {
|
||||||
plugin_manager.get_http_authentication(&window).await.map_err(|e| e.to_string())
|
let results =
|
||||||
|
plugin_manager.get_http_authentication(&window).await.map_err(|e| e.to_string())?;
|
||||||
|
Ok(results.into_iter().map(|(_, a)| a).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ impl TemplateCallback for PluginTemplateCallback {
|
|||||||
let mut args_with_defaults = args.clone();
|
let mut args_with_defaults = args.clone();
|
||||||
|
|
||||||
// Fill in default values for all args
|
// Fill in default values for all args
|
||||||
for a_def in function.args {
|
for arg in function.args {
|
||||||
let base = match a_def {
|
let base = match arg {
|
||||||
FormInput::Text(a) => a.base,
|
FormInput::Text(a) => a.base,
|
||||||
|
FormInput::Editor(a) => a.base,
|
||||||
FormInput::Select(a) => a.base,
|
FormInput::Select(a) => a.base,
|
||||||
FormInput::Checkbox(a) => a.base,
|
FormInput::Checkbox(a) => a.base,
|
||||||
FormInput::File(a) => a.base,
|
FormInput::File(a) => a.base,
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ __export(src_exports, {
|
|||||||
module.exports = __toCommonJS(src_exports);
|
module.exports = __toCommonJS(src_exports);
|
||||||
var plugin = {
|
var plugin = {
|
||||||
authentication: {
|
authentication: {
|
||||||
name: "Basic",
|
name: "basic",
|
||||||
|
label: "Basic Auth",
|
||||||
|
shortLabel: "Basic",
|
||||||
config: [{
|
config: [{
|
||||||
type: "text",
|
type: "text",
|
||||||
name: "username",
|
name: "username",
|
||||||
@@ -35,7 +37,8 @@ var plugin = {
|
|||||||
type: "text",
|
type: "text",
|
||||||
name: "password",
|
name: "password",
|
||||||
label: "Password",
|
label: "Password",
|
||||||
optional: true
|
optional: true,
|
||||||
|
password: true
|
||||||
}],
|
}],
|
||||||
async onApply(_ctx, args) {
|
async onApply(_ctx, args) {
|
||||||
const { username, password } = args.config;
|
const { username, password } = args.config;
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ __export(src_exports, {
|
|||||||
module.exports = __toCommonJS(src_exports);
|
module.exports = __toCommonJS(src_exports);
|
||||||
var plugin = {
|
var plugin = {
|
||||||
authentication: {
|
authentication: {
|
||||||
name: "Bearer",
|
name: "bearer",
|
||||||
|
label: "Bearer Token",
|
||||||
|
shortLabel: "Bearer",
|
||||||
config: [{
|
config: [{
|
||||||
type: "text",
|
type: "text",
|
||||||
name: "token",
|
name: "token",
|
||||||
label: "Token",
|
label: "Token",
|
||||||
optional: true
|
optional: true,
|
||||||
|
password: true
|
||||||
}],
|
}],
|
||||||
async onApply(_ctx, args) {
|
async onApply(_ctx, args) {
|
||||||
const { token } = args.config;
|
const { token } = args.config;
|
||||||
|
|||||||
56
src-tauri/vendored/plugins/auth-jwt/build/index.js
generated
56
src-tauri/vendored/plugins/auth-jwt/build/index.js
generated
@@ -3793,17 +3793,57 @@ __export(src_exports, {
|
|||||||
});
|
});
|
||||||
module.exports = __toCommonJS(src_exports);
|
module.exports = __toCommonJS(src_exports);
|
||||||
var import_jsonwebtoken = __toESM(require_jsonwebtoken());
|
var import_jsonwebtoken = __toESM(require_jsonwebtoken());
|
||||||
|
var algorithms = [
|
||||||
|
"HS256",
|
||||||
|
"HS384",
|
||||||
|
"HS512",
|
||||||
|
"RS256",
|
||||||
|
"RS384",
|
||||||
|
"RS512",
|
||||||
|
"PS256",
|
||||||
|
"PS384",
|
||||||
|
"PS512",
|
||||||
|
"ES256",
|
||||||
|
"ES384",
|
||||||
|
"ES512"
|
||||||
|
];
|
||||||
|
var defaultAlgorithm = algorithms[0];
|
||||||
var plugin = {
|
var plugin = {
|
||||||
authentication: {
|
authentication: {
|
||||||
name: "JWT",
|
name: "jwt",
|
||||||
config: [{
|
label: "JWT Bearer",
|
||||||
type: "text",
|
shortLabel: "JWT",
|
||||||
name: "foo",
|
config: [
|
||||||
label: "Foo",
|
{
|
||||||
optional: true
|
type: "select",
|
||||||
}],
|
name: "algorithm",
|
||||||
|
label: "Algorithm",
|
||||||
|
defaultValue: defaultAlgorithm,
|
||||||
|
options: algorithms.map((value) => ({ name: value, value }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
name: "secret",
|
||||||
|
label: "Secret",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "checkbox",
|
||||||
|
name: "secretBase64",
|
||||||
|
label: "Secret Base64 Encoded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "editor",
|
||||||
|
name: "payload",
|
||||||
|
label: "Payload",
|
||||||
|
language: "json",
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
],
|
||||||
async onApply(_ctx, args) {
|
async onApply(_ctx, args) {
|
||||||
const token = import_jsonwebtoken.default.sign({ foo: "bar" }, null);
|
const { algorithm, secret: _secret, secretBase64, payload } = args.config;
|
||||||
|
const secret = secretBase64 ? Buffer.from(`${_secret}`, "base64") : `${_secret}`;
|
||||||
|
const token = import_jsonwebtoken.default.sign(`${payload}`, secret, { algorithm });
|
||||||
return {
|
return {
|
||||||
url: args.url,
|
url: args.url,
|
||||||
headers: [{ name: "Authorization", value: `Bearer ${token}` }]
|
headers: [{ name: "Authorization", value: `Bearer ${token}` }]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ tokio-stream = "0.1.14"
|
|||||||
prost-types = "0.13.4"
|
prost-types = "0.13.4"
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
serde_json = "1.0.113"
|
serde_json = "1.0.113"
|
||||||
prost-reflect = { version = "0.14.4", features = ["serde", "derive"] }
|
prost-reflect = { version = "0.14.4", default-features = false, features = ["serde", "derive"] }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.79"
|
||||||
hyper = "1.5.2"
|
hyper = "1.5.2"
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "s
|
|||||||
|
|
||||||
export type CopyTextRequest = { text: string, };
|
export type CopyTextRequest = { text: string, };
|
||||||
|
|
||||||
|
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||||
|
|
||||||
export type EmptyPayload = {};
|
export type EmptyPayload = {};
|
||||||
|
|
||||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||||
@@ -49,7 +51,7 @@ export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
|||||||
|
|
||||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||||
|
|
||||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
|
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
|
||||||
|
|
||||||
export type FormInputBase = { name: string,
|
export type FormInputBase = { name: string,
|
||||||
/**
|
/**
|
||||||
@@ -79,6 +81,24 @@ label?: string,
|
|||||||
*/
|
*/
|
||||||
defaultValue?: string, };
|
defaultValue?: string, };
|
||||||
|
|
||||||
|
export type FormInputEditor = {
|
||||||
|
/**
|
||||||
|
* Placeholder for the text input
|
||||||
|
*/
|
||||||
|
placeholder?: string | null, language: EditorLanguage, name: string,
|
||||||
|
/**
|
||||||
|
* Whether the user must fill in the argument
|
||||||
|
*/
|
||||||
|
optional?: boolean,
|
||||||
|
/**
|
||||||
|
* The label of the input
|
||||||
|
*/
|
||||||
|
label?: string,
|
||||||
|
/**
|
||||||
|
* The default value
|
||||||
|
*/
|
||||||
|
defaultValue?: string, };
|
||||||
|
|
||||||
export type FormInputFile = {
|
export type FormInputFile = {
|
||||||
/**
|
/**
|
||||||
* The title of the file selection window
|
* The title of the file selection window
|
||||||
@@ -139,7 +159,11 @@ export type FormInputText = {
|
|||||||
/**
|
/**
|
||||||
* Placeholder for the text input
|
* Placeholder for the text input
|
||||||
*/
|
*/
|
||||||
placeholder?: string | null, name: string,
|
placeholder?: string | null,
|
||||||
|
/**
|
||||||
|
* Placeholder for the text input
|
||||||
|
*/
|
||||||
|
password?: boolean, name: string,
|
||||||
/**
|
/**
|
||||||
* Whether the user must fill in the argument
|
* Whether the user must fill in the argument
|
||||||
*/
|
*/
|
||||||
@@ -153,7 +177,7 @@ label?: string,
|
|||||||
*/
|
*/
|
||||||
defaultValue?: string, };
|
defaultValue?: string, };
|
||||||
|
|
||||||
export type GetHttpAuthenticationResponse = { name: string, pluginName: string, config: Array<FormInput>, };
|
export type GetHttpAuthenticationResponse = { name: string, label: string, shortLabel: string, config: Array<FormInput>, };
|
||||||
|
|
||||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("Plugin not found: {0}")]
|
#[error("Plugin not found: {0}")]
|
||||||
PluginNotFoundErr(String),
|
PluginNotFoundErr(String),
|
||||||
|
|
||||||
|
#[error("Auth plugin not found: {0}")]
|
||||||
|
AuthPluginNotFound(String),
|
||||||
|
|
||||||
#[error("Plugin error: {0}")]
|
#[error("Plugin error: {0}")]
|
||||||
PluginErr(String),
|
PluginErr(String),
|
||||||
|
|||||||
@@ -298,7 +298,8 @@ pub enum Icon {
|
|||||||
#[ts(export, export_to = "events.ts")]
|
#[ts(export, export_to = "events.ts")]
|
||||||
pub struct GetHttpAuthenticationResponse {
|
pub struct GetHttpAuthenticationResponse {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub plugin_name: String,
|
pub label: String,
|
||||||
|
pub short_label: String,
|
||||||
pub config: Vec<FormInput>,
|
pub config: Vec<FormInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +357,7 @@ pub struct TemplateFunction {
|
|||||||
#[ts(export, export_to = "events.ts")]
|
#[ts(export, export_to = "events.ts")]
|
||||||
pub enum FormInput {
|
pub enum FormInput {
|
||||||
Text(FormInputText),
|
Text(FormInputText),
|
||||||
|
Editor(FormInputEditor),
|
||||||
Select(FormInputSelect),
|
Select(FormInputSelect),
|
||||||
Checkbox(FormInputCheckbox),
|
Checkbox(FormInputCheckbox),
|
||||||
File(FormInputFile),
|
File(FormInputFile),
|
||||||
@@ -391,6 +393,43 @@ pub struct FormInputText {
|
|||||||
/// Placeholder for the text input
|
/// Placeholder for the text input
|
||||||
#[ts(optional = nullable)]
|
#[ts(optional = nullable)]
|
||||||
pub placeholder: Option<String>,
|
pub placeholder: Option<String>,
|
||||||
|
|
||||||
|
/// Placeholder for the text input
|
||||||
|
#[ts(optional)]
|
||||||
|
pub password: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[ts(export, export_to = "events.ts")]
|
||||||
|
pub enum EditorLanguage {
|
||||||
|
Text,
|
||||||
|
Javascript,
|
||||||
|
Json,
|
||||||
|
Html,
|
||||||
|
Xml,
|
||||||
|
Graphql,
|
||||||
|
Markdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EditorLanguage {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "events.ts")]
|
||||||
|
pub struct FormInputEditor {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub base: FormInputBase,
|
||||||
|
|
||||||
|
/// Placeholder for the text input
|
||||||
|
#[ts(optional = nullable)]
|
||||||
|
pub placeholder: Option<String>,
|
||||||
|
|
||||||
|
pub language: EditorLanguage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use crate::error::Error::{ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr};
|
use crate::error::Error::{
|
||||||
|
AuthPluginNotFound, ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr,
|
||||||
|
};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
BootRequest, CallHttpAuthenticationRequest, CallHttpAuthenticationResponse,
|
BootRequest, CallHttpAuthenticationRequest, CallHttpAuthenticationResponse,
|
||||||
CallHttpRequestActionRequest, CallTemplateFunctionArgs, CallTemplateFunctionRequest,
|
CallHttpRequestActionRequest, CallTemplateFunctionArgs, CallTemplateFunctionRequest,
|
||||||
CallTemplateFunctionResponse, EmptyPayload, FilterRequest, FilterResponse,
|
CallTemplateFunctionResponse, EmptyPayload, FilterRequest, FilterResponse, FormInput,
|
||||||
GetHttpAuthenticationResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
|
GetHttpAuthenticationResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
|
||||||
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose,
|
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose,
|
||||||
WindowContext,
|
WindowContext,
|
||||||
@@ -462,7 +464,7 @@ impl PluginManager {
|
|||||||
pub async fn get_http_authentication<R: Runtime>(
|
pub async fn get_http_authentication<R: Runtime>(
|
||||||
&self,
|
&self,
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
) -> Result<Vec<GetHttpAuthenticationResponse>> {
|
) -> Result<Vec<(PluginHandle, GetHttpAuthenticationResponse)>> {
|
||||||
let window_context = WindowContext::from_window(window);
|
let window_context = WindowContext::from_window(window);
|
||||||
let reply_events = self
|
let reply_events = self
|
||||||
.send_and_wait(
|
.send_and_wait(
|
||||||
@@ -474,7 +476,11 @@ impl PluginManager {
|
|||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for event in reply_events {
|
for event in reply_events {
|
||||||
if let InternalEventPayload::GetHttpAuthenticationResponse(resp) = event.payload {
|
if let InternalEventPayload::GetHttpAuthenticationResponse(resp) = event.payload {
|
||||||
results.push(resp.clone());
|
let plugin = self
|
||||||
|
.get_plugin_by_ref_id(&event.plugin_ref_id)
|
||||||
|
.await
|
||||||
|
.ok_or(PluginNotFoundErr(event.plugin_ref_id))?;
|
||||||
|
results.push((plugin, resp.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,13 +490,37 @@ impl PluginManager {
|
|||||||
pub async fn call_http_authentication<R: Runtime>(
|
pub async fn call_http_authentication<R: Runtime>(
|
||||||
&self,
|
&self,
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
plugin_name: &str,
|
auth_name: &str,
|
||||||
req: CallHttpAuthenticationRequest,
|
req: CallHttpAuthenticationRequest,
|
||||||
) -> Result<CallHttpAuthenticationResponse> {
|
) -> Result<CallHttpAuthenticationResponse> {
|
||||||
let plugin = self
|
let handlers = self.get_http_authentication(window).await?;
|
||||||
.get_plugin_by_name(plugin_name)
|
let (plugin, authentication) = handlers
|
||||||
.await
|
.iter()
|
||||||
.ok_or(PluginNotFoundErr(plugin_name.to_string()))?;
|
.find(|(_, a)| a.name == auth_name)
|
||||||
|
.ok_or(AuthPluginNotFound(auth_name.to_string()))?;
|
||||||
|
|
||||||
|
// Clone for mutability
|
||||||
|
let mut req = req.clone();
|
||||||
|
|
||||||
|
// Fill in default values
|
||||||
|
for arg in authentication.config.clone() {
|
||||||
|
let base = match arg {
|
||||||
|
FormInput::Text(a) => a.base,
|
||||||
|
FormInput::Editor(a) => a.base,
|
||||||
|
FormInput::Select(a) => a.base,
|
||||||
|
FormInput::Checkbox(a) => a.base,
|
||||||
|
FormInput::File(a) => a.base,
|
||||||
|
FormInput::HttpRequest(a) => a.base,
|
||||||
|
};
|
||||||
|
if let None = req.config.get(base.name.as_str()) {
|
||||||
|
let default = match base.default_value {
|
||||||
|
None => serde_json::Value::Null,
|
||||||
|
Some(s) => serde_json::Value::String(s),
|
||||||
|
};
|
||||||
|
req.config.insert(base.name, default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let event = self
|
let event = self
|
||||||
.send_to_plugin_and_wait(
|
.send_to_plugin_and_wait(
|
||||||
WindowContext::from_window(window),
|
WindowContext::from_window(window),
|
||||||
|
|||||||
@@ -2,19 +2,24 @@ import type { Folder, HttpRequest } from '@yaakapp-internal/models';
|
|||||||
import type {
|
import type {
|
||||||
FormInput,
|
FormInput,
|
||||||
FormInputCheckbox,
|
FormInputCheckbox,
|
||||||
|
FormInputEditor,
|
||||||
FormInputFile,
|
FormInputFile,
|
||||||
FormInputHttpRequest,
|
FormInputHttpRequest,
|
||||||
FormInputSelect,
|
FormInputSelect,
|
||||||
FormInputText,
|
FormInputText,
|
||||||
} from '@yaakapp-internal/plugins';
|
} from '@yaakapp-internal/plugins';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useFolders } from '../hooks/useFolders';
|
import { useFolders } from '../hooks/useFolders';
|
||||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
import { Checkbox } from './core/Checkbox';
|
import { Checkbox } from './core/Checkbox';
|
||||||
|
import { Editor } from './core/Editor/Editor';
|
||||||
import { Input } from './core/Input';
|
import { Input } from './core/Input';
|
||||||
|
import { Label } from './core/Label';
|
||||||
import { Select } from './core/Select';
|
import { Select } from './core/Select';
|
||||||
|
import { VStack } from './core/Stacks';
|
||||||
import { SelectFile } from './SelectFile';
|
import { SelectFile } from './SelectFile';
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
@@ -41,7 +46,7 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<VStack space={3}>
|
||||||
{config.map((a, i) => {
|
{config.map((a, i) => {
|
||||||
switch (a.type) {
|
switch (a.type) {
|
||||||
case 'select':
|
case 'select':
|
||||||
@@ -50,7 +55,7 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
|||||||
key={i + stateKey}
|
key={i + stateKey}
|
||||||
arg={a}
|
arg={a}
|
||||||
onChange={(v) => setDataAttr(a.name, v)}
|
onChange={(v) => setDataAttr(a.name, v)}
|
||||||
value={data[a.name] ? String(data[a.name]) : '__ERROR__'}
|
value={data[a.name] ? String(data[a.name]) : DYNAMIC_FORM_NULL_ARG}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'text':
|
case 'text':
|
||||||
@@ -64,6 +69,17 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
|||||||
value={data[a.name] ? String(data[a.name]) : ''}
|
value={data[a.name] ? String(data[a.name]) : ''}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case 'editor':
|
||||||
|
return (
|
||||||
|
<EditorArg
|
||||||
|
key={i}
|
||||||
|
stateKey={stateKey}
|
||||||
|
arg={a}
|
||||||
|
useTemplating={useTemplating || false}
|
||||||
|
onChange={(v) => setDataAttr(a.name, v)}
|
||||||
|
value={data[a.name] ? String(data[a.name]) : ''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
return (
|
return (
|
||||||
<CheckboxArg
|
<CheckboxArg
|
||||||
@@ -93,7 +109,7 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +139,11 @@ function TextArg({
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? '' : value}
|
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? '' : value}
|
||||||
require={!arg.optional}
|
require={!arg.optional}
|
||||||
|
type={arg.password ? 'password' : 'text'}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
{arg.label ?? arg.name}
|
{arg.label ?? arg.name}
|
||||||
{arg.optional && <span> (optional)</span>}
|
{arg.optional && <span className="text-xs text-text-subtlest"> (optional)</span>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
hideLabel={arg.label == null}
|
hideLabel={arg.label == null}
|
||||||
@@ -138,6 +155,50 @@ function TextArg({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EditorArg({
|
||||||
|
arg,
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
useTemplating,
|
||||||
|
stateKey,
|
||||||
|
}: {
|
||||||
|
arg: FormInputEditor;
|
||||||
|
value: string;
|
||||||
|
onChange: (v: string) => void;
|
||||||
|
useTemplating: boolean;
|
||||||
|
stateKey: string;
|
||||||
|
}) {
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
onChange(value === '' ? DYNAMIC_FORM_NULL_ARG : value);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const id = `input-${arg.name}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||||
|
<Label htmlFor={id}>{arg.label}</Label>
|
||||||
|
<Editor
|
||||||
|
id={id}
|
||||||
|
className={classNames(
|
||||||
|
'border border-border rounded-md overflow-hidden px-2 py-1.5',
|
||||||
|
'focus-within:border-border-focus',
|
||||||
|
)}
|
||||||
|
language={arg.language}
|
||||||
|
onChange={handleChange}
|
||||||
|
heightMode="auto"
|
||||||
|
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? '' : value}
|
||||||
|
placeholder={arg.placeholder ?? arg.defaultValue ?? ''}
|
||||||
|
useTemplating={useTemplating}
|
||||||
|
stateKey={stateKey}
|
||||||
|
forceUpdateKey={stateKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SelectArg({
|
function SelectArg({
|
||||||
arg,
|
arg,
|
||||||
value,
|
value,
|
||||||
@@ -155,7 +216,7 @@ function SelectArg({
|
|||||||
value={value}
|
value={value}
|
||||||
options={[
|
options={[
|
||||||
...arg.options.map((a) => ({
|
...arg.options.map((a) => ({
|
||||||
label: a.name + (arg.defaultValue === a.value ? ' (default)' : ''),
|
label: a.name,
|
||||||
value: a.value === arg.defaultValue ? DYNAMIC_FORM_NULL_ARG : a.value,
|
value: a.value === arg.defaultValue ? DYNAMIC_FORM_NULL_ARG : a.value,
|
||||||
})),
|
})),
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -143,8 +143,9 @@ export function GrpcConnectionSetupPane({
|
|||||||
value: activeRequest.authenticationType,
|
value: activeRequest.authenticationType,
|
||||||
items: [
|
items: [
|
||||||
...authentication.map((a) => ({
|
...authentication.map((a) => ({
|
||||||
label: a.name,
|
label: a.label || 'UNKNOWN',
|
||||||
value: a.pluginName,
|
shortLabel: a.shortLabel,
|
||||||
|
value: a.name,
|
||||||
})),
|
})),
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function HttpAuthenticationEditor({ request }: Props) {
|
|||||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||||
const auths = useHttpAuthentication();
|
const auths = useHttpAuthentication();
|
||||||
const auth = auths.find((a) => a.pluginName === request.authenticationType);
|
const auth = auths.find((a) => a.name === request.authenticationType);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(authentication: Record<string, boolean>) => {
|
(authentication: Record<string, boolean>) => {
|
||||||
|
|||||||
@@ -235,8 +235,9 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
value: activeRequest.authenticationType,
|
value: activeRequest.authenticationType,
|
||||||
items: [
|
items: [
|
||||||
...authentication.map((a) => ({
|
...authentication.map((a) => ({
|
||||||
label: a.name,
|
label: a.label || 'UNKNOWN',
|
||||||
value: a.pluginName,
|
shortLabel: a.shortLabel,
|
||||||
|
value: a.name,
|
||||||
})),
|
})),
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { emacs } from '@replit/codemirror-emacs';
|
|||||||
import { vim } from '@replit/codemirror-vim';
|
import { vim } from '@replit/codemirror-vim';
|
||||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||||
import type { EditorKeymap, EnvironmentVariable } from '@yaakapp-internal/models';
|
import type { EditorKeymap, EnvironmentVariable } from '@yaakapp-internal/models';
|
||||||
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
import type { EditorLanguage, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import type { MutableRefObject, ReactNode } from 'react';
|
import type { MutableRefObject, ReactNode } from 'react';
|
||||||
@@ -51,16 +51,7 @@ export interface EditorProps {
|
|||||||
type?: 'text' | 'password';
|
type?: 'text' | 'password';
|
||||||
className?: string;
|
className?: string;
|
||||||
heightMode?: 'auto' | 'full';
|
heightMode?: 'auto' | 'full';
|
||||||
language?:
|
language?: EditorLanguage | 'pairs';
|
||||||
| 'javascript'
|
|
||||||
| 'json'
|
|
||||||
| 'html'
|
|
||||||
| 'xml'
|
|
||||||
| 'graphql'
|
|
||||||
| 'url'
|
|
||||||
| 'pairs'
|
|
||||||
| 'text'
|
|
||||||
| 'markdown';
|
|
||||||
forceUpdateKey?: string | number;
|
forceUpdateKey?: string | number;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
autoSelect?: boolean;
|
autoSelect?: boolean;
|
||||||
@@ -90,10 +81,6 @@ const stateFields = { history: historyField, folds: foldState };
|
|||||||
const emptyVariables: EnvironmentVariable[] = [];
|
const emptyVariables: EnvironmentVariable[] = [];
|
||||||
const emptyExtension: Extension = [];
|
const emptyExtension: Extension = [];
|
||||||
|
|
||||||
// NOTE: For some reason, the cursor doesn't appear if the field is empty and there is no
|
|
||||||
// placeholder. So we set it to a space to force it to show.
|
|
||||||
const emptyPlaceholder = ' ';
|
|
||||||
|
|
||||||
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
||||||
{
|
{
|
||||||
readOnly,
|
readOnly,
|
||||||
@@ -178,11 +165,11 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
useEffect(
|
useEffect(
|
||||||
function configurePlaceholder() {
|
function configurePlaceholder() {
|
||||||
if (cm.current === null) return;
|
if (cm.current === null) return;
|
||||||
const ext = placeholderExt(placeholderElFromText(placeholder || emptyPlaceholder));
|
const ext = placeholderExt(placeholderElFromText(placeholder ?? '', type));
|
||||||
const effect = placeholderCompartment.current.reconfigure(ext);
|
const effect = placeholderCompartment.current.reconfigure(ext);
|
||||||
cm.current?.view.dispatch({ effects: effect });
|
cm.current?.view.dispatch({ effects: effect });
|
||||||
},
|
},
|
||||||
[placeholder],
|
[placeholder, type],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update vim
|
// Update vim
|
||||||
@@ -354,7 +341,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
const extensions = [
|
const extensions = [
|
||||||
languageCompartment.of(langExt),
|
languageCompartment.of(langExt),
|
||||||
placeholderCompartment.current.of(
|
placeholderCompartment.current.of(
|
||||||
placeholderExt(placeholderElFromText(placeholder || emptyPlaceholder)),
|
placeholderExt(placeholderElFromText(placeholder ?? '', type)),
|
||||||
),
|
),
|
||||||
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : []),
|
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : []),
|
||||||
keymapCompartment.current.of(
|
keymapCompartment.current.of(
|
||||||
@@ -592,18 +579,21 @@ function getExtensions({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const placeholderElFromText = (text: string) => {
|
const placeholderElFromText = (text: string, type: EditorProps['type']) => {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.innerHTML = text.replaceAll('\n', '<br/>');
|
if (type === 'password') {
|
||||||
|
// Will be obscured (dots) so just needs to be something to take up space
|
||||||
|
el.innerHTML = 'aaaaaaaaaa';
|
||||||
|
el.setAttribute('aria-hidden', 'true');
|
||||||
|
} else {
|
||||||
|
el.innerHTML = text ? text.replaceAll('\n', '<br/>') : ' ';
|
||||||
|
}
|
||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
|
|
||||||
function saveCachedEditorState(stateKey: string | null, state: EditorState | null) {
|
function saveCachedEditorState(stateKey: string | null, state: EditorState | null) {
|
||||||
if (!stateKey || state == null) return;
|
if (!stateKey || state == null) return;
|
||||||
sessionStorage.setItem(
|
sessionStorage.setItem(computeFullStateKey(stateKey), JSON.stringify(state.toJSON(stateFields)));
|
||||||
computeFullStateKey(stateKey),
|
|
||||||
JSON.stringify(state.toJSON(stateFields)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCachedEditorState(doc: string, stateKey: string | null) {
|
function getCachedEditorState(doc: string, stateKey: string | null) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { EditorView } from 'codemirror';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||||
|
import { generateId } from '../../lib/generateId';
|
||||||
import type { EditorProps } from './Editor/Editor';
|
import type { EditorProps } from './Editor/Editor';
|
||||||
import { Editor } from './Editor/Editor';
|
import { Editor } from './Editor/Editor';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
@@ -94,7 +95,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
|||||||
onBlur?.();
|
onBlur?.();
|
||||||
}, [onBlur]);
|
}, [onBlur]);
|
||||||
|
|
||||||
const id = `input-${label}`;
|
const id = useRef(`input-${generateId()}`);
|
||||||
const editorClassName = classNames(
|
const editorClassName = classNames(
|
||||||
className,
|
className,
|
||||||
'!bg-transparent min-w-0 h-auto w-full focus:outline-none placeholder:text-placeholder',
|
'!bg-transparent min-w-0 h-auto w-full focus:outline-none placeholder:text-placeholder',
|
||||||
@@ -140,7 +141,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
|||||||
labelPosition === 'top' && 'flex-row gap-0.5',
|
labelPosition === 'top' && 'flex-row gap-0.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Label htmlFor={id} className={classNames(labelClassName, hideLabel && 'sr-only')}>
|
<Label htmlFor={id.current} className={classNames(labelClassName, hideLabel && 'sr-only')}>
|
||||||
{label}
|
{label}
|
||||||
</Label>
|
</Label>
|
||||||
<HStack
|
<HStack
|
||||||
@@ -168,7 +169,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
|||||||
>
|
>
|
||||||
<Editor
|
<Editor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
id={id}
|
id={id.current}
|
||||||
singleLine
|
singleLine
|
||||||
stateKey={stateKey}
|
stateKey={stateKey}
|
||||||
wrapLines={wrapLines}
|
wrapLines={wrapLines}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export function RadioDropdown<T = string | null>({
|
|||||||
return {
|
return {
|
||||||
key: item.value,
|
key: item.value,
|
||||||
label: item.label,
|
label: item.label,
|
||||||
shortLabel: item.shortLabel,
|
|
||||||
rightSlot: item.rightSlot,
|
rightSlot: item.rightSlot,
|
||||||
onSelect: () => onChange(item.value),
|
onSelect: () => onChange(item.value),
|
||||||
leftSlot: <Icon icon={value === item.value ? 'check' : 'empty'} />,
|
leftSlot: <Icon icon={value === item.value ? 'check' : 'empty'} />,
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export function Tabs({
|
|||||||
}
|
}
|
||||||
className={btnClassName}
|
className={btnClassName}
|
||||||
>
|
>
|
||||||
{option && 'shortLabel' in option
|
{option && 'shortLabel' in option && option.shortLabel
|
||||||
? option.shortLabel
|
? option.shortLabel
|
||||||
: (option?.label ?? 'Unknown')}
|
: (option?.label ?? 'Unknown')}
|
||||||
{t.rightSlot}
|
{t.rightSlot}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useAtomValue } from 'jotai';
|
|||||||
import { atom, useSetAtom } from 'jotai/index';
|
import { atom, useSetAtom } from 'jotai/index';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
|
import { showErrorToast } from '../lib/toast';
|
||||||
|
|
||||||
const httpAuthenticationAtom = atom<GetHttpAuthenticationResponse[]>([]);
|
const httpAuthenticationAtom = atom<GetHttpAuthenticationResponse[]>([]);
|
||||||
const orderedHttpAuthenticationAtom = atom((get) =>
|
const orderedHttpAuthenticationAtom = atom((get) =>
|
||||||
@@ -27,12 +28,16 @@ export function useSubscribeHttpAuthentication() {
|
|||||||
refetchInterval: numResults > 0 ? Infinity : 1000,
|
refetchInterval: numResults > 0 ? Infinity : 1000,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const result = await invokeCmd<GetHttpAuthenticationResponse[]>(
|
try {
|
||||||
'cmd_get_http_authentication',
|
const result = await invokeCmd<GetHttpAuthenticationResponse[]>(
|
||||||
);
|
'cmd_get_http_authentication',
|
||||||
setNumResults(result.length);
|
);
|
||||||
setAtom(result);
|
setNumResults(result.length);
|
||||||
return result;
|
setAtom(result);
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
showErrorToast('http-authentication-error', err);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,3 +34,12 @@ export function hideToast(id: string) {
|
|||||||
return all.filter((t) => t.id !== id);
|
return all.filter((t) => t.id !== id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showErrorToast<T>(id: string, message: T) {
|
||||||
|
return showToast({
|
||||||
|
id,
|
||||||
|
message: String(message),
|
||||||
|
timeout: 8000,
|
||||||
|
color: 'danger',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user