Files
website/templates/itemeditor_command_storage.html
2026-01-07 04:01:26 +01:00

573 lines
15 KiB
HTML

{% extends "base.html" %}
{% block title %}Item Editor - Command Storage - Devanturas{% endblock %}
{% block description %}Store commands longer than 256 characters temporarily for Minecraft Item Editor plugin{% endblock %}
{% block content %}
<section class="page-header">
<div class="container">
<div class="project-breadcrumb">
<a href="{{ url_for('projects') }}">Projects</a> / Item Editor / Command Storage
</div>
<h1>Item Editor Command Storage</h1>
<p>Store long commands (>256 chars) temporarily and generate a retrievable link</p>
</div>
</section>
<section class="command-storage-section">
<div class="container">
<div class="storage-grid">
<!-- Left: Command Input -->
<div class="storage-card">
<div class="card-header">
<i class="fas fa-terminal"></i>
<h2>Store Command</h2>
</div>
<p class="card-description">
Paste your Minecraft command below. Commands longer than 256 characters will be stored
for 30 minutes and a unique retrieval link will be generated.
</p>
<form id="commandForm">
<div class="form-group">
<label for="command">Minecraft Command</label>
<textarea
id="command"
name="command"
rows="6"
placeholder="Paste your command here..."
required
></textarea>
<div class="char-counter">
<span id="charCount">0</span> characters
<span id="charStatus"></span>
</div>
</div>
<button type="submit" class="btn btn-primary" id="submitBtn">
<i class="fas fa-save"></i> Store Command
</button>
</form>
<!-- Success Message -->
<div id="successMessage" style="display: none;" class="success-box">
<div class="success-header">
<i class="fas fa-check-circle"></i>
<h3>Command Stored Successfully!</h3>
</div>
<p class="success-description">Your command has been stored for 30 minutes.</p>
<div class="link-display">
<label>Retrieval Link (JSON):</label>
<div class="link-box">
<input type="text" id="generatedLink" readonly>
<button class="btn btn-secondary btn-copy" onclick="copyLink()">
<i class="fas fa-copy"></i> Copy
</button>
</div>
<small>Use this link in your Item Editor plugin to load the command.</small>
</div>
<div class="expiry-info">
<i class="fas fa-clock"></i>
<span>Expires in: <strong id="expiryTime">30 minutes</strong></span>
</div>
<button class="btn btn-outline" onclick="resetForm()">
<i class="fas fa-plus"></i> Store Another Command
</button>
</div>
</div>
<!-- Right: Info & Stats -->
<div class="info-sidebar">
<div class="info-card">
<div class="info-icon">
<i class="fas fa-info-circle"></i>
</div>
<h3>How It Works</h3>
<ol class="info-steps">
<li>Paste your long Minecraft command</li>
<li>Click "Store Command" to generate a link</li>
<li>Copy the generated JSON link</li>
<li>Use the link in your Item Editor plugin</li>
</ol>
</div>
<div class="info-card">
<div class="info-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h3>Storage Limits</h3>
<ul class="info-list">
<li><i class="fas fa-check"></i> <strong>Duration:</strong> 30 minutes</li>
<li><i class="fas fa-check"></i> <strong>Max Length:</strong> 10,000 characters</li>
<li><i class="fas fa-check"></i> <strong>Format:</strong> JSON response</li>
<li><i class="fas fa-check"></i> <strong>Access:</strong> Anyone with link</li>
</ul>
</div>
<div class="info-card">
<div class="info-icon">
<i class="fas fa-server"></i>
</div>
<h3>Self-Hosting</h3>
<p class="code-description">Host this system on your own webserver with this simple PHP script:</p>
<pre class="code-block" style="font-size: 0.75rem; line-height: 1.4;">&lt;?php
header('Content-Type: application/json');
$storage_file = 'commands.json';
// Store command (POST)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$command = $data['command'] ?? '';
if (strlen($command) > 10000) {
http_response_code(400);
echo json_encode(['error' => 'Too long']);
exit;
}
$uuid = bin2hex(random_bytes(16));
$expires = time() + 1800; // 30 min
$storage = file_exists($storage_file)
? json_decode(file_get_contents($storage_file), true)
: [];
$storage[$uuid] = [
'command' => $command,
'expires_at' => $expires
];
file_put_contents($storage_file, json_encode($storage));
echo json_encode([
'success' => true,
'url' => "https://yourserver.com/storage.php?id=$uuid"
]);
exit;
}
// Retrieve command (GET)
if (isset($_GET['id'])) {
$storage = file_exists($storage_file)
? json_decode(file_get_contents($storage_file), true)
: [];
$uuid = $_GET['id'];
if (!isset($storage[$uuid])) {
http_response_code(404);
echo json_encode(['error' => 'Not found']);
exit;
}
if ($storage[$uuid]['expires_at'] < time()) {
unset($storage[$uuid]);
file_put_contents($storage_file, json_encode($storage));
http_response_code(410);
echo json_encode(['error' => 'Expired']);
exit;
}
echo json_encode($storage[$uuid]);
exit;
}
?&gt;</pre>
<p style="color: #cfcfcf; font-size: 0.9rem; margin-top: 1rem;">
<strong>Setup:</strong> Save as <code>storage.php</code> on your webserver.
Make sure the directory is writable for <code>commands.json</code>.
</p>
</div>
</div>
</div>
</div>
</section>
<style>
.page-header {
padding: 110px 0 40px;
background: #0f0f0f;
border-bottom: 1px solid #1f1f1f;
}
.command-storage-section {
padding: 60px 0;
background: #0a0a0a;
}
.storage-grid {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 2rem;
align-items: start;
}
.storage-card, .info-card {
background: linear-gradient(145deg, #161616, #1e1e1e);
border: 1px solid #2a2a2a;
border-radius: 16px;
padding: 2rem;
}
.card-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.card-header i {
color: #00d4ff;
font-size: 2rem;
}
.card-header h2 {
color: #fff;
font-size: 1.8rem;
margin: 0;
}
.card-description {
color: #cfcfcf;
margin-bottom: 2rem;
line-height: 1.6;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
color: #00d4ff;
font-weight: 600;
margin-bottom: 0.5rem;
}
.form-group textarea {
width: 100%;
background: #121212;
border: 1px solid #2a2a2a;
border-radius: 10px;
padding: 1rem;
color: #fff;
font-family: 'Courier New', monospace;
font-size: 0.95rem;
resize: vertical;
transition: border-color 0.3s;
}
.form-group textarea:focus {
outline: none;
border-color: #00d4ff;
}
.char-counter {
display: flex;
justify-content: space-between;
margin-top: 0.5rem;
font-size: 0.9rem;
}
#charCount {
color: #00d4ff;
font-weight: 700;
}
#charStatus {
color: #999;
}
#charStatus.warning {
color: #ffaa00;
}
#charStatus.error {
color: #ff5555;
}
.success-box {
margin-top: 2rem;
padding: 2rem;
background: rgba(0, 255, 166, 0.05);
border: 1px solid rgba(0, 255, 166, 0.2);
border-radius: 12px;
}
.success-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.success-header i {
color: #00ffa6;
font-size: 2rem;
}
.success-header h3 {
color: #00ffa6;
margin: 0;
}
.success-description {
color: #cfcfcf;
margin-bottom: 1.5rem;
}
.link-display {
margin-bottom: 1.5rem;
}
.link-display label {
display: block;
color: #00d4ff;
font-weight: 600;
margin-bottom: 0.5rem;
}
.link-box {
display: flex;
gap: 0.5rem;
}
.link-box input {
flex: 1;
background: #121212;
border: 1px solid #2a2a2a;
border-radius: 8px;
padding: 0.75rem;
color: #00d4ff;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.btn-copy {
padding: 0.75rem 1rem;
white-space: nowrap;
}
.link-display small {
display: block;
color: #999;
margin-top: 0.5rem;
}
.expiry-info {
display: flex;
align-items: center;
gap: 0.5rem;
color: #cfcfcf;
margin-bottom: 1.5rem;
}
.expiry-info i {
color: #ffaa00;
}
.info-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.info-icon {
width: 50px;
height: 50px;
background: rgba(0, 212, 255, 0.1);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
}
.info-icon i {
color: #00d4ff;
font-size: 1.5rem;
}
.info-card h3 {
color: #fff;
margin-bottom: 1rem;
}
.info-steps {
padding-left: 1.5rem;
color: #cfcfcf;
line-height: 1.8;
}
.info-list {
list-style: none;
padding: 0;
}
.info-list li {
color: #cfcfcf;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.info-list i {
color: #00ffa6;
font-size: 0.9rem;
}
.code-description {
color: #cfcfcf;
margin-bottom: 1rem;
}
.code-block {
background: #0a0a0a;
border: 1px solid #1f1f1f;
border-radius: 8px;
padding: 1rem;
color: #00d4ff;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
overflow-x: auto;
line-height: 1.6;
}
@media (max-width: 992px) {
.storage-grid {
grid-template-columns: 1fr;
}
}
</style>
<script>
const textarea = document.getElementById('command');
const charCount = document.getElementById('charCount');
const charStatus = document.getElementById('charStatus');
const commandForm = document.getElementById('commandForm');
const submitBtn = document.getElementById('submitBtn');
const successMessage = document.getElementById('successMessage');
// Character counter
textarea.addEventListener('input', function() {
const length = this.value.length;
charCount.textContent = length;
if (length === 0) {
charStatus.textContent = '';
charStatus.className = '';
} else if (length <= 256) {
charStatus.textContent = '(within chat limit)';
charStatus.className = '';
} else if (length <= 10000) {
charStatus.textContent = '(exceeds chat limit - storage required)';
charStatus.className = 'warning';
} else {
charStatus.textContent = '(exceeds maximum length)';
charStatus.className = 'error';
}
});
// Form submission
commandForm.addEventListener('submit', async function(e) {
e.preventDefault();
const command = textarea.value.trim();
if (!command) return;
if (command.length > 10000) {
alert('Command exceeds maximum length of 10,000 characters');
return;
}
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Storing...';
try {
const response = await fetch('/projects/itemeditor/storage', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ command: command })
});
const data = await response.json();
if (data.success) {
// Hide form, show success
commandForm.style.display = 'none';
successMessage.style.display = 'block';
// Set generated link
document.getElementById('generatedLink').value = data.url;
// Start countdown
startCountdown(data.expires_at);
} else {
alert('Error: ' + (data.error || 'Failed to store command'));
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Store Command';
}
} catch (error) {
console.error('Error:', error);
alert('Failed to store command. Please try again.');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Store Command';
}
});
function copyLink() {
const linkInput = document.getElementById('generatedLink');
linkInput.select();
document.execCommand('copy');
const copyBtn = event.target.closest('.btn-copy');
const originalHTML = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i> Copied!';
setTimeout(() => {
copyBtn.innerHTML = originalHTML;
}, 2000);
}
function resetForm() {
commandForm.style.display = 'block';
successMessage.style.display = 'none';
textarea.value = '';
charCount.textContent = '0';
charStatus.textContent = '';
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Store Command';
}
function startCountdown(expiresAt) {
const expiryTime = document.getElementById('expiryTime');
const endTime = new Date(expiresAt).getTime();
// If expiry is in the past, show expired immediately
if (endTime < new Date().getTime()) {
expiryTime.innerHTML = '<span style="color: #ff5555;">Expired</span>';
return;
}
const interval = setInterval(() => {
const now = new Date().getTime();
const distance = endTime - now;
if (distance < 0) {
clearInterval(interval);
expiryTime.innerHTML = '<span style="color: #ff5555;">Expired</span>';
return;
}
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
expiryTime.textContent = `${minutes}m ${seconds}s`;
}, 1000);
}
</script>
{% endblock %}