next step - added control.html and functions
This commit is contained in:
parent
8955cff47b
commit
127754ba15
62
app.py
62
app.py
@ -47,35 +47,69 @@ def index():
|
|||||||
return render_template('index.html', configs=configs)
|
return render_template('index.html', configs=configs)
|
||||||
|
|
||||||
|
|
||||||
|
current_config = None
|
||||||
|
current_filename = None
|
||||||
|
|
||||||
@app.route('/load_config/<filename>')
|
@app.route('/load_config/<filename>')
|
||||||
def load_config(filename):
|
def load_config(filename):
|
||||||
# Später: Config in Session speichern oder global (für Entwicklung erstmal redirect)
|
global current_config, current_filename
|
||||||
# Hier nur redirect zur Steuerseite – echte Logik kommt später
|
|
||||||
path = os.path.join(app.config['CONFIG_DIR'], filename)
|
path = os.path.join(app.config['CONFIG_DIR'], filename)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return "Konfigurationsdatei nicht gefunden", 404
|
return "Konfigurationsdatei nicht gefunden", 404
|
||||||
|
|
||||||
# Für den Moment nur redirect – später speichern wir current_config
|
try:
|
||||||
return redirect(url_for('control_page', filename=filename))
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
current_config = json.load(f)
|
||||||
|
current_config['filename'] = filename
|
||||||
|
current_filename = filename
|
||||||
|
logger.info(f"Konfiguration geladen: {filename}")
|
||||||
|
return redirect(url_for('control_page'))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Laden der Config {filename}: {e}")
|
||||||
|
return "Fehler beim Laden der Konfiguration", 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/control/<filename>')
|
@app.route('/control')
|
||||||
def control_page(filename):
|
def control_page():
|
||||||
# Später: Config laden und übergeben
|
if current_config is None:
|
||||||
# Aktuell nur Dummy-Template
|
return redirect(url_for('index'))
|
||||||
return render_template('control.html', filename=filename)
|
|
||||||
|
return render_template('control.html', config=current_config)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/configs/<path:filename>')
|
# Neue API-Route (Platzhalter – später echte Bluetooth-Steuerung)
|
||||||
def serve_config_file(filename):
|
@app.route('/api/connect', methods=['POST'])
|
||||||
"""Bilder und andere Dateien aus configs/ ausliefern"""
|
def api_connect():
|
||||||
return send_from_directory(app.config['CONFIG_DIR'], filename)
|
# Hier kommt später der echte Code mit MouldKing
|
||||||
|
# Aktuell nur simuliert
|
||||||
|
if current_config is None:
|
||||||
|
return jsonify({"success": False, "message": "Keine Konfiguration geladen"}), 400
|
||||||
|
|
||||||
|
logger.info(f"Simulierter Connect zu Hub {current_config.get('hub_id')}")
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"message": "Verbunden (Simulation)",
|
||||||
|
"hub_id": current_config.get('hub_id')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/control', methods=['POST'])
|
||||||
|
def api_control():
|
||||||
|
# Platzhalter für spätere echte Steuerung
|
||||||
|
data = request.get_json()
|
||||||
|
logger.info(f"Steuerbefehl empfangen: {data}")
|
||||||
|
return jsonify({"success": True, "message": "Befehl gesendet (Simulation)"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/stop_all', methods=['POST'])
|
||||||
|
def api_stop_all():
|
||||||
|
logger.info("Alle stoppen (Simulation)")
|
||||||
|
return jsonify({"success": True, "message": "Alle Kanäle gestoppt"})
|
||||||
|
|
||||||
@app.route('/admin')
|
@app.route('/admin')
|
||||||
def admin():
|
def admin():
|
||||||
return render_template('admin.html')
|
return render_template('admin.html')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5050, debug=True)
|
app.run(host='0.0.0.0', port=5055, debug=True)
|
||||||
|
|||||||
136
static/js/app.js
136
static/js/app.js
@ -3,5 +3,139 @@ console.log("MK Control JS geladen");
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('MK Control Frontend geladen');
|
console.log('MK Control Frontend geladen');
|
||||||
// Hier kommen später alle Fetch-Logiken, Slider-Handler etc. rein
|
|
||||||
|
const connectBtn = document.getElementById('connect-btn');
|
||||||
|
const connectSection = document.getElementById('connect-section');
|
||||||
|
const controlSection = document.getElementById('control-section');
|
||||||
|
const channelsContainer = document.getElementById('channels-container');
|
||||||
|
const stopAllBtn = document.getElementById('stop-all-btn');
|
||||||
|
|
||||||
|
if (!connectBtn) return; // Sicherstellen, dass wir auf der richtigen Seite sind
|
||||||
|
|
||||||
|
connectBtn.addEventListener('click', async () => {
|
||||||
|
connectBtn.disabled = true;
|
||||||
|
connectBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Verbinde...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/connect', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
connectSection.style.display = 'none';
|
||||||
|
controlSection.style.display = 'block';
|
||||||
|
renderChannels();
|
||||||
|
alert('Verbunden! (Simulation)');
|
||||||
|
} else {
|
||||||
|
alert('Verbindung fehlgeschlagen: ' + (result.message || 'Unbekannter Fehler'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert('Fehler bei der Verbindung: ' + err.message);
|
||||||
|
} finally {
|
||||||
|
connectBtn.disabled = false;
|
||||||
|
connectBtn.innerHTML = '<i class="bi bi-bluetooth me-2"></i> Mit Hub verbinden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stopAllBtn.addEventListener('click', async () => {
|
||||||
|
if (!confirm('Wirklich alle Kanäle stoppen?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/stop_all', { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
alert('Alle Kanäle gestoppt');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Fehler beim Stoppen');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderChannels() {
|
||||||
|
channelsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (!config.channels || config.channels.length === 0) {
|
||||||
|
channelsContainer.innerHTML = '<p class="text-center text-muted py-4">Keine Kanäle in der Konfiguration definiert.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.channels.forEach(channel => {
|
||||||
|
const col = document.createElement('div');
|
||||||
|
col.className = 'col-md-6 mb-4';
|
||||||
|
|
||||||
|
let controlHTML = '';
|
||||||
|
|
||||||
|
if (channel.type === 'motor') {
|
||||||
|
controlHTML = `
|
||||||
|
<label class="form-label">${channel.name} (${channel.port})</label>
|
||||||
|
<input type="range" class="form-range motor-slider"
|
||||||
|
min="-100" max="100" value="0" step="5"
|
||||||
|
data-port="${channel.port}">
|
||||||
|
<div class="text-center mt-1">
|
||||||
|
<span class="value-display">0 %</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// Licht, Sound, Fogger → Toggle-Button
|
||||||
|
controlHTML = `
|
||||||
|
<label class="form-label">${channel.name} (${channel.port})</label>
|
||||||
|
<button class="btn btn-outline-secondary w-100 toggle-btn"
|
||||||
|
data-port="${channel.port}" data-state="off">
|
||||||
|
AUS
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
col.innerHTML = `
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
${controlHTML}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
channelsContainer.appendChild(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event-Listener für Slider
|
||||||
|
document.querySelectorAll('.motor-slider').forEach(slider => {
|
||||||
|
const display = slider.parentElement.querySelector('.value-display');
|
||||||
|
slider.addEventListener('input', async () => {
|
||||||
|
const value = parseInt(slider.value) / 100;
|
||||||
|
display.textContent = `${slider.value} %`;
|
||||||
|
await sendControl(slider.dataset.port, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event-Listener für Toggle-Buttons
|
||||||
|
document.querySelectorAll('.toggle-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const current = btn.dataset.state;
|
||||||
|
const newState = current === 'off' ? 'on' : 'off';
|
||||||
|
btn.dataset.state = newState;
|
||||||
|
btn.textContent = newState === 'on' ? 'EIN' : 'AUS';
|
||||||
|
btn.classList.toggle('btn-success', newState === 'on');
|
||||||
|
btn.classList.toggle('btn-outline-secondary', newState === 'off');
|
||||||
|
|
||||||
|
await sendControl(btn.dataset.port, newState === 'on' ? 1 : 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendControl(port, value) {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/control', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ port, value })
|
||||||
|
});
|
||||||
|
// Kann später Feedback geben
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Steuerbefehl fehlgeschlagen:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ config.name }} – Steuerung{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container my-4">
|
||||||
|
|
||||||
|
<div class="row mb-4 align-items-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h1 class="mb-1">{{ config.name }}</h1>
|
||||||
|
<p class="text-muted">
|
||||||
|
Hub-ID: {{ config.hub_id | default('unbekannt') }} •
|
||||||
|
Typ: {{ config.hub_type | default('unbekannt') }} •
|
||||||
|
Datei: {{ config.filename }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-md-end">
|
||||||
|
{% if config.image %}
|
||||||
|
<img src="{{ url_for('serve_config_file', filename=config.image) }}"
|
||||||
|
alt="{{ config.name }}"
|
||||||
|
class="img-fluid rounded shadow"
|
||||||
|
style="max-height: 180px; object-fit: cover;">
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-light rounded d-flex align-items-center justify-content-center shadow"
|
||||||
|
style="height: 180px; width: 100%; max-width: 300px; margin-left: auto;">
|
||||||
|
<i class="bi bi-train-freight-front display-1 text-muted"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Connect-Button -->
|
||||||
|
<div class="text-center mb-5" id="connect-section">
|
||||||
|
<button id="connect-btn" class="btn btn-lg btn-success px-5 py-3">
|
||||||
|
<i class="bi bi-bluetooth me-2"></i> Mit Hub verbinden
|
||||||
|
</button>
|
||||||
|
<p class="mt-3 text-muted small">Stelle sicher, dass der Hub eingeschaltet ist und im Bluetooth-Modus ausgewählt wurde.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Steuerbereich – anfangs versteckt -->
|
||||||
|
<div id="control-section" style="display: none;">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0">Steuerung</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="channels-container">
|
||||||
|
<!-- Dynamische Kanäle werden hier per JavaScript eingefügt -->
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<button id="stop-all-btn" class="btn btn-danger btn-lg px-5">
|
||||||
|
<i class="bi bi-stop-fill me-2"></i> Alle stoppen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
// Config direkt ins JS übergeben
|
||||||
|
const config = {{ config | tojson | safe }};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user