Skip to content

Commit 9e0efe3

Browse files
committed
feat: interactive creator and crd explorer.
Signed-off-by: will <[email protected]>
1 parent 49c04f9 commit 9e0efe3

File tree

14 files changed

+1723
-0
lines changed

14 files changed

+1723
-0
lines changed

docs/docs/reference/interactive.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
sidebar_position: 2
3+
---
4+
5+
import SchemaExplorer from '@site/src/components/SchemaExplorer';
6+
import ImageSwapSimulator from '@site/src/components/ImageSwapSimulator';
7+
8+
# Interactive Tools
9+
10+
Explore the ImageShift CRD and test image transformations interactively.
11+
12+
## Schema Explorer
13+
14+
Explore the Imageshift CRD structure interactively. Click on fields to expand or collapse them.
15+
16+
<SchemaExplorer />
17+
18+
## Image Swap Simulator
19+
20+
Test how your image references will be transformed with different mapping configurations.
21+
22+
<ImageSwapSimulator />

docs/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const sidebars = {
1717
label: 'Reference',
1818
items: [
1919
'reference/crd',
20+
'reference/interactive',
2021
'reference/configuration',
2122
],
2223
},
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import React, { useState, useEffect } from 'react';
2+
import MappingEditor from './MappingEditor';
3+
import SwapResult from './SwapResult';
4+
import { swapImage, presetExamples } from './swapLogic';
5+
import styles from './ImageSwapSimulator.module.css';
6+
7+
const defaultConfig = {
8+
defaultRegistry: 'docker.io',
9+
mappings: {
10+
swap: [{ registry: 'docker.io', target: 'internal.example.com' }],
11+
exactSwap: [],
12+
regexSwap: [],
13+
},
14+
};
15+
16+
function generateYaml(config) {
17+
const { defaultRegistry, mappings } = config;
18+
const { swap = [], exactSwap = [], regexSwap = [] } = mappings;
19+
20+
let yaml = `apiVersion: imageshift.dev/v1
21+
kind: Imageshift
22+
metadata:
23+
name: imageshift
24+
spec:
25+
default: ${defaultRegistry}
26+
namespaceSelector: imageshift.dev`;
27+
28+
const hasAnyMappings = swap.length > 0 || exactSwap.length > 0 || regexSwap.length > 0;
29+
30+
if (hasAnyMappings) {
31+
yaml += `\n mappings:`;
32+
33+
if (swap.length > 0) {
34+
yaml += `\n swap:`;
35+
for (const rule of swap) {
36+
if (rule.registry && rule.target) {
37+
yaml += `\n - registry: ${rule.registry}`;
38+
yaml += `\n target: ${rule.target}`;
39+
}
40+
}
41+
}
42+
43+
if (exactSwap.length > 0) {
44+
yaml += `\n exactSwap:`;
45+
for (const rule of exactSwap) {
46+
if (rule.reference && rule.target) {
47+
yaml += `\n - reference: ${rule.reference}`;
48+
yaml += `\n target: ${rule.target}`;
49+
}
50+
}
51+
}
52+
53+
if (regexSwap.length > 0) {
54+
yaml += `\n regexSwap:`;
55+
for (const rule of regexSwap) {
56+
if (rule.expression && rule.target) {
57+
yaml += `\n - expression: "${rule.expression.replace(/\\/g, '\\\\')}"`;
58+
yaml += `\n target: "${rule.target}"`;
59+
}
60+
}
61+
}
62+
}
63+
64+
return yaml;
65+
}
66+
67+
export default function ImageSwapSimulator() {
68+
const [image, setImage] = useState('nginx:latest');
69+
const [config, setConfig] = useState(defaultConfig);
70+
const [result, setResult] = useState(null);
71+
const [copied, setCopied] = useState(false);
72+
const [showYaml, setShowYaml] = useState(false);
73+
74+
useEffect(() => {
75+
if (image.trim()) {
76+
const swapResult = swapImage(image, config);
77+
setResult(swapResult);
78+
} else {
79+
setResult(null);
80+
}
81+
}, [image, config]);
82+
83+
const handleMappingsChange = (mappings) => {
84+
setConfig({ ...config, mappings });
85+
};
86+
87+
const handlePresetChange = (e) => {
88+
const presetIndex = e.target.value;
89+
if (presetIndex === '') return;
90+
91+
const preset = presetExamples[parseInt(presetIndex, 10)];
92+
setImage(preset.image);
93+
setConfig(preset.config);
94+
};
95+
96+
const handleReset = () => {
97+
setImage('nginx:latest');
98+
setConfig(defaultConfig);
99+
};
100+
101+
const yaml = generateYaml(config);
102+
103+
const handleCopyYaml = async () => {
104+
try {
105+
await navigator.clipboard.writeText(yaml);
106+
setCopied(true);
107+
setTimeout(() => setCopied(false), 2000);
108+
} catch (err) {
109+
console.error('Failed to copy:', err);
110+
}
111+
};
112+
113+
return (
114+
<div className={styles.container}>
115+
<div className={styles.inputSection}>
116+
<div className={styles.inputRow}>
117+
<div className={styles.inputGroup}>
118+
<label className={styles.label}>Source Image</label>
119+
<input
120+
type="text"
121+
value={image}
122+
onChange={(e) => setImage(e.target.value)}
123+
placeholder="Enter image reference (e.g., nginx:latest)"
124+
className={styles.imageInput}
125+
/>
126+
</div>
127+
128+
<div className={styles.inputGroup}>
129+
<label className={styles.label}>Default Registry</label>
130+
<input
131+
type="text"
132+
value={config.defaultRegistry}
133+
onChange={(e) => setConfig({ ...config, defaultRegistry: e.target.value })}
134+
placeholder="Default registry"
135+
className={styles.registryInput}
136+
/>
137+
</div>
138+
</div>
139+
140+
<div className={styles.presetRow}>
141+
<select onChange={handlePresetChange} className={styles.presetSelect} defaultValue="">
142+
<option value="">Load preset example...</option>
143+
{presetExamples.map((preset, index) => (
144+
<option key={index} value={index}>
145+
{preset.name}
146+
</option>
147+
))}
148+
</select>
149+
<button onClick={handleReset} className={styles.resetButton}>
150+
Reset
151+
</button>
152+
</div>
153+
</div>
154+
155+
<div className={styles.editorSection}>
156+
<h4 className={styles.sectionTitle}>Mapping Rules</h4>
157+
<MappingEditor
158+
mappings={config.mappings}
159+
onChange={handleMappingsChange}
160+
/>
161+
</div>
162+
163+
<SwapResult result={result} />
164+
165+
<div className={styles.yamlSection}>
166+
<div className={styles.yamlHeader}>
167+
<button
168+
className={styles.yamlToggle}
169+
onClick={() => setShowYaml(!showYaml)}
170+
>
171+
{showYaml ? '▼' : '▶'} Generated YAML
172+
</button>
173+
{showYaml && (
174+
<button className={styles.copyButton} onClick={handleCopyYaml}>
175+
{copied ? 'Copied!' : 'Copy'}
176+
</button>
177+
)}
178+
</div>
179+
{showYaml && (
180+
<pre className={styles.yamlCode}><code>{yaml}</code></pre>
181+
)}
182+
</div>
183+
</div>
184+
);
185+
}

0 commit comments

Comments
 (0)