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
|
### Niedrige Prioritaet
|
||||||
|
|
||||||
- [ ] Upload-Fortschrittsbalken in Manage-UI
|
- [x] Upload-Fortschrittsbalken in Manage-UI
|
||||||
- [ ] vars.yml Download-Button in Provision-UI statt Copy-Paste
|
- [x] vars.yml Download-Button in Provision-UI statt Copy-Paste
|
||||||
- [ ] Toggle-Switch statt Ja/Nein-Select fuer Enabled-Feld
|
- [x] Toggle-Switch statt Ja/Nein-Select fuer Enabled-Feld
|
||||||
|
|
||||||
## Querschnittsthemen
|
## Querschnittsthemen
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,10 @@ ansible_user: {{.SSHUser}}
|
||||||
screen_id: {{.Screen.Slug}}
|
screen_id: {{.Screen.Slug}}
|
||||||
screen_name: "{{.Screen.Name}}"
|
screen_name: "{{.Screen.Name}}"
|
||||||
screen_orientation: {{.Orientation}}</pre>
|
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>
|
<p class="help mt-2">Tipp: <code>mkdir -p ansible/host_vars/{{.Screen.Slug}}</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -115,17 +118,26 @@ ansible-playbook -i ansible/inventory.yml ansible/site.yml --limit {{.Screen.Slu
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function copy(id) {
|
function copy(id, btnId) {
|
||||||
var el = document.getElementById(id);
|
var el = document.getElementById(id);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
navigator.clipboard.writeText(el.innerText).then(function() {
|
navigator.clipboard.writeText(el.innerText).then(function() {
|
||||||
var btn = el.nextElementSibling;
|
var btn = btnId
|
||||||
|
? document.getElementById(btnId)
|
||||||
|
: el.nextElementSibling;
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
var orig = btn.textContent;
|
var orig = btn.textContent;
|
||||||
btn.textContent = '✓ Kopiert!';
|
btn.textContent = '✓ Kopiert!';
|
||||||
setTimeout(function() { btn.textContent = orig; }, 1500);
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
|
|
@ -581,11 +593,11 @@ document.addEventListener('keydown', function(e) {
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<label class="label is-small">Aktiv</label>
|
<label class="label is-small">Aktiv</label>
|
||||||
<div class="select is-small">
|
<div class="control" style="padding-top:0.4rem">
|
||||||
<select name="enabled">
|
<label class="checkbox">
|
||||||
<option value="true"{{if .Enabled}} selected{{end}}>Ja</option>
|
<input type="checkbox" name="enabled" value="true" {{if .Enabled}}checked{{end}}>
|
||||||
<option value="false"{{if not .Enabled}} selected{{end}}>Nein</option>
|
Aktiv
|
||||||
</select>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
|
|
@ -673,7 +685,7 @@ document.addEventListener('keydown', function(e) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="panel-file" class="tab-panel is-active">
|
<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="columns is-vcentered">
|
||||||
<div class="column is-2">
|
<div class="column is-2">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
@ -698,7 +710,7 @@ document.addEventListener('keydown', function(e) {
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Datei</label>
|
<label class="label">Datei</label>
|
||||||
<div class="control">
|
<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">
|
accept="image/*,video/*,application/pdf">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -706,10 +718,14 @@ document.addEventListener('keydown', function(e) {
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label"> </label>
|
<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>
|
||||||
</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>
|
</form>
|
||||||
</div>
|
</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>
|
</script>
|
||||||
|
|
||||||
</body>
|
</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 {
|
if d, err := strconv.Atoi(strings.TrimSpace(r.FormValue("duration_seconds"))); err == nil && d > 0 {
|
||||||
durationSeconds = d
|
durationSeconds = d
|
||||||
}
|
}
|
||||||
enabled := r.FormValue("enabled") != "false"
|
enabled := r.FormValue("enabled") == "true"
|
||||||
validFrom, _ := parseOptionalTime(r.FormValue("valid_from"))
|
validFrom, _ := parseOptionalTime(r.FormValue("valid_from"))
|
||||||
validUntil, _ := parseOptionalTime(r.FormValue("valid_until"))
|
validUntil, _ := parseOptionalTime(r.FormValue("valid_until"))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue