mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-16 05:56:47 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5b7b1638d | ||
|
|
9d6ac8a107 | ||
|
|
6440df492e | ||
|
|
2cdd97cabb | ||
|
|
20681e5be3 |
@@ -57,6 +57,9 @@
|
||||
"migration": "node scripts/create-migration.cjs",
|
||||
"build": "npm run --workspaces --if-present build",
|
||||
"build-plugins": "npm run --workspaces --if-present build",
|
||||
"icons": "run-p icons:*",
|
||||
"icons:dev": "tauri icon src-tauri/icons/icon.png --output src-tauri/icons/release",
|
||||
"icons:release": "tauri icon src-tauri/icons/icon-dev.png --output src-tauri/icons/dev",
|
||||
"bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap",
|
||||
"bootstrap:vendor-node": "node scripts/vendor-node.cjs",
|
||||
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import type { AccessToken } from './store';
|
||||
import { getToken } from './store';
|
||||
|
||||
export async function getAccessTokenIfNotExpired(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
): Promise<AccessToken | null> {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null || isTokenExpired(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function isTokenExpired(token: AccessToken) {
|
||||
return token.expiresAt && Date.now() > token.expiresAt;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { Context, HttpRequest } from '@yaakapp/api';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { isTokenExpired } from './getAccessTokenIfNotExpired';
|
||||
import type { AccessToken, AccessTokenRawResponse } from './store';
|
||||
import type { AccessToken, AccessTokenRawResponse, TokenStoreArgs } from './store';
|
||||
import { deleteToken, getToken, storeToken } from './store';
|
||||
import { isTokenExpired } from './util';
|
||||
|
||||
export async function getOrRefreshAccessToken(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
tokenArgs: TokenStoreArgs,
|
||||
{
|
||||
scope,
|
||||
accessTokenUrl,
|
||||
@@ -23,7 +23,7 @@ export async function getOrRefreshAccessToken(
|
||||
forceRefresh?: boolean;
|
||||
},
|
||||
): Promise<AccessToken | null> {
|
||||
const token = await getToken(ctx, contextId);
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ export async function getOrRefreshAccessToken(
|
||||
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
|
||||
// and returning null;
|
||||
console.log('[oauth2] Unauthorized refresh_token request');
|
||||
await deleteToken(ctx, contextId);
|
||||
await deleteToken(ctx, tokenArgs);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -108,5 +108,5 @@ export async function getOrRefreshAccessToken(
|
||||
refresh_token: response.refresh_token ?? token.response.refresh_token,
|
||||
};
|
||||
|
||||
return storeToken(ctx, contextId, newResponse);
|
||||
return storeToken(ctx, tokenArgs, newResponse);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Context } from '@yaakapp/api';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||
import type { AccessToken } from '../store';
|
||||
import type { AccessToken, TokenStoreArgs } from '../store';
|
||||
import { getDataDirKey, storeToken } from '../store';
|
||||
|
||||
export const PKCE_SHA256 = 'S256';
|
||||
@@ -41,7 +41,14 @@ export async function getAuthorizationCode(
|
||||
tokenName: 'access_token' | 'id_token';
|
||||
},
|
||||
): Promise<AccessToken> {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
clientId,
|
||||
accessTokenUrl,
|
||||
authorizationUrl: authorizationUrlRaw,
|
||||
};
|
||||
|
||||
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
clientId,
|
||||
@@ -128,7 +135,7 @@ export async function getAuthorizationCode(
|
||||
],
|
||||
});
|
||||
|
||||
return storeToken(ctx, contextId, response, tokenName);
|
||||
return storeToken(ctx, tokenArgs, response, tokenName);
|
||||
}
|
||||
|
||||
export function genPkceCodeVerifier() {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
|
||||
import type { TokenStoreArgs } from '../store';
|
||||
import { getToken, storeToken } from '../store';
|
||||
import { isTokenExpired } from '../util';
|
||||
|
||||
export async function getClientCredentials(
|
||||
ctx: Context,
|
||||
@@ -22,7 +23,13 @@ export async function getClientCredentials(
|
||||
credentialsInBody: boolean;
|
||||
},
|
||||
) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
clientId,
|
||||
accessTokenUrl,
|
||||
authorizationUrl: null,
|
||||
};
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token && !isTokenExpired(token)) {
|
||||
return token;
|
||||
}
|
||||
@@ -38,5 +45,5 @@ export async function getClientCredentials(
|
||||
params: [],
|
||||
});
|
||||
|
||||
return storeToken(ctx, contextId, response);
|
||||
return storeToken(ctx, tokenArgs, response);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
|
||||
import type { AccessToken, AccessTokenRawResponse} from '../store';
|
||||
import type { AccessToken, AccessTokenRawResponse } from '../store';
|
||||
import { getToken, storeToken } from '../store';
|
||||
import { isTokenExpired } from '../util';
|
||||
|
||||
export async function getImplicit(
|
||||
ctx: Context,
|
||||
@@ -26,7 +26,13 @@ export async function getImplicit(
|
||||
tokenName: 'access_token' | 'id_token';
|
||||
},
|
||||
): Promise<AccessToken> {
|
||||
const token = await getToken(ctx, contextId);
|
||||
const tokenArgs = {
|
||||
contextId,
|
||||
clientId,
|
||||
accessTokenUrl: null,
|
||||
authorizationUrl: authorizationUrlRaw,
|
||||
};
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token != null && !isTokenExpired(token)) {
|
||||
return token;
|
||||
}
|
||||
@@ -82,7 +88,7 @@ export async function getImplicit(
|
||||
|
||||
const response = Object.fromEntries(params) as unknown as AccessTokenRawResponse;
|
||||
try {
|
||||
resolve(storeToken(ctx, contextId, response));
|
||||
resolve(storeToken(ctx, tokenArgs, response));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||
import type { AccessToken} from '../store';
|
||||
import type { AccessToken, TokenStoreArgs } from '../store';
|
||||
import { storeToken } from '../store';
|
||||
|
||||
export async function getPassword(
|
||||
@@ -27,7 +27,13 @@ export async function getPassword(
|
||||
credentialsInBody: boolean;
|
||||
},
|
||||
): Promise<AccessToken> {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
clientId,
|
||||
accessTokenUrl,
|
||||
authorizationUrl: null,
|
||||
};
|
||||
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
clientId,
|
||||
@@ -52,5 +58,5 @@ export async function getPassword(
|
||||
],
|
||||
});
|
||||
|
||||
return storeToken(ctx, contextId, response);
|
||||
return storeToken(ctx, tokenArgs, response);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { getClientCredentials } from './grants/clientCredentials';
|
||||
import { getImplicit } from './grants/implicit';
|
||||
import { getPassword } from './grants/password';
|
||||
import type { AccessToken } from './store';
|
||||
import type { AccessToken, TokenStoreArgs } from './store';
|
||||
import { deleteToken, getToken, resetDataDirKey } from './store';
|
||||
|
||||
type GrantType = 'authorization_code' | 'implicit' | 'password' | 'client_credentials';
|
||||
@@ -83,8 +83,14 @@ export const plugin: PluginDefinition = {
|
||||
actions: [
|
||||
{
|
||||
label: 'Copy Current Token',
|
||||
async onSelect(ctx, { contextId }) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
async onSelect(ctx, { contextId, values }) {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
};
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token == null) {
|
||||
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
|
||||
} else {
|
||||
@@ -99,8 +105,14 @@ export const plugin: PluginDefinition = {
|
||||
},
|
||||
{
|
||||
label: 'Delete Token',
|
||||
async onSelect(ctx, { contextId }) {
|
||||
if (await deleteToken(ctx, contextId)) {
|
||||
async onSelect(ctx, { contextId, values }) {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
};
|
||||
if (await deleteToken(ctx, tokenArgs)) {
|
||||
await ctx.toast.show({ message: 'Token deleted', color: 'success' });
|
||||
} else {
|
||||
await ctx.toast.show({ message: 'No token to delete', color: 'warning' });
|
||||
@@ -281,8 +293,14 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Access Token Response',
|
||||
async dynamic(ctx, { contextId }) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
async dynamic(ctx, { contextId, values }) {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
};
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token == null) {
|
||||
return { hidden: true };
|
||||
}
|
||||
@@ -316,9 +334,10 @@ export const plugin: PluginDefinition = {
|
||||
accessTokenUrl === '' || accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
authorizationUrl: authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
|
||||
? authorizationUrl
|
||||
: `https://${authorizationUrl}`,
|
||||
authorizationUrl:
|
||||
authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
|
||||
? authorizationUrl
|
||||
: `https://${authorizationUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
export async function storeToken(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
args: TokenStoreArgs,
|
||||
response: AccessTokenRawResponse,
|
||||
tokenName: 'access_token' | 'id_token' = 'access_token',
|
||||
) {
|
||||
@@ -15,16 +16,16 @@ export async function storeToken(
|
||||
response,
|
||||
expiresAt,
|
||||
};
|
||||
await ctx.store.set<AccessToken>(tokenStoreKey(contextId), token);
|
||||
await ctx.store.set<AccessToken>(tokenStoreKey(args), token);
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function getToken(ctx: Context, contextId: string) {
|
||||
return ctx.store.get<AccessToken>(tokenStoreKey(contextId));
|
||||
export async function getToken(ctx: Context, args: TokenStoreArgs) {
|
||||
return ctx.store.get<AccessToken>(tokenStoreKey(args));
|
||||
}
|
||||
|
||||
export async function deleteToken(ctx: Context, contextId: string) {
|
||||
return ctx.store.delete(tokenStoreKey(contextId));
|
||||
export async function deleteToken(ctx: Context, args: TokenStoreArgs) {
|
||||
return ctx.store.delete(tokenStoreKey(args));
|
||||
}
|
||||
|
||||
export async function resetDataDirKey(ctx: Context, contextId: string) {
|
||||
@@ -37,8 +38,25 @@ export async function getDataDirKey(ctx: Context, contextId: string) {
|
||||
return `${contextId}::${key}`;
|
||||
}
|
||||
|
||||
function tokenStoreKey(contextId: string) {
|
||||
return ['token', contextId].join('::');
|
||||
export interface TokenStoreArgs {
|
||||
contextId: string;
|
||||
clientId: string;
|
||||
accessTokenUrl: string | null;
|
||||
authorizationUrl: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a store key to use based on some arguments. The arguments will be normalized a bit to
|
||||
* account for slight variations (like domains with and without a protocol scheme).
|
||||
*/
|
||||
function tokenStoreKey(args: TokenStoreArgs) {
|
||||
const hash = createHash('md5');
|
||||
if (args.contextId) hash.update(args.contextId.trim());
|
||||
if (args.clientId) hash.update(args.clientId.trim());
|
||||
if (args.accessTokenUrl) hash.update(args.accessTokenUrl.trim().replace(/^https?:\/\//, ''));
|
||||
if (args.authorizationUrl) hash.update(args.authorizationUrl.trim().replace(/^https?:\/\//, ''));
|
||||
const key = hash.digest('hex');
|
||||
return ['token', key].join('::');
|
||||
}
|
||||
|
||||
function dataDirStoreKey(contextId: string) {
|
||||
|
||||
5
plugins/auth-oauth2/src/util.ts
Normal file
5
plugins/auth-oauth2/src/util.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { AccessToken } from './store';
|
||||
|
||||
export function isTokenExpired(token: AccessToken) {
|
||||
return token.expiresAt && Date.now() > token.expiresAt;
|
||||
}
|
||||
BIN
src-tauri/icons/dev/64x64.png
Normal file
BIN
src-tauri/icons/dev/64x64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
BIN
src-tauri/icons/release/64x64.png
Normal file
BIN
src-tauri/icons/release/64x64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
@@ -55,11 +55,6 @@ pub async fn send_http_request<R: Runtime>(
|
||||
let response_id = og_response.id.clone();
|
||||
let response = Arc::new(Mutex::new(og_response.clone()));
|
||||
|
||||
let cb = PluginTemplateCallback::new(
|
||||
window.app_handle(),
|
||||
&PluginWindowContext::new(window),
|
||||
RenderPurpose::Send,
|
||||
);
|
||||
let update_source = UpdateSource::from_window(window);
|
||||
|
||||
let (resolved_request, auth_context_id) = match resolve_http_request(window, unrendered_request)
|
||||
@@ -75,6 +70,12 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
};
|
||||
|
||||
let cb = PluginTemplateCallback::new(
|
||||
window.app_handle(),
|
||||
&PluginWindowContext::new(window),
|
||||
RenderPurpose::Send,
|
||||
);
|
||||
|
||||
let request =
|
||||
match render_http_request(&resolved_request, &base_environment, environment.as_ref(), &cb)
|
||||
.await
|
||||
|
||||
@@ -35,7 +35,13 @@ use yaak_models::models::{
|
||||
};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
||||
use yaak_plugins::events::{CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs, CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest};
|
||||
use yaak_plugins::events::{
|
||||
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
|
||||
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
|
||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent,
|
||||
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest,
|
||||
};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::plugin_meta::PluginMetadata;
|
||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||
@@ -818,9 +824,29 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
||||
auth_name: &str,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
workspace_id: &str,
|
||||
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
||||
let base_environment = window.db().get_base_environment(&workspace_id)?;
|
||||
let environment = match environment_id {
|
||||
Some(id) => match window.db().get_environment(id) {
|
||||
Ok(env) => Some(env),
|
||||
Err(e) => {
|
||||
warn!("Failed to find environment by id {id} {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
Ok(plugin_manager
|
||||
.get_http_authentication_config(&window, auth_name, values, request_id)
|
||||
.get_http_authentication_config(
|
||||
&window,
|
||||
&base_environment,
|
||||
environment.as_ref(),
|
||||
auth_name,
|
||||
values,
|
||||
request_id,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -872,9 +898,30 @@ async fn cmd_call_http_authentication_action<R: Runtime>(
|
||||
action_index: i32,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
model_id: &str,
|
||||
workspace_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
) -> YaakResult<()> {
|
||||
let base_environment = window.db().get_base_environment(&workspace_id)?;
|
||||
let environment = match environment_id {
|
||||
Some(id) => match window.db().get_environment(id) {
|
||||
Ok(env) => Some(env),
|
||||
Err(e) => {
|
||||
warn!("Failed to find environment by id {id} {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
Ok(plugin_manager
|
||||
.call_http_authentication_action(&window, auth_name, action_index, values, model_id)
|
||||
.call_http_authentication_action(
|
||||
&window,
|
||||
&base_environment,
|
||||
environment.as_ref(),
|
||||
auth_name,
|
||||
action_index,
|
||||
values,
|
||||
model_id,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -1238,7 +1285,10 @@ pub fn run() {
|
||||
let _ = app_handle.emit(
|
||||
"show_toast",
|
||||
ShowToastRequest {
|
||||
message: format!("Error handling deep link: {}", e.to_string()),
|
||||
message: format!(
|
||||
"Error handling deep link: {}",
|
||||
e.to_string()
|
||||
),
|
||||
color: Some(Color::Danger),
|
||||
icon: None,
|
||||
},
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
{
|
||||
"productName": "yaak"
|
||||
"productName": "yaak",
|
||||
"bundle": {
|
||||
"linux": {
|
||||
"deb": {
|
||||
"desktopTemplate": "./template.desktop"
|
||||
},
|
||||
"rpm": {
|
||||
"desktopTemplate": "./template.desktop"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
src-tauri/template.desktop
Normal file
9
src-tauri/template.desktop
Normal file
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Categories={{categories}}
|
||||
Comment={{comment}}
|
||||
Exec={{exec}}
|
||||
Icon={{icon}}
|
||||
Name={{name}}
|
||||
StartupWMClass={{exec}}
|
||||
Terminal=false
|
||||
Type=Application
|
||||
@@ -18,7 +18,9 @@ use crate::native_template_functions::template_function_secure;
|
||||
use crate::nodejs::start_nodejs_plugin_runtime;
|
||||
use crate::plugin_handle::PluginHandle;
|
||||
use crate::server_ws::PluginRuntimeServerWebsocket;
|
||||
use crate::template_callback::PluginTemplateCallback;
|
||||
use log::{error, info, warn};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -30,10 +32,13 @@ use tokio::fs::read_dir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
use tokio::time::{Instant, timeout};
|
||||
use yaak_models::models::Environment;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::render::make_vars_hashmap;
|
||||
use yaak_models::util::generate_id;
|
||||
use yaak_templates::error::Error::RenderError;
|
||||
use yaak_templates::error::Result as TemplateResult;
|
||||
use yaak_templates::render_json_value_raw;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PluginManager {
|
||||
@@ -569,6 +574,8 @@ impl PluginManager {
|
||||
pub async fn get_http_authentication_config<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
auth_name: &str,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
@@ -579,13 +586,23 @@ impl PluginManager {
|
||||
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
||||
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
||||
|
||||
let vars = &make_vars_hashmap(&base_environment, environment);
|
||||
let cb = PluginTemplateCallback::new(
|
||||
window.app_handle(),
|
||||
&PluginWindowContext::new(&window),
|
||||
RenderPurpose::Preview,
|
||||
);
|
||||
let rendered_values = render_json_value_raw(json!(values), vars, &cb).await?;
|
||||
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
||||
let event = self
|
||||
.send_to_plugin_and_wait(
|
||||
&PluginWindowContext::new(window),
|
||||
&plugin,
|
||||
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
|
||||
GetHttpAuthenticationConfigRequest { values, context_id },
|
||||
GetHttpAuthenticationConfigRequest {
|
||||
values: serde_json::from_value(rendered_values)?,
|
||||
context_id,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
@@ -602,11 +619,24 @@ impl PluginManager {
|
||||
pub async fn call_http_authentication_action<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
auth_name: &str,
|
||||
action_index: i32,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
model_id: &str,
|
||||
) -> Result<()> {
|
||||
let vars = &make_vars_hashmap(&base_environment, environment);
|
||||
let rendered_values = render_json_value_raw(
|
||||
json!(values),
|
||||
vars,
|
||||
&PluginTemplateCallback::new(
|
||||
window.app_handle(),
|
||||
&PluginWindowContext::new(&window),
|
||||
RenderPurpose::Preview,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
let results = self.get_http_authentication_summaries(window).await?;
|
||||
let plugin = results
|
||||
.iter()
|
||||
@@ -621,7 +651,10 @@ impl PluginManager {
|
||||
CallHttpAuthenticationActionRequest {
|
||||
index: action_index,
|
||||
plugin_ref_id: plugin.clone().ref_id,
|
||||
args: CallHttpAuthenticationActionArgs { context_id, values },
|
||||
args: CallHttpAuthenticationActionArgs {
|
||||
context_id,
|
||||
values: serde_json::from_value(rendered_values)?,
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -182,23 +182,24 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
);
|
||||
case 'accordion':
|
||||
return (
|
||||
<DetailsBanner
|
||||
key={i}
|
||||
summary={input.label}
|
||||
className={classNames(disabled && 'opacity-disabled')}
|
||||
>
|
||||
<div className="mb-3 px-3">
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
autocompleteFunctions={autocompleteFunctions || false}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
/>
|
||||
</div>
|
||||
</DetailsBanner>
|
||||
<div key={i}>
|
||||
<DetailsBanner
|
||||
summary={input.label}
|
||||
className={classNames('!mb-auto', disabled && 'opacity-disabled')}
|
||||
>
|
||||
<div className="mb-3 px-3">
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
autocompleteFunctions={autocompleteFunctions || false}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
/>
|
||||
</div>
|
||||
</DetailsBanner>
|
||||
</div>
|
||||
);
|
||||
case 'banner':
|
||||
return (
|
||||
@@ -309,6 +310,7 @@ function EditorArg({
|
||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||
disabled={arg.disabled}
|
||||
language={arg.language}
|
||||
readOnly={arg.readOnly}
|
||||
onChange={onChange}
|
||||
heightMode="auto"
|
||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||
@@ -329,9 +331,9 @@ function EditorArg({
|
||||
showDialog({
|
||||
id: 'id',
|
||||
size: 'full',
|
||||
title: 'Edit Value',
|
||||
title: arg.readOnly ? 'View Value' : 'Edit Value',
|
||||
className: '!max-w-[50rem] !max-h-[60rem]',
|
||||
description: (
|
||||
description: arg.label && (
|
||||
<Label
|
||||
htmlFor={id}
|
||||
required={!arg.optional}
|
||||
@@ -355,6 +357,7 @@ function EditorArg({
|
||||
}
|
||||
disabled={arg.disabled}
|
||||
language={arg.language}
|
||||
readOnly={arg.readOnly}
|
||||
onChange={onChange}
|
||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||
placeholder={arg.placeholder ?? undefined}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
||||
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
||||
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||
import { WindowControls } from './WindowControls';
|
||||
|
||||
@@ -23,7 +24,7 @@ export function HeaderSize({
|
||||
children,
|
||||
}: HeaderSizeProps) {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const stoplightsVisible = useStoplightsVisible();
|
||||
const isFullscreen = useIsFullscreen();
|
||||
const finalStyle = useMemo<CSSProperties>(() => {
|
||||
const s = { ...style };
|
||||
|
||||
@@ -31,20 +32,22 @@ export function HeaderSize({
|
||||
if (size === 'md') s.minHeight = HEADER_SIZE_MD;
|
||||
if (size === 'lg') s.minHeight = HEADER_SIZE_LG;
|
||||
|
||||
// Add large padding for window controls
|
||||
if (stoplightsVisible && !ignoreControlsSpacing) {
|
||||
s.paddingLeft = 72 / settings.interfaceScale;
|
||||
} else if (!stoplightsVisible && !ignoreControlsSpacing && !settings.hideWindowControls) {
|
||||
if (type() === 'macos') {
|
||||
if (!isFullscreen) {
|
||||
// Add large padding for window controls
|
||||
s.paddingLeft = 72 / settings.interfaceScale;
|
||||
}
|
||||
} else if (!ignoreControlsSpacing && !settings.hideWindowControls) {
|
||||
s.paddingRight = WINDOW_CONTROLS_WIDTH;
|
||||
}
|
||||
|
||||
return s;
|
||||
}, [
|
||||
ignoreControlsSpacing,
|
||||
isFullscreen,
|
||||
settings.hideWindowControls,
|
||||
settings.interfaceScale,
|
||||
size,
|
||||
stoplightsVisible,
|
||||
style,
|
||||
]);
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ export type HotkeyAction =
|
||||
| 'workspace_settings.show';
|
||||
|
||||
const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'app.zoom_in': ['CmdCtrl+='],
|
||||
'app.zoom_out': ['CmdCtrl+-'],
|
||||
'app.zoom_in': ['CmdCtrl+Equal'],
|
||||
'app.zoom_out': ['CmdCtrl+Minus'],
|
||||
'app.zoom_reset': ['CmdCtrl+0'],
|
||||
'command_palette.toggle': ['CmdCtrl+k'],
|
||||
'environmentEditor.toggle': ['CmdCtrl+Shift+E', 'CmdCtrl+Shift+e'],
|
||||
@@ -67,6 +67,8 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'workspace_settings.show': 'Open Workspace Settings',
|
||||
};
|
||||
|
||||
const layoutInsensitiveKeys = ['Equal', 'Minus', 'BracketLeft', 'BracketRight', 'Backquote'];
|
||||
|
||||
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];
|
||||
|
||||
interface Options {
|
||||
@@ -106,7 +108,8 @@ export function useHotKey(
|
||||
return;
|
||||
}
|
||||
|
||||
currentKeys.current.add(e.key);
|
||||
const keyToAdd = layoutInsensitiveKeys.includes(e.code) ? e.code : e.key;
|
||||
currentKeys.current.add(keyToAdd);
|
||||
|
||||
const currentKeysWithModifiers = new Set(currentKeys.current);
|
||||
if (e.altKey) currentKeysWithModifiers.add('Alt');
|
||||
@@ -150,7 +153,9 @@ export function useHotKey(
|
||||
if (options.enable === false) {
|
||||
return;
|
||||
}
|
||||
currentKeys.current.delete(e.key);
|
||||
|
||||
const keyToRemove = layoutInsensitiveKeys.includes(e.code) ? e.code : e.key;
|
||||
currentKeys.current.delete(keyToRemove);
|
||||
|
||||
// Clear all keys if no longer holding modifier
|
||||
// HACK: This is to get around the case of DOWN SHIFT -> DOWN : -> UP SHIFT -> UP ;
|
||||
|
||||
@@ -12,12 +12,16 @@ import { useAtomValue } from 'jotai';
|
||||
import { md5 } from 'js-md5';
|
||||
import { useState } from 'react';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { activeEnvironmentIdAtom } from './useActiveEnvironment';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
|
||||
export function useHttpAuthenticationConfig(
|
||||
authName: string | null,
|
||||
values: Record<string, JsonPrimitive>,
|
||||
requestId: string,
|
||||
) {
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||
const responses = useAtomValue(httpResponsesAtom);
|
||||
const [forceRefreshCounter, setForceRefreshCounter] = useState<number>(0);
|
||||
|
||||
@@ -38,6 +42,8 @@ export function useHttpAuthenticationConfig(
|
||||
values,
|
||||
responseKey,
|
||||
forceRefreshCounter,
|
||||
workspaceId,
|
||||
environmentId,
|
||||
],
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
@@ -48,6 +54,8 @@ export function useHttpAuthenticationConfig(
|
||||
authName,
|
||||
values,
|
||||
requestId,
|
||||
workspaceId,
|
||||
environmentId,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -64,6 +72,8 @@ export function useHttpAuthenticationConfig(
|
||||
authName,
|
||||
values,
|
||||
modelId,
|
||||
environmentId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
// Ensure the config is refreshed after the action is done
|
||||
|
||||
Reference in New Issue
Block a user