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', 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/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.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', 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/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))
|
app.add_url_rule('/admin/search_db_analyzer/folders', view_func=auth.require_admin(sdb.search_db_folders))
|
||||||
|
|||||||
32
auth.py
32
auth.py
@ -117,8 +117,29 @@ def require_secret(f):
|
|||||||
if not is_valid_token(token_in_session):
|
if not is_valid_token(token_in_session):
|
||||||
session['valid_tokens'].remove(token_in_session)
|
session['valid_tokens'].remove(token_in_session)
|
||||||
|
|
||||||
|
# Check for admin access first
|
||||||
|
args_admin = request.args.get('admin', None)
|
||||||
|
config_admin = app_config.get('ADMIN_KEY', None)
|
||||||
|
|
||||||
|
if args_admin and config_admin:
|
||||||
|
if args_admin == config_admin:
|
||||||
|
print(f"Admin access granted with key: {args_admin}")
|
||||||
|
session['admin'] = True
|
||||||
|
else:
|
||||||
|
print(f"Admin access denied with key: {args_admin}")
|
||||||
|
session['admin'] = False
|
||||||
|
|
||||||
# 5) Build session['folders'] fresh from the valid secrets
|
# 5) Build session['folders'] fresh from the valid secrets
|
||||||
|
# If admin, grant access to ALL folders
|
||||||
session['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', []):
|
for secret_in_session in session.get('valid_secrets', []):
|
||||||
config_item = next(
|
config_item = next(
|
||||||
(c for c in folder_config if c['secret'] == secret_in_session),
|
(c for c in folder_config if c['secret'] == secret_in_session),
|
||||||
@ -133,17 +154,6 @@ def require_secret(f):
|
|||||||
for folder_info in token_item['folders']:
|
for folder_info in token_item['folders']:
|
||||||
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
session['folders'][folder_info['foldername']] = folder_info['folderpath']
|
||||||
|
|
||||||
args_admin = request.args.get('admin', None)
|
|
||||||
config_admin = app_config.get('ADMIN_KEY', None)
|
|
||||||
|
|
||||||
if args_admin and config_admin:
|
|
||||||
if args_admin == config_admin:
|
|
||||||
print(f"Admin access granted with key: {args_admin}")
|
|
||||||
session['admin'] = True
|
|
||||||
else:
|
|
||||||
print(f"Admin access denied with key: {args_admin}")
|
|
||||||
session['admin'] = False
|
|
||||||
|
|
||||||
# 6) If we have folders, proceed; otherwise show index
|
# 6) If we have folders, proceed; otherwise show index
|
||||||
if session['folders']:
|
if session['folders']:
|
||||||
# assume since visitor has a valid secret, they are ok with annonymous tracking
|
# assume since visitor has a valid secret, they are ok with annonymous tracking
|
||||||
|
|||||||
@ -39,6 +39,17 @@
|
|||||||
let editing = new Set();
|
let editing = new Set();
|
||||||
let pendingDelete = null;
|
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
|
// helper to format DD.MM.YYYY → YYYY-MM-DD
|
||||||
function formatISO(d) {
|
function formatISO(d) {
|
||||||
const [dd, mm, yyyy] = d.split('.');
|
const [dd, mm, yyyy] = d.split('.');
|
||||||
@ -87,14 +98,39 @@
|
|||||||
wrapper.className = 'card mb-3';
|
wrapper.className = 'card mb-3';
|
||||||
wrapper.dataset.secret = key;
|
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');
|
const body = document.createElement('div');
|
||||||
body.className = `card-body ${cls}`;
|
body.className = `card-body ${cls}`;
|
||||||
|
|
||||||
// header
|
// Create row for two-column layout (only if not editing)
|
||||||
const h5 = document.createElement('h5');
|
if (!isEdit) {
|
||||||
folderNames = rec.folders.map(f => f.foldername).join(', ');
|
const topRow = document.createElement('div');
|
||||||
h5.innerHTML = `Ordner: ${folderNames}${expired ? ' <span class="text-danger fw-bold"> ! abgelaufen !</span>' : ''}`;
|
topRow.className = 'row mb-3';
|
||||||
body.appendChild(h5);
|
|
||||||
|
// Left column for secret and validity
|
||||||
|
const leftCol = document.createElement('div');
|
||||||
|
leftCol.className = 'col-md-8';
|
||||||
|
|
||||||
// secret input
|
// secret input
|
||||||
const secDiv = document.createElement('div');
|
const secDiv = document.createElement('div');
|
||||||
@ -104,7 +140,54 @@
|
|||||||
secInput.className = 'form-control';
|
secInput.className = 'form-control';
|
||||||
secInput.type = 'text';
|
secInput.type = 'text';
|
||||||
secInput.value = rec.secret;
|
secInput.value = rec.secret;
|
||||||
secInput.readOnly = !isEdit;
|
secInput.readOnly = true;
|
||||||
|
secInput.dataset.field = 'secret';
|
||||||
|
secDiv.appendChild(secInput);
|
||||||
|
leftCol.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';
|
secInput.dataset.field = 'secret';
|
||||||
secDiv.appendChild(secInput);
|
secDiv.appendChild(secInput);
|
||||||
body.appendChild(secDiv);
|
body.appendChild(secDiv);
|
||||||
@ -117,10 +200,11 @@
|
|||||||
valInput.className = 'form-control';
|
valInput.className = 'form-control';
|
||||||
valInput.type = 'date';
|
valInput.type = 'date';
|
||||||
valInput.value = formatISO(rec.validity);
|
valInput.value = formatISO(rec.validity);
|
||||||
valInput.readOnly = !isEdit;
|
valInput.readOnly = false;
|
||||||
valInput.dataset.field = 'validity';
|
valInput.dataset.field = 'validity';
|
||||||
valDiv.appendChild(valInput);
|
valDiv.appendChild(valInput);
|
||||||
body.appendChild(valDiv);
|
body.appendChild(valDiv);
|
||||||
|
}
|
||||||
|
|
||||||
// folders
|
// folders
|
||||||
const folderHeader = document.createElement('h6');
|
const folderHeader = document.createElement('h6');
|
||||||
@ -219,22 +303,23 @@
|
|||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
const saveBtn = document.createElement('button');
|
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.type = 'button';
|
||||||
saveBtn.textContent = 'speichern';
|
saveBtn.textContent = 'speichern';
|
||||||
saveBtn.addEventListener('click', () => saveRec(key));
|
saveBtn.addEventListener('click', () => saveRec(key));
|
||||||
actions.appendChild(saveBtn);
|
actions.appendChild(saveBtn);
|
||||||
} else {
|
|
||||||
const editBtn = document.createElement('button');
|
const cancelBtn = document.createElement('button');
|
||||||
editBtn.className = 'btn btn-warning btn-sm';
|
cancelBtn.className = 'btn btn-secondary btn-sm';
|
||||||
editBtn.type = 'button';
|
cancelBtn.type = 'button';
|
||||||
editBtn.textContent = 'bearbeiten';
|
cancelBtn.textContent = 'abbrechen';
|
||||||
editBtn.addEventListener('click', () => editRec(key));
|
cancelBtn.addEventListener('click', () => cancelEdit(key));
|
||||||
actions.appendChild(editBtn);
|
actions.appendChild(cancelBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.appendChild(actions);
|
body.appendChild(actions);
|
||||||
wrapper.appendChild(body);
|
collapseDiv.appendChild(body);
|
||||||
|
wrapper.appendChild(collapseDiv);
|
||||||
cont.appendChild(wrapper);
|
cont.appendChild(wrapper);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -249,9 +334,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CRUD helpers
|
// CRUD helpers
|
||||||
function editRec(secret) {
|
function cancelEdit(secret) {
|
||||||
editing.add(secret);
|
editing.delete(secret);
|
||||||
render();
|
// 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) {
|
function cloneRec(secret) {
|
||||||
const idx = data.findIndex(r => r.secret === secret);
|
const idx = data.findIndex(r => r.secret === secret);
|
||||||
@ -268,6 +355,14 @@
|
|||||||
data.splice(idx+1, 0, rec);
|
data.splice(idx+1, 0, rec);
|
||||||
editing.add(rec.secret);
|
editing.add(rec.secret);
|
||||||
render();
|
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) {
|
async function renewRec(secret) {
|
||||||
// find current record
|
// find current record
|
||||||
@ -364,6 +459,14 @@
|
|||||||
data.push({ secret: newSecret, validity: new Date().toISOString().slice(0,10), folders: [] });
|
data.push({ secret: newSecret, validity: new Date().toISOString().slice(0,10), folders: [] });
|
||||||
editing.add(newSecret);
|
editing.add(newSecret);
|
||||||
render();
|
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();
|
loadData();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user