bethaus-app/templates/folder_secret_config_editor.html
2025-05-04 11:57:49 +00:00

196 lines
7.6 KiB
HTML

{# templates/mylinks.html #}
{% extends 'base.html' %}
{# page title #}
{% block title %}Edit Folder Config{% endblock %}
{# override navbar text: #}
{% block nav_brand %}Folder Config Editor{% endblock %}
{# page content #}
{% block content %}
<div class="container">
<div id="records"></div>
<button id="add-btn" class="btn btn-primary mt-3">Add New Record</button>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteLabel">Confirm Deletion</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Do you really want to delete this record?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger confirm-delete">Delete</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<!-- jQuery and Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const ALPHABET = {{ alphabet|tojson }};
let data = [];
let editing = new Set();
let pendingDelete = null; // store secret pending confirmation
function loadData() {
$.getJSON('/admin/folder_secret_config_editor/data', res => { data = res; render(); });
}
function render() {
const $cont = $('#records').empty();
data.forEach(rec => {
const key = rec.secret;
const isEdit = editing.has(key);
const cls = isEdit ? 'unlocked' : 'locked';
// Determine if entry has expired
const validityISO = formatISO(rec.validity);
const expired = validityISO ? (new Date(validityISO) < new Date()) : false;
// Build card HTML
let html = `<div class="card mb-3 ${cls}" data-secret="${key}"><div class="card-body">`;
// Show exclamation if expired
html += `<h5>Link${expired ? ' <span class="text-danger fw-bold"> ! abgelaufen !</span>' : ''}</h5>`;
html += `<div class="mb-2">Secret: <input class="form-control" type="text" value="${rec.secret}" ${isEdit?'':'readonly'} data-field="secret"></div>`;
html += `<div class="mb-2">Validity: <input class="form-control" type="date" value="${validityISO}" ${isEdit?'':'readonly'} data-field="validity"></div>`;
html += `<h6>Ordner</h6>`;
rec.folders.forEach((f,i) => {
html += `<div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; border-radius: 5px;">`;
html += `Ordnername: <div class="input-group mb-2">`;
html += `<input class="form-control" type="text" value="${f.foldername}" ${isEdit?'':'readonly'} data-field="foldername-${i}">`;
if(isEdit) html += `<button class="btn btn-outline-danger" onclick="removeFolder('${key}',${i})">Remove</button>`;
html += `</div>`;
html += `Ordnerpfad: <input class="form-control mb-2" type="text" value="${f.folderpath}" ${isEdit?'':'readonly'} data-field="folderpath-${i}">`;
html += `</div>`;
});
if(isEdit) html += `<button class="btn btn-sm btn-primary mb-2" onclick="addFolder('${key}')">Add Folder</button>`;
html += `<div>`;
if (!isEdit) html += `<a class="btn btn-secondary btn-sm me-2" href="/?secret=${rec.secret}">Link öffnen</a>`;
html += `<button class="btn btn-danger btn-sm me-2 delete-btn" data-secret="${key}">Delete</button>`;
html += `<button class="btn btn-secondary btn-sm me-2" onclick="cloneRec('${key}')">Clone</button>`;
if(isEdit) html += `<button class="btn btn-success btn-sm" onclick="saveRec('${key}')">Save</button>`;
else html += `<button class="btn btn-warning btn-sm" onclick="editRec('${key}')">Edit</button>`;
html += `</div></div></div>`;
$cont.append(html);
});
}
function formatISO(d) {
const p = d.split('.');
return p.length===3 ? `${p[2]}-${p[1]}-${p[0]}` : '';
}
// original delete function, called after confirmation
function deleteRec(secret) {
$.ajax({
url: '/admin/folder_secret_config_editor/action',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ action: 'delete', secret })
}).always(loadData);
}
// hook delete buttons to show modal
$(document).on('click', '.delete-btn', function() {
pendingDelete = $(this).data('secret');
const modal = new bootstrap.Modal(document.getElementById('confirmDeleteModal'));
modal.show();
});
// when user confirms deletion
$('.confirm-delete').on('click', function() {
if (pendingDelete) {
deleteRec(pendingDelete);
pendingDelete = null;
}
const modalEl = document.getElementById('confirmDeleteModal');
const modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
});
function editRec(secret) { editing.add(secret); render(); }
function cloneRec(secret) {
const rec = data.find(r=>r.secret===secret);
const existing = data.map(r=>r.secret);
const clone = JSON.parse(JSON.stringify(rec));
clone.secret = generateSecret(existing);
data.splice(data.findIndex(r=>r.secret===secret)+1, 0, clone);
editing.add(clone.secret);
render();
}
function addFolder(secret) {
const rec = data.find(r=>r.secret===secret);
rec.folders.push({foldername:'',folderpath:''}); render();
}
function removeFolder(secret,i) {
const rec = data.find(r=>r.secret===secret);
rec.folders.splice(i,1); render();
}
function saveRec(secret) {
const rec = data.find(r=>r.secret===secret);
const $card = $(`[data-secret='${secret}']`);
const newSecret = $card.find('input[data-field=secret]').val();
const validity = $card.find('input[data-field=validity]').val();
const folders = rec.folders.map((_,i) => ({
foldername: $card.find(`[data-field=foldername-${i}]`).val(),
folderpath: $card.find(`[data-field=folderpath-${i}]`).val()
}));
if(!newSecret||data.some(r=>r.secret!==secret&&r.secret===newSecret)){
alert('Secret must be unique and non-empty');
return;
}
if(!validity){
alert('Validity required');
return;
}
if(folders.some(f=>!f.foldername||!f.folderpath)){
alert('Folder entries cannot be empty');
return;
}
$.ajax({
url:'/admin/folder_secret_config_editor/action',
method:'POST',
contentType:'application/json',
data: JSON.stringify({
action:'update',
oldSecret:secret,
newSecret,
validity,
folders
})
}).always(()=>{
editing.delete(secret);
loadData();
});
}
function generateSecret(existing) {
let s;
do {
s = Array.from({length:32},_=>ALPHABET[Math.floor(Math.random()*ALPHABET.length)]).join('');
} while(existing.includes(s));
return s;
}
$(document).ready(loadData);
$('#add-btn').click(() => {
const existing = data.map(r=>r.secret);
const newSecret = generateSecret(existing);
data.push({secret:newSecret, validity:new Date().toISOString().substr(0,10), folders:[]});
editing.add(newSecret);
render();
});
</script>
{% endblock %}