Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57371feeb0 |
@@ -1,15 +1,15 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Position=0, HelpMessage='API base URL')]
|
[Parameter(HelpMessage='Path to JSON file; default: read from stdin')]
|
||||||
|
[string]$JsonFile = '-',
|
||||||
|
|
||||||
|
[Parameter(HelpMessage='API base URL')]
|
||||||
[string]$ApiUrl,
|
[string]$ApiUrl,
|
||||||
|
|
||||||
[Parameter(HelpMessage='API key for authentication')]
|
[Parameter(HelpMessage='API key for authentication')]
|
||||||
[string]$ApiKey,
|
[string]$ApiKey,
|
||||||
|
|
||||||
[Parameter(HelpMessage='Path to JSON file; default: read from stdin')]
|
|
||||||
[string]$JsonFile = '-',
|
|
||||||
|
|
||||||
[Parameter(HelpMessage='Show help')]
|
[Parameter(HelpMessage='Show help')]
|
||||||
[switch]$Help
|
[switch]$Help
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jmespath-playground",
|
"name": "jmespath-playground",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"description": "A React-based web application for testing JMESPath expressions against JSON data",
|
"description": "A React-based web application for testing JMESPath expressions against JSON data",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"server": "node server.js",
|
"server": "node server.js --dev",
|
||||||
"dev": "concurrently \"npm start\" \"npm run server\"",
|
"dev": "concurrently \"npm start\" \"npm run server\"",
|
||||||
"build-image": "node scripts/build-image.js"
|
"build-image": "node scripts/build-image.js"
|
||||||
},
|
},
|
||||||
|
|||||||
70
src/App.css
70
src/App.css
@@ -5,6 +5,22 @@
|
|||||||
--font-mono: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
--font-mono: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||||
--accent-color: #007bff;
|
--accent-color: #007bff;
|
||||||
|
|
||||||
|
/* Brand colors */
|
||||||
|
--brand-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
--brand-white: #ffffff;
|
||||||
|
--brand-dark: #212529;
|
||||||
|
--brand-warning: #ffc107;
|
||||||
|
|
||||||
|
/* Brand opacity levels */
|
||||||
|
--brand-white-60: rgba(255, 255, 255, 0.6);
|
||||||
|
--brand-white-10: rgba(255, 255, 255, 0.1);
|
||||||
|
--brand-warning-50: rgba(255, 193, 7, 0.5);
|
||||||
|
--brand-warning-10: rgba(255, 193, 7, 0.1);
|
||||||
|
|
||||||
|
/* Elevation and overlays */
|
||||||
|
--shadow-light: rgba(0, 0, 0, 0.1);
|
||||||
|
--focus-ring: rgba(0, 123, 255, 0.25);
|
||||||
|
|
||||||
/* Button variants */
|
/* Button variants */
|
||||||
--btn-success: #28a745;
|
--btn-success: #28a745;
|
||||||
--btn-info: #17a2b8;
|
--btn-info: #17a2b8;
|
||||||
@@ -36,23 +52,57 @@ body {
|
|||||||
|
|
||||||
/* Header section styling - more compact */
|
/* Header section styling - more compact */
|
||||||
.header-section {
|
.header-section {
|
||||||
/* Removed gradient background to fix text visibility */
|
background: var(--brand-gradient);
|
||||||
|
color: var(--brand-white);
|
||||||
|
padding: 1.2rem 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-section h2 {
|
||||||
|
color: var(--brand-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure buttons in header are clearly visible against gradient */
|
||||||
|
.header-section .btn-light.active {
|
||||||
|
background-color: var(--brand-white);
|
||||||
|
color: var(--brand-dark) !important; /* Deep dark text for selected states */
|
||||||
|
border-color: var(--brand-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section .btn-outline-light {
|
||||||
|
color: var(--brand-white);
|
||||||
|
border-color: var(--brand-white-60);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section .btn-outline-light:hover {
|
||||||
|
background-color: var(--brand-white-10);
|
||||||
|
color: var(--brand-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section .btn-outline-warning {
|
||||||
|
color: var(--brand-warning);
|
||||||
|
border-color: var(--brand-warning-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section .btn-outline-warning:hover {
|
||||||
|
background-color: var(--brand-warning-10);
|
||||||
|
color: var(--brand-warning);
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom card styling */
|
/* Custom card styling */
|
||||||
.card {
|
.card {
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px var(--shadow-light);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
background-color: #f8f9fa;
|
background-color: var(--bg-secondary);
|
||||||
border-bottom: 2px solid #dee2e6;
|
border-bottom: 2px solid var(--border);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #212529;
|
color: var(--text-primary);
|
||||||
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
|
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +274,7 @@ footer a:hover {
|
|||||||
/* Focus states */
|
/* Focus states */
|
||||||
.jmespath-input:focus {
|
.jmespath-input:focus {
|
||||||
border-color: var(--accent-color, #007bff);
|
border-color: var(--accent-color, #007bff);
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
box-shadow: 0 0 0 0.2rem var(--focus-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
.json-input:focus,
|
.json-input:focus,
|
||||||
@@ -232,7 +282,7 @@ footer a:hover {
|
|||||||
background-color: var(--bg-primary);
|
background-color: var(--bg-primary);
|
||||||
border-color: var(--accent-color, #007bff);
|
border-color: var(--accent-color, #007bff);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
box-shadow: 0 0 0 0.2rem var(--focus-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Placeholder colors */
|
/* Placeholder colors */
|
||||||
@@ -249,6 +299,12 @@ footer a:hover {
|
|||||||
color: var(--error-text);
|
color: var(--error-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background-color: var(--success-bg);
|
||||||
|
border-color: var(--success-border);
|
||||||
|
color: var(--success-text);
|
||||||
|
}
|
||||||
|
|
||||||
/* Code block styles */
|
/* Code block styles */
|
||||||
pre.bg-light {
|
pre.bg-light {
|
||||||
background-color: var(--bg-secondary) !important;
|
background-color: var(--bg-secondary) !important;
|
||||||
|
|||||||
18
src/App.jsx
18
src/App.jsx
@@ -31,6 +31,10 @@ function App() {
|
|||||||
// Load theme from localStorage or default to 'auto'
|
// Load theme from localStorage or default to 'auto'
|
||||||
return localStorage.getItem("theme") || "auto";
|
return localStorage.getItem("theme") || "auto";
|
||||||
});
|
});
|
||||||
|
const [shellType, setShellType] = useState(() => {
|
||||||
|
// Load shell type from localStorage or default to 'bash'
|
||||||
|
return localStorage.getItem("jmespath-shell-type") || "bash";
|
||||||
|
});
|
||||||
const [showReloadButton, setShowReloadButton] = useState(false);
|
const [showReloadButton, setShowReloadButton] = useState(false);
|
||||||
const [currentStateGuid, setCurrentStateGuid] = useState(null);
|
const [currentStateGuid, setCurrentStateGuid] = useState(null);
|
||||||
const [jmespathExpression, setJmespathExpression] =
|
const [jmespathExpression, setJmespathExpression] =
|
||||||
@@ -81,6 +85,11 @@ function App() {
|
|||||||
localStorage.setItem("theme", theme);
|
localStorage.setItem("theme", theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
// Shell type management
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("jmespath-shell-type", shellType);
|
||||||
|
}, [shellType]);
|
||||||
|
|
||||||
// Get headers for API requests
|
// Get headers for API requests
|
||||||
const getApiHeaders = () => {
|
const getApiHeaders = () => {
|
||||||
return {
|
return {
|
||||||
@@ -163,7 +172,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid vh-100 d-flex flex-column">
|
<div className="vh-100 d-flex flex-column">
|
||||||
<Header
|
<Header
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onThemeChange={handleThemeChange}
|
onThemeChange={handleThemeChange}
|
||||||
@@ -187,7 +196,12 @@ function App() {
|
|||||||
setJsonData={setJsonData}
|
setJsonData={setJsonData}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ApiKeyPage apiKey={apiKey} onRegenerateApiKey={regenerateApiKey} />
|
<ApiKeyPage
|
||||||
|
apiKey={apiKey}
|
||||||
|
onRegenerateApiKey={regenerateApiKey}
|
||||||
|
shellType={shellType}
|
||||||
|
onShellTypeChange={setShellType}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,36 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
function ApiKeyPage({ apiKey, onRegenerateApiKey }) {
|
function CodeBlock({ code }) {
|
||||||
|
const [copySuccess, setCopySuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
setCopySuccess(true);
|
||||||
|
setTimeout(() => setCopySuccess(false), 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy to clipboard:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="position-relative">
|
||||||
|
<pre className="bg-light p-3 pe-5 rounded border shadow-sm">
|
||||||
|
<code className="d-block" style={{ whiteSpace: 'pre-wrap' }}>{code}</code>
|
||||||
|
</pre>
|
||||||
|
<button
|
||||||
|
className={`btn btn-sm ${copySuccess ? 'btn-success' : 'btn-outline-secondary'} position-absolute top-0 end-0 m-2`}
|
||||||
|
onClick={handleCopy}
|
||||||
|
title="Copy code to clipboard"
|
||||||
|
style={{ opacity: 0.8 }}
|
||||||
|
>
|
||||||
|
{copySuccess ? '✓' : '📋'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApiKeyPage({ apiKey, onRegenerateApiKey, shellType, onShellTypeChange }) {
|
||||||
const [copySuccess, setCopySuccess] = useState(false);
|
const [copySuccess, setCopySuccess] = useState(false);
|
||||||
|
|
||||||
const handleCopyToClipboard = async () => {
|
const handleCopyToClipboard = async () => {
|
||||||
@@ -60,28 +90,50 @@ function ApiKeyPage({ apiKey, onRegenerateApiKey }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h6>📡 Remote Data Upload API</h6>
|
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 className="mb-0">📡 Remote Data Upload API</h6>
|
||||||
|
<div className="btn-group btn-group-sm" role="group" aria-label="Shell type selector">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="btn-check"
|
||||||
|
name="shellType"
|
||||||
|
id="shellBash"
|
||||||
|
autoComplete="off"
|
||||||
|
checked={shellType === 'bash'}
|
||||||
|
onChange={() => onShellTypeChange('bash')}
|
||||||
|
/>
|
||||||
|
<label className="btn btn-outline-secondary" htmlFor="shellBash">UNIX (Bash)</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="btn-check"
|
||||||
|
name="shellType"
|
||||||
|
id="shellPowerShell"
|
||||||
|
autoComplete="off"
|
||||||
|
checked={shellType === 'powershell'}
|
||||||
|
onChange={() => onShellTypeChange('powershell')}
|
||||||
|
/>
|
||||||
|
<label className="btn btn-outline-secondary" htmlFor="shellPowerShell">Windows (PowerShell)</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p className="text-muted">
|
<p className="text-muted">
|
||||||
External tools can upload sample data remotely using the REST API.
|
External tools can upload sample data remotely using the REST API.
|
||||||
The API key is required for authentication. Define two
|
The API key is required for authentication. Define two
|
||||||
environment variables in your <code>.bashrc</code>.
|
environment variables in your {shellType === 'bash' ? <code>.bashrc</code> : <code>PowerShell profile</code>}.
|
||||||
</p>
|
</p>
|
||||||
<pre className="bg-light p-3 rounded border">
|
<CodeBlock
|
||||||
<code>export JMESPATH_PLAYGROUND_API_URL={window.location.origin}<br/>export JMESPATH_PLAYGROUND_API_KEY={apiKey}</code>
|
code={shellType === 'bash'
|
||||||
</pre>
|
? `export JMESPATH_PLAYGROUND_API_URL=${window.location.origin}\nexport JMESPATH_PLAYGROUND_API_KEY=${apiKey}`
|
||||||
<p className="text-muted">Then, use the following <code>curl</code> command to upload your data:</p>
|
: `$env:JMESPATH_PLAYGROUND_API_URL = "${window.location.origin}"\n$env:JMESPATH_PLAYGROUND_API_KEY = "${apiKey}"`}
|
||||||
<pre className="bg-light p-3 rounded border">
|
/>
|
||||||
<code>{`curl -s -X POST \\
|
<p className="text-muted">Then, use the following {shellType === 'bash' ? <code>curl</code> : <code>PowerShell</code>} command to upload your data:</p>
|
||||||
-H "Content-Type: application/json" \\
|
<CodeBlock
|
||||||
-H "Accept: application/json" \\
|
code={shellType === 'bash'
|
||||||
-H "X-API-Key: $JMESPATH_PLAYGROUND_API_KEY" \\
|
? `curl -s -X POST \\\n -H "Content-Type: application/json" \\\n -H "Accept: application/json" \\\n -H "X-API-Key: $JMESPATH_PLAYGROUND_API_KEY" \\\n --data @__JSON_FILE_NAME__ \\\n "$JMESPATH_PLAYGROUND_API_URL/api/v1/upload"`
|
||||||
--data @__JSON_FILE_NAME__ \\
|
: `Invoke-RestMethod -Uri "$env:JMESPATH_PLAYGROUND_API_URL/api/v1/upload" \\\n -Method Post \\\n -ContentType "application/json" \\\n -Headers @{\n "X-API-Key" = $env:JMESPATH_PLAYGROUND_API_KEY\n "Accept" = "application/json"\n } \\\n -InFile __JSON_FILE_NAME__`}
|
||||||
"$\{JMESPATH_PLAYGROUND_API_URL}/api/v1/upload"`}</code>
|
/>
|
||||||
</pre>
|
|
||||||
<div className="form-text">
|
<div className="form-text">
|
||||||
Replace <code>{"__JSON_FILE_NAME__"}</code> with the path to your
|
Replace <code>{"__JSON_FILE_NAME__"}</code> with the path to your
|
||||||
JSON file containing the sample data. or use <code>-</code> to
|
JSON file containing the sample data. {shellType === 'bash' && <span>or use <code>-</code> to read from standard input.</span>}
|
||||||
read from standard input.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import React from 'react';
|
|||||||
|
|
||||||
function Header({ theme, onThemeChange, currentPage, onPageChange }) {
|
function Header({ theme, onThemeChange, currentPage, onPageChange }) {
|
||||||
return (
|
return (
|
||||||
<div className="header-section py-2">
|
<div className="header-section">
|
||||||
<div className="container">
|
<div className="container-fluid px-4">
|
||||||
<div className="row">
|
<div className="row align-items-center">
|
||||||
<div className="col-12 text-center position-relative">
|
<div className="col-12 text-center position-relative">
|
||||||
<h2 className="mb-1">JMESPath Testing Tool</h2>
|
<h2 className="mb-1">JMESPath Testing Tool</h2>
|
||||||
{/* Right side controls - better positioning */}
|
{/* Right side controls - better positioning */}
|
||||||
<div className="position-absolute top-0 end-0 d-flex align-items-center gap-2">
|
<div className="position-absolute top-50 end-0 translate-middle-y d-flex align-items-center gap-2 me-4">
|
||||||
{/* API Key Management Button - more prominent */}
|
{/* API Key Management Button - more prominent */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm ${
|
className={`btn btn-sm ${
|
||||||
currentPage === 'apikey'
|
currentPage === 'apikey'
|
||||||
? 'btn-warning fw-bold'
|
? 'btn-warning fw-bold text-dark'
|
||||||
: 'btn-outline-warning'
|
: 'btn-outline-warning'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onPageChange(currentPage === 'main' ? 'apikey' : 'main')}
|
onClick={() => onPageChange(currentPage === 'main' ? 'apikey' : 'main')}
|
||||||
@@ -28,8 +28,8 @@ function Header({ theme, onThemeChange, currentPage, onPageChange }) {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`btn ${
|
className={`btn ${
|
||||||
theme === 'auto'
|
theme === 'auto'
|
||||||
? 'btn-primary'
|
? 'btn-light active'
|
||||||
: 'btn-outline-secondary'
|
: 'btn-outline-light'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onThemeChange('auto')}
|
onClick={() => onThemeChange('auto')}
|
||||||
title="Auto (follow system)"
|
title="Auto (follow system)"
|
||||||
@@ -40,8 +40,8 @@ function Header({ theme, onThemeChange, currentPage, onPageChange }) {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`btn ${
|
className={`btn ${
|
||||||
theme === 'light'
|
theme === 'light'
|
||||||
? 'btn-primary'
|
? 'btn-light active'
|
||||||
: 'btn-outline-secondary'
|
: 'btn-outline-light'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onThemeChange('light')}
|
onClick={() => onThemeChange('light')}
|
||||||
title="Light theme"
|
title="Light theme"
|
||||||
@@ -52,8 +52,8 @@ function Header({ theme, onThemeChange, currentPage, onPageChange }) {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`btn ${
|
className={`btn ${
|
||||||
theme === 'dark'
|
theme === 'dark'
|
||||||
? 'btn-primary'
|
? 'btn-light active'
|
||||||
: 'btn-outline-secondary'
|
: 'btn-outline-light'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onThemeChange('dark')}
|
onClick={() => onThemeChange('dark')}
|
||||||
title="Dark theme"
|
title="Dark theme"
|
||||||
|
|||||||
@@ -8,12 +8,6 @@ code {
|
|||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-fluid {
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-section {
|
.content-section {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -52,13 +46,6 @@ code {
|
|||||||
color: var(--success-text-light);
|
color: var(--success-text-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-section {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 2rem 0;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode support for error states */
|
/* Dark mode support for error states */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.error {
|
.error {
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import react from '@vitejs/plugin-react';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3000',
|
target: 'http://127.0.0.1:3000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user