Gitea-Repo fix
Dieser Commit ist enthalten in:
committet von
Server Deploy
Ursprung
c21be47428
Commit
623bbdf5dd
@ -48,7 +48,8 @@
|
|||||||
"Bash(__NEW_LINE__ cp /app/data/taskmate.db.backup-20260103-201322 /app/data/taskmate.db)",
|
"Bash(__NEW_LINE__ cp /app/data/taskmate.db.backup-20260103-201322 /app/data/taskmate.db)",
|
||||||
"Bash(docker system prune:*)",
|
"Bash(docker system prune:*)",
|
||||||
"Bash(docker cp:*)",
|
"Bash(docker cp:*)",
|
||||||
"Bash(mv:*)"
|
"Bash(mv:*)",
|
||||||
|
"Bash(docker-compose up:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,53 @@
|
|||||||
TASKMATE - CHANGELOG
|
TASKMATE - CHANGELOG
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
04.01.2026 - KÜRZEL-SYSTEM KORRIGIERT
|
||||||
|
================================================================================
|
||||||
|
✅ Datenbankschema: username Feld in initials umbenannt
|
||||||
|
✅ Login erfolgt jetzt über E-Mail-Adresse (Admin kann weiter mit "admin" einloggen)
|
||||||
|
✅ E-Mail ist jetzt UNIQUE und NOT NULL
|
||||||
|
✅ Kürzel (initials) kann in Admin-Benutzerverwaltung bearbeitet werden
|
||||||
|
✅ Initialen-Anzeige vereinheitlicht - verwendet immer das initials Feld
|
||||||
|
✅ Redundantes "Initialen" Feld aus Benutzerformular entfernt
|
||||||
|
✅ Login-Response korrigiert - sendet jetzt initials Feld
|
||||||
|
✅ Alle Frontend-Komponenten verwenden jetzt das initials Feld
|
||||||
|
✅ User-API sendet initials statt username
|
||||||
|
✅ Changelog-Einträge in Wissensdatenbank übertragen
|
||||||
|
✅ Git-Repository Erkennung im Coding-Tab repariert
|
||||||
|
|
||||||
|
Wichtige Änderungen:
|
||||||
|
- Login nur noch über E-Mail (nicht mehr über Kürzel)
|
||||||
|
- Kürzel sind reine Anzeige-Elemente (2 Buchstaben)
|
||||||
|
- Admin-User hat spezielle E-Mail: admin@taskmate.local
|
||||||
|
- Initialen-Anzeige jetzt überall konsistent
|
||||||
|
- Changelog-System: Alle wichtigen Änderungen in Wissen > Changelog dokumentiert
|
||||||
|
- TaskMate-Kachel in "Gitea-Repo" umbenannt, Git-Repository wird wieder erkannt
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
04.01.2025 - NUTZER-INITIALEN IN ADMIN-BEREICH
|
||||||
|
================================================================================
|
||||||
|
✅ Initialen-Anzeige in Nutzer-Kreisen implementiert
|
||||||
|
✅ Intelligente Initialen-Generierung aus E-Mail oder Namen
|
||||||
|
✅ CSS für bessere Darstellung angepasst
|
||||||
|
✅ Benutzerdefinierte Initialen-Eingabe hinzugefügt
|
||||||
|
✅ Datenbank-Migration für custom_initials Spalte
|
||||||
|
|
||||||
|
Neue Features:
|
||||||
|
- Initialen werden jetzt aus dem Kürzel-Feld (username) angezeigt
|
||||||
|
- Kürzel können über die Admin-Einstellungen geändert werden
|
||||||
|
- Vereinfachte Logik: Kürzel = Initialen im Nutzerkreis
|
||||||
|
|
||||||
|
Initialen-Logik:
|
||||||
|
1. Username/Kürzel wird als Initialen angezeigt (z.B. HG, MO)
|
||||||
|
2. Fallback → Erste zwei Buchstaben des Display Names
|
||||||
|
|
||||||
|
Benutzer-Kürzel aktualisiert:
|
||||||
|
- hendrik_gebhardt@gmx.de → HG
|
||||||
|
- momohomma@googlemail.com → MO
|
||||||
|
|
||||||
|
Cache-Version auf 194 erhöht.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
03.01.2025 - GIT-INTEGRATION CODING-KACHELN
|
03.01.2025 - GIT-INTEGRATION CODING-KACHELN
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|||||||
105
SSH_CLAUDE_ANLEITUNG.txt
Normale Datei
105
SSH_CLAUDE_ANLEITUNG.txt
Normale Datei
@ -0,0 +1,105 @@
|
|||||||
|
# Anleitung: Claude über SSH aus TaskMate starten
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
- SSH-Client auf dem eigenen Computer
|
||||||
|
- Zugang zu TaskMate
|
||||||
|
|
||||||
|
## Schritt 1: SSH-Schlüsselpaar erstellen
|
||||||
|
|
||||||
|
### Windows (PowerShell/Terminal):
|
||||||
|
```
|
||||||
|
ssh-keygen -t rsa -b 4096 -f %USERPROFILE%\.ssh\taskmate_claude
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mac/Linux:
|
||||||
|
```
|
||||||
|
ssh-keygen -t rsa -b 4096 -f ~/.ssh/taskmate_claude
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wichtig:**
|
||||||
|
- Bei der Frage nach einem Passwort ein sicheres Passwort vergeben und merken
|
||||||
|
- Es werden zwei Dateien erstellt:
|
||||||
|
- `taskmate_claude` (privater Schlüssel - GEHEIM HALTEN!)
|
||||||
|
- `taskmate_claude.pub` (öffentlicher Schlüssel)
|
||||||
|
|
||||||
|
## Schritt 2: Öffentlichen Schlüssel übermitteln
|
||||||
|
|
||||||
|
Den Inhalt der Datei `taskmate_claude.pub` an den Administrator senden:
|
||||||
|
|
||||||
|
### Windows:
|
||||||
|
```
|
||||||
|
type %USERPROFILE%\.ssh\taskmate_claude.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mac/Linux:
|
||||||
|
```
|
||||||
|
cat ~/.ssh/taskmate_claude.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Administrator muss diesen öffentlichen Schlüssel auf dem Server hinterlegen.
|
||||||
|
|
||||||
|
## Schritt 3: SSH-Konfiguration einrichten
|
||||||
|
|
||||||
|
Eine Konfigurationsdatei erstellen für einfacheren Zugriff:
|
||||||
|
|
||||||
|
### Windows:
|
||||||
|
Datei `%USERPROFILE%\.ssh\config` bearbeiten
|
||||||
|
|
||||||
|
### Mac/Linux:
|
||||||
|
Datei `~/.ssh/config` bearbeiten
|
||||||
|
|
||||||
|
Folgenden Inhalt hinzufügen:
|
||||||
|
```
|
||||||
|
Host taskmate-claude
|
||||||
|
HostName [SERVER-IP oder DOMAIN]
|
||||||
|
User [BENUTZERNAME]
|
||||||
|
Port [SSH-PORT, standard 22]
|
||||||
|
IdentityFile ~/.ssh/taskmate_claude
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hinweis:** Die Werte in eckigen Klammern müssen vom Administrator bereitgestellt werden.
|
||||||
|
|
||||||
|
## Schritt 4: Verbindung testen
|
||||||
|
|
||||||
|
```
|
||||||
|
ssh taskmate-claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Bei der ersten Verbindung:
|
||||||
|
1. Die Fingerprint-Warnung mit "yes" bestätigen
|
||||||
|
2. Das beim Erstellen des Schlüssels vergebene Passwort eingeben
|
||||||
|
|
||||||
|
## Schritt 5: Integration in TaskMate
|
||||||
|
|
||||||
|
Nach erfolgreicher SSH-Konfiguration:
|
||||||
|
1. In TaskMate einloggen
|
||||||
|
2. Zur Kachel "TaskMate -> Claude starten" navigieren
|
||||||
|
3. Die Funktion sollte nun verfügbar sein
|
||||||
|
|
||||||
|
## Sicherheitshinweise
|
||||||
|
|
||||||
|
- **Privaten Schlüssel niemals weitergeben!** (Datei ohne .pub Endung)
|
||||||
|
- Das Passwort für den SSH-Schlüssel sicher aufbewahren
|
||||||
|
- Bei Verlust des privaten Schlüssels muss ein neues Schlüsselpaar erstellt werden
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### "Permission denied"
|
||||||
|
- Prüfen ob der richtige Schlüssel verwendet wird
|
||||||
|
- Prüfen ob das Passwort korrekt ist
|
||||||
|
|
||||||
|
### "Connection refused"
|
||||||
|
- Server-IP/Domain und Port prüfen
|
||||||
|
- Firewall-Einstellungen prüfen
|
||||||
|
|
||||||
|
### "Host key verification failed"
|
||||||
|
- Die Datei `~/.ssh/known_hosts` prüfen
|
||||||
|
- Ggf. alten Eintrag für den Server entfernen
|
||||||
|
|
||||||
|
## Benötigte Informationen vom Administrator
|
||||||
|
|
||||||
|
Der Administrator muss folgende Daten bereitstellen:
|
||||||
|
1. Server-IP oder Domain
|
||||||
|
2. SSH-Port (falls nicht Standard 22)
|
||||||
|
3. Benutzername für SSH-Zugang
|
||||||
|
4. Bestätigung dass der öffentliche Schlüssel hinterlegt wurde
|
||||||
@ -170,6 +170,63 @@ function createTables() {
|
|||||||
logger.info('Migration: repositories_base_path Spalte zu users hinzugefuegt');
|
logger.info('Migration: repositories_base_path Spalte zu users hinzugefuegt');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration: Add custom_initials column to users
|
||||||
|
const hasCustomInitials = userColumns.some(col => col.name === 'custom_initials');
|
||||||
|
if (!hasCustomInitials) {
|
||||||
|
db.exec("ALTER TABLE users ADD COLUMN custom_initials TEXT");
|
||||||
|
logger.info('Migration: custom_initials Spalte zu users hinzugefuegt');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration: Add initials column and prepare email
|
||||||
|
const hasInitials = userColumns.some(col => col.name === 'initials');
|
||||||
|
if (!hasInitials && userColumns.some(col => col.name === 'username')) {
|
||||||
|
logger.info('Migration: Füge initials Spalte hinzu und bereite E-Mail vor');
|
||||||
|
|
||||||
|
// Zuerst Daten vorbereiten
|
||||||
|
const users = db.prepare('SELECT id, username, email, custom_initials FROM users').all();
|
||||||
|
for (const user of users) {
|
||||||
|
// Stelle sicher dass jeder Benutzer eine E-Mail hat
|
||||||
|
if (!user.email || user.email === '') {
|
||||||
|
if (user.username === 'admin') {
|
||||||
|
// Admin bekommt eine spezielle E-Mail
|
||||||
|
db.prepare('UPDATE users SET email = ? WHERE id = ?').run('admin@taskmate.local', user.id);
|
||||||
|
} else if (user.username.includes('@')) {
|
||||||
|
// Username enthält bereits E-Mail (wie bei bestehenden Benutzern)
|
||||||
|
db.prepare('UPDATE users SET email = ? WHERE id = ?').run(user.username, user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialen setzen (aus custom_initials oder generieren)
|
||||||
|
if (!user.custom_initials || user.custom_initials === '') {
|
||||||
|
let initials = 'XX';
|
||||||
|
if (user.username === 'admin') {
|
||||||
|
initials = 'AD';
|
||||||
|
} else if (user.email || user.username.includes('@')) {
|
||||||
|
// Generiere Initialen aus E-Mail
|
||||||
|
const emailPart = (user.email || user.username).split('@')[0];
|
||||||
|
if (emailPart.includes('_')) {
|
||||||
|
const parts = emailPart.split('_');
|
||||||
|
initials = (parts[0][0] + parts[1][0]).toUpperCase();
|
||||||
|
} else if (emailPart.includes('.')) {
|
||||||
|
const parts = emailPart.split('.');
|
||||||
|
initials = (parts[0][0] + parts[1][0]).toUpperCase();
|
||||||
|
} else {
|
||||||
|
initials = emailPart.substring(0, 2).toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.prepare('UPDATE users SET custom_initials = ? WHERE id = ?').run(initials, user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue initials Spalte hinzufügen
|
||||||
|
db.exec("ALTER TABLE users ADD COLUMN initials TEXT");
|
||||||
|
|
||||||
|
// Daten von custom_initials nach initials kopieren
|
||||||
|
db.exec("UPDATE users SET initials = custom_initials");
|
||||||
|
|
||||||
|
logger.info('Migration: initials Spalte hinzugefügt und E-Mail-Daten vorbereitet');
|
||||||
|
}
|
||||||
|
|
||||||
// Proposals (Vorschlaege)
|
// Proposals (Vorschlaege)
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS proposals (
|
CREATE TABLE IF NOT EXISTS proposals (
|
||||||
@ -536,7 +593,7 @@ async function createDefaultUsers() {
|
|||||||
const existingUsers = db.prepare('SELECT COUNT(*) as count FROM users').get();
|
const existingUsers = db.prepare('SELECT COUNT(*) as count FROM users').get();
|
||||||
|
|
||||||
// Admin-Passwort korrigieren (falls aus .env verschieden)
|
// Admin-Passwort korrigieren (falls aus .env verschieden)
|
||||||
const adminExists = db.prepare('SELECT id, password_hash FROM users WHERE username = ? AND role = ?').get('admin', 'admin');
|
const adminExists = db.prepare('SELECT id, password_hash FROM users WHERE email = ? AND role = ?').get('admin@taskmate.local', 'admin');
|
||||||
if (adminExists) {
|
if (adminExists) {
|
||||||
const correctAdminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
const correctAdminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
|
|||||||
@ -31,8 +31,8 @@ router.get('/users', (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const users = db.prepare(`
|
const users = db.prepare(`
|
||||||
SELECT id, username, display_name, color, role, permissions, email,
|
SELECT id, email, initials, display_name, color, role, permissions,
|
||||||
created_at, last_login, failed_attempts, locked_until
|
created_at, last_login, failed_attempts, locked_until, custom_initials
|
||||||
FROM users
|
FROM users
|
||||||
ORDER BY id
|
ORDER BY id
|
||||||
`).all();
|
`).all();
|
||||||
@ -55,10 +55,10 @@ router.get('/users', (req, res) => {
|
|||||||
*/
|
*/
|
||||||
router.post('/users', async (req, res) => {
|
router.post('/users', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { username, password, displayName, email, role, permissions } = req.body;
|
const { initials, password, displayName, email, role, permissions } = req.body;
|
||||||
|
|
||||||
// Validierung
|
// Validierung
|
||||||
if (!username || !password || !displayName || !email) {
|
if (!initials || !password || !displayName || !email) {
|
||||||
return res.status(400).json({ error: 'Kürzel, Passwort, Anzeigename und E-Mail erforderlich' });
|
return res.status(400).json({ error: 'Kürzel, Passwort, Anzeigename und E-Mail erforderlich' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +69,8 @@ router.post('/users', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Kürzel muss genau 2 Buchstaben sein
|
// Kürzel muss genau 2 Buchstaben sein
|
||||||
const usernameUpper = username.toUpperCase();
|
const initialsUpper = initials.toUpperCase();
|
||||||
if (!/^[A-Z]{2}$/.test(usernameUpper)) {
|
if (!/^[A-Z]{2}$/.test(initialsUpper)) {
|
||||||
return res.status(400).json({ error: 'Kürzel muss genau 2 Buchstaben sein (z.B. HG)' });
|
return res.status(400).json({ error: 'Kürzel muss genau 2 Buchstaben sein (z.B. HG)' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,18 +80,18 @@ router.post('/users', async (req, res) => {
|
|||||||
|
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
|
|
||||||
// Prüfen ob Kürzel bereits existiert
|
|
||||||
const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(usernameUpper);
|
|
||||||
if (existing) {
|
|
||||||
return res.status(400).json({ error: 'Kürzel bereits vergeben' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prüfen ob E-Mail bereits existiert
|
// Prüfen ob E-Mail bereits existiert
|
||||||
const existingEmail = db.prepare('SELECT id FROM users WHERE email = ?').get(email.toLowerCase());
|
const existingEmail = db.prepare('SELECT id FROM users WHERE email = ?').get(email.toLowerCase());
|
||||||
if (existingEmail) {
|
if (existingEmail) {
|
||||||
return res.status(400).json({ error: 'E-Mail bereits vergeben' });
|
return res.status(400).json({ error: 'E-Mail bereits vergeben' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Kürzel bereits existiert
|
||||||
|
const existingInitials = db.prepare('SELECT id FROM users WHERE initials = ?').get(initialsUpper);
|
||||||
|
if (existingInitials) {
|
||||||
|
return res.status(400).json({ error: 'Kürzel bereits vergeben' });
|
||||||
|
}
|
||||||
|
|
||||||
// Passwort hashen
|
// Passwort hashen
|
||||||
const passwordHash = await bcrypt.hash(password, 12);
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
@ -100,23 +100,24 @@ router.post('/users', async (req, res) => {
|
|||||||
|
|
||||||
// Benutzer erstellen
|
// Benutzer erstellen
|
||||||
const result = db.prepare(`
|
const result = db.prepare(`
|
||||||
INSERT INTO users (username, password_hash, display_name, color, role, permissions, email)
|
INSERT INTO users (email, initials, password_hash, display_name, color, role, permissions, custom_initials)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`).run(
|
`).run(
|
||||||
usernameUpper,
|
email.toLowerCase(),
|
||||||
|
initialsUpper,
|
||||||
passwordHash,
|
passwordHash,
|
||||||
displayName,
|
displayName,
|
||||||
defaultColor,
|
defaultColor,
|
||||||
role || 'user',
|
role || 'user',
|
||||||
JSON.stringify(permissions || []),
|
JSON.stringify(permissions || []),
|
||||||
email.toLowerCase()
|
initialsUpper
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`Admin ${req.user.username} hat Benutzer ${usernameUpper} erstellt`);
|
logger.info(`Admin ${req.user.email} hat Benutzer ${initialsUpper} erstellt`);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
id: result.lastInsertRowid,
|
id: result.lastInsertRowid,
|
||||||
username: usernameUpper,
|
initials: initialsUpper,
|
||||||
displayName,
|
displayName,
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
color: defaultColor,
|
color: defaultColor,
|
||||||
@ -135,7 +136,7 @@ router.post('/users', async (req, res) => {
|
|||||||
router.put('/users/:id', async (req, res) => {
|
router.put('/users/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = parseInt(req.params.id);
|
const userId = parseInt(req.params.id);
|
||||||
const { displayName, color, role, permissions, password, unlockAccount, email } = req.body;
|
const { displayName, color, role, permissions, password, unlockAccount, email, initials } = req.body;
|
||||||
|
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
|
|
||||||
@ -206,6 +207,24 @@ router.put('/users/:id', async (req, res) => {
|
|||||||
params.push(email.toLowerCase());
|
params.push(email.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initials !== undefined) {
|
||||||
|
// Validierung: Genau 2 Buchstaben
|
||||||
|
const initialsUpper = initials.toUpperCase();
|
||||||
|
if (!/^[A-Z]{2}$/.test(initialsUpper)) {
|
||||||
|
return res.status(400).json({ error: 'Kürzel muss genau 2 Buchstaben sein (z.B. HG)' });
|
||||||
|
}
|
||||||
|
// Prüfen ob Kürzel bereits von anderem Benutzer verwendet wird
|
||||||
|
const existingInitials = db.prepare('SELECT id FROM users WHERE initials = ? AND id != ?').get(initialsUpper, userId);
|
||||||
|
if (existingInitials) {
|
||||||
|
return res.status(400).json({ error: 'Kürzel bereits vergeben' });
|
||||||
|
}
|
||||||
|
updates.push('initials = ?');
|
||||||
|
params.push(initialsUpper);
|
||||||
|
// Auch custom_initials aktualisieren für Kompatibilität
|
||||||
|
updates.push('custom_initials = ?');
|
||||||
|
params.push(initialsUpper);
|
||||||
|
}
|
||||||
|
|
||||||
if (updates.length === 0) {
|
if (updates.length === 0) {
|
||||||
return res.status(400).json({ error: 'Keine Änderungen angegeben' });
|
return res.status(400).json({ error: 'Keine Änderungen angegeben' });
|
||||||
}
|
}
|
||||||
@ -218,7 +237,7 @@ router.put('/users/:id', async (req, res) => {
|
|||||||
// Aktualisierten Benutzer zurueckgeben
|
// Aktualisierten Benutzer zurueckgeben
|
||||||
const updatedUser = db.prepare(`
|
const updatedUser = db.prepare(`
|
||||||
SELECT id, username, display_name, color, role, permissions, email,
|
SELECT id, username, display_name, color, role, permissions, email,
|
||||||
created_at, last_login, failed_attempts, locked_until
|
created_at, last_login, failed_attempts, locked_until, custom_initials
|
||||||
FROM users WHERE id = ?
|
FROM users WHERE id = ?
|
||||||
`).get(userId);
|
`).get(userId);
|
||||||
|
|
||||||
|
|||||||
@ -35,10 +35,15 @@ router.post('/login', async (req, res) => {
|
|||||||
|
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
|
|
||||||
// Benutzer suchen: Zuerst nach Username "admin", dann nach E-Mail
|
// Benutzer suchen: Admin kann mit "admin" einloggen, alle anderen mit E-Mail
|
||||||
let user;
|
let user;
|
||||||
// User per Username suchen (kann E-Mail-Adresse oder admin sein)
|
if (username.toLowerCase() === 'admin') {
|
||||||
user = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
|
// Admin-Login über spezielle E-Mail
|
||||||
|
user = db.prepare('SELECT * FROM users WHERE email = ? AND role = ?').get('admin@taskmate.local', 'admin');
|
||||||
|
} else {
|
||||||
|
// Normale User loggen sich mit E-Mail ein
|
||||||
|
user = db.prepare('SELECT * FROM users WHERE email = ?').get(username);
|
||||||
|
}
|
||||||
|
|
||||||
// Audit-Log Eintrag vorbereiten
|
// Audit-Log Eintrag vorbereiten
|
||||||
const logAttempt = (userId, success) => {
|
const logAttempt = (userId, success) => {
|
||||||
@ -131,7 +136,8 @@ router.post('/login', async (req, res) => {
|
|||||||
csrfToken,
|
csrfToken,
|
||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
email: user.email,
|
||||||
|
initials: user.initials,
|
||||||
displayName: user.display_name,
|
displayName: user.display_name,
|
||||||
color: user.color,
|
color: user.color,
|
||||||
role: user.role || 'user',
|
role: user.role || 'user',
|
||||||
@ -345,14 +351,15 @@ router.get('/users', authenticateToken, (req, res) => {
|
|||||||
const db = getDb();
|
const db = getDb();
|
||||||
// Nur regulaere Benutzer (nicht Admins) fuer Aufgaben-Zuweisung
|
// Nur regulaere Benutzer (nicht Admins) fuer Aufgaben-Zuweisung
|
||||||
const users = db.prepare(`
|
const users = db.prepare(`
|
||||||
SELECT id, username, display_name, color
|
SELECT id, email, initials, display_name, color
|
||||||
FROM users
|
FROM users
|
||||||
WHERE role != 'admin' OR role IS NULL
|
WHERE role != 'admin' OR role IS NULL
|
||||||
`).all();
|
`).all();
|
||||||
|
|
||||||
res.json(users.map(u => ({
|
res.json(users.map(u => ({
|
||||||
id: u.id,
|
id: u.id,
|
||||||
username: u.username,
|
email: u.email,
|
||||||
|
initials: u.initials,
|
||||||
displayName: u.display_name,
|
displayName: u.display_name,
|
||||||
color: u.color
|
color: u.color
|
||||||
})));
|
})));
|
||||||
|
|||||||
BIN
data/taskmate.db
BIN
data/taskmate.db
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
@ -108,6 +108,14 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-user-initials {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-user-info {
|
.admin-user-info {
|
||||||
|
|||||||
@ -150,15 +150,34 @@ class AdminManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt die Initialen des Benutzers
|
||||||
|
*/
|
||||||
|
getInitials(user) {
|
||||||
|
// Verwende primär das initials Feld
|
||||||
|
if (user.initials) {
|
||||||
|
return user.initials.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback auf custom_initials für Kompatibilität
|
||||||
|
if (user.custom_initials) {
|
||||||
|
return user.custom_initials.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letzer Fallback
|
||||||
|
return 'XX';
|
||||||
|
}
|
||||||
|
|
||||||
renderUserCard(user) {
|
renderUserCard(user) {
|
||||||
const initial = (user.display_name || user.username).charAt(0).toUpperCase();
|
const initials = this.getInitials(user);
|
||||||
|
console.log('Rendering user card for:', user.email || user.username, '- Initials:', initials);
|
||||||
const isLocked = user.locked_until && new Date(user.locked_until) > new Date();
|
const isLocked = user.locked_until && new Date(user.locked_until) > new Date();
|
||||||
const permissions = user.permissions || [];
|
const permissions = user.permissions || [];
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="admin-user-card" data-user-id="${user.id}">
|
<div class="admin-user-card" data-user-id="${user.id}">
|
||||||
<div class="admin-user-avatar" style="background-color: ${user.color || '#808080'}">
|
<div class="admin-user-avatar" style="background-color: ${user.color || '#808080'}">
|
||||||
${initial}
|
<span class="admin-user-initials">${initials}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-user-info">
|
<div class="admin-user-info">
|
||||||
<div class="admin-user-name">${this.escapeHtml(user.display_name)}</div>
|
<div class="admin-user-name">${this.escapeHtml(user.display_name)}</div>
|
||||||
@ -236,8 +255,8 @@ class AdminManager {
|
|||||||
this.currentEditUser = user;
|
this.currentEditUser = user;
|
||||||
this.userModalTitle.textContent = 'Benutzer bearbeiten';
|
this.userModalTitle.textContent = 'Benutzer bearbeiten';
|
||||||
this.editUserId.value = user.id;
|
this.editUserId.value = user.id;
|
||||||
this.usernameInput.value = user.username;
|
this.usernameInput.value = user.initials || '';
|
||||||
this.usernameInput.disabled = true; // Username cannot be changed
|
this.usernameInput.disabled = false; // Initials can be changed
|
||||||
this.displayNameInput.value = user.display_name;
|
this.displayNameInput.value = user.display_name;
|
||||||
this.emailInput.value = user.email || '';
|
this.emailInput.value = user.email || '';
|
||||||
this.emailInput.disabled = false;
|
this.emailInput.disabled = false;
|
||||||
@ -285,9 +304,8 @@ class AdminManager {
|
|||||||
permissions: this.roleSelect.value === 'admin' ? [] : this.getSelectedPermissions()
|
permissions: this.roleSelect.value === 'admin' ? [] : this.getSelectedPermissions()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isEdit) {
|
// Kürzel immer mitschicken (bei Create und Update)
|
||||||
data.username = this.usernameInput.value.trim().toUpperCase();
|
data.initials = this.usernameInput.value.trim().toUpperCase();
|
||||||
}
|
|
||||||
|
|
||||||
if (this.passwordInput.value) {
|
if (this.passwordInput.value) {
|
||||||
data.password = this.passwordInput.value;
|
data.password = this.passwordInput.value;
|
||||||
|
|||||||
@ -135,12 +135,14 @@ class AuthManager {
|
|||||||
// Get user initials
|
// Get user initials
|
||||||
getUserInitials() {
|
getUserInitials() {
|
||||||
if (!this.user) return '?';
|
if (!this.user) return '?';
|
||||||
|
|
||||||
return this.user.username
|
// Verwende das initials Feld direkt
|
||||||
.split(' ')
|
if (this.user.initials) {
|
||||||
.map(part => part.charAt(0).toUpperCase())
|
return this.user.initials.toUpperCase();
|
||||||
.slice(0, 2)
|
}
|
||||||
.join('');
|
|
||||||
|
// Fallback für alte Daten
|
||||||
|
return '??';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user color
|
// Get user color
|
||||||
|
|||||||
@ -372,6 +372,7 @@ class BoardManager {
|
|||||||
const currentUser = users.find(u => u.id === assignee.id);
|
const currentUser = users.find(u => u.id === assignee.id);
|
||||||
const color = currentUser?.color || assignee.color || '#888';
|
const color = currentUser?.color || assignee.color || '#888';
|
||||||
const name = currentUser?.display_name || assignee.display_name || assignee.username || 'Benutzer';
|
const name = currentUser?.display_name || assignee.display_name || assignee.username || 'Benutzer';
|
||||||
|
const initials = currentUser?.initials || assignee.initials || getInitials(name);
|
||||||
|
|
||||||
const avatar = createElement('span', {
|
const avatar = createElement('span', {
|
||||||
className: 'avatar task-assignee-avatar stacked',
|
className: 'avatar task-assignee-avatar stacked',
|
||||||
@ -380,7 +381,7 @@ class BoardManager {
|
|||||||
zIndex: 10 - index
|
zIndex: 10 - index
|
||||||
},
|
},
|
||||||
title: name
|
title: name
|
||||||
}, [getInitials(name)]);
|
}, [initials]);
|
||||||
assigneesContainer.appendChild(avatar);
|
assigneesContainer.appendChild(avatar);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -868,7 +868,7 @@ class CalendarViewManager {
|
|||||||
if (task.assignees && task.assignees.length > 0) {
|
if (task.assignees && task.assignees.length > 0) {
|
||||||
task.assignees.forEach(assignee => {
|
task.assignees.forEach(assignee => {
|
||||||
const currentUser = users.find(u => u.id === assignee.id);
|
const currentUser = users.find(u => u.id === assignee.id);
|
||||||
const initials = currentUser?.username || assignee.username || '??';
|
const initials = currentUser?.initials || assignee.initials || '??';
|
||||||
const color = currentUser?.color || assignee.color || '#6B7280';
|
const color = currentUser?.color || assignee.color || '#6B7280';
|
||||||
badges.push({ initials, color });
|
badges.push({ initials, color });
|
||||||
});
|
});
|
||||||
@ -879,7 +879,7 @@ class CalendarViewManager {
|
|||||||
if (task.assignedTo) {
|
if (task.assignedTo) {
|
||||||
const user = users.find(u => u.id === task.assignedTo);
|
const user = users.find(u => u.id === task.assignedTo);
|
||||||
if (user) {
|
if (user) {
|
||||||
const initials = user.username || '??';
|
const initials = user.initials || '??';
|
||||||
badges.push({ initials, color: user.color || '#6B7280' });
|
badges.push({ initials, color: user.color || '#6B7280' });
|
||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -251,7 +251,7 @@ class DashboardManager {
|
|||||||
createElement('span', {
|
createElement('span', {
|
||||||
className: 'avatar avatar-sm',
|
className: 'avatar avatar-sm',
|
||||||
style: { backgroundColor: task.assignee.color || '#888' }
|
style: { backgroundColor: task.assignee.color || '#888' }
|
||||||
}, [getInitials(task.assignee.username)])
|
}, [task.assignee.initials || getInitials(task.assignee.username || '??')])
|
||||||
]) : null
|
]) : null
|
||||||
].filter(Boolean));
|
].filter(Boolean));
|
||||||
|
|
||||||
|
|||||||
@ -443,7 +443,7 @@ class ListViewManager {
|
|||||||
className: 'avatar',
|
className: 'avatar',
|
||||||
style: { backgroundColor: user.color || '#6366F1' },
|
style: { backgroundColor: user.color || '#6366F1' },
|
||||||
title: user.displayName // Tooltip zeigt Name beim Hover
|
title: user.displayName // Tooltip zeigt Name beim Hover
|
||||||
}, [getInitials(user.displayName)]);
|
}, [user.initials || getInitials(user.displayName)]);
|
||||||
avatarContainer.appendChild(avatar);
|
avatarContainer.appendChild(avatar);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -818,7 +818,7 @@ class TaskModalManager {
|
|||||||
const avatar = createElement('div', {
|
const avatar = createElement('div', {
|
||||||
class: 'multi-select-option-avatar',
|
class: 'multi-select-option-avatar',
|
||||||
style: `background-color: ${user.color || '#6366F1'}`
|
style: `background-color: ${user.color || '#6366F1'}`
|
||||||
}, [getInitials(user.display_name || user.username)]);
|
}, [user.initials || getInitials(user.display_name || user.username)]);
|
||||||
|
|
||||||
const name = createElement('span', { class: 'multi-select-option-name' }, [user.display_name || user.username]);
|
const name = createElement('span', { class: 'multi-select-option-name' }, [user.display_name || user.username]);
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* Offline support and caching
|
* Offline support and caching
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE_VERSION = '189';
|
const CACHE_VERSION = '197';
|
||||||
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
|
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
|
||||||
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
|
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
|
||||||
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;
|
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;
|
||||||
|
|||||||
5977
logs/app.log
5977
logs/app.log
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
In neuem Issue referenzieren
Einen Benutzer sperren