improve folder config
This commit is contained in:
parent
c797960fa3
commit
d7bdb646fd
17
app.py
17
app.py
@ -63,6 +63,23 @@ app.add_url_rule('/searchcommand', view_func=search.searchcommand, methods=['POS
|
||||
app.add_url_rule('/admin/folder_secret_config_editor', view_func=auth.require_admin(fsce.folder_secret_config_editor), methods=['GET', 'POST'])
|
||||
app.add_url_rule('/admin/folder_secret_config_editor/data', view_func=auth.require_admin(auth.load_folder_config))
|
||||
app.add_url_rule('/admin/folder_secret_config_editor/action', view_func=auth.require_admin(fsce.folder_secret_config_action), methods=['POST'])
|
||||
|
||||
@app.route('/admin/generate_qr/<secret>')
|
||||
@auth.require_admin
|
||||
def generate_qr_code(secret):
|
||||
scheme = request.scheme
|
||||
host = request.host
|
||||
url = f"{scheme}://{host}?secret={secret}"
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=4)
|
||||
qr.add_data(url)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="PNG")
|
||||
buffer.seek(0)
|
||||
img_base64 = base64.b64encode(buffer.getvalue()).decode('ascii')
|
||||
return jsonify({'qr_code': img_base64})
|
||||
|
||||
app.add_url_rule('/admin/search_db_analyzer', view_func=auth.require_admin(sdb.search_db_analyzer))
|
||||
app.add_url_rule('/admin/search_db_analyzer/query', view_func=auth.require_admin(sdb.search_db_query), methods=['POST'])
|
||||
app.add_url_rule('/admin/search_db_analyzer/folders', view_func=auth.require_admin(sdb.search_db_folders))
|
||||
|
||||
42
auth.py
42
auth.py
@ -117,22 +117,7 @@ def require_secret(f):
|
||||
if not is_valid_token(token_in_session):
|
||||
session['valid_tokens'].remove(token_in_session)
|
||||
|
||||
# 5) Build session['folders'] fresh from the valid secrets
|
||||
session['folders'] = {}
|
||||
for secret_in_session in session.get('valid_secrets', []):
|
||||
config_item = next(
|
||||
(c for c in folder_config if c['secret'] == secret_in_session),
|
||||
None
|
||||
)
|
||||
if config_item:
|
||||
for folder_info in config_item['folders']:
|
||||
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
||||
|
||||
for token_in_session in session.get('valid_tokens', []):
|
||||
token_item = decode_token(token_in_session)
|
||||
for folder_info in token_item['folders']:
|
||||
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
||||
|
||||
# Check for admin access first
|
||||
args_admin = request.args.get('admin', None)
|
||||
config_admin = app_config.get('ADMIN_KEY', None)
|
||||
|
||||
@ -144,6 +129,31 @@ def require_secret(f):
|
||||
print(f"Admin access denied with key: {args_admin}")
|
||||
session['admin'] = False
|
||||
|
||||
# 5) Build session['folders'] fresh from the valid secrets
|
||||
# If admin, grant access to ALL folders
|
||||
session['folders'] = {}
|
||||
|
||||
if is_admin():
|
||||
# Admin gets access to all folders in the config
|
||||
for config_item in folder_config:
|
||||
for folder_info in config_item['folders']:
|
||||
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
||||
else:
|
||||
# Normal users only get folders from their valid secrets/tokens
|
||||
for secret_in_session in session.get('valid_secrets', []):
|
||||
config_item = next(
|
||||
(c for c in folder_config if c['secret'] == secret_in_session),
|
||||
None
|
||||
)
|
||||
if config_item:
|
||||
for folder_info in config_item['folders']:
|
||||
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
||||
|
||||
for token_in_session in session.get('valid_tokens', []):
|
||||
token_item = decode_token(token_in_session)
|
||||
for folder_info in token_item['folders']:
|
||||
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
||||
|
||||
# 6) If we have folders, proceed; otherwise show index
|
||||
if session['folders']:
|
||||
# assume since visitor has a valid secret, they are ok with annonymous tracking
|
||||
|
||||
@ -39,6 +39,17 @@
|
||||
let editing = new Set();
|
||||
let pendingDelete = null;
|
||||
|
||||
// QR Code generation function using backend
|
||||
async function generateQRCode(secret, imgElement) {
|
||||
try {
|
||||
const response = await fetch(`/admin/generate_qr/${encodeURIComponent(secret)}`);
|
||||
const data = await response.json();
|
||||
imgElement.src = `data:image/png;base64,${data.qr_code}`;
|
||||
} catch (error) {
|
||||
console.error('Error generating QR code:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// helper to format DD.MM.YYYY → YYYY-MM-DD
|
||||
function formatISO(d) {
|
||||
const [dd, mm, yyyy] = d.split('.');
|
||||
@ -87,40 +98,113 @@
|
||||
wrapper.className = 'card mb-3';
|
||||
wrapper.dataset.secret = key;
|
||||
|
||||
// Card header for collapse toggle
|
||||
const cardHeader = document.createElement('div');
|
||||
cardHeader.className = 'card-header';
|
||||
cardHeader.style.cursor = 'pointer';
|
||||
const collapseId = `collapse-${key}`;
|
||||
cardHeader.setAttribute('data-bs-toggle', 'collapse');
|
||||
cardHeader.setAttribute('data-bs-target', `#${collapseId}`);
|
||||
cardHeader.setAttribute('aria-expanded', 'false');
|
||||
cardHeader.setAttribute('aria-controls', collapseId);
|
||||
|
||||
const headerTitle = document.createElement('div');
|
||||
headerTitle.className = 'd-flex justify-content-between align-items-center';
|
||||
const folderNames = rec.folders.map(f => f.foldername).join(', ');
|
||||
headerTitle.innerHTML = `<strong>Ordner: ${folderNames}${expired ? ' <span class="text-danger fw-bold"> ! abgelaufen !</span>' : ''}</strong><i class="bi bi-chevron-down"></i>`;
|
||||
cardHeader.appendChild(headerTitle);
|
||||
wrapper.appendChild(cardHeader);
|
||||
|
||||
// Collapsible body
|
||||
const collapseDiv = document.createElement('div');
|
||||
collapseDiv.className = 'collapse';
|
||||
collapseDiv.id = collapseId;
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = `card-body ${cls}`;
|
||||
|
||||
// header
|
||||
const h5 = document.createElement('h5');
|
||||
folderNames = rec.folders.map(f => f.foldername).join(', ');
|
||||
h5.innerHTML = `Ordner: ${folderNames}${expired ? ' <span class="text-danger fw-bold"> ! abgelaufen !</span>' : ''}`;
|
||||
body.appendChild(h5);
|
||||
// Create row for two-column layout (only if not editing)
|
||||
if (!isEdit) {
|
||||
const topRow = document.createElement('div');
|
||||
topRow.className = 'row mb-3';
|
||||
|
||||
// Left column for secret and validity
|
||||
const leftCol = document.createElement('div');
|
||||
leftCol.className = 'col-md-8';
|
||||
|
||||
// secret input
|
||||
const secDiv = document.createElement('div');
|
||||
secDiv.className = 'mb-2';
|
||||
secDiv.innerHTML = `Secret: `;
|
||||
const secInput = document.createElement('input');
|
||||
secInput.className = 'form-control';
|
||||
secInput.type = 'text';
|
||||
secInput.value = rec.secret;
|
||||
secInput.readOnly = true;
|
||||
secInput.dataset.field = 'secret';
|
||||
secDiv.appendChild(secInput);
|
||||
leftCol.appendChild(secDiv);
|
||||
|
||||
// secret input
|
||||
const secDiv = document.createElement('div');
|
||||
secDiv.className = 'mb-2';
|
||||
secDiv.innerHTML = `Secret: `;
|
||||
const secInput = document.createElement('input');
|
||||
secInput.className = 'form-control';
|
||||
secInput.type = 'text';
|
||||
secInput.value = rec.secret;
|
||||
secInput.readOnly = !isEdit;
|
||||
secInput.dataset.field = 'secret';
|
||||
secDiv.appendChild(secInput);
|
||||
body.appendChild(secDiv);
|
||||
// validity input
|
||||
const valDiv = document.createElement('div');
|
||||
valDiv.className = 'mb-2';
|
||||
valDiv.innerHTML = `Gültig bis: `;
|
||||
const valInput = document.createElement('input');
|
||||
valInput.className = 'form-control';
|
||||
valInput.type = 'date';
|
||||
valInput.value = formatISO(rec.validity);
|
||||
valInput.readOnly = true;
|
||||
valInput.dataset.field = 'validity';
|
||||
valDiv.appendChild(valInput);
|
||||
leftCol.appendChild(valDiv);
|
||||
|
||||
topRow.appendChild(leftCol);
|
||||
|
||||
// Right column for QR code
|
||||
const rightCol = document.createElement('div');
|
||||
rightCol.className = 'col-md-4 text-center';
|
||||
|
||||
const qrImg = document.createElement('img');
|
||||
qrImg.className = 'qr-code';
|
||||
qrImg.style.maxWidth = '200px';
|
||||
qrImg.style.width = '100%';
|
||||
qrImg.style.border = '1px solid #ddd';
|
||||
qrImg.style.padding = '10px';
|
||||
qrImg.style.borderRadius = '5px';
|
||||
qrImg.alt = 'QR Code';
|
||||
generateQRCode(key, qrImg);
|
||||
rightCol.appendChild(qrImg);
|
||||
|
||||
topRow.appendChild(rightCol);
|
||||
body.appendChild(topRow);
|
||||
} else {
|
||||
// When editing, show fields vertically without QR code
|
||||
// secret input
|
||||
const secDiv = document.createElement('div');
|
||||
secDiv.className = 'mb-2';
|
||||
secDiv.innerHTML = `Secret: `;
|
||||
const secInput = document.createElement('input');
|
||||
secInput.className = 'form-control';
|
||||
secInput.type = 'text';
|
||||
secInput.value = rec.secret;
|
||||
secInput.readOnly = false;
|
||||
secInput.dataset.field = 'secret';
|
||||
secDiv.appendChild(secInput);
|
||||
body.appendChild(secDiv);
|
||||
|
||||
// validity input
|
||||
const valDiv = document.createElement('div');
|
||||
valDiv.className = 'mb-2';
|
||||
valDiv.innerHTML = `Gültig bis: `;
|
||||
const valInput = document.createElement('input');
|
||||
valInput.className = 'form-control';
|
||||
valInput.type = 'date';
|
||||
valInput.value = formatISO(rec.validity);
|
||||
valInput.readOnly = !isEdit;
|
||||
valInput.dataset.field = 'validity';
|
||||
valDiv.appendChild(valInput);
|
||||
body.appendChild(valDiv);
|
||||
// validity input
|
||||
const valDiv = document.createElement('div');
|
||||
valDiv.className = 'mb-2';
|
||||
valDiv.innerHTML = `Gültig bis: `;
|
||||
const valInput = document.createElement('input');
|
||||
valInput.className = 'form-control';
|
||||
valInput.type = 'date';
|
||||
valInput.value = formatISO(rec.validity);
|
||||
valInput.readOnly = false;
|
||||
valInput.dataset.field = 'validity';
|
||||
valDiv.appendChild(valInput);
|
||||
body.appendChild(valDiv);
|
||||
}
|
||||
|
||||
// folders
|
||||
const folderHeader = document.createElement('h6');
|
||||
@ -219,22 +303,23 @@
|
||||
|
||||
if (isEdit) {
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.className = 'btn btn-success btn-sm';
|
||||
saveBtn.className = 'btn btn-success btn-sm me-2';
|
||||
saveBtn.type = 'button';
|
||||
saveBtn.textContent = 'speichern';
|
||||
saveBtn.addEventListener('click', () => saveRec(key));
|
||||
actions.appendChild(saveBtn);
|
||||
} else {
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'btn btn-warning btn-sm';
|
||||
editBtn.type = 'button';
|
||||
editBtn.textContent = 'bearbeiten';
|
||||
editBtn.addEventListener('click', () => editRec(key));
|
||||
actions.appendChild(editBtn);
|
||||
|
||||
const cancelBtn = document.createElement('button');
|
||||
cancelBtn.className = 'btn btn-secondary btn-sm';
|
||||
cancelBtn.type = 'button';
|
||||
cancelBtn.textContent = 'abbrechen';
|
||||
cancelBtn.addEventListener('click', () => cancelEdit(key));
|
||||
actions.appendChild(cancelBtn);
|
||||
}
|
||||
|
||||
body.appendChild(actions);
|
||||
wrapper.appendChild(body);
|
||||
collapseDiv.appendChild(body);
|
||||
wrapper.appendChild(collapseDiv);
|
||||
cont.appendChild(wrapper);
|
||||
});
|
||||
}
|
||||
@ -249,9 +334,11 @@
|
||||
}
|
||||
|
||||
// CRUD helpers
|
||||
function editRec(secret) {
|
||||
editing.add(secret);
|
||||
render();
|
||||
function cancelEdit(secret) {
|
||||
editing.delete(secret);
|
||||
// If this was a new record that was never saved, remove it
|
||||
const originalExists = data.some(r => r.secret === secret && !editing.has(secret));
|
||||
loadData(); // Reload to discard changes
|
||||
}
|
||||
function cloneRec(secret) {
|
||||
const idx = data.findIndex(r => r.secret === secret);
|
||||
@ -268,6 +355,14 @@
|
||||
data.splice(idx+1, 0, rec);
|
||||
editing.add(rec.secret);
|
||||
render();
|
||||
// Auto-expand the cloned item
|
||||
setTimeout(() => {
|
||||
const collapseEl = document.getElementById(`collapse-${rec.secret}`);
|
||||
if (collapseEl) {
|
||||
const bsCollapse = new bootstrap.Collapse(collapseEl, { toggle: false });
|
||||
bsCollapse.show();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
async function renewRec(secret) {
|
||||
// find current record
|
||||
@ -364,6 +459,14 @@
|
||||
data.push({ secret: newSecret, validity: new Date().toISOString().slice(0,10), folders: [] });
|
||||
editing.add(newSecret);
|
||||
render();
|
||||
// Auto-expand the new item
|
||||
setTimeout(() => {
|
||||
const collapseEl = document.getElementById(`collapse-${newSecret}`);
|
||||
if (collapseEl) {
|
||||
const bsCollapse = new bootstrap.Collapse(collapseEl, { toggle: false });
|
||||
bsCollapse.show();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
loadData();
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user