fix 1
This commit is contained in:
1
src-tauri/.gitignore
vendored
1
src-tauri/.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
|
/target*/
|
||||||
|
|
||||||
# Generated by Tauri
|
# Generated by Tauri
|
||||||
# will have schema files for capabilities auto-completion
|
# will have schema files for capabilities auto-completion
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use tauri::State;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct HttpResponse {
|
struct HttpResponse {
|
||||||
@@ -39,8 +40,13 @@ struct AuthConfig {
|
|||||||
api_key: ApiKeyAuth,
|
api_key: ApiKeyAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
client: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn execute_request(
|
async fn execute_request(
|
||||||
|
state: State<'_, AppState>,
|
||||||
method: String,
|
method: String,
|
||||||
url: String,
|
url: String,
|
||||||
headers: HashMap<String, String>,
|
headers: HashMap<String, String>,
|
||||||
@@ -48,14 +54,8 @@ async fn execute_request(
|
|||||||
query_params: Option<HashMap<String, String>>,
|
query_params: Option<HashMap<String, String>>,
|
||||||
auth: Option<AuthConfig>,
|
auth: Option<AuthConfig>,
|
||||||
) -> Result<HttpResponse, String> {
|
) -> Result<HttpResponse, String> {
|
||||||
let client = reqwest::Client::builder()
|
|
||||||
.timeout(std::time::Duration::from_secs(30))
|
|
||||||
.build()
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let req_method = method.parse::<reqwest::Method>().map_err(|e| e.to_string())?;
|
let req_method = method.parse::<reqwest::Method>().map_err(|e| e.to_string())?;
|
||||||
|
let mut request_builder = state.client.request(req_method, &url);
|
||||||
let mut request_builder = client.request(req_method, &url);
|
|
||||||
|
|
||||||
// Add Query Params
|
// Add Query Params
|
||||||
if let Some(params) = query_params {
|
if let Some(params) = query_params {
|
||||||
@@ -126,9 +126,15 @@ async fn execute_request(
|
|||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
|
.build()
|
||||||
|
.expect("failed to create HTTP client");
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.manage(AppState { client })
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![execute_request])
|
.invoke_handler(tauri::generate_handler![execute_request])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const updateRow = (index: number, key: keyof KeyValue, value: string | boolean) => {
|
||||||
|
const newData = props.modelValue.map((item, itemIndex) => {
|
||||||
|
if (itemIndex !== index) return item;
|
||||||
|
return { ...item, [key]: value };
|
||||||
|
});
|
||||||
|
emit('update:modelValue', newData);
|
||||||
|
};
|
||||||
|
|
||||||
const addRow = () => {
|
const addRow = () => {
|
||||||
const newData = [...props.modelValue, {
|
const newData = [...props.modelValue, {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
@@ -47,14 +55,16 @@ const removeRow = (index: number) => {
|
|||||||
<td class="py-1 pl-2 align-middle">
|
<td class="py-1 pl-2 align-middle">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="item.enabled"
|
:checked="item.enabled"
|
||||||
|
@change="updateRow(index, 'enabled', ($event.target as HTMLInputElement).checked)"
|
||||||
class="rounded bg-slate-800 border-slate-700 text-indigo-500 focus:ring-indigo-500/50 focus:ring-offset-0"
|
class="rounded bg-slate-800 border-slate-700 text-indigo-500 focus:ring-indigo-500/50 focus:ring-offset-0"
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-1 pr-2 align-middle">
|
<td class="py-1 pr-2 align-middle">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="item.key"
|
:value="item.key"
|
||||||
|
@input="updateRow(index, 'key', ($event.target as HTMLInputElement).value)"
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
class="w-full bg-transparent border-b border-transparent hover:border-slate-700 focus:border-indigo-500 focus:outline-none py-1 px-1 text-sm text-slate-300 transition-colors"
|
class="w-full bg-transparent border-b border-transparent hover:border-slate-700 focus:border-indigo-500 focus:outline-none py-1 px-1 text-sm text-slate-300 transition-colors"
|
||||||
>
|
>
|
||||||
@@ -62,7 +72,8 @@ const removeRow = (index: number) => {
|
|||||||
<td class="py-1 pr-2 align-middle">
|
<td class="py-1 pr-2 align-middle">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="item.value"
|
:value="item.value"
|
||||||
|
@input="updateRow(index, 'value', ($event.target as HTMLInputElement).value)"
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
class="w-full bg-transparent border-b border-transparent hover:border-slate-700 focus:border-indigo-500 focus:outline-none py-1 px-1 text-sm text-slate-300 transition-colors"
|
class="w-full bg-transparent border-b border-transparent hover:border-slate-700 focus:border-indigo-500 focus:outline-none py-1 px-1 text-sm text-slate-300 transition-colors"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const extensions = computed(() => {
|
|||||||
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
|
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
|
||||||
|
|
||||||
const executeRequest = async () => {
|
const executeRequest = async () => {
|
||||||
if (!store.activeRequest.url) return;
|
if (isLoading.value || !store.activeRequest.url) return;
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
@@ -73,7 +73,6 @@ const executeRequest = async () => {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
// Simple alert for now, or could use a toast
|
|
||||||
alert('Request Failed: ' + error);
|
alert('Request Failed: ' + error);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ const store = useRequestStore();
|
|||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const isCopied = ref(false);
|
const isCopied = ref(false);
|
||||||
|
|
||||||
|
const isJsonResponse = computed(() => {
|
||||||
|
const response = store.activeRequest.response;
|
||||||
|
if (!response) return false;
|
||||||
|
|
||||||
|
const contentType = response.headers['content-type'] || response.headers['Content-Type'] || '';
|
||||||
|
if (contentType.toLowerCase().includes('json')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSON.parse(response.body);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const extensions = computed(() => {
|
const extensions = computed(() => {
|
||||||
const theme = EditorView.theme({
|
const theme = EditorView.theme({
|
||||||
"&": {
|
"&": {
|
||||||
@@ -26,15 +43,20 @@ const extensions = computed(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return [json(), oneDark, theme, EditorView.editable.of(false)];
|
return [
|
||||||
|
oneDark,
|
||||||
|
theme,
|
||||||
|
EditorView.editable.of(false),
|
||||||
|
...(isJsonResponse.value ? [json()] : []),
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
const formattedBody = computed(() => {
|
const formattedBody = computed(() => {
|
||||||
if (!store.activeRequest.response) return '';
|
if (!store.activeRequest.response) return '';
|
||||||
|
if (!isJsonResponse.value) return store.activeRequest.response.body;
|
||||||
try {
|
try {
|
||||||
// Auto pretty print
|
|
||||||
return JSON.stringify(JSON.parse(store.activeRequest.response.body), null, 2);
|
return JSON.stringify(JSON.parse(store.activeRequest.response.body), null, 2);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return store.activeRequest.response.body;
|
return store.activeRequest.response.body;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ const displayFontOptions = computed(() => {
|
|||||||
placeholder="Select or enter font family"
|
placeholder="Select or enter font family"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="!fontOptions.includes(settings.editorFontFamily)"
|
|
||||||
type="text"
|
type="text"
|
||||||
v-model="settings.editorFontFamily"
|
v-model="settings.editorFontFamily"
|
||||||
placeholder="e.g. 'JetBrains Mono', monospace"
|
placeholder="e.g. 'JetBrains Mono', monospace"
|
||||||
|
|||||||
@@ -33,34 +33,110 @@ export interface RequestData {
|
|||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createEmptyKeyValue = (): KeyValue => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createDefaultAuthState = (): AuthState => ({
|
||||||
|
type: 'none',
|
||||||
|
basic: { username: '', password: '' },
|
||||||
|
bearer: { token: '' },
|
||||||
|
apiKey: { key: '', value: '', addTo: 'header' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const createDefaultRequest = (): RequestData => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
method: 'GET',
|
||||||
|
url: '',
|
||||||
|
params: [createEmptyKeyValue()],
|
||||||
|
headers: [createEmptyKeyValue()],
|
||||||
|
body: '',
|
||||||
|
auth: createDefaultAuthState(),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloneKeyValueList = (items: KeyValue[] | undefined): KeyValue[] => {
|
||||||
|
if (!Array.isArray(items) || items.length === 0) return [];
|
||||||
|
return items.map((item) => ({
|
||||||
|
id: item.id || crypto.randomUUID(),
|
||||||
|
key: item.key || '',
|
||||||
|
value: item.value || '',
|
||||||
|
enabled: item.enabled ?? true,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const cloneAuthState = (auth: Partial<AuthState> | undefined): AuthState => ({
|
||||||
|
type: auth?.type || 'none',
|
||||||
|
basic: {
|
||||||
|
username: auth?.basic?.username || '',
|
||||||
|
password: auth?.basic?.password || '',
|
||||||
|
},
|
||||||
|
bearer: {
|
||||||
|
token: auth?.bearer?.token || '',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
key: auth?.apiKey?.key || '',
|
||||||
|
value: auth?.apiKey?.value || '',
|
||||||
|
addTo: auth?.apiKey?.addTo || 'header',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloneRequest = (req: Partial<RequestData>): RequestData => ({
|
||||||
|
id: typeof req.id === 'string' && req.id ? req.id : crypto.randomUUID(),
|
||||||
|
method: req.method || 'GET',
|
||||||
|
url: req.url || '',
|
||||||
|
params: cloneKeyValueList(req.params),
|
||||||
|
headers: cloneKeyValueList(req.headers),
|
||||||
|
body: req.body || '',
|
||||||
|
auth: cloneAuthState(req.auth),
|
||||||
|
response: req.response
|
||||||
|
? {
|
||||||
|
status: req.response.status,
|
||||||
|
headers: { ...req.response.headers },
|
||||||
|
body: req.response.body,
|
||||||
|
time: req.response.time,
|
||||||
|
size: req.response.size,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
timestamp: typeof req.timestamp === 'number' ? req.timestamp : Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizeHistoryItem = (item: Partial<RequestData>): RequestData => {
|
||||||
|
const cloned = cloneRequest(item);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: cloned.id,
|
||||||
|
method: cloned.method,
|
||||||
|
url: cloned.url,
|
||||||
|
params: cloned.params.length > 0
|
||||||
|
? cloned.params
|
||||||
|
: [createEmptyKeyValue()],
|
||||||
|
headers: cloned.headers.length > 0
|
||||||
|
? cloned.headers
|
||||||
|
: [createEmptyKeyValue()],
|
||||||
|
body: cloned.body,
|
||||||
|
auth: cloned.auth,
|
||||||
|
response: cloned.response,
|
||||||
|
timestamp: cloned.timestamp,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const useRequestStore = defineStore('request', () => {
|
export const useRequestStore = defineStore('request', () => {
|
||||||
// State
|
// State
|
||||||
const history = ref<RequestData[]>([]);
|
const history = ref<RequestData[]>([]);
|
||||||
const activeRequest = ref<RequestData>({
|
const activeRequest = ref<RequestData>(createDefaultRequest());
|
||||||
id: crypto.randomUUID(),
|
|
||||||
method: 'GET',
|
|
||||||
url: '',
|
|
||||||
params: [
|
|
||||||
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
|
|
||||||
],
|
|
||||||
headers: [
|
|
||||||
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
|
|
||||||
],
|
|
||||||
body: '',
|
|
||||||
auth: {
|
|
||||||
type: 'none',
|
|
||||||
basic: { username: '', password: '' },
|
|
||||||
bearer: { token: '' },
|
|
||||||
apiKey: { key: '', value: '', addTo: 'header' }
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load history from local storage
|
// Load history from local storage
|
||||||
const savedHistory = localStorage.getItem('request_history');
|
const savedHistory = localStorage.getItem('request_history');
|
||||||
if (savedHistory) {
|
if (savedHistory) {
|
||||||
try {
|
try {
|
||||||
history.value = JSON.parse(savedHistory);
|
const parsed = JSON.parse(savedHistory);
|
||||||
|
history.value = Array.isArray(parsed)
|
||||||
|
? parsed.map((item) => normalizeHistoryItem(item))
|
||||||
|
: [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse history', e);
|
console.error('Failed to parse history', e);
|
||||||
}
|
}
|
||||||
@@ -68,13 +144,12 @@ export const useRequestStore = defineStore('request', () => {
|
|||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const addToHistory = (req: RequestData) => {
|
const addToHistory = (req: RequestData) => {
|
||||||
// Add to beginning, limit to 50
|
const newEntry = cloneRequest(req);
|
||||||
const newEntry = { ...req, timestamp: Date.now() };
|
newEntry.id = crypto.randomUUID();
|
||||||
// Don't store the empty trailing rows in history to save space?
|
newEntry.timestamp = Date.now();
|
||||||
// Actually keep them for consistency or filter them. Let's filter empty keys.
|
newEntry.params = newEntry.params.filter((p) => p.key.trim() !== '');
|
||||||
newEntry.params = newEntry.params.filter(p => p.key.trim() !== '');
|
newEntry.headers = newEntry.headers.filter((h) => h.key.trim() !== '');
|
||||||
newEntry.headers = newEntry.headers.filter(h => h.key.trim() !== '');
|
|
||||||
|
|
||||||
history.value.unshift(newEntry);
|
history.value.unshift(newEntry);
|
||||||
if (history.value.length > 50) {
|
if (history.value.length > 50) {
|
||||||
history.value.pop();
|
history.value.pop();
|
||||||
@@ -93,46 +168,14 @@ export const useRequestStore = defineStore('request', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetActiveRequest = () => {
|
const resetActiveRequest = () => {
|
||||||
activeRequest.value = {
|
activeRequest.value = createDefaultRequest();
|
||||||
id: crypto.randomUUID(),
|
|
||||||
method: 'GET',
|
|
||||||
url: '',
|
|
||||||
params: [
|
|
||||||
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
|
|
||||||
],
|
|
||||||
headers: [
|
|
||||||
{ id: crypto.randomUUID(), key: '', value: '', enabled: true }
|
|
||||||
],
|
|
||||||
body: '',
|
|
||||||
auth: {
|
|
||||||
type: 'none',
|
|
||||||
basic: { username: '', password: '' },
|
|
||||||
bearer: { token: '' },
|
|
||||||
apiKey: { key: '', value: '', addTo: 'header' }
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadRequest = (req: RequestData) => {
|
const loadRequest = (req: RequestData) => {
|
||||||
// Deep copy
|
const loaded = normalizeHistoryItem(cloneRequest(req));
|
||||||
const loaded = JSON.parse(JSON.stringify(req));
|
|
||||||
loaded.id = crypto.randomUUID();
|
loaded.id = crypto.randomUUID();
|
||||||
// loaded.response = undefined; // Keep response for history viewing
|
if (loaded.params.length === 0) loaded.params.push(createEmptyKeyValue());
|
||||||
|
if (loaded.headers.length === 0) loaded.headers.push(createEmptyKeyValue());
|
||||||
// Ensure at least one empty row for editing
|
|
||||||
if (loaded.params.length === 0) loaded.params.push({ id: crypto.randomUUID(), key: '', value: '', enabled: true });
|
|
||||||
if (loaded.headers.length === 0) loaded.headers.push({ id: crypto.randomUUID(), key: '', value: '', enabled: true });
|
|
||||||
|
|
||||||
// Ensure auth object exists (migration for old history)
|
|
||||||
if (!loaded.auth) {
|
|
||||||
loaded.auth = {
|
|
||||||
type: 'none',
|
|
||||||
basic: { username: '', password: '' },
|
|
||||||
bearer: { token: '' },
|
|
||||||
apiKey: { key: '', value: '', addTo: 'header' }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
activeRequest.value = loaded;
|
activeRequest.value = loaded;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user