582 lines
23 KiB
HTML
582 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Active Directory Datenverwaltung</title>
|
|
<style>
|
|
:root {
|
|
--color-white: rgba(255, 255, 255, 1);
|
|
--color-black: rgba(0, 0, 0, 1);
|
|
--color-cream-50: rgba(252, 252, 249, 1);
|
|
--color-cream-100: rgba(255, 255, 253, 1);
|
|
--color-gray-200: rgba(245, 245, 245, 1);
|
|
--color-gray-300: rgba(167, 169, 169, 1);
|
|
--color-slate-500: rgba(98, 108, 113, 1);
|
|
--color-brown-600: rgba(94, 82, 64, 1);
|
|
--color-charcoal-700: rgba(31, 33, 33, 1);
|
|
--color-charcoal-800: rgba(38, 40, 40, 1);
|
|
--color-slate-900: rgba(19, 52, 59, 1);
|
|
--color-teal-500: rgba(33, 128, 141, 1);
|
|
--color-teal-600: rgba(29, 116, 128, 1);
|
|
--color-teal-700: rgba(26, 104, 115, 1);
|
|
--color-teal-300: rgba(50, 184, 198, 1);
|
|
--color-teal-400: rgba(45, 166, 178, 1);
|
|
--color-brown-600-rgb: 94, 82, 64;
|
|
--color-teal-500-rgb: 33, 128, 141;
|
|
--color-background: var(--color-cream-50);
|
|
--color-surface: var(--color-cream-100);
|
|
--color-text: var(--color-slate-900);
|
|
--color-text-secondary: var(--color-slate-500);
|
|
--color-primary: var(--color-teal-500);
|
|
--color-primary-hover: var(--color-teal-600);
|
|
--color-primary-active: var(--color-teal-700);
|
|
--color-secondary: rgba(var(--color-brown-600-rgb), 0.12);
|
|
--color-secondary-hover: rgba(var(--color-brown-600-rgb), 0.2);
|
|
--color-border: rgba(var(--color-brown-600-rgb), 0.2);
|
|
--color-btn-primary-text: var(--color-cream-50);
|
|
--color-focus-ring: rgba(var(--color-teal-500-rgb), 0.4);
|
|
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
--font-size-sm: 12px;
|
|
--font-size-base: 14px;
|
|
--font-size-lg: 16px;
|
|
--font-size-xl: 18px;
|
|
--font-size-2xl: 20px;
|
|
--font-size-3xl: 24px;
|
|
--space-4: 4px;
|
|
--space-6: 6px;
|
|
--space-8: 8px;
|
|
--space-12: 12px;
|
|
--space-16: 16px;
|
|
--space-20: 20px;
|
|
--space-24: 24px;
|
|
--space-32: 32px;
|
|
--radius-base: 8px;
|
|
--radius-lg: 12px;
|
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04), 0 2px 4px -1px rgba(0, 0, 0, 0.02);
|
|
--duration-fast: 150ms;
|
|
--ease-standard: cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--color-gray-400-rgb: 119, 124, 124;
|
|
--color-teal-300-rgb: 50, 184, 198;
|
|
--color-gray-300-rgb: 167, 169, 169;
|
|
--color-gray-200-rgb: 245, 245, 245;
|
|
--color-background: var(--color-charcoal-700);
|
|
--color-surface: var(--color-charcoal-800);
|
|
--color-text: var(--color-gray-200);
|
|
--color-text-secondary: rgba(var(--color-gray-300-rgb), 0.7);
|
|
--color-primary: var(--color-teal-300);
|
|
--color-primary-hover: var(--color-teal-400);
|
|
--color-secondary: rgba(var(--color-gray-400-rgb), 0.15);
|
|
--color-secondary-hover: rgba(var(--color-gray-400-rgb), 0.25);
|
|
--color-border: rgba(var(--color-gray-400-rgb), 0.3);
|
|
--color-btn-primary-text: var(--color-slate-900);
|
|
--color-focus-ring: rgba(var(--color-teal-300-rgb), 0.4);
|
|
}
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: var(--font-family-base);
|
|
background-color: var(--color-background);
|
|
color: var(--color-text);
|
|
line-height: 1.5;
|
|
padding: var(--space-20);
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
background-color: var(--color-surface);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-md);
|
|
padding: var(--space-32);
|
|
}
|
|
|
|
h1 {
|
|
font-size: var(--font-size-3xl);
|
|
margin-bottom: var(--space-24);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
h2 {
|
|
font-size: var(--font-size-xl);
|
|
margin-bottom: var(--space-16);
|
|
margin-top: var(--space-24);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.form-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--space-12);
|
|
margin-bottom: var(--space-16);
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.form-group.full-width {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
label {
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 500;
|
|
color: var(--color-text);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
input[type="text"],
|
|
input[type="password"],
|
|
input[type="email"],
|
|
select {
|
|
padding: var(--space-8) var(--space-12);
|
|
font-size: var(--font-size-base);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-base);
|
|
background-color: var(--color-surface);
|
|
color: var(--color-text);
|
|
transition: border-color var(--duration-fast) var(--ease-standard);
|
|
}
|
|
|
|
input:focus,
|
|
select:focus {
|
|
outline: none;
|
|
border-color: var(--color-primary);
|
|
box-shadow: 0 0 0 3px var(--color-focus-ring);
|
|
}
|
|
|
|
.btn {
|
|
padding: var(--space-8) var(--space-16);
|
|
font-size: var(--font-size-base);
|
|
font-weight: 500;
|
|
border: none;
|
|
border-radius: var(--radius-base);
|
|
cursor: pointer;
|
|
transition: all var(--duration-fast) var(--ease-standard);
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: var(--color-primary);
|
|
color: var(--color-btn-primary-text);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: var(--color-primary-hover);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: var(--color-secondary);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: var(--color-secondary-hover);
|
|
}
|
|
|
|
.btn-small {
|
|
padding: var(--space-4) var(--space-12);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.divider {
|
|
border: none;
|
|
border-top: 2px solid var(--color-border);
|
|
margin: var(--space-32) 0;
|
|
}
|
|
|
|
.dynamic-fields {
|
|
margin-top: var(--space-16);
|
|
}
|
|
|
|
.dynamic-field-item {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr auto;
|
|
gap: var(--space-12);
|
|
margin-bottom: var(--space-12);
|
|
align-items: end;
|
|
}
|
|
|
|
.csv-section {
|
|
margin-top: var(--space-24);
|
|
}
|
|
|
|
input[type="file"] {
|
|
padding: var(--space-8);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-base);
|
|
background-color: var(--color-surface);
|
|
color: var(--color-text);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.button-group {
|
|
display: flex;
|
|
gap: var(--space-12);
|
|
margin-top: var(--space-16);
|
|
}
|
|
|
|
.btn-remove {
|
|
background-color: rgba(192, 21, 47, 0.1);
|
|
color: #c0152f;
|
|
padding: var(--space-8) var(--space-12);
|
|
}
|
|
|
|
.btn-remove:hover {
|
|
background-color: rgba(192, 21, 47, 0.2);
|
|
}
|
|
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
padding: var(--space-8) var(--space-12);
|
|
border: 1px solid var(--color-border);
|
|
text-align: left;
|
|
}
|
|
|
|
background-color: var(--color-secondary);
|
|
font-weight: 500;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
width: 100%;
|
|
padding: var(--space-4) var(--space-8);
|
|
font-size: var(--font-size-sm);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-base);
|
|
background-color: var(--color-surface);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
background-color: var(--color-secondary);
|
|
}
|
|
|
|
/* Password hint and invalid input styles */
|
|
.password-hint { display:block; margin-top: 6px; font-size: 0.9rem; color: #6c757d; }
|
|
.invalid { border: 1px solid #c0152f; background-color: rgba(192,21,47,0.04); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Active Directory Datenverwaltung</h1>
|
|
|
|
<form id="adForm">
|
|
<h2>Benutzer manuell anlegen</h2>
|
|
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="firstname">Vorname</label>
|
|
<input type="text" id="firstname" name="firstname" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Passwort</label>
|
|
<input type="password" id="password" name="password" required>
|
|
<small class="text-muted password-hint">Das Passwort muss mindestens 7 Zeichen lang sein, darf keine größeren Teile des Benutzernamens enthalten und muss Zeichen aus mindestens 3 von 4 Kategorien enthalten: Großbuchstaben, Kleinbuchstaben, Ziffern, Sonderzeichen.</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="lastname">Nachname</label>
|
|
<input type="text" id="lastname" name="lastname" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="group">Gruppe</label>
|
|
<select id="group" name="group" required>
|
|
<option value="">-- Gruppe wählen --</option>
|
|
<option value="Admins">Admins</option>
|
|
<option value="Benutzer">Benutzer</option>
|
|
<option value="IT">IT</option>
|
|
<option value="Buchhaltung">Buchhaltung</option>
|
|
<option value="Vertrieb">Vertrieb</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dynamic-fields">
|
|
<h2>Zusätzliche Felder</h2>
|
|
<div id="customFields"></div>
|
|
<button type="button" class="btn btn-secondary btn-small" onclick="addCustomField()">+ Feld hinzufügen</button>
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
<button type="submit" class="btn btn-primary">Benutzer erstellen</button>
|
|
<button type="reset" class="btn btn-secondary">Zurücksetzen</button>
|
|
</div>
|
|
</form>
|
|
|
|
<hr class="divider">
|
|
|
|
<div class="csv-section">
|
|
<h2>CSV-Datei importieren</h2>
|
|
<form id="csvForm">
|
|
<div class="form-group">
|
|
<label for="csvFile">CSV-Datei auswählen</label>
|
|
<input type="file" id="csvFile" name="csvFile" accept=".csv" required>
|
|
</div>
|
|
<button type="button" class="btn btn-primary" onclick="loadCSVPreview()">CSV laden</button>
|
|
</form>
|
|
|
|
<div id="csvPreview" style="margin-top: var(--space-24);">
|
|
<h2>CSV-Vorschau und Bearbeitung</h2>
|
|
<div class="preview-info" style="margin-bottom: var(--space-16); padding: var(--space-12); background-color: var(--color-secondary); border-radius: var(--radius-base); font-size: var(--font-size-sm);">
|
|
<strong>Hinweis:</strong> Sie können die Werte direkt in der Tabelle bearbeiten, bevor Sie importieren.
|
|
</div>
|
|
<div style="overflow-x: auto; margin-bottom: var(--space-16);">
|
|
<table id="csvTable" style="width: 100%; border-collapse: collapse;">
|
|
<thead id="csvTableHead"></thead>
|
|
<tbody id="csvTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="button-group">
|
|
<button type="button" class="btn btn-primary" onclick="importCSVData()">Endgültig importieren</button>
|
|
<button type="button" class="btn btn-secondary" onclick="cancelCSVPreview()">Abbrechen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let customFieldCounter = 0;
|
|
|
|
function addCustomField() {
|
|
customFieldCounter++;
|
|
const container = document.getElementById('customFields');
|
|
const fieldItem = document.createElement('div');
|
|
fieldItem.className = 'dynamic-field-item';
|
|
fieldItem.id = `customField_${customFieldCounter}`;
|
|
|
|
fieldItem.innerHTML = `
|
|
<div class="form-group">
|
|
<label for="fieldName_${customFieldCounter}">Feldname</label>
|
|
<input type="text" id="fieldName_${customFieldCounter}" placeholder="z.B. Telefonnummer">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="fieldValue_${customFieldCounter}">Wert</label>
|
|
<input type="text" id="fieldValue_${customFieldCounter}" placeholder="Wert eingeben">
|
|
</div>
|
|
<div class="form-group">
|
|
<button type="button" class="btn btn-remove btn-small" onclick="removeCustomField(${customFieldCounter})">Entfernen</button>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(fieldItem);
|
|
}
|
|
|
|
function removeCustomField(id) {
|
|
const field = document.getElementById(`customField_${id}`);
|
|
if (field) {
|
|
field.remove();
|
|
}
|
|
}
|
|
|
|
function validatePasswordJS(password, sam) {
|
|
const errors = [];
|
|
if (!password || password.length < 7) {
|
|
errors.push('Passwort muss mindestens 7 Zeichen lang sein.');
|
|
}
|
|
let categories = 0;
|
|
if (/[A-Z]/.test(password)) categories++;
|
|
if (/[a-z]/.test(password)) categories++;
|
|
if (/\d/.test(password)) categories++;
|
|
if (/[^A-Za-z0-9]/.test(password)) categories++;
|
|
if (categories < 3) {
|
|
errors.push('Passwort muss Zeichen aus mindestens 3 von 4 Kategorien enthalten.');
|
|
}
|
|
if (sam) {
|
|
const pwLower = password.toLowerCase();
|
|
const samLower = sam.toLowerCase();
|
|
if (pwLower.includes(samLower)) {
|
|
errors.push('Passwort darf den Benutzernamen nicht enthalten.');
|
|
} else {
|
|
const minLen = 4;
|
|
if (samLower.length >= minLen) {
|
|
outer: for (let len = minLen; len <= samLower.length; len++) {
|
|
for (let s = 0; s <= samLower.length - len; s++) {
|
|
const sub = samLower.substr(s, len);
|
|
if (pwLower.includes(sub)) {
|
|
errors.push('Passwort darf keine größeren Teile des Benutzernamens enthalten.');
|
|
break outer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
document.getElementById('adForm').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
|
|
const firstname = document.getElementById('firstname').value.trim();
|
|
const lastname = document.getElementById('lastname').value.trim();
|
|
const samGuess = (firstname + lastname).replace(/\s+/g, '');
|
|
const password = document.getElementById('password').value;
|
|
const pwErrors = validatePasswordJS(password, samGuess);
|
|
if (pwErrors.length > 0) {
|
|
alert('Passwort ungültig:\n' + pwErrors.join('\n'));
|
|
return;
|
|
}
|
|
|
|
const formData = {
|
|
firstname: firstname,
|
|
lastname: lastname,
|
|
group: document.getElementById('group').value,
|
|
password: password,
|
|
customFields: {}
|
|
};
|
|
|
|
const customFields = document.querySelectorAll('.dynamic-field-item');
|
|
customFields.forEach((field) => {
|
|
const nameInput = field.querySelector('[id^="fieldName_"]');
|
|
const valueInput = field.querySelector('[id^="fieldValue_"]');
|
|
if (nameInput && valueInput && nameInput.value) {
|
|
formData.customFields[nameInput.value] = valueInput.value;
|
|
}
|
|
});
|
|
|
|
console.log('AD Benutzer Daten:', formData);
|
|
alert('Benutzer wurde erfolgreich erstellt!\n\nDaten in der Konsole (F12) einsehen.');
|
|
});
|
|
|
|
let csvData = [];
|
|
|
|
function loadCSVPreview() {
|
|
const fileInput = document.getElementById('csvFile');
|
|
const file = fileInput.files[0];
|
|
|
|
if (!file) {
|
|
alert('Bitte wählen Sie eine CSV-Datei aus.');
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
const csvContent = event.target.result;
|
|
parseCSV(csvContent);
|
|
displayCSVPreview();
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function parseCSV(content) {
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
csvData = lines.map(line => {
|
|
// Einfaches CSV-Parsing (für komplexere CSVs ggf. Bibliothek verwenden)
|
|
return line.split(/[,;]/).map(cell => cell.trim().replace(/^"|"$/g, ''));
|
|
});
|
|
}
|
|
|
|
function displayCSVPreview() {
|
|
if (csvData.length === 0) return;
|
|
|
|
const previewDiv = document.getElementById('csvPreview');
|
|
const tableHead = document.getElementById('csvTableHead');
|
|
const tableBody = document.getElementById('csvTableBody');
|
|
|
|
// Header erstellen
|
|
const headers = csvData[0];
|
|
tableHead.innerHTML = '<tr>' + headers.map(h => `<th>${h}</th>`).join('') + '</tr>';
|
|
|
|
// Datenzeilen erstellen (mit editierbaren Inputs)
|
|
tableBody.innerHTML = '';
|
|
const pwdHeaderIndex = headers.findIndex(h => /pass(word)?/i.test(h));
|
|
const samHeaderIndex = headers.findIndex(h => /(sam(accountname)?)|samaccountname/i.test(h));
|
|
let foundInvalid = false;
|
|
for (let i = 1; i < csvData.length; i++) {
|
|
const row = csvData[i];
|
|
const tr = document.createElement('tr');
|
|
row.forEach((cell, index) => {
|
|
const td = document.createElement('td');
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.value = cell;
|
|
input.dataset.row = i;
|
|
input.dataset.col = index;
|
|
input.addEventListener('input', (e) => {
|
|
csvData[i][index] = e.target.value;
|
|
// live re-validate if this is password or sam column
|
|
if (index === pwdHeaderIndex || index === samHeaderIndex) {
|
|
validateCsvPasswords(headers);
|
|
}
|
|
});
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
});
|
|
tableBody.appendChild(tr);
|
|
}
|
|
|
|
// Validate password column if present
|
|
function validateCsvPasswords(headersLocal) {
|
|
const pwdIdx = pwdHeaderIndex;
|
|
const samIdx = samHeaderIndex;
|
|
foundInvalid = false;
|
|
// Clear previous highlights/messages
|
|
const rows = tableBody.querySelectorAll('tr');
|
|
rows.forEach((r, idx) => {
|
|
r.querySelectorAll('input').forEach(inp => inp.classList.remove('invalid'));
|
|
});
|
|
|
|
if (pwdIdx >= 0) {
|
|
for (let r = 0; r < rows.length; r++) {
|
|
const inputs = rows[r].querySelectorAll('input');
|
|
const pwdVal = inputs[pwdIdx] ? inputs[pwdIdx].value : '';
|
|
const samVal = (samIdx >= 0 && inputs[samIdx]) ? inputs[samIdx].value : '';
|
|
const errs = validatePasswordJS(pwdVal, samVal);
|
|
if (errs.length > 0) {
|
|
foundInvalid = true;
|
|
if (inputs[pwdIdx]) inputs[pwdIdx].classList.add('invalid');
|
|
}
|
|
}
|
|
}
|
|
|
|
const previewInfo = previewDiv.querySelector('.preview-info');
|
|
if (!previewInfo) return;
|
|
if (foundInvalid) {
|
|
previewInfo.innerHTML = '<strong>Hinweis:</strong> Einige Passwörter entsprechen nicht den Anforderungen. Bitte korrigieren Sie diese in der Tabelle bevor Sie importieren.';
|
|
} else {
|
|
previewInfo.innerHTML = '<strong>Hinweis:</strong> Sie können die Werte direkt in der Tabelle bearbeiten, bevor Sie importieren.';
|
|
}
|
|
}
|
|
|
|
// initial validation run
|
|
validateCsvPasswords(headers);
|
|
|
|
previewDiv.style.display = 'block';
|
|
}
|
|
|
|
function importCSVData() {
|
|
// Prevent import if any password entries are invalid (highlighted)
|
|
const invalids = document.querySelectorAll('#csvTableBody input.invalid');
|
|
if (invalids.length > 0) {
|
|
alert('Import abgebrochen: Es gibt ungültige Passwörter in der CSV-Vorschau. Bitte korrigieren Sie diese zuerst.');
|
|
return;
|
|
}
|
|
|
|
console.log('Importierte CSV-Daten:', csvData);
|
|
alert(`${csvData.length - 1} Benutzer erfolgreich importiert!\n\nDaten in der Konsole (F12) einsehen.`);
|
|
cancelCSVPreview();
|
|
}
|
|
|
|
function cancelCSVPreview() {
|
|
document.getElementById('csvPreview').style.display = 'none';
|
|
document.getElementById('csvFile').value = '';
|
|
csvData = [];
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|