Add YAML editor with real-time linting

- Added editable YAML textarea
- Real-time YAML validation with js-yaml library
- Visual feedback for valid/invalid YAML
- Ability to create server from edited YAML
- Download edited YAML functionality
This commit is contained in:
robojerk 2025-10-31 11:55:45 -07:00
parent c31e48e2b1
commit 1406f9f120

View file

@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stronghold - Minecraft Server Generator</title>
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
<style>
* {
margin: 0;
@ -526,8 +527,20 @@
<div id="preview" class="preview" style="display: none;">
<h3>Generated docker-compose.yml</h3>
<pre id="yamlPreview"></pre>
<button class="download-btn" id="downloadBtn">Download docker-compose.yml</button>
<div class="yaml-tab">
<div class="yaml-tab-header">
<span class="yaml-status" id="yamlStatus">Valid YAML</span>
</div>
<textarea id="yamlEditor" class="yaml-editor" placeholder="YAML will appear here..."></textarea>
<div id="yamlError" class="yaml-error"></div>
<div id="yamlSuccess" class="yaml-success">✓ Valid YAML</div>
<div class="yaml-actions">
<button class="btn btn-primary btn-sm" id="validateYamlBtn">Validate YAML</button>
<button class="btn btn-success btn-sm" id="downloadYamlBtn">Download YAML</button>
<button class="btn btn-primary btn-sm" id="createFromYamlBtn">Create Server from YAML</button>
</div>
</div>
<pre id="yamlPreview" style="display: none;"></pre>
</div>
</div>
@ -583,26 +596,162 @@
const API_BASE = window.location.origin;
const yamlEditor = document.getElementById('yamlEditor');
const yamlError = document.getElementById('yamlError');
const yamlSuccess = document.getElementById('yamlSuccess');
const yamlStatus = document.getElementById('yamlStatus');
let currentYaml = '';
// YAML validation function
function validateYAML(yamlText) {
yamlError.classList.remove('show');
yamlSuccess.classList.remove('show');
yamlEditor.classList.remove('error', 'valid');
yamlStatus.textContent = '';
yamlStatus.classList.remove('valid', 'invalid');
if (!yamlText || yamlText.trim() === '') {
return { valid: false, error: 'YAML is empty' };
}
try {
jsyaml.load(yamlText);
yamlEditor.classList.add('valid');
yamlError.classList.remove('show');
yamlSuccess.classList.add('show');
yamlStatus.textContent = '✓ Valid YAML';
yamlStatus.classList.add('valid');
return { valid: true };
} catch (error) {
yamlEditor.classList.add('error');
yamlEditor.classList.remove('valid');
yamlError.textContent = `YAML Error: ${error.message}`;
yamlError.classList.add('show');
yamlSuccess.classList.remove('show');
yamlStatus.textContent = '✗ Invalid YAML';
yamlStatus.classList.add('invalid');
return { valid: false, error: error.message };
}
}
// Real-time YAML linting with debounce
let lintTimeout;
yamlEditor.addEventListener('input', () => {
clearTimeout(lintTimeout);
lintTimeout = setTimeout(() => {
validateYAML(yamlEditor.value);
}, 500); // Wait 500ms after user stops typing
});
// Manual validate button
document.getElementById('validateYamlBtn').addEventListener('click', () => {
validateYAML(yamlEditor.value);
});
// Download YAML button
document.getElementById('downloadYamlBtn').addEventListener('click', () => {
const yaml = yamlEditor.value;
const blob = new Blob([yaml], { type: 'text/yaml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'docker-compose.yml';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// Create server from YAML button
document.getElementById('createFromYamlBtn').addEventListener('click', async () => {
const yaml = yamlEditor.value;
const validation = validateYAML(yaml);
if (!validation.valid) {
alert('Please fix YAML errors before creating server.');
return;
}
// Parse YAML to extract server name and config
try {
const config = jsyaml.load(yaml);
const serviceName = Object.keys(config.services || {})[0];
const service = config.services[serviceName];
if (!serviceName || !service) {
throw new Error('No service found in YAML');
}
// Convert docker-compose YAML to our config format
const serverConfig = {
serverName: serviceName,
serverType: service.environment?.TYPE || 'PAPER',
version: service.environment?.VERSION || 'LATEST',
memory: service.environment?.MEMORY || '2G',
port: Object.keys(service.ports || {})[0]?.split(':')[0] || '25565',
difficulty: service.environment?.DIFFICULTY || '',
gamemode: service.environment?.MODE || '',
levelType: service.environment?.LEVEL_TYPE || '',
motd: service.environment?.MOTD || '',
maxPlayers: parseInt(service.environment?.MAX_PLAYERS) || 20,
viewDistance: parseInt(service.environment?.VIEW_DISTANCE) || 10,
acceptEULA: service.environment?.EULA === 'TRUE',
enableRCON: service.environment?.ENABLE_RCON === 'true',
rconPort: service.environment?.RCON_PORT || '25575',
rconPassword: service.environment?.RCON_PASSWORD || '',
pvpEnabled: service.environment?.PVP !== 'false',
allowFlight: service.environment?.ALLOW_FLIGHT === 'true',
restartPolicy: service.restart || 'unless-stopped'
};
// Submit to API
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = true;
submitBtn.textContent = 'Creating Server...';
const response = await fetch(`${API_BASE}/api/containers`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(serverConfig),
});
const result = await response.json();
if (response.ok) {
preview.innerHTML = `
<h3 style="color: #28a745;">✓ Server Created Successfully!</h3>
<p><strong>Name:</strong> ${result.name}</p>
<p><strong>Status:</strong> ${result.status}</p>
<br>
<a href="manage.html" class="btn btn-primary" style="text-decoration: none; display: inline-block; padding: 10px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 6px;">Go to Manage Page</a>
`;
} else {
throw new Error(result.detail || 'Failed to create server');
}
submitBtn.disabled = false;
submitBtn.textContent = 'Create Server';
} catch (error) {
alert('Error creating server: ' + error.message);
}
});
// Generate YAML only (existing functionality)
document.getElementById('generateYamlBtn').addEventListener('click', () => {
const config = gatherFormData();
const yaml = generateDockerCompose(config);
yamlPreview.textContent = yaml;
currentYaml = yaml;
yamlEditor.value = yaml;
preview.style.display = 'block';
downloadBtn.disabled = false;
downloadBtn.onclick = () => {
const blob = new Blob([yaml], { type: 'text/yaml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'docker-compose.yml';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
// Hide old preview, show editor
yamlPreview.style.display = 'none';
// Validate the generated YAML
validateYAML(yaml);
});
// Create server (new functionality)