Fix: the expression input box was getting reset while switching pages. Formatted the code text.

This commit is contained in:
2026-01-31 09:05:07 +01:00
parent 72d1be0bdc
commit b7df3e731f
2 changed files with 140 additions and 120 deletions

View File

@@ -1,16 +1,16 @@
import React, { useState, useEffect } from 'react';
import Header from './components/Header';
import Footer from './components/Footer';
import MainPage from './components/MainPage';
import ApiKeyPage from './components/ApiKeyPage';
import './App.css';
import React, { useState, useEffect } from "react";
import Header from "./components/Header";
import Footer from "./components/Footer";
import MainPage from "./components/MainPage";
import ApiKeyPage from "./components/ApiKeyPage";
import "./App.css";
// Utility function to generate a cryptographically secure API key
function generateApiKey() {
const array = new Uint8Array(16);
// Use crypto.getRandomValues if available (browser), fallback for tests
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
crypto.getRandomValues(array);
} else {
// Fallback for test environments - not cryptographically secure
@@ -19,59 +19,83 @@ function generateApiKey() {
}
}
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
"",
);
}
// JMESPath Testing Tool - Main Application Component
function App() {
const [currentPage, setCurrentPage] = useState('main'); // 'main' or 'apikey'
const [currentPage, setCurrentPage] = useState("main"); // 'main' or 'apikey'
const [theme, setTheme] = useState(() => {
// Load theme from localStorage or default to 'auto'
return localStorage.getItem('theme') || 'auto';
return localStorage.getItem("theme") || "auto";
});
const [showReloadButton, setShowReloadButton] = useState(false);
const [currentStateGuid, setCurrentStateGuid] = useState(null);
const [sampleData, setSampleData] = useState(null);
const [jmespathExpression, setJmespathExpression] =
useState("people[0].name");
const [jsonData, setJsonData] = useState(`{
"people": [
{
"name": "John Doe",
"age": 30,
"city": "New York"
},
{
"name": "Jane Smith",
"age": 25,
"city": "Los Angeles"
}
],
"total": 2
}`);
const [apiKey, setApiKey] = useState(() => {
// Load API key from localStorage or generate new one
const stored = localStorage.getItem('jmespath-api-key');
const stored = localStorage.getItem("jmespath-api-key");
if (stored && /^[0-9a-f]{32}$/i.test(stored)) {
return stored;
}
const newKey = generateApiKey();
localStorage.setItem('jmespath-api-key', newKey);
localStorage.setItem("jmespath-api-key", newKey);
return newKey;
});
// Theme management
useEffect(() => {
const applyTheme = (selectedTheme) => {
const effectiveTheme = selectedTheme === 'auto'
? (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
const effectiveTheme =
selectedTheme === "auto"
? window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
: selectedTheme;
document.documentElement.setAttribute('data-bs-theme', effectiveTheme);
document.documentElement.setAttribute("data-bs-theme", effectiveTheme);
};
applyTheme(theme);
// Save theme preference
localStorage.setItem('theme', theme);
localStorage.setItem("theme", theme);
}, [theme]);
// Get headers for API requests
const getApiHeaders = () => {
const headers = {
'Accept': 'application/json'
Accept: "application/json",
};
// Only send API key for non-localhost requests
// For localhost, let server use its default LOCALHOST_API_KEY
if (window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1' &&
!window.location.hostname.startsWith('127.') &&
window.location.hostname !== '::1') {
headers['X-API-Key'] = apiKey;
if (
window.location.hostname !== "localhost" &&
window.location.hostname !== "127.0.0.1" &&
!window.location.hostname.startsWith("127.") &&
window.location.hostname !== "::1"
) {
headers["X-API-Key"] = apiKey;
}
return headers;
@@ -90,8 +114,8 @@ function App() {
// Check if state has changed (new data uploaded)
const checkStateChange = async () => {
try {
const response = await fetch('/api/v1/state', {
headers: getApiHeaders()
const response = await fetch("/api/v1/state", {
headers: getApiHeaders(),
});
if (response.ok) {
@@ -109,19 +133,19 @@ function App() {
const loadSampleData = async () => {
try {
setShowReloadButton(false);
const response = await fetch('/api/v1/sample', {
headers: getApiHeaders()
const response = await fetch("/api/v1/sample", {
headers: getApiHeaders(),
});
if (response.ok) {
const data = await response.json();
if (data) {
setSampleData(data);
setJsonData(JSON.stringify(data, null, 2));
}
// Update current state GUID
const stateResponse = await fetch('/api/v1/state', {
headers: getApiHeaders()
const stateResponse = await fetch("/api/v1/state", {
headers: getApiHeaders(),
});
if (stateResponse.ok) {
const stateData = await stateResponse.json();
@@ -129,7 +153,7 @@ function App() {
}
}
} catch (error) {
console.error('Failed to load sample data:', error);
console.error("Failed to load sample data:", error);
}
};
@@ -137,7 +161,7 @@ function App() {
const regenerateApiKey = () => {
const newKey = generateApiKey();
setApiKey(newKey);
localStorage.setItem('jmespath-api-key', newKey);
localStorage.setItem("jmespath-api-key", newKey);
setShowReloadButton(false);
setCurrentStateGuid(null);
};
@@ -160,19 +184,22 @@ function App() {
/>
{/* Main Content Section - flex-grow to fill space */}
<div className="container-fluid flex-grow-1 d-flex flex-column" style={{ minHeight: 0 }}>
{currentPage === 'main' ? (
<div
className="container-fluid flex-grow-1 d-flex flex-column"
style={{ minHeight: 0 }}
>
{currentPage === "main" ? (
<MainPage
apiKey={apiKey}
showReloadButton={showReloadButton}
onReloadSampleData={loadSampleData}
initialSampleData={sampleData}
jmespathExpression={jmespathExpression}
setJmespathExpression={setJmespathExpression}
jsonData={jsonData}
setJsonData={setJsonData}
/>
) : (
<ApiKeyPage
apiKey={apiKey}
onRegenerateApiKey={regenerateApiKey}
/>
<ApiKeyPage apiKey={apiKey} onRegenerateApiKey={regenerateApiKey} />
)}
</div>

View File

@@ -1,41 +1,23 @@
import React, { useState, useEffect } from 'react';
import jmespath from 'jmespath';
function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleData }) {
const [jmespathExpression, setJmespathExpression] = useState('people[0].name');
const [jsonData, setJsonData] = useState(`{
"people": [
{
"name": "John Doe",
"age": 30,
"city": "New York"
},
{
"name": "Jane Smith",
"age": 25,
"city": "Los Angeles"
}
],
"total": 2
}`);
const [result, setResult] = useState('');
const [error, setError] = useState('');
const [jsonError, setJsonError] = useState('');
// Use initial sample data when provided
useEffect(() => {
if (initialSampleData) {
setJsonData(JSON.stringify(initialSampleData, null, 2));
}
}, [initialSampleData]);
import React, { useState, useEffect } from "react";
import jmespath from "jmespath";
function MainPage({
showReloadButton,
onReloadSampleData,
jmespathExpression,
setJmespathExpression,
jsonData,
setJsonData,
}) {
const [result, setResult] = useState("");
const [error, setError] = useState("");
const [jsonError, setJsonError] = useState("");
const evaluateExpression = () => {
try {
// Clear previous errors
setError('');
setJsonError('');
setError("");
setJsonError("");
// Validate and parse JSON
let parsedData;
@@ -43,7 +25,7 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
parsedData = JSON.parse(jsonData);
} catch (jsonErr) {
setJsonError(`Invalid JSON: ${jsonErr.message}`);
setResult('');
setResult("");
return;
}
@@ -52,13 +34,13 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
// Format the result
if (queryResult === null || queryResult === undefined) {
setResult('null');
setResult("null");
} else {
setResult(JSON.stringify(queryResult, null, 2));
}
} catch (jmesErr) {
setError(`JMESPath Error: ${jmesErr.message}`);
setResult('');
setResult("");
}
};
@@ -88,30 +70,30 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
};
const clearAll = () => {
setJmespathExpression('');
setJsonData('');
setResult('');
setError('');
setJsonError('');
setJmespathExpression("");
setJsonData("");
setResult("");
setError("");
setJsonError("");
};
const loadSample = () => {
const sampleData = {
"users": [
{"name": "Alice", "age": 30, "city": "New York"},
{"name": "Bob", "age": 25, "city": "San Francisco"},
{"name": "Charlie", "age": 35, "city": "Chicago"}
users: [
{ name: "Alice", age: 30, city: "New York" },
{ name: "Bob", age: 25, city: "San Francisco" },
{ name: "Charlie", age: 35, city: "Chicago" },
],
"total": 3
total: 3,
};
setJsonData(JSON.stringify(sampleData, null, 2));
setJmespathExpression('users[?age > `30`].name');
setJmespathExpression("users[?age > `30`].name");
};
const loadFromDisk = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e) => {
const file = e.target.files[0];
if (file) {
@@ -122,7 +104,7 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
const parsed = JSON.parse(content);
setJsonData(JSON.stringify(parsed, null, 2));
} catch (error) {
alert('Invalid JSON file');
alert("Invalid JSON file");
}
};
reader.readAsText(file);
@@ -132,9 +114,9 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
};
const loadLogFile = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.log,.jsonl,.ndjson';
const input = document.createElement("input");
input.type = "file";
input.accept = ".log,.jsonl,.ndjson";
input.onchange = (e) => {
const file = e.target.files[0];
if (file) {
@@ -142,12 +124,12 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
reader.onload = (e) => {
try {
const content = e.target.result;
const lines = content.trim().split('\n');
const logs = lines.map(line => JSON.parse(line));
const lines = content.trim().split("\n");
const logs = lines.map((line) => JSON.parse(line));
setJsonData(JSON.stringify(logs, null, 2));
setJmespathExpression('[*].message');
setJmespathExpression("[*].message");
} catch (error) {
alert('Invalid JSON Lines file');
alert("Invalid JSON Lines file");
}
};
reader.readAsText(file);
@@ -162,8 +144,9 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
<div className="row mb-2">
<div className="col-12">
<p className="text-muted text-center mb-2 small">
Validate and test JMESPath expressions against JSON data in real-time.
Enter your JMESPath query and JSON data below to see the results instantly.
Validate and test JMESPath expressions against JSON data in
real-time. Enter your JMESPath query and JSON data below to see the
results instantly.
</p>
</div>
</div>
@@ -218,13 +201,17 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
<div className="card-body">
<input
type="text"
className={`form-control jmespath-input ${error ? 'error' : 'success'}`}
className={`form-control jmespath-input ${error ? "error" : "success"}`}
value={jmespathExpression}
onChange={handleJmespathChange}
placeholder="Enter JMESPath expression (e.g., people[*].name)"
/>
<div className={`alert mt-2 mb-0 d-flex justify-content-between align-items-center ${error ? 'alert-danger' : 'alert-success'}`}>
<small className="mb-0">{error || 'Expression is correct'}</small>
<div
className={`alert mt-2 mb-0 d-flex justify-content-between align-items-center ${error ? "alert-danger" : "alert-success"}`}
>
<small className="mb-0">
{error || "Expression is correct"}
</small>
{showReloadButton && (
<button
className="btn btn-light btn-sm ms-2 border"
@@ -254,13 +241,16 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
JSON Data
</h6>
</div>
<div className="card-body flex-grow-1 d-flex flex-column" style={{ minHeight: 0 }}>
<div
className="card-body flex-grow-1 d-flex flex-column"
style={{ minHeight: 0 }}
>
<textarea
className={`form-control json-input flex-grow-1 ${jsonError ? 'error' : 'success'}`}
className={`form-control json-input flex-grow-1 ${jsonError ? "error" : "success"}`}
value={jsonData}
onChange={handleJsonChange}
placeholder="Enter JSON data here..."
style={{ minHeight: 0, resize: 'none' }}
style={{ minHeight: 0, resize: "none" }}
/>
{jsonError && (
<div className="alert alert-danger mt-2 mb-0">
@@ -280,13 +270,16 @@ function MainPage({ apiKey, showReloadButton, onReloadSampleData, initialSampleD
Results
</h6>
</div>
<div className="card-body flex-grow-1 d-flex flex-column" style={{ minHeight: 0 }}>
<div
className="card-body flex-grow-1 d-flex flex-column"
style={{ minHeight: 0 }}
>
<textarea
className="form-control result-output flex-grow-1"
value={result}
readOnly
placeholder="Results will appear here..."
style={{ minHeight: 0, resize: 'none' }}
style={{ minHeight: 0, resize: "none" }}
/>
</div>
</div>