UX Block 3: Upload-Fortschritt, Toggle-Switch, vars.yml-Download
- Upload-Fortschrittsbalken per XHR mit Progress-Event - Checkbox-Toggle statt Ja/Nein-Select für Enabled-Feld - vars.yml Download-Button im Provisioning-Workflow - Alle UX-Aufgaben in TODO.md abgehakt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
62c1b8cd5c
commit
fa74ceb5d8
3 changed files with 87 additions and 15 deletions
6
TODO.md
6
TODO.md
|
|
@ -155,9 +155,9 @@
|
|||
|
||||
### Niedrige Prioritaet
|
||||
|
||||
- [ ] Upload-Fortschrittsbalken in Manage-UI
|
||||
- [ ] vars.yml Download-Button in Provision-UI statt Copy-Paste
|
||||
- [ ] Toggle-Switch statt Ja/Nein-Select fuer Enabled-Feld
|
||||
- [x] Upload-Fortschrittsbalken in Manage-UI
|
||||
- [x] vars.yml Download-Button in Provision-UI statt Copy-Paste
|
||||
- [x] Toggle-Switch statt Ja/Nein-Select fuer Enabled-Feld
|
||||
|
||||
## Querschnittsthemen
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,10 @@ ansible_user: {{.SSHUser}}
|
|||
screen_id: {{.Screen.Slug}}
|
||||
screen_name: "{{.Screen.Name}}"
|
||||
screen_orientation: {{.Orientation}}</pre>
|
||||
<button class="button is-small is-light copy-btn mt-2" onclick="copy('hostvars')">📋 Kopieren</button>
|
||||
<div class="buttons mt-2">
|
||||
<button id="copy-btn-hostvars" class="button is-small is-light copy-btn" onclick="copy('hostvars', 'copy-btn-hostvars')">📋 Kopieren</button>
|
||||
<button class="button is-small is-light" onclick="downloadFile(document.getElementById('hostvars').innerText, 'vars.yml')">⬇ Als Datei herunterladen</button>
|
||||
</div>
|
||||
<p class="help mt-2">Tipp: <code>mkdir -p ansible/host_vars/{{.Screen.Slug}}</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -115,17 +118,26 @@ ansible-playbook -i ansible/inventory.yml ansible/site.yml --limit {{.Screen.Slu
|
|||
</section>
|
||||
|
||||
<script>
|
||||
function copy(id) {
|
||||
function copy(id, btnId) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
navigator.clipboard.writeText(el.innerText).then(function() {
|
||||
var btn = el.nextElementSibling;
|
||||
var btn = btnId
|
||||
? document.getElementById(btnId)
|
||||
: el.nextElementSibling;
|
||||
if (!btn) return;
|
||||
var orig = btn.textContent;
|
||||
btn.textContent = '✓ Kopiert!';
|
||||
setTimeout(function() { btn.textContent = orig; }, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
function downloadFile(content, filename) {
|
||||
var a = document.createElement('a');
|
||||
a.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(content);
|
||||
a.download = filename;
|
||||
a.click();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
|
@ -581,11 +593,11 @@ document.addEventListener('keydown', function(e) {
|
|||
</div>
|
||||
<div class="column is-narrow">
|
||||
<label class="label is-small">Aktiv</label>
|
||||
<div class="select is-small">
|
||||
<select name="enabled">
|
||||
<option value="true"{{if .Enabled}} selected{{end}}>Ja</option>
|
||||
<option value="false"{{if not .Enabled}} selected{{end}}>Nein</option>
|
||||
</select>
|
||||
<div class="control" style="padding-top:0.4rem">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="enabled" value="true" {{if .Enabled}}checked{{end}}>
|
||||
Aktiv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
|
|
@ -673,7 +685,7 @@ document.addEventListener('keydown', function(e) {
|
|||
</div>
|
||||
|
||||
<div id="panel-file" class="tab-panel is-active">
|
||||
<form method="POST" action="/manage/{{.Screen.Slug}}/upload" enctype="multipart/form-data">
|
||||
<form id="upload-form" method="POST" action="/manage/{{.Screen.Slug}}/upload" enctype="multipart/form-data">
|
||||
<div class="columns is-vcentered">
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
|
|
@ -698,7 +710,7 @@ document.addEventListener('keydown', function(e) {
|
|||
<div class="field">
|
||||
<label class="label">Datei</label>
|
||||
<div class="control">
|
||||
<input class="input" type="file" name="file" required
|
||||
<input class="input" type="file" name="file" id="upload-file-input" required
|
||||
accept="image/*,video/*,application/pdf">
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -706,10 +718,14 @@ document.addEventListener('keydown', function(e) {
|
|||
<div class="column is-narrow">
|
||||
<div class="field">
|
||||
<label class="label"> </label>
|
||||
<button class="button is-primary" type="submit">Hochladen</button>
|
||||
<button class="button is-primary" type="button" id="upload-btn" onclick="startUpload()">Hochladen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="upload-progress-wrap" style="display:none" class="mt-2">
|
||||
<progress id="upload-progress" class="progress is-primary" value="0" max="100">0%</progress>
|
||||
</div>
|
||||
<div id="upload-error" class="notification is-danger is-light mt-2" style="display:none"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
@ -800,6 +816,62 @@ if (sortableEl) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// XHR-Upload mit Fortschrittsbalken
|
||||
function startUpload() {
|
||||
var form = document.getElementById('upload-form');
|
||||
var fileInput = document.getElementById('upload-file-input');
|
||||
var btn = document.getElementById('upload-btn');
|
||||
var progressWrap = document.getElementById('upload-progress-wrap');
|
||||
var progress = document.getElementById('upload-progress');
|
||||
var errorBox = document.getElementById('upload-error');
|
||||
|
||||
errorBox.style.display = 'none';
|
||||
|
||||
if (!fileInput.files || fileInput.files.length === 0) {
|
||||
errorBox.textContent = 'Bitte zuerst eine Datei auswählen.';
|
||||
errorBox.style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData(form);
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var pct = Math.round((e.loaded / e.total) * 100);
|
||||
progress.value = pct;
|
||||
progress.textContent = pct + '%';
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onloadstart = function() {
|
||||
btn.style.display = 'none';
|
||||
progressWrap.style.display = '';
|
||||
progress.value = 0;
|
||||
};
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status >= 200 && xhr.status < 400) {
|
||||
window.location.href = '/manage/{{.Screen.Slug}}?msg=uploaded';
|
||||
} else {
|
||||
progressWrap.style.display = 'none';
|
||||
btn.style.display = '';
|
||||
errorBox.textContent = 'Upload fehlgeschlagen (HTTP ' + xhr.status + '): ' + xhr.responseText;
|
||||
errorBox.style.display = '';
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
progressWrap.style.display = 'none';
|
||||
btn.style.display = '';
|
||||
errorBox.textContent = 'Netzwerkfehler beim Upload. Bitte erneut versuchen.';
|
||||
errorBox.style.display = '';
|
||||
};
|
||||
|
||||
xhr.open('POST', form.action);
|
||||
xhr.send(formData);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ func HandleUpdateItemUI(playlists *store.PlaylistStore) http.HandlerFunc {
|
|||
if d, err := strconv.Atoi(strings.TrimSpace(r.FormValue("duration_seconds"))); err == nil && d > 0 {
|
||||
durationSeconds = d
|
||||
}
|
||||
enabled := r.FormValue("enabled") != "false"
|
||||
enabled := r.FormValue("enabled") == "true"
|
||||
validFrom, _ := parseOptionalTime(r.FormValue("valid_from"))
|
||||
validUntil, _ := parseOptionalTime(r.FormValue("valid_until"))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue