Compare commits

...

12 Commits

Author SHA1 Message Date
Aiirondev_dev ee25c46ff3 Correction of the table library view
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-24 16:15:47 +02:00
Aiirondev_dev f7fe2fcc11 Correction of an Miss placed Label
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-24 16:13:04 +02:00
Aiirondev_dev 4449007a15 Default Filternames Changes for the main_admin.html
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-24 16:08:24 +02:00
Aiirondev_dev 6770d42f99 Default Filternames Changed
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-24 16:00:58 +02:00
Aiirondev_dev 333f9fc89a New Modal Overlay
Release Inventarsystem / release-docker (push) Successful in 2m18s
2026-08-24 15:45:08 +02:00
Aiirondev_dev a286236834 New Modal Overlay
Release Inventarsystem / release-docker (push) Successful in 2m17s
2026-08-24 15:41:33 +02:00
Aiirondev_dev 84cc8de833 Reverse of faulty changes
Release Inventarsystem / release-docker (push) Successful in 2m16s
2026-08-24 14:52:50 +02:00
Aiirondev_dev 4b3c7e646f Replaced the Browser side pop ups with custom ones from the site itsself
Release Inventarsystem / release-docker (push) Successful in 2m17s
2026-08-24 14:45:21 +02:00
Aiirondev_dev f9b013508c Change of the Schaden melden button to be correctly considert a button
Release Inventarsystem / release-docker (push) Successful in 2m16s
2026-08-24 14:12:28 +02:00
Aiirondev_dev 2a6c40eb5a Fix for the CRSF Token implementation callability
Release Inventarsystem / release-docker (push) Successful in 2m16s
2026-08-24 12:35:16 +02:00
Aiirondev_dev 432d6da798 Fix for the CRSF Token implementation
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-24 12:12:55 +02:00
Aiirondev_dev 2cdf0d2169 implementation of the export for users
Release Inventarsystem / release-docker (push) Successful in 3m8s
2026-08-24 11:56:03 +02:00
7 changed files with 382 additions and 32 deletions
+69
View File
@@ -3203,6 +3203,8 @@ def home_admin():
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('library_view'))
filter_names = it.get_filter_names()
return render_template(
'main_admin.html',
username=session['username'],
@@ -3212,6 +3214,7 @@ def home_admin():
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS,
school_info=_get_school_info_for_export(),
filter_names=filter_names,
open_item=request.args.get('open_item')
)
@@ -8628,6 +8631,72 @@ def register_csv():
mimetype='application/pdf'
)
@app.route('/export_users_csv', methods=['POST'])
def export_users_csv():
"""
Exports a filtered list of users to a CSV file.
Receives a JSON array of usernames from the frontend.
"""
if 'username' not in session:
flash('Ihnen ist es nicht gestattet, diese Aktion auszuführen.', 'error')
return redirect(url_for('login'))
usernames_json = request.form.get('usernames')
if not usernames_json:
flash('Keine Benutzer zum Exportieren ausgewählt.', 'error')
return redirect(url_for('user_del'))
try:
target_usernames = json.loads(usernames_json)
except json.JSONDecodeError:
flash('Ungültige Daten beim Export übermittelt.', 'error')
return redirect(url_for('user_del'))
# Fetch all users
all_users = us.get_all_users()
# Create CSV in memory
si = io.StringIO()
# Delimiter set to semicolon (standard for Excel in German locales)
cw = csv.writer(si, delimiter=';')
# Write headers
cw.writerow(['Benutzername', 'Vorname', 'Nachname', 'Administrator', 'Rechte-Preset'])
for user in all_users:
encrypted_username = user.get('Username')
if not encrypted_username:
continue
uname = decrypt_text(encrypted_username)
# Only process users that were present in the frontend's filtered view
if uname in target_usernames:
try:
permissions_payload = us.get_effective_permissions(uname)
preset = permissions_payload.get('preset', 'standard_user')
except Exception:
preset = 'standard_user'
try:
name = us.get_name(uname) or ""
last_name = us.get_last_name(uname) or ""
except Exception:
name = ""
last_name = ""
admin_status = "Ja" if user.get('Admin', False) else "Nein"
cw.writerow([uname, name, last_name, admin_status, preset])
# Prefix with BOM (Byte Order Mark) to ensure Excel reads UTF-8 (Umlauts) correctly
output = '\ufeff' + si.getvalue()
return Response(
output,
mimetype="text/csv",
headers={"Content-disposition": "attachment; filename=benutzer_export.csv"}
)
@app.route('/user_del', methods=['GET'])
def user_del():
+3 -3
View File
@@ -911,9 +911,9 @@ def get_filter_names():
if names_doc and 'names' in names_doc:
return names_doc['names']
return {
'1': 'Fach/Kategorie',
'2': 'System/Bereich',
'3': 'Typ/Art'
'1': 'Jahrgangsstufe',
'2': 'Fach',
'3': 'Schlagwort'
}
def set_filter_name(filter_num, name):
+3 -3
View File
@@ -78,15 +78,15 @@
<div class="form-row">
<div class="form-group">
<label for="filter_name_1">Name Filter 1</label>
<input type="text" id="filter_name_1" name="filter_name_1" value="{{ filter_names.get('1', 'Fach/Kategorie') }}" placeholder="z. B. Fach/Kategorie">
<input type="text" id="filter_name_1" name="filter_name_1" value="{{ filter_names.get('1', 'Klassenstufe') }}" placeholder="z. B. Klassenstufe">
</div>
<div class="form-group">
<label for="filter_name_2">Name Filter 2</label>
<input type="text" id="filter_name_2" name="filter_name_2" value="{{ filter_names.get('2', 'System/Bereich') }}" placeholder="z. B. System/Bereich">
<input type="text" id="filter_name_2" name="filter_name_2" value="{{ filter_names.get('2', 'Fach') }}" placeholder="z. B. Fach">
</div>
<div class="form-group">
<label for="filter_name_3">Name Filter 3</label>
<input type="text" id="filter_name_3" name="filter_name_3" value="{{ filter_names.get('3', 'Typ/Art') }}" placeholder="z. B. Typ/Art">
<input type="text" id="filter_name_3" name="filter_name_3" value="{{ filter_names.get('3', 'Schlagwort') }}" placeholder="z. B. Schlagwort">
</div>
</div>
+28 -2
View File
@@ -122,7 +122,7 @@
letter-spacing: 0.04em;
color: #64748b;
background: var(--ui-surface-soft);
user-select: none; /* Verhindert Textmarkierung beim Klicken */
user-select: none;
}
.library-table tr:hover td {
@@ -151,7 +151,7 @@
.badge-open { background: #fee2e2; color: #991b1b; }
.badge-paid { background: #dcfce7; color: #166534; }
.badge-damaged { background: #fee2e2; color: #991b1b; }
.badge-class { background: #f1f5f9; color: #475569; border: 1px solid #cbd5e1; } /* Neues Badge für Klasse */
.badge-class { background: #f1f5f9; color: #475569; border: 1px solid #cbd5e1; }
.row-actions {
display: flex;
@@ -168,6 +168,32 @@
white-space: nowrap;
}
.row-actions .btn-outline-danger {
color: #dc2626;
background-color: #fef2f2; /* Light red tint so it doesn't blend into the white table */
border: 2px solid #dc2626; /* Thicker, clear border */
padding: 6px 14px;
border-radius: 6px;
font-weight: bold;
font-size: 0.85rem;
cursor: pointer;
box-shadow: 0 2px 4px rgba(220, 38, 38, 0.1); /* Subtle shadow for depth */
transition: all 0.2s ease;
}
.row-actions .btn-outline-danger:hover {
background-color: #dc2626;
color: #ffffff;
box-shadow: 0 4px 8px rgba(220, 38, 38, 0.25);
transform: translateY(-1px); /* Slight lift effect on hover */
}
.row-actions .btn-outline-danger:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(220, 38, 38, 0.15); /* Pressed state */
}
/* ------------------------------------------------------ */
.muted {
color: #6b7280;
font-size: 0.92rem;
+211 -10
View File
@@ -614,6 +614,199 @@
</div>
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
<script>
// =========================================================================
// CUSTOM MODAL HELPERS (Replaces native browser alert, confirm & prompt)
// =========================================================================
function createModalOverlay() {
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100vw';
overlay.style.height = '100vh';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.zIndex = '999999';
return overlay;
}
function createModalBox() {
const box = document.createElement('div');
box.style.backgroundColor = '#fff';
box.style.padding = '24px';
box.style.borderRadius = '8px';
box.style.boxShadow = '0 10px 25px rgba(0,0,0,0.2)';
box.style.fontFamily = 'sans-serif';
box.style.minWidth = '320px';
box.style.maxWidth = '90%';
box.style.textAlign = 'left';
box.style.color = '#333';
return box;
}
function customAlert(message) {
return new Promise((resolve) => {
const overlay = createModalOverlay();
const box = createModalBox();
const text = document.createElement('p');
text.textContent = message;
text.style.marginBottom = '20px';
text.style.lineHeight = '1.5';
text.style.whiteSpace = 'pre-wrap';
const btnContainer = document.createElement('div');
btnContainer.style.display = 'flex';
btnContainer.style.justifyContent = 'flex-end';
const btn = document.createElement('button');
btn.textContent = 'OK';
btn.style.padding = '8px 16px';
btn.style.cursor = 'pointer';
btn.style.border = 'none';
btn.style.backgroundColor = '#007bff';
btn.style.color = '#fff';
btn.style.borderRadius = '4px';
btn.onclick = () => {
document.body.removeChild(overlay);
resolve();
};
btnContainer.appendChild(btn);
box.appendChild(text);
box.appendChild(btnContainer);
overlay.appendChild(box);
document.body.appendChild(overlay);
btn.focus();
});
}
function customConfirm(message) {
return new Promise((resolve) => {
const overlay = createModalOverlay();
const box = createModalBox();
const text = document.createElement('p');
text.textContent = message;
text.style.marginBottom = '20px';
text.style.lineHeight = '1.5';
text.style.whiteSpace = 'pre-wrap';
const btnContainer = document.createElement('div');
btnContainer.style.display = 'flex';
btnContainer.style.justifyContent = 'flex-end';
btnContainer.style.gap = '10px';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Abbrechen';
cancelBtn.style.padding = '8px 16px';
cancelBtn.style.cursor = 'pointer';
cancelBtn.style.border = '1px solid #ccc';
cancelBtn.style.backgroundColor = '#f8f9fa';
cancelBtn.style.color = '#333';
cancelBtn.style.borderRadius = '4px';
const okBtn = document.createElement('button');
okBtn.textContent = 'OK';
okBtn.style.padding = '8px 16px';
okBtn.style.cursor = 'pointer';
okBtn.style.border = 'none';
okBtn.style.backgroundColor = '#007bff';
okBtn.style.color = '#fff';
okBtn.style.borderRadius = '4px';
const closeAndResolve = (val) => {
document.body.removeChild(overlay);
resolve(val);
};
cancelBtn.onclick = () => closeAndResolve(false);
okBtn.onclick = () => closeAndResolve(true);
btnContainer.appendChild(cancelBtn);
btnContainer.appendChild(okBtn);
box.appendChild(text);
box.appendChild(btnContainer);
overlay.appendChild(box);
document.body.appendChild(overlay);
okBtn.focus();
});
}
function customPrompt(message, defaultValue = '') {
return new Promise((resolve) => {
const overlay = createModalOverlay();
const box = createModalBox();
const text = document.createElement('p');
text.textContent = message;
text.style.marginBottom = '15px';
text.style.lineHeight = '1.5';
text.style.whiteSpace = 'pre-wrap';
const input = document.createElement('input');
input.type = 'text';
input.value = defaultValue;
input.style.width = '100%';
input.style.marginBottom = '20px';
input.style.padding = '10px';
input.style.boxSizing = 'border-box';
input.style.border = '1px solid #ccc';
input.style.borderRadius = '4px';
const btnContainer = document.createElement('div');
btnContainer.style.display = 'flex';
btnContainer.style.justifyContent = 'flex-end';
btnContainer.style.gap = '10px';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Abbrechen';
cancelBtn.style.padding = '8px 16px';
cancelBtn.style.cursor = 'pointer';
cancelBtn.style.border = '1px solid #ccc';
cancelBtn.style.backgroundColor = '#f8f9fa';
cancelBtn.style.color = '#333';
cancelBtn.style.borderRadius = '4px';
const okBtn = document.createElement('button');
okBtn.textContent = 'OK';
okBtn.style.padding = '8px 16px';
okBtn.style.cursor = 'pointer';
okBtn.style.border = 'none';
okBtn.style.backgroundColor = '#007bff';
okBtn.style.color = '#fff';
okBtn.style.borderRadius = '4px';
const closeAndResolve = (val) => {
document.body.removeChild(overlay);
resolve(val);
};
cancelBtn.onclick = () => closeAndResolve(null);
okBtn.onclick = () => closeAndResolve(input.value);
input.onkeydown = (e) => {
if (e.key === 'Enter') closeAndResolve(input.value);
if (e.key === 'Escape') closeAndResolve(null);
};
btnContainer.appendChild(cancelBtn);
btnContainer.appendChild(okBtn);
box.appendChild(text);
box.appendChild(input);
box.appendChild(btnContainer);
overlay.appendChild(box);
document.body.appendChild(overlay);
input.focus();
input.select();
});
}
// =========================================================================
// 1. GLOBAL STATE DEFINITIONS
// =========================================================================
@@ -884,7 +1077,7 @@
name: "Live",
type: "LiveStream",
// This MUST match the inner div where the video should appear
target: document.querySelector('.library-scan-reader'),
target: document.querySelector('.library-scan-reader'),
constraints: {
width: 640,
height: 480,
@@ -897,7 +1090,7 @@
},
decoder: {
// Keep only the barcode types you actually use to improve performance
readers: ["code_128_reader", "ean_reader", "code_39_reader"]
readers: ["code_128_reader", "ean_reader", "code_39_reader"]
},
locate: true
}, function(err) {
@@ -1243,31 +1436,39 @@
// =========================================================================
// 4. UI INTERACTIONS & UTILITIES
// =========================================================================
function borrowItem(itemId) {
async function borrowItem(itemId) {
const selectedItem = (libraryItems || []).find(item => item._id === itemId);
if (selectedItem && selectedItem.LibraryDisplayStatus === 'damaged') {
alert('Dieses Medium ist als defekt/zerstört markiert und kann nicht ausgeliehen werden.');
await customAlert('Dieses Medium ist als defekt/zerstört markiert und kann nicht ausgeliehen werden.');
return;
}
const defaultCardId = activeStudentCardId || '';
const cardId = (window.prompt('Bitte Bibliotheksausweis-ID eingeben:', defaultCardId) || '').trim().toUpperCase();
const promptCardResult = await customPrompt('Bitte Bibliotheksausweis-ID eingeben:', defaultCardId);
if (promptCardResult === null) return;
const cardId = promptCardResult.trim().toUpperCase();
if (!cardId) {
alert('Ausleihe abgebrochen: Für Bibliotheksmedien ist eine gültige Bibliotheksausweis-ID erforderlich.');
await customAlert('Ausleihe abgebrochen: Für Bibliotheksmedien ist eine gültige Bibliotheksausweis-ID erforderlich.');
return;
}
setActiveStudentCard(cardId);
const durationInput = (window.prompt('Ausleihdauer in Tagen (optional):') || '').trim();
const promptDurationResult = await customPrompt('Ausleihdauer in Tagen (optional):');
if (promptDurationResult === null) return;
const durationInput = promptDurationResult.trim();
const maxAvailable = Math.max(1, parseInt(selectedItem?.AvailableGroupedCount || selectedItem?.Quantity || 1, 10) || 1);
const countPrompt = (window.prompt(`Anzahl ausleihen? (Standard: 1, verfügbar: ${maxAvailable})`, '1') || '').trim();
let borrowCount = parseInt(countPrompt || '1', 10);
const countPromptResult = await customPrompt(`Anzahl ausleihen? (Standard: 1, verfügbar: ${maxAvailable})`, '1');
if (countPromptResult === null) return;
let borrowCount = parseInt(countPromptResult.trim() || '1', 10);
if (!Number.isFinite(borrowCount) || borrowCount < 1) {
borrowCount = 1;
}
if (borrowCount > maxAvailable) {
alert(`Es sind nur ${maxAvailable} Exemplar(e) verfügbar.`);
await customAlert(`Es sind nur ${maxAvailable} Exemplar(e) verfügbar.`);
return;
}
+6 -6
View File
@@ -2343,7 +2343,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
<div class="filter-container">
<div class="filter-group">
<div class="filter-header">
<label>Unterrichtsfach:</label>
<label>{{ filter_names.get('2', 'Fach') }}</label>
<button type="button" class="filter-toggle" onclick="toggleFilterDropdown('filter1-dropdown')"></button>
<button type="button" class="clear-filter" onclick="clearFilter(1)">Clear</button>
</div>
@@ -2357,7 +2357,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
<div class="filter-group">
<div class="filter-header">
<label>Jahrgangsstufe:</label>
<label>{{ filter_names.get('1', 'Klassenstufe') }}</label>
<button type="button" class="filter-toggle" onclick="toggleFilterDropdown('filter2-dropdown')"></button>
<button type="button" class="clear-filter" onclick="clearFilter(2)">Clear</button>
</div>
@@ -2371,7 +2371,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
<div class="filter-group">
<div class="filter-header">
<label>Schlagwort:</label>
<label>{{ filter_names.get('3', 'Schlagwort') }}</label>
<button type="button" class="filter-toggle" onclick="toggleFilterDropdown('filter3-dropdown')"></button>
<button type="button" class="clear-filter" onclick="clearFilter(3)">Clear</button>
</div>
@@ -2420,9 +2420,9 @@ document.addEventListener('DOMContentLoaded', ()=>{
<div id="table-view-header" class="table-view-header" aria-hidden="true">
<span>Name</span>
<span>Ort</span>
<span>Unterrichtsfach</span>
<span>Jahrgangsstufe</span>
<span>Schlagwort</span>
<span>{{ filter_names.get('2', 'Fach') }}</span>
<span>{{ filter_names.get('1', 'Jahrgangsstufe') }}</span>
<span>{{ filter_names.get('3', 'Schlagwort') }}</span>
<span>Barcode</span>
<span>Anzahl</span>
</div>
+62 -8
View File
@@ -53,8 +53,11 @@
<option value="desc">Absteigend</option>
</select>
</div>
<div class="col-md-2">
<button class="btn btn-secondary w-100" onclick="resetFilters()">Filter zurücksetzen</button>
<div class="col-md-3">
<div class="d-flex gap-2">
<button class="btn btn-secondary flex-grow-1" onclick="resetFilters()">Zurücksetzen</button>
<button class="btn btn-success flex-grow-1" onclick="exportFilteredUsers()">Exportieren</button>
</div>
</div>
</div>
<div class="mt-2 text-muted small" id="filter-count"></div>
@@ -82,19 +85,20 @@
<td>{{ permission_presets.get(user.permission_preset, {}).get('label', user.permission_preset) }}</td>
<td class="actions">
<form method="POST" action="{{ url_for('delete_user') }}" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="username" value="{{ user.username }}">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Sind Sie sicher, dass Sie den Benutzer {{ user.username }} löschen möchten?')">
Löschen
</button>
</form>
<button type="button" class="btn btn-primary btn-sm"
<button type="button" class="btn btn-primary btn-sm"
data-username="{{ user.username }}"
data-firstname="{{ user.name if user.name else '' }}"
data-lastname="{{ user.last_name if user.last_name else '' }}"
onclick="openEditUserModal(this)">
Bearbeiten
</button>
<button type="button" class="btn btn-warning btn-sm"
<button type="button" class="btn btn-warning btn-sm"
onclick="openResetPasswordModal('{{ user.username }}')">
Passwort zurücksetzen
</button>
@@ -124,6 +128,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" action="{{ url_for('admin_update_user_permissions') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="modal-body permissions-modal-body">
<input type="hidden" id="perm-username" name="username">
<div class="mb-3">
@@ -180,6 +185,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" action="{{ url_for('admin_update_user_name') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="modal-body">
<input type="hidden" id="edit-username" name="username">
<div class="mb-3">
@@ -213,6 +219,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" action="{{ url_for('admin_reset_user_password') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="modal-body">
<input type="hidden" id="reset-username" name="username">
<div class="mb-3">
@@ -313,6 +320,54 @@
applyFilters();
}
function exportFilteredUsers() {
var table = document.getElementById('user-table');
var tbody = table.querySelector('tbody');
var rows = Array.from(tbody.querySelectorAll('tr'));
var visibleUsernames = [];
// Sammle alle Benutzernamen, die durch den Filter aktuell NICHT versteckt sind
rows.forEach(function(row) {
if (row.style.display !== 'none') {
var cells = row.querySelectorAll('td');
if (cells.length > 0) {
visibleUsernames.push(cells[0].textContent.trim());
}
}
});
if (visibleUsernames.length === 0) {
alert('Keine Benutzer zum Exportieren vorhanden.');
return;
}
// Dynamisches Formular erstellen
var form = document.createElement('form');
form.method = 'POST';
form.action = "{{ url_for('export_users_csv') }}";
// 1. CSRF-Token als Wert (ohne Klammern) übergeben
var csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrf_token';
csrfInput.value = "{{ csrf_token }}";
form.appendChild(csrfInput);
// 2. Gefilterte Benutzernamen hinzufügen
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'usernames';
input.value = JSON.stringify(visibleUsernames);
form.appendChild(input);
document.body.appendChild(form);
form.submit();
// Aufräumen
document.body.removeChild(form);
}
document.addEventListener('DOMContentLoaded', function() {
applyFilters();
document.getElementById('filter-search').addEventListener('input', applyFilters);
@@ -332,12 +387,12 @@
var username = button.getAttribute('data-username');
var name = button.getAttribute('data-firstname');
var lastName = button.getAttribute('data-lastname');
document.getElementById('edit-username').value = username;
document.getElementById('edit-username-display').value = username;
document.getElementById('edit-name').value = name;
document.getElementById('edit-last-name').value = lastName;
var modal = new bootstrap.Modal(document.getElementById('editUserModal'));
modal.show();
}
@@ -345,8 +400,7 @@
function openResetPasswordModal(username) {
document.getElementById('reset-username').value = username;
document.getElementById('username-display').value = username;
// Open modal using Bootstrap
var modal = new bootstrap.Modal(document.getElementById('resetPasswordModal'));
modal.show();
}