/** * TASKMATE - Coding Manager * ========================= * Verwaltung von Server-Anwendungen mit Claude/Codex Integration */ import api from './api.js'; import { escapeHtml } from './utils.js'; // Toast-Funktion (verwendet das globale Toast-Event) function showToast(message, type = 'info') { window.dispatchEvent(new CustomEvent('toast:show', { detail: { message, type } })); } // Basis-Pfad für alle Anwendungen auf dem Server const BASE_PATH = '/home/claude-dev'; // Farb-Presets für Anwendungen const COLOR_PRESETS = [ '#4F46E5', // Indigo '#7C3AED', // Violet '#EC4899', // Pink '#EF4444', // Red '#F59E0B', // Amber '#10B981', // Emerald '#06B6D4', // Cyan '#3B82F6', // Blue '#8B5CF6', // Purple '#6366F1' // Indigo Light ]; class CodingManager { constructor() { this.initialized = false; this.directories = []; this.refreshInterval = null; this.editingDirectory = null; this.giteaRepos = []; } /** * Manager initialisieren */ async init() { if (this.initialized) return; this.bindEvents(); this.initialized = true; console.log('[CodingManager] Initialisiert'); } /** * Event-Listener binden */ bindEvents() { // Add-Button const addBtn = document.getElementById('add-coding-directory-btn'); if (addBtn) { addBtn.addEventListener('click', () => this.openModal()); } // Modal Events const modal = document.getElementById('coding-modal'); if (modal) { // Close-Button modal.querySelector('.modal-close')?.addEventListener('click', () => this.closeModal()); modal.querySelector('.modal-cancel')?.addEventListener('click', () => this.closeModal()); // Save-Button document.getElementById('coding-save-btn')?.addEventListener('click', () => this.handleSave()); // Delete-Button document.getElementById('coding-delete-btn')?.addEventListener('click', () => this.handleDelete()); // Backdrop-Click modal.addEventListener('click', (e) => { if (e.target === modal) this.closeModal(); }); // Farb-Presets this.renderColorPresets(); // Name-Eingabe (kein Pfad-Preview mehr nötig) const nameInput = document.getElementById('coding-name'); // CLAUDE.md Link Event const claudeLink = document.getElementById('coding-claude-link'); if (claudeLink) { claudeLink.addEventListener('click', () => this.openClaudeModal()); } } // Command-Modal Events const cmdModal = document.getElementById('coding-command-modal'); if (cmdModal) { cmdModal.querySelector('.modal-close')?.addEventListener('click', () => this.closeCommandModal()); cmdModal.addEventListener('click', (e) => { if (e.target === cmdModal) this.closeCommandModal(); }); document.getElementById('coding-copy-command')?.addEventListener('click', () => this.copyCommand()); } // Gitea-Repo Dropdown laden bei Details-Toggle const giteaSection = document.querySelector('.coding-gitea-section'); if (giteaSection) { giteaSection.addEventListener('toggle', (e) => { if (e.target.open) { this.loadGiteaRepos(); } }); } // CLAUDE.md Modal Events const claudeModal = document.getElementById('claude-md-modal'); if (claudeModal) { // Close-Button claudeModal.querySelector('.modal-close')?.addEventListener('click', () => this.closeClaudeModal()); // Backdrop-Click claudeModal.addEventListener('click', (e) => { if (e.target === claudeModal) this.closeClaudeModal(); }); // ESC-Taste document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !claudeModal.classList.contains('hidden')) { this.closeClaudeModal(); } }); } } /** * Farb-Presets rendern */ renderColorPresets() { const container = document.getElementById('coding-color-presets'); if (!container) return; container.innerHTML = COLOR_PRESETS.map(color => ` `).join('') + ` `; // Event-Listener für Presets container.querySelectorAll('.color-preset').forEach(btn => { btn.addEventListener('click', () => { container.querySelectorAll('.color-preset').forEach(b => b.classList.remove('selected')); btn.classList.add('selected'); document.getElementById('coding-color-custom').value = btn.dataset.color; }); }); // Custom Color Input document.getElementById('coding-color-custom')?.addEventListener('input', (e) => { container.querySelectorAll('.color-preset').forEach(b => b.classList.remove('selected')); }); } // switchClaudeTab entfernt - CLAUDE.md ist jetzt nur readonly /** * CLAUDE.md Link aktualisieren */ updateClaudeLink(content, projectName) { const link = document.getElementById('coding-claude-link'); const textSpan = link?.querySelector('.claude-text'); // Debug entfernt if (!link || !textSpan) { console.error('CLAUDE.md link elements not found!'); return; } // Content für Modal speichern this.currentClaudeContent = content; this.currentProjectName = projectName; if (content) { link.disabled = false; textSpan.textContent = `CLAUDE.md anzeigen (${Math.round(content.length / 1024)}KB)`; } else { link.disabled = true; textSpan.textContent = 'Keine CLAUDE.md vorhanden'; } } /** * CLAUDE.md Modal öffnen */ openClaudeModal() { if (!this.currentClaudeContent) { console.warn('No CLAUDE.md content to display'); return; } const modal = document.getElementById('claude-md-modal'); const overlay = document.querySelector('.modal-overlay'); const content = document.getElementById('claude-md-content'); const title = modal?.querySelector('.modal-header h3'); if (!modal || !content) { console.error('CLAUDE.md modal elements not found!'); return; } // Titel setzen if (title && this.currentProjectName) { title.textContent = `CLAUDE.md - ${this.currentProjectName}`; } // Content setzen content.textContent = this.currentClaudeContent; // Modal anzeigen modal.classList.remove('hidden'); modal.classList.add('visible'); if (overlay) { overlay.classList.remove('hidden'); overlay.classList.add('visible'); } // Modal opened } /** * CLAUDE.md Modal schließen */ closeClaudeModal() { const modal = document.getElementById('claude-md-modal'); const overlay = document.querySelector('.modal-overlay'); if (modal) { modal.classList.remove('visible'); setTimeout(() => modal.classList.add('hidden'), 200); } if (overlay) { overlay.classList.remove('visible'); setTimeout(() => overlay.classList.add('hidden'), 200); } // Modal closed } /** * Gitea-Repositories laden */ async loadGiteaRepos() { try { const select = document.getElementById('coding-gitea-repo'); if (!select) return; // Lade-Indikator select.innerHTML = ''; const result = await api.getGiteaRepositories(); console.log('Gitea API Response:', result); this.giteaRepos = result?.repositories || []; select.innerHTML = '' + this.giteaRepos.map(repo => ` `).join(''); // Wenn Editing, vorhandenen Wert setzen if (this.editingDirectory?.giteaRepoUrl) { select.value = this.editingDirectory.giteaRepoUrl; } } catch (error) { console.error('Fehler beim Laden der Gitea-Repos:', error); const select = document.getElementById('coding-gitea-repo'); if (select) { select.innerHTML = ''; } } } /** * Anwendungen laden */ async loadDirectories() { try { this.directories = await api.getCodingDirectories(); this.render(); } catch (error) { console.error('Fehler beim Laden der Anwendungen:', error); showToast('Fehler beim Laden der Anwendungen', 'error'); } } /** * View rendern */ render() { const grid = document.getElementById('coding-grid'); const empty = document.getElementById('coding-empty'); if (!grid) return; if (this.directories.length === 0) { grid.innerHTML = ''; grid.classList.add('hidden'); empty?.classList.remove('hidden'); return; } empty?.classList.add('hidden'); grid.classList.remove('hidden'); grid.innerHTML = this.directories.map(dir => this.renderTile(dir)).join(''); // Event-Listener für Tiles this.bindTileEvents(); // Git-Status für jede Anwendung laden this.directories.forEach(dir => this.updateTileStatus(dir.id)); } /** * Einzelne Kachel rendern */ renderTile(directory) { const hasGitea = !!directory.giteaRepoUrl; return `
📁
${escapeHtml(directory.name)}
${escapeHtml(directory.localPath)}
${directory.description ? `
${escapeHtml(directory.description)}
` : ''} ${directory.hasCLAUDEmd ? '
CLAUDE.md
' : ''}
Lade...
${hasGitea ? `
` : ''}
`; } /** * Event-Listener für Tiles binden */ bindTileEvents() { // Kachel-Klick für Modal document.querySelectorAll('.coding-tile').forEach(tile => { tile.addEventListener('click', (e) => { // Nicht triggern wenn Button-Kind geklickt wird if (e.target.closest('button')) return; const id = parseInt(tile.dataset.id); const dir = this.directories.find(d => d.id === id); if (dir) this.openModal(dir); }); }); // Claude-Buttons document.querySelectorAll('.btn-claude').forEach(btn => { btn.addEventListener('click', () => this.launchClaude(btn.dataset.path)); }); // Git-Buttons document.querySelectorAll('.coding-git-fetch').forEach(btn => { btn.addEventListener('click', () => this.gitFetch(parseInt(btn.dataset.id))); }); document.querySelectorAll('.coding-git-pull').forEach(btn => { btn.addEventListener('click', () => this.gitPull(parseInt(btn.dataset.id))); }); document.querySelectorAll('.coding-git-push').forEach(btn => { btn.addEventListener('click', () => this.gitPush(parseInt(btn.dataset.id))); }); document.querySelectorAll('.coding-git-commit').forEach(btn => { btn.addEventListener('click', () => this.promptCommit(parseInt(btn.dataset.id))); }); } /** * Git-Status für eine Kachel aktualisieren */ async updateTileStatus(id) { const statusEl = document.getElementById(`coding-status-${id}`); if (!statusEl) return; try { const status = await api.getCodingDirectoryStatus(id); if (!status.isGitRepo) { statusEl.innerHTML = 'Kein Git-Repo'; return; } const statusClass = status.isClean ? 'clean' : 'dirty'; const statusText = status.isClean ? 'Clean' : `${status.changes?.length || 0} Änderungen`; statusEl.innerHTML = ` ${escapeHtml(status.branch)} ${statusText} ${status.ahead > 0 ? `↑${status.ahead}` : ''} ${status.behind > 0 ? `↓${status.behind}` : ''} `; } catch (error) { statusEl.innerHTML = 'Fehler'; } } /** * Claude Code starten - SSH-Befehl kopieren */ async launchClaude(path) { const command = `ssh claude-dev@91.99.192.14 -t "cd ${path} && claude"`; try { await navigator.clipboard.writeText(command); this.showCommandModal( command, 'Befehl kopiert! Öffne Terminal/CMD und füge ein (Strg+V). Passwort: z0E1Al}q2H?Yqd!O' ); showToast('SSH-Befehl kopiert!', 'success'); } catch (error) { // Fallback wenn Clipboard nicht verfügbar this.showCommandModal( command, 'Kopiere diesen Befehl und füge ihn im Terminal ein. Passwort: z0E1Al}q2H?Yqd!O' ); } } /** * Command-Modal anzeigen */ showCommandModal(command, hint) { const modal = document.getElementById('coding-command-modal'); const hintEl = document.getElementById('coding-command-hint'); const textEl = document.getElementById('coding-command-text'); if (!modal || !textEl) return; hintEl.textContent = hint || 'Führe diesen Befehl aus:'; textEl.textContent = command; this.currentCommand = command; modal.classList.remove('hidden'); } /** * Command-Modal schließen */ closeCommandModal() { const modal = document.getElementById('coding-command-modal'); if (modal) modal.classList.add('hidden'); } /** * Befehl in Zwischenablage kopieren */ async copyCommand() { if (!this.currentCommand) return; try { await navigator.clipboard.writeText(this.currentCommand); showToast('Befehl kopiert!', 'success'); } catch (error) { console.error('Kopieren fehlgeschlagen:', error); showToast('Kopieren fehlgeschlagen', 'error'); } } /** * Git Fetch */ async gitFetch(id) { try { showToast('Fetch läuft...', 'info'); await api.codingGitFetch(id); showToast('Fetch erfolgreich', 'success'); this.updateTileStatus(id); } catch (error) { showToast('Fetch fehlgeschlagen', 'error'); } } /** * Git Pull */ async gitPull(id) { try { showToast('Pull läuft...', 'info'); await api.codingGitPull(id); showToast('Pull erfolgreich', 'success'); this.updateTileStatus(id); } catch (error) { showToast('Pull fehlgeschlagen: ' + (error.message || 'Unbekannter Fehler'), 'error'); } } /** * Git Push */ async gitPush(id) { try { showToast('Push läuft...', 'info'); await api.codingGitPush(id); showToast('Push erfolgreich', 'success'); this.updateTileStatus(id); } catch (error) { showToast('Push fehlgeschlagen: ' + (error.message || 'Unbekannter Fehler'), 'error'); } } /** * Commit-Dialog anzeigen */ promptCommit(id) { const message = prompt('Commit-Nachricht eingeben:'); if (message && message.trim()) { this.gitCommit(id, message.trim()); } } /** * Git Commit */ async gitCommit(id, message) { try { showToast('Commit läuft...', 'info'); await api.codingGitCommit(id, message); showToast('Commit erfolgreich', 'success'); this.updateTileStatus(id); } catch (error) { showToast('Commit fehlgeschlagen: ' + (error.message || 'Unbekannter Fehler'), 'error'); } } /** * Modal öffnen */ openModal(directory = null) { this.editingDirectory = directory; const modal = document.getElementById('coding-modal'); const overlay = document.querySelector('.modal-overlay'); const title = document.getElementById('coding-modal-title'); const deleteBtn = document.getElementById('coding-delete-btn'); if (!modal) return; // Titel setzen title.textContent = directory ? 'Anwendung bearbeiten' : 'Anwendung hinzufügen'; // Delete-Button anzeigen/verstecken if (deleteBtn) { deleteBtn.classList.toggle('hidden', !directory); } // Felder füllen document.getElementById('coding-name').value = directory?.name || ''; document.getElementById('coding-path').value = directory?.localPath || ''; document.getElementById('coding-description').value = directory?.description || ''; document.getElementById('coding-branch').value = directory?.defaultBranch || 'main'; // CLAUDE.md: Nur aus Dateisystem anzeigen const claudeContent = directory?.claudeMdFromDisk || ''; this.updateClaudeLink(claudeContent, directory?.name); // Farbe setzen const color = directory?.color || '#4F46E5'; document.getElementById('coding-color-custom').value = color; document.querySelectorAll('.color-preset').forEach(btn => { btn.classList.toggle('selected', btn.dataset.color === color); }); // Gitea-Sektion zurücksetzen const giteaSection = document.querySelector('.coding-gitea-section'); if (giteaSection) { giteaSection.open = !!directory?.giteaRepoUrl; } // Repos laden wenn nötig if (directory?.giteaRepoUrl) { this.loadGiteaRepos(); } // Modal und Overlay anzeigen modal.classList.remove('hidden'); modal.classList.add('visible'); if (overlay) { overlay.classList.remove('hidden'); overlay.classList.add('visible'); } } /** * Modal schließen */ closeModal() { const modal = document.getElementById('coding-modal'); const overlay = document.querySelector('.modal-overlay'); if (modal) { modal.classList.remove('visible'); setTimeout(() => modal.classList.add('hidden'), 200); this.editingDirectory = null; } if (overlay) { overlay.classList.remove('visible'); setTimeout(() => overlay.classList.add('hidden'), 200); } } /** * Speichern-Handler */ async handleSave() { const name = document.getElementById('coding-name').value.trim(); const localPath = document.getElementById('coding-path').value.trim(); const description = document.getElementById('coding-description').value.trim(); // CLAUDE.md wird nicht mehr gespeichert - nur readonly const defaultBranch = document.getElementById('coding-branch').value.trim() || 'main'; // Farbe ermitteln const selectedPreset = document.querySelector('.color-preset.selected'); const color = selectedPreset?.dataset.color || document.getElementById('coding-color-custom').value; // Gitea-Daten const giteaSelect = document.getElementById('coding-gitea-repo'); const giteaRepoUrl = giteaSelect?.value || null; const giteaRepoOwner = giteaSelect?.selectedOptions[0]?.dataset.owner || null; const giteaRepoName = giteaSelect?.selectedOptions[0]?.dataset.name || null; if (!name) { showToast('Anwendungsname ist erforderlich', 'error'); return; } if (!localPath) { showToast('Ordnerpfad ist erforderlich', 'error'); return; } const data = { name, localPath, description, color, // claudeInstructions entfernt - CLAUDE.md ist readonly giteaRepoUrl, giteaRepoOwner, giteaRepoName, defaultBranch }; try { if (this.editingDirectory) { await api.updateCodingDirectory(this.editingDirectory.id, data); showToast('Anwendung aktualisiert', 'success'); } else { await api.createCodingDirectory(data); showToast('Anwendung hinzugefügt', 'success'); } this.closeModal(); await this.loadDirectories(); } catch (error) { console.error('Fehler beim Speichern:', error); showToast(error.message || 'Fehler beim Speichern', 'error'); } } /** * Löschen-Handler */ async handleDelete() { if (!this.editingDirectory) return; if (!confirm(`Anwendung "${this.editingDirectory.name}" wirklich löschen?`)) { return; } try { await api.deleteCodingDirectory(this.editingDirectory.id); showToast('Anwendung gelöscht', 'success'); this.closeModal(); await this.loadDirectories(); } catch (error) { console.error('Fehler beim Löschen:', error); showToast('Fehler beim Löschen', 'error'); } } /** * Auto-Refresh starten */ startAutoRefresh() { this.stopAutoRefresh(); // Alle 30 Sekunden aktualisieren this.refreshInterval = setInterval(() => { this.directories.forEach(dir => this.updateTileStatus(dir.id)); }, 30000); } /** * Auto-Refresh stoppen */ stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } } /** * View anzeigen */ async show() { await this.loadDirectories(); this.startAutoRefresh(); } /** * View verstecken */ hide() { this.stopAutoRefresh(); } } // Singleton-Instanz erstellen und exportieren const codingManager = new CodingManager(); export default codingManager;