/**
* TASKMATE - Gitea Manager
* ========================
* Git-Repository-Verwaltung pro Projekt
*/
import api from './api.js';
import { $, $$, escapeHtml } from './utils.js';
import store from './store.js';
class GiteaManager {
constructor() {
// Gemeinsame Eigenschaften
this.initialized = false;
this.refreshInterval = null;
this.isLoading = false;
this.currentMode = 'server'; // 'server' oder 'project'
// Projekt-Modus Eigenschaften
this.application = null;
this.gitStatus = null;
this.branches = [];
this.commits = [];
this.giteaRepos = [];
this.giteaConnected = false;
this.hiddenCommits = new Set();
// Server-Modus Eigenschaften
this.serverInfo = null;
this.serverStatus = null;
this.serverBranches = [];
this.serverCommits = [];
this.hiddenServerCommits = new Set();
// Browser-Upload Eigenschaften
this.browserUploadFiles = [];
this.browserUploadSessionId = null;
this.supportsDirectoryPicker = 'showDirectoryPicker' in window;
// Zu ignorierende Ordner/Dateien
this.ignorePatterns = [
'.git',
'node_modules',
'__pycache__',
'.env',
'.env.local',
'.env.production',
'.DS_Store',
'Thumbs.db',
'.idea',
'.vscode',
'dist',
'build',
'.cache',
'coverage'
];
}
async init() {
if (this.initialized) {
return;
}
// DOM Elements - Gemeinsam
this.giteaView = $('#view-gitea');
this.modeSwitch = $('#gitea-mode-switch');
// DOM Elements - Server-Modus
this.serverModeSection = $('#gitea-server-mode');
// DOM Elements - Projekt-Modus
this.noProjectSection = $('#gitea-no-project');
this.configSection = $('#gitea-config-section');
this.mainSection = $('#gitea-main-section');
this.connectionStatus = $('#gitea-connection-status');
// DOM Elements - Browser-Upload
this.browserUploadSection = $('#gitea-browser-upload');
this.bindEvents();
this.bindServerEvents();
this.bindBrowserUploadEvents();
this.subscribeToStore();
this.initialized = true;
}
bindEvents() {
// Modus-Schalter
$$('.gitea-mode-btn').forEach(btn => {
btn.addEventListener('click', (e) => this.handleModeSwitch(e));
});
// Konfiguration speichern
$('#gitea-config-form')?.addEventListener('submit', (e) => this.handleConfigSave(e));
// Repository auswählen
$('#gitea-repo-select')?.addEventListener('change', (e) => this.handleRepoSelect(e));
// Repositories aktualisieren
$('#btn-refresh-repos')?.addEventListener('click', () => this.loadGiteaRepos());
// Neues Repository erstellen
$('#btn-create-repo')?.addEventListener('click', () => this.openCreateRepoModal());
// Git-Operationen
$('#btn-git-fetch')?.addEventListener('click', () => this.handleFetch());
$('#btn-git-pull')?.addEventListener('click', () => this.handlePull());
$('#btn-git-push')?.addEventListener('click', () => this.handlePush());
$('#btn-git-commit')?.addEventListener('click', () => this.openCommitModal());
// Branch wechseln
$('#branch-select')?.addEventListener('change', (e) => this.handleBranchChange(e));
// Pfad validieren
$('#local-path-input')?.addEventListener('blur', (e) => this.validateLocalPath(e.target.value));
// Konfiguration bearbeiten/entfernen
$('#btn-edit-config')?.addEventListener('click', () => this.showConfigSection());
$('#btn-remove-config')?.addEventListener('click', () => this.handleRemoveConfig());
// Create Repo Modal
$('#create-repo-form')?.addEventListener('submit', (e) => this.handleCreateRepo(e));
// Commit Modal
$('#git-commit-form')?.addEventListener('submit', (e) => this.handleCommit(e));
// Push Modal
$('#git-push-form')?.addEventListener('submit', (e) => this.executePush(e));
// Branch umbenennen
$('#btn-rename-branch')?.addEventListener('click', () => this.openRenameBranchModal());
$('#git-rename-branch-form')?.addEventListener('submit', (e) => this.executeRenameBranch(e));
// Commits ausblenden
$('#btn-clear-commits')?.addEventListener('click', () => this.clearAllCommits());
$('#git-commits-list')?.addEventListener('click', (e) => this.handleCommitListClick(e));
}
bindServerEvents() {
// Server Git-Operationen
$('#btn-server-fetch')?.addEventListener('click', () => this.handleServerFetch());
$('#btn-server-pull')?.addEventListener('click', () => this.handleServerPull());
$('#btn-server-push')?.addEventListener('click', () => this.openServerPushModal());
$('#btn-server-commit')?.addEventListener('click', () => this.openServerCommitModal());
// Server Branch wechseln
$('#server-branch-select')?.addEventListener('change', (e) => this.handleServerBranchChange(e));
// Server Commits ausblenden
$('#btn-server-clear-commits')?.addEventListener('click', () => this.clearAllServerCommits());
$('#server-commits-list')?.addEventListener('click', (e) => this.handleServerCommitListClick(e));
}
// Modus-Wechsel Handler
handleModeSwitch(e) {
const btn = e.currentTarget;
const newMode = btn.dataset.mode;
if (newMode === this.currentMode) return;
// Button-Status aktualisieren
$$('.gitea-mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.currentMode = newMode;
this.updateModeView();
}
updateModeView() {
if (this.currentMode === 'server') {
// Server-Modus
this.serverModeSection?.classList.remove('hidden');
this.browserUploadSection?.classList.add('hidden');
this.noProjectSection?.classList.add('hidden');
this.configSection?.classList.add('hidden');
this.mainSection?.classList.add('hidden');
this.loadServerData();
} else {
// Projekt-Modus (Browser-Upload)
this.serverModeSection?.classList.add('hidden');
this.browserUploadSection?.classList.remove('hidden');
this.noProjectSection?.classList.add('hidden');
this.configSection?.classList.add('hidden');
this.mainSection?.classList.add('hidden');
this.initBrowserUpload();
}
}
// =====================
// SERVER-MODUS METHODEN
// =====================
async loadServerData() {
this.showLoading();
try {
// Server-Info laden
const infoResult = await api.getServerGitInfo();
this.serverInfo = infoResult;
if (infoResult.accessible && infoResult.isRepository) {
// Remote-URL anzeigen
if (infoResult.remoteUrl) {
const repoUrl = $('#server-repo-url');
if (repoUrl) {
// Gitea HTML URL generieren (ohne .git)
const htmlUrl = infoResult.remoteUrl.replace(/\.git$/, '');
repoUrl.href = htmlUrl;
repoUrl.textContent = htmlUrl;
repoUrl.classList.remove('hidden');
}
}
// Git-Daten laden
await this.loadServerGitData();
} else {
this.showToast('Server-Verzeichnis ist kein Git-Repository', 'error');
}
} catch (error) {
console.error('[Gitea Server] Fehler beim Laden:', error);
this.showToast('Fehler beim Laden der Server-Daten', 'error');
}
}
async loadServerGitData() {
try {
const [statusResult, branchesResult, commitsResult] = await Promise.allSettled([
api.getServerGitStatus(),
api.getServerGitBranches(),
api.getServerGitCommits(10)
]);
if (statusResult.status === 'fulfilled') {
this.serverStatus = statusResult.value;
} else {
this.serverStatus = null;
console.error('[Gitea Server] Status-Fehler:', statusResult.reason);
}
if (branchesResult.status === 'fulfilled') {
this.serverBranches = branchesResult.value.branches || [];
} else {
this.serverBranches = [];
}
if (commitsResult.status === 'fulfilled') {
this.serverCommits = commitsResult.value.commits || [];
} else {
this.serverCommits = [];
}
this.renderServerStatus();
this.renderServerBranches();
this.renderServerCommits();
this.renderServerChanges();
} catch (error) {
console.error('[Gitea Server] Git-Daten laden fehlgeschlagen:', error);
}
}
renderServerStatus() {
const statusBadge = $('#server-status-indicator');
const changesCount = $('#server-changes-count');
if (!this.serverStatus) {
if (statusBadge) {
statusBadge.textContent = 'Fehler';
statusBadge.className = 'status-badge error';
}
if (changesCount) changesCount.textContent = '-';
return;
}
if (statusBadge) {
if (!this.serverStatus.success) {
statusBadge.textContent = 'Fehler';
statusBadge.className = 'status-badge error';
} else if (this.serverStatus.isClean) {
statusBadge.textContent = 'Sauber';
statusBadge.className = 'status-badge clean';
} else if (this.serverStatus.hasChanges) {
statusBadge.textContent = 'Geändert';
statusBadge.className = 'status-badge dirty';
} else {
statusBadge.textContent = 'OK';
statusBadge.className = 'status-badge clean';
}
}
const changes = this.serverStatus.changes || [];
if (changesCount) changesCount.textContent = changes.length;
}
renderServerBranches() {
const select = $('#server-branch-select');
if (!select) return;
const currentBranch = this.serverStatus?.branch || 'main';
select.innerHTML = '';
const localBranches = this.serverBranches.filter(b => !b.isRemote);
localBranches.forEach(branch => {
const option = document.createElement('option');
option.value = branch.name;
option.textContent = branch.name;
if (branch.name === currentBranch) {
option.selected = true;
}
select.appendChild(option);
});
}
renderServerCommits() {
const listEl = $('#server-commits-list');
const clearBtn = $('#btn-server-clear-commits');
if (!listEl) return;
const visibleCommits = this.serverCommits.filter(commit => {
const hash = commit.hash || commit.sha || commit.shortHash;
return !this.hiddenServerCommits.has(hash);
});
if (clearBtn) {
clearBtn.style.display = visibleCommits.length > 0 ? '' : 'none';
}
if (visibleCommits.length === 0) {
const message = this.serverCommits.length > 0
? 'Alle Commits ausgeblendet'
: 'Keine Commits gefunden';
listEl.innerHTML = `
`;
return;
}
listEl.innerHTML = visibleCommits.map(commit => {
const hash = commit.hash || commit.sha || '';
const shortHash = commit.shortHash || hash.substring(0, 7);
return `
${escapeHtml(shortHash)}
${escapeHtml(commit.message?.split('\n')[0] || '')}
${escapeHtml(commit.author)} · ${this.formatDate(commit.date)}
`}).join('');
}
renderServerChanges() {
const changesSection = $('#server-changes-section');
const listEl = $('#server-changes-list');
if (!changesSection || !listEl) return;
const changes = this.serverStatus?.changes || [];
if (changes.length === 0) {
changesSection.classList.add('hidden');
return;
}
changesSection.classList.remove('hidden');
listEl.innerHTML = changes.map(change => {
const statusClass = this.getChangeStatusClass(change.status);
const statusLabel = this.getChangeStatusLabel(change.status);
return `
${change.status}
${escapeHtml(change.file)}
`;
}).join('');
}
// Server Git-Operationen
async handleServerFetch() {
this.setServerOperationLoading('fetch', true);
try {
const result = await api.serverGitFetch();
if (result.success) {
this.showToast('Fetch erfolgreich', 'success');
await this.loadServerGitData();
} else {
this.showToast(result.error || 'Fetch fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Fetch fehlgeschlagen', 'error');
} finally {
this.setServerOperationLoading('fetch', false);
}
}
async handleServerPull() {
this.setServerOperationLoading('pull', true);
try {
const branch = $('#server-branch-select')?.value || null;
const result = await api.serverGitPull(branch);
if (result.success) {
this.showToast('Pull erfolgreich', 'success');
await this.loadServerGitData();
} else {
this.showToast(result.error || 'Pull fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Pull fehlgeschlagen', 'error');
} finally {
this.setServerOperationLoading('pull', false);
}
}
openServerPushModal() {
const modal = $('#git-push-modal');
if (!modal) return;
const currentBranch = $('#server-branch-select')?.value || this.serverStatus?.branch || 'main';
$('#push-local-branch').textContent = currentBranch;
$('#push-target-branch').value = '';
$('#push-force').checked = false;
// Temporär Attribut setzen um Server-Modus zu markieren
modal.dataset.serverMode = 'true';
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-push-modal');
}
async executeServerPush(force = false) {
this.setServerOperationLoading('push', true);
try {
const targetBranch = $('#push-target-branch')?.value || null;
const localBranch = $('#server-branch-select')?.value || this.serverStatus?.branch || 'main';
const result = await api.serverGitPush(targetBranch || localBranch, force);
if (result.success) {
const pushedBranch = result.branch || targetBranch || localBranch;
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
await this.loadServerGitData();
} else {
this.showToast(result.error || 'Push fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Push fehlgeschlagen', 'error');
} finally {
this.setServerOperationLoading('push', false);
}
}
openServerCommitModal() {
const modal = $('#git-commit-modal');
if (!modal) return;
$('#commit-message').value = '';
$('#commit-stage-all').checked = true;
// Temporär Attribut setzen um Server-Modus zu markieren
modal.dataset.serverMode = 'true';
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-commit-modal');
}
async executeServerCommit(message, stageAll = true) {
try {
const result = await api.serverGitCommit(message, stageAll);
if (result.success) {
this.showToast('Commit erstellt', 'success');
await this.loadServerGitData();
return true;
} else {
this.showToast(result.error || 'Commit fehlgeschlagen', 'error');
return false;
}
} catch (error) {
this.showToast(error.message || 'Commit fehlgeschlagen', 'error');
return false;
}
}
async handleServerBranchChange(e) {
const branch = e.target.value;
if (!branch) return;
try {
const result = await api.serverGitCheckout(branch);
if (result.success) {
this.showToast(`Gewechselt zu ${branch}`, 'success');
await this.loadServerGitData();
} else {
this.showToast(result.error || 'Branch-Wechsel fehlgeschlagen', 'error');
await this.loadServerGitData();
}
} catch (error) {
this.showToast(error.message || 'Branch-Wechsel fehlgeschlagen', 'error');
await this.loadServerGitData();
}
}
handleServerCommitListClick(e) {
const deleteBtn = e.target.closest('.commit-delete');
if (deleteBtn) {
const hash = deleteBtn.dataset.hash;
if (hash) {
this.hideServerCommit(hash);
}
}
}
hideServerCommit(hash) {
this.hiddenServerCommits.add(hash);
this.renderServerCommits();
this.showToast('Commit ausgeblendet', 'info');
}
clearAllServerCommits() {
this.serverCommits.forEach(commit => {
const hash = commit.hash || commit.sha || commit.shortHash;
if (hash) {
this.hiddenServerCommits.add(hash);
}
});
this.renderServerCommits();
this.showToast('Alle Commits ausgeblendet', 'info');
}
setServerOperationLoading(operation, loading) {
const buttonId = `btn-server-${operation}`;
const button = $(`#${buttonId}`);
if (button) {
button.disabled = loading;
if (loading) {
button.classList.add('loading');
} else {
button.classList.remove('loading');
}
}
}
// =====================
// BROWSER-UPLOAD METHODEN
// =====================
bindBrowserUploadEvents() {
// Verzeichnis auswählen
$('#btn-select-directory')?.addEventListener('click', () => this.handleSelectDirectory());
// Repository für Upload aktualisieren
$('#btn-refresh-upload-repos')?.addEventListener('click', () => this.loadBrowserUploadRepos());
// Upload abbrechen
$('#btn-cancel-upload')?.addEventListener('click', () => this.cancelBrowserUpload());
// Upload ausführen
$('#btn-execute-upload')?.addEventListener('click', () => this.executeBrowserUpload());
// Drop Zone Events
const dropZone = $('#drop-zone');
if (dropZone) {
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
this.handleDroppedFiles(e.dataTransfer);
});
}
}
async initBrowserUpload() {
// Browser-Kompatibilität prüfen
const compatNotice = $('#browser-upload-compat');
if (compatNotice) {
if (!this.supportsDirectoryPicker) {
compatNotice.classList.remove('hidden');
// Drop-Zone anzeigen als Alternative
$('#drop-zone')?.classList.remove('hidden');
} else {
compatNotice.classList.add('hidden');
}
}
// Upload-Zustand zurücksetzen
this.browserUploadFiles = [];
this.browserUploadSessionId = null;
this.resetBrowserUploadUI();
// Repositories laden
await this.loadBrowserUploadRepos();
}
async loadBrowserUploadRepos() {
const select = $('#browser-upload-repo-select');
if (!select) return;
try {
const result = await api.getGiteaRepositories();
// API gibt { success, repositories } zurück
this.giteaRepos = result.repositories || [];
select.innerHTML = '';
this.giteaRepos.forEach(repo => {
const option = document.createElement('option');
// API-Felder: cloneUrl, fullName, owner, name
option.value = repo.cloneUrl;
option.textContent = repo.fullName;
option.dataset.owner = repo.owner || '';
option.dataset.name = repo.name;
select.appendChild(option);
});
if (this.giteaRepos.length === 0) {
this.showToast('Keine Repositories gefunden', 'info');
}
} catch (error) {
console.error('[Browser-Upload] Repositories laden fehlgeschlagen:', error);
this.showToast('Repositories konnten nicht geladen werden', 'error');
}
}
async handleSelectDirectory() {
if (!this.supportsDirectoryPicker) {
this.showToast('Verzeichnis-Auswahl wird in diesem Browser nicht unterstützt', 'error');
return;
}
try {
// File System Access API verwenden
const dirHandle = await window.showDirectoryPicker({
mode: 'read'
});
this.showToast('Lese Verzeichnis...', 'info');
// Dateien rekursiv lesen
const files = await this.readDirectoryRecursive(dirHandle, '');
if (files.length === 0) {
this.showToast('Keine Dateien gefunden oder alle wurden ignoriert', 'warning');
return;
}
this.browserUploadFiles = files;
this.renderUploadPreview();
this.showToast(`${files.length} Dateien ausgewählt`, 'success');
} catch (error) {
if (error.name === 'AbortError') {
// Benutzer hat abgebrochen
return;
}
console.error('[Browser-Upload] Verzeichnis lesen fehlgeschlagen:', error);
this.showToast('Verzeichnis konnte nicht gelesen werden', 'error');
}
}
async readDirectoryRecursive(dirHandle, basePath) {
const files = [];
for await (const entry of dirHandle.values()) {
const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
// Ignorierte Muster prüfen
if (this.shouldIgnore(entry.name, relativePath)) {
continue;
}
if (entry.kind === 'file') {
try {
const file = await entry.getFile();
files.push({
file: file,
relativePath: relativePath,
size: file.size
});
} catch (err) {
console.warn(`Datei konnte nicht gelesen werden: ${relativePath}`, err);
}
} else if (entry.kind === 'directory') {
const subFiles = await this.readDirectoryRecursive(entry, relativePath);
files.push(...subFiles);
}
}
return files;
}
shouldIgnore(name, path) {
// Einfache Muster prüfen
for (const pattern of this.ignorePatterns) {
if (pattern.startsWith('*.')) {
// Wildcard-Muster (z.B. *.log)
const ext = pattern.substring(1);
if (name.endsWith(ext)) {
return true;
}
} else if (name === pattern || path.includes(`/${pattern}/`) || path.startsWith(`${pattern}/`)) {
return true;
}
}
return false;
}
async handleDroppedFiles(dataTransfer) {
const items = dataTransfer.items;
if (!items || items.length === 0) return;
const files = [];
for (const item of items) {
if (item.kind === 'file') {
// webkitGetAsEntry für Verzeichnis-Support
const entry = item.webkitGetAsEntry?.();
if (entry) {
const entryFiles = await this.readEntry(entry, '');
files.push(...entryFiles);
} else {
// Fallback: Einzelne Datei
const file = item.getAsFile();
if (file && !this.shouldIgnore(file.name, file.name)) {
files.push({
file: file,
relativePath: file.name,
size: file.size
});
}
}
}
}
if (files.length === 0) {
this.showToast('Keine Dateien gefunden oder alle wurden ignoriert', 'warning');
return;
}
this.browserUploadFiles = files;
this.renderUploadPreview();
this.showToast(`${files.length} Dateien ausgewählt`, 'success');
}
async readEntry(entry, basePath) {
const files = [];
const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
if (this.shouldIgnore(entry.name, relativePath)) {
return files;
}
if (entry.isFile) {
return new Promise((resolve) => {
entry.file((file) => {
resolve([{
file: file,
relativePath: relativePath,
size: file.size
}]);
}, () => resolve([]));
});
} else if (entry.isDirectory) {
const reader = entry.createReader();
return new Promise((resolve) => {
reader.readEntries(async (entries) => {
for (const subEntry of entries) {
const subFiles = await this.readEntry(subEntry, relativePath);
files.push(...subFiles);
}
resolve(files);
}, () => resolve([]));
});
}
return files;
}
renderUploadPreview() {
const previewSection = $('#upload-preview-section');
const commitSection = $('#upload-commit-section');
const filesList = $('#upload-files-list');
const fileCount = $('#upload-file-count');
if (!previewSection || !filesList) return;
// Sektionen anzeigen
previewSection.classList.remove('hidden');
commitSection?.classList.remove('hidden');
// Dateianzahl
if (fileCount) {
fileCount.textContent = `${this.browserUploadFiles.length} Dateien`;
}
// Dateiliste rendern (max 100 anzeigen)
const displayFiles = this.browserUploadFiles.slice(0, 100);
filesList.innerHTML = displayFiles.map(f => `
${escapeHtml(f.relativePath)}
${this.formatFileSizeUpload(f.size)}
`).join('');
if (this.browserUploadFiles.length > 100) {
filesList.innerHTML += `
... und ${this.browserUploadFiles.length - 100} weitere Dateien
`;
}
}
formatFileSizeUpload(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
resetBrowserUploadUI() {
// Vorschau und Commit-Sektion verstecken
$('#upload-preview-section')?.classList.add('hidden');
$('#upload-commit-section')?.classList.add('hidden');
$('#upload-progress-container')?.classList.add('hidden');
// Felder zurücksetzen
const commitMessage = $('#browser-upload-commit-message');
if (commitMessage) commitMessage.value = '';
const filesList = $('#upload-files-list');
if (filesList) filesList.innerHTML = '';
// Progress zurücksetzen
const progressBar = $('#upload-progress-bar');
if (progressBar) progressBar.style.width = '0%';
const progressText = $('#upload-progress-text');
if (progressText) progressText.textContent = '0%';
}
cancelBrowserUpload() {
this.browserUploadFiles = [];
this.resetBrowserUploadUI();
this.showToast('Upload abgebrochen', 'info');
}
async executeBrowserUpload() {
// Validierung
const repoSelect = $('#browser-upload-repo-select');
const repoUrl = repoSelect?.value;
if (!repoUrl) {
this.showToast('Bitte wählen Sie ein Repository aus', 'error');
return;
}
if (this.browserUploadFiles.length === 0) {
this.showToast('Keine Dateien ausgewählt', 'error');
return;
}
const commitMessage = $('#browser-upload-commit-message')?.value.trim();
if (!commitMessage) {
this.showToast('Bitte geben Sie eine Commit-Nachricht ein', 'error');
return;
}
const branch = $('#browser-upload-branch')?.value || 'main';
// UI aktualisieren
const executeBtn = $('#btn-execute-upload');
const cancelBtn = $('#btn-cancel-upload');
const progressContainer = $('#upload-progress-container');
if (executeBtn) {
executeBtn.disabled = true;
executeBtn.innerHTML = ' Lädt hoch...';
}
if (cancelBtn) cancelBtn.disabled = true;
progressContainer?.classList.remove('hidden');
try {
// Session vorbereiten
const prepareResult = await api.prepareBrowserUpload();
this.browserUploadSessionId = prepareResult.sessionId;
// Upload durchführen
const result = await api.browserUploadAndPush({
files: this.browserUploadFiles,
repoUrl: repoUrl,
branch: branch,
commitMessage: commitMessage,
sessionId: this.browserUploadSessionId,
onProgress: (percent) => {
const progressBar = $('#upload-progress-bar');
const progressText = $('#upload-progress-text');
if (progressBar) progressBar.style.width = `${percent}%`;
if (progressText) progressText.textContent = `${percent}%`;
}
});
if (result.success) {
this.showToast(`Erfolgreich gepusht: ${result.filesCount} Dateien`, 'success');
this.cancelBrowserUpload(); // UI zurücksetzen
} else {
throw new Error(result.error || 'Upload fehlgeschlagen');
}
} catch (error) {
console.error('[Browser-Upload] Fehler:', error);
this.showToast(error.message || 'Upload fehlgeschlagen', 'error');
} finally {
// UI wiederherstellen
if (executeBtn) {
executeBtn.disabled = false;
executeBtn.innerHTML = `
Commit & Push
`;
}
if (cancelBtn) cancelBtn.disabled = false;
progressContainer?.classList.add('hidden');
this.browserUploadSessionId = null;
}
}
// =====================
// PROJEKT-MODUS METHODEN
// =====================
subscribeToStore() {
// Bei Projektwechsel neu laden
store.subscribe('currentProjectId', async (projectId) => {
if (projectId && store.get('currentView') === 'gitea') {
await this.loadApplication();
}
});
}
async loadApplication() {
const projectId = store.get('currentProjectId');
if (!projectId) {
this.showNoProjectMessage();
return;
}
this.showLoading();
try {
const result = await api.getProjectApplication(projectId);
this.application = result;
if (result.configured) {
await this.loadGitData();
this.renderConfiguredView();
} else {
await this.loadGiteaRepos();
this.renderConfigurationView();
}
} catch (error) {
console.error('[Gitea] Fehler beim Laden:', error);
this.showError('Fehler beim Laden der Konfiguration');
}
}
async loadGitData() {
const projectId = store.get('currentProjectId');
try {
const [statusResult, branchesResult, commitsResult] = await Promise.allSettled([
api.getGitStatus(projectId),
api.getGitBranches(projectId),
api.getGitCommits(projectId, 10)
]);
if (statusResult.status === 'fulfilled') {
this.gitStatus = statusResult.value;
} else {
this.gitStatus = null;
console.error('[Gitea] Status-Fehler:', statusResult.reason);
}
if (branchesResult.status === 'fulfilled') {
this.branches = branchesResult.value.branches || [];
} else {
this.branches = [];
}
if (commitsResult.status === 'fulfilled') {
this.commits = commitsResult.value.commits || [];
} else {
this.commits = [];
}
this.renderStatus();
this.renderBranches();
this.renderCommits();
this.renderChanges();
} catch (error) {
console.error('[Gitea] Git-Daten laden fehlgeschlagen:', error);
}
}
async loadGiteaRepos() {
try {
const result = await api.testGiteaConnection();
this.giteaConnected = result.connected;
this.updateConnectionStatus(result);
if (this.giteaConnected) {
const reposResult = await api.getGiteaRepositories();
this.giteaRepos = reposResult.repositories || [];
this.populateRepoSelect();
}
} catch (error) {
this.giteaConnected = false;
this.updateConnectionStatus({ connected: false, error: error.message });
console.error('[Gitea] Verbindung fehlgeschlagen:', error);
}
}
updateConnectionStatus(result) {
const statusEl = this.connectionStatus;
if (!statusEl) return;
statusEl.classList.remove('connected', 'disconnected');
if (result.connected) {
statusEl.classList.add('connected');
statusEl.querySelector('.status-text').textContent =
`Verbunden als ${result.user?.login || 'Benutzer'}`;
} else {
statusEl.classList.add('disconnected');
statusEl.querySelector('.status-text').textContent =
result.error || 'Verbindung fehlgeschlagen';
}
}
populateRepoSelect() {
const select = $('#gitea-repo-select');
if (!select) return;
select.innerHTML = '';
this.giteaRepos.forEach(repo => {
const option = document.createElement('option');
option.value = JSON.stringify({
url: repo.cloneUrl,
owner: repo.owner,
name: repo.name,
fullName: repo.fullName,
htmlUrl: repo.htmlUrl,
defaultBranch: repo.defaultBranch
});
option.textContent = repo.fullName;
select.appendChild(option);
});
}
handleRepoSelect(e) {
const value = e.target.value;
if (!value) return;
try {
const repo = JSON.parse(value);
$('#default-branch-input').value = repo.defaultBranch || 'main';
} catch (error) {
console.error('[Gitea] Fehler beim Parsen des Repository:', error);
}
}
async validateLocalPath(path) {
const resultEl = $('#path-validation-result');
if (!resultEl || !path) {
if (resultEl) {
resultEl.textContent = '';
resultEl.className = 'form-hint';
}
return;
}
try {
const result = await api.validatePath(path);
if (result.valid) {
if (result.isRepository) {
resultEl.textContent = 'Pfad ist ein Git-Repository';
resultEl.className = 'form-hint success';
} else {
resultEl.textContent = 'Pfad ist erreichbar (kein Git-Repository)';
resultEl.className = 'form-hint';
}
} else {
resultEl.textContent = 'Pfad nicht erreichbar';
resultEl.className = 'form-hint error';
}
} catch (error) {
resultEl.textContent = 'Fehler bei der Validierung';
resultEl.className = 'form-hint error';
}
}
async handleConfigSave(e) {
e.preventDefault();
const projectId = store.get('currentProjectId');
if (!projectId) return;
const repoSelectValue = $('#gitea-repo-select').value;
const localPath = $('#local-path-input').value.trim();
const defaultBranch = $('#default-branch-input').value.trim() || 'main';
if (!localPath) {
this.showToast('Bitte geben Sie einen lokalen Pfad an', 'error');
return;
}
let giteaRepoUrl = null;
let giteaRepoOwner = null;
let giteaRepoName = null;
let cloneUrl = null;
if (repoSelectValue) {
try {
const repo = JSON.parse(repoSelectValue);
giteaRepoUrl = repo.htmlUrl;
giteaRepoOwner = repo.owner;
giteaRepoName = repo.name;
cloneUrl = repo.url; // Clone URL für git remote
} catch (error) {
console.error('[Gitea] Fehler beim Parsen des Repository:', error);
}
}
try {
// 1. Konfiguration speichern
const result = await api.saveProjectApplication({
projectId,
localPath,
giteaRepoUrl,
giteaRepoOwner,
giteaRepoName,
defaultBranch
});
if (result.success) {
// 2. Repository für Gitea vorbereiten (init, remote setzen)
if (cloneUrl) {
this.showToast('Repository wird vorbereitet...', 'info');
const prepareResult = await api.prepareRepository(projectId, cloneUrl, defaultBranch);
if (prepareResult.success) {
this.showToast('Konfiguration gespeichert und Repository vorbereitet', 'success');
} else {
this.showToast('Konfiguration gespeichert, aber Repository-Vorbereitung fehlgeschlagen: ' + (prepareResult.error || 'Unbekannter Fehler'), 'warning');
}
} else {
this.showToast('Konfiguration gespeichert', 'success');
}
await this.loadApplication();
} else {
this.showToast(result.error || 'Fehler beim Speichern', 'error');
}
} catch (error) {
this.showToast(error.message || 'Fehler beim Speichern', 'error');
}
}
async handleRemoveConfig() {
const projectId = store.get('currentProjectId');
if (!projectId) return;
if (!confirm('Möchten Sie die Repository-Konfiguration wirklich entfernen?')) {
return;
}
try {
await api.deleteProjectApplication(projectId);
this.showToast('Konfiguration entfernt', 'success');
await this.loadApplication();
} catch (error) {
this.showToast(error.message || 'Fehler beim Entfernen', 'error');
}
}
showConfigSection() {
this.hideAllSections();
this.configSection?.classList.remove('hidden');
// Formular mit aktuellen Werten füllen
if (this.application?.configured) {
$('#local-path-input').value = this.application.local_path || '';
$('#default-branch-input').value = this.application.default_branch || 'main';
// Repository im Dropdown auswählen falls vorhanden
if (this.application.gitea_repo_url) {
const select = $('#gitea-repo-select');
const options = select?.querySelectorAll('option');
options?.forEach(option => {
if (option.value) {
try {
const repo = JSON.parse(option.value);
if (repo.owner === this.application.gitea_repo_owner &&
repo.name === this.application.gitea_repo_name) {
select.value = option.value;
}
} catch (e) {}
}
});
}
}
this.loadGiteaRepos();
}
// Git Operations
async handleFetch() {
const projectId = store.get('currentProjectId');
if (!projectId) return;
this.setOperationLoading('fetch', true);
try {
const result = await api.gitFetch(projectId);
if (result.success) {
this.showToast('Fetch erfolgreich', 'success');
await this.loadGitData();
} else {
this.showToast(result.error || 'Fetch fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Fetch fehlgeschlagen', 'error');
} finally {
this.setOperationLoading('fetch', false);
}
}
async handlePull() {
const projectId = store.get('currentProjectId');
if (!projectId) return;
this.setOperationLoading('pull', true);
try {
const branch = $('#branch-select')?.value || null;
const result = await api.gitPull(projectId, branch);
if (result.success) {
this.showToast('Pull erfolgreich', 'success');
await this.loadGitData();
} else {
this.showToast(result.error || 'Pull fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Pull fehlgeschlagen', 'error');
} finally {
this.setOperationLoading('pull', false);
}
}
handlePush() {
// Öffne das Push-Modal zur Branch-Auswahl
this.openPushModal();
}
openPushModal() {
const modal = $('#git-push-modal');
if (!modal) return;
// Aktuellen lokalen Branch anzeigen
const currentBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'unbekannt';
$('#push-local-branch').textContent = currentBranch;
// Ziel-Branch zurücksetzen (Standard: gleicher Name wie lokal)
$('#push-target-branch').value = '';
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-push-modal');
}
async executePush(e) {
e.preventDefault();
const modal = $('#git-push-modal');
const isServerMode = modal?.dataset.serverMode === 'true';
// Modal schließen
modal?.classList.add('hidden');
modal?.classList.remove('visible');
$('#modal-overlay')?.classList.add('hidden');
store.closeModal();
if (isServerMode) {
// Server-Modus: Push für Server-Dateien
delete modal.dataset.serverMode;
const forcePush = $('#push-force')?.checked || false;
await this.executeServerPush(forcePush);
} else {
// Projekt-Modus: Push für Projekt-Repository
const projectId = store.get('currentProjectId');
if (!projectId) return;
this.setOperationLoading('push', true);
try {
const targetBranch = $('#push-target-branch')?.value || null;
const forcePush = $('#push-force')?.checked || false;
const localBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'master';
let result;
// Wenn ein anderer Target-Branch ausgewählt wurde oder Force-Push, verwende init-push
if ((targetBranch && targetBranch !== localBranch) || forcePush) {
const forceText = forcePush ? ' (Force)' : '';
const branchText = targetBranch && targetBranch !== localBranch
? `Push von "${localBranch}" nach "${targetBranch}"${forceText}...`
: `Push nach "${localBranch}"${forceText}...`;
this.showToast(branchText, 'info');
result = await api.gitInitPush(projectId, targetBranch, forcePush);
} else {
// Normaler Push (gleicher Branch-Name)
result = await api.gitPush(projectId, localBranch);
// Falls Push wegen fehlendem Upstream/Remote fehlschlägt, versuche init-push
if (!result.success && result.error &&
(result.error.includes('No configured push destination') ||
result.error.includes('no upstream') ||
result.error.includes('Kein Remote'))) {
this.showToast('Kein Upstream konfiguriert, führe initialen Push durch...', 'info');
result = await api.gitInitPush(projectId, targetBranch, false);
}
}
if (result.success) {
const pushedBranch = result.branch || targetBranch || localBranch;
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
await this.loadGitData();
} else {
this.showToast(result.error || 'Push fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Push fehlgeschlagen', 'error');
} finally {
this.setOperationLoading('push', false);
}
}
}
openRenameBranchModal() {
const modal = $('#git-rename-branch-modal');
if (!modal) return;
const currentBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'master';
$('#rename-current-branch').textContent = currentBranch;
$('#rename-new-branch').value = '';
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-rename-branch-modal');
// Focus auf das Eingabefeld
setTimeout(() => $('#rename-new-branch')?.focus(), 100);
}
async executeRenameBranch(e) {
e.preventDefault();
const projectId = store.get('currentProjectId');
if (!projectId) return;
const oldName = $('#rename-current-branch')?.textContent;
const newName = $('#rename-new-branch')?.value.trim();
if (!newName) {
this.showToast('Bitte geben Sie einen neuen Branch-Namen ein', 'error');
return;
}
if (oldName === newName) {
this.showToast('Der neue Name ist identisch mit dem aktuellen', 'error');
return;
}
// Modal schließen
const modal = $('#git-rename-branch-modal');
modal?.classList.add('hidden');
modal?.classList.remove('visible');
$('#modal-overlay')?.classList.add('hidden');
store.closeModal();
try {
const result = await api.gitRenameBranch(projectId, oldName, newName);
if (result.success) {
this.showToast(`Branch umbenannt: "${oldName}" → "${newName}"`, 'success');
await this.loadGitData();
} else {
this.showToast(result.error || 'Umbenennen fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Umbenennen fehlgeschlagen', 'error');
}
}
openCommitModal() {
const modal = $('#git-commit-modal');
if (!modal) return;
$('#commit-message').value = '';
$('#commit-stage-all').checked = true;
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-commit-modal');
}
async handleCommit(e) {
e.preventDefault();
const modal = $('#git-commit-modal');
const isServerMode = modal?.dataset.serverMode === 'true';
const message = $('#commit-message')?.value.trim();
const stageAll = $('#commit-stage-all')?.checked ?? true;
if (!message) {
this.showToast('Bitte geben Sie eine Commit-Nachricht ein', 'error');
return;
}
if (isServerMode) {
// Server-Modus: Commit für Server-Dateien
const success = await this.executeServerCommit(message, stageAll);
if (success) {
this.closeModal('git-commit-modal');
delete modal.dataset.serverMode;
}
} else {
// Projekt-Modus: Commit für Projekt-Repository
const projectId = store.get('currentProjectId');
if (!projectId) return;
try {
const result = await api.gitCommit(projectId, message, stageAll);
if (result.success) {
this.showToast('Commit erstellt', 'success');
this.closeModal('git-commit-modal');
await this.loadGitData();
} else {
this.showToast(result.error || 'Commit fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Commit fehlgeschlagen', 'error');
}
}
}
async handleBranchChange(e) {
const projectId = store.get('currentProjectId');
const branch = e.target.value;
if (!projectId || !branch) return;
try {
const result = await api.gitCheckout(projectId, branch);
if (result.success) {
this.showToast(`Gewechselt zu ${branch}`, 'success');
await this.loadGitData();
} else {
this.showToast(result.error || 'Branch-Wechsel fehlgeschlagen', 'error');
// Zurück zum vorherigen Branch
await this.loadGitData();
}
} catch (error) {
this.showToast(error.message || 'Branch-Wechsel fehlgeschlagen', 'error');
await this.loadGitData();
}
}
openCreateRepoModal() {
const modal = $('#create-repo-modal');
if (!modal) return;
$('#new-repo-name').value = '';
$('#new-repo-description').value = '';
$('#new-repo-private').checked = true;
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('create-repo-modal');
}
async handleCreateRepo(e) {
e.preventDefault();
const name = $('#new-repo-name')?.value.trim();
const description = $('#new-repo-description')?.value.trim();
const isPrivate = $('#new-repo-private')?.checked ?? true;
if (!name) {
this.showToast('Bitte geben Sie einen Repository-Namen ein', 'error');
return;
}
try {
const result = await api.createGiteaRepository({
name,
description,
private: isPrivate
});
if (result.success) {
this.showToast('Repository erstellt', 'success');
this.closeModal('create-repo-modal');
await this.loadGiteaRepos();
// Neues Repository im Dropdown auswählen
const select = $('#gitea-repo-select');
if (select && result.repository) {
const option = document.createElement('option');
option.value = JSON.stringify({
url: result.repository.cloneUrl,
owner: result.repository.owner,
name: result.repository.name,
fullName: result.repository.fullName,
htmlUrl: result.repository.htmlUrl,
defaultBranch: result.repository.defaultBranch
});
option.textContent = result.repository.fullName;
select.appendChild(option);
select.value = option.value;
}
} else {
this.showToast(result.error || 'Repository-Erstellung fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Repository-Erstellung fehlgeschlagen', 'error');
}
}
// Rendering
renderConfigurationView() {
this.hideAllSections();
this.configSection?.classList.remove('hidden');
// Formular zurücksetzen
$('#gitea-repo-select').value = '';
$('#local-path-input').value = '';
$('#default-branch-input').value = 'main';
$('#path-validation-result').textContent = '';
}
renderConfiguredView() {
this.hideAllSections();
this.mainSection?.classList.remove('hidden');
// Repository-Info
const repoNameEl = $('#gitea-repo-name span');
const repoUrlEl = $('#gitea-repo-url');
const localPathEl = $('#gitea-local-path-display');
if (this.application) {
if (this.application.gitea_repo_owner && this.application.gitea_repo_name) {
repoNameEl.textContent = `${this.application.gitea_repo_owner}/${this.application.gitea_repo_name}`;
} else {
repoNameEl.textContent = 'Lokales Repository';
}
if (this.application.gitea_repo_url) {
repoUrlEl.href = this.application.gitea_repo_url;
repoUrlEl.textContent = this.application.gitea_repo_url;
repoUrlEl.classList.remove('hidden');
} else {
repoUrlEl.classList.add('hidden');
}
localPathEl.textContent = this.application.local_path || '-';
}
}
renderStatus() {
const statusBadge = $('#git-status-indicator');
const changesCount = $('#git-changes-count');
if (!this.gitStatus) {
statusBadge.textContent = 'Fehler';
statusBadge.className = 'status-badge error';
changesCount.textContent = '-';
return;
}
// Status Badge
if (!this.gitStatus.success) {
statusBadge.textContent = 'Fehler';
statusBadge.className = 'status-badge error';
} else if (this.gitStatus.isClean) {
statusBadge.textContent = 'Sauber';
statusBadge.className = 'status-badge clean';
} else if (this.gitStatus.hasChanges) {
statusBadge.textContent = 'Geändert';
statusBadge.className = 'status-badge dirty';
} else {
statusBadge.textContent = 'OK';
statusBadge.className = 'status-badge clean';
}
// Änderungen
const changes = this.gitStatus.changes || [];
changesCount.textContent = changes.length;
}
renderBranches() {
const select = $('#branch-select');
if (!select) return;
const currentBranch = this.gitStatus?.branch || 'main';
select.innerHTML = '';
// Lokale Branches zuerst
const localBranches = this.branches.filter(b => !b.isRemote);
localBranches.forEach(branch => {
const option = document.createElement('option');
option.value = branch.name;
option.textContent = branch.name;
if (branch.name === currentBranch) {
option.selected = true;
}
select.appendChild(option);
});
}
renderCommits() {
const listEl = $('#git-commits-list');
const clearBtn = $('#btn-clear-commits');
if (!listEl) return;
// Filtern: Ausgeblendete Commits nicht anzeigen
const visibleCommits = this.commits.filter(commit => {
const hash = commit.hash || commit.sha || commit.shortHash;
return !this.hiddenCommits.has(hash);
});
// Button-Text anpassen
if (clearBtn) {
clearBtn.style.display = visibleCommits.length > 0 ? '' : 'none';
}
if (visibleCommits.length === 0) {
const message = this.commits.length > 0
? 'Alle Commits ausgeblendet'
: 'Keine Commits gefunden';
listEl.innerHTML = ``;
return;
}
listEl.innerHTML = visibleCommits.map(commit => {
const hash = commit.hash || commit.sha || '';
const shortHash = commit.shortHash || hash.substring(0, 7);
return `
${escapeHtml(shortHash)}
${escapeHtml(commit.message?.split('\n')[0] || '')}
${escapeHtml(commit.author)} · ${this.formatDate(commit.date)}
`}).join('');
}
handleCommitListClick(e) {
const deleteBtn = e.target.closest('.commit-delete');
if (deleteBtn) {
const hash = deleteBtn.dataset.hash;
if (hash) {
this.hideCommit(hash);
}
}
}
hideCommit(hash) {
this.hiddenCommits.add(hash);
this.renderCommits();
this.showToast('Commit ausgeblendet', 'info');
}
clearAllCommits() {
// Alle sichtbaren Commits ausblenden
this.commits.forEach(commit => {
const hash = commit.hash || commit.sha || commit.shortHash;
if (hash) {
this.hiddenCommits.add(hash);
}
});
this.renderCommits();
this.showToast('Alle Commits ausgeblendet', 'info');
}
renderChanges() {
const changesSection = $('#gitea-changes-section');
const listEl = $('#git-changes-list');
if (!changesSection || !listEl) return;
const changes = this.gitStatus?.changes || [];
if (changes.length === 0) {
changesSection.classList.add('hidden');
return;
}
changesSection.classList.remove('hidden');
listEl.innerHTML = changes.map(change => {
const statusClass = this.getChangeStatusClass(change.status);
const statusLabel = this.getChangeStatusLabel(change.status);
return `
${change.status}
${escapeHtml(change.file)}
`;
}).join('');
}
getChangeStatusClass(status) {
const first = status.charAt(0);
const second = status.charAt(1);
if (first === 'M' || second === 'M') return 'modified';
if (first === 'A' || second === 'A') return 'added';
if (first === 'D' || second === 'D') return 'deleted';
if (first === 'R' || second === 'R') return 'renamed';
if (first === '?' || second === '?') return 'untracked';
return '';
}
getChangeStatusLabel(status) {
const first = status.charAt(0);
const second = status.charAt(1);
if (first === 'M' || second === 'M') return 'Geändert';
if (first === 'A' || second === 'A') return 'Hinzugefügt';
if (first === 'D' || second === 'D') return 'Gelöscht';
if (first === 'R' || second === 'R') return 'Umbenannt';
if (first === '?' || second === '?') return 'Nicht verfolgt';
return status;
}
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'gerade eben';
if (diffMins < 60) return `vor ${diffMins} Minute${diffMins !== 1 ? 'n' : ''}`;
if (diffHours < 24) return `vor ${diffHours} Stunde${diffHours !== 1 ? 'n' : ''}`;
if (diffDays < 7) return `vor ${diffDays} Tag${diffDays !== 1 ? 'en' : ''}`;
// Lokale Formatierung
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}.${month}.${year}`;
}
// UI Helpers
hideAllSections() {
this.noProjectSection?.classList.add('hidden');
this.configSection?.classList.add('hidden');
this.mainSection?.classList.add('hidden');
}
showNoProjectMessage() {
this.hideAllSections();
this.noProjectSection?.classList.remove('hidden');
}
showLoading() {
this.isLoading = true;
}
showError(message) {
this.showToast(message, 'error');
}
showToast(message, type = 'info') {
window.dispatchEvent(new CustomEvent('toast:show', {
detail: { message, type }
}));
}
closeModal(modalId) {
const modal = $(`#${modalId}`);
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('visible');
}
$('#modal-overlay')?.classList.add('hidden');
store.closeModal(modalId);
}
setOperationLoading(operation, loading) {
const buttonId = `btn-git-${operation}`;
const button = $(`#${buttonId}`);
if (button) {
button.disabled = loading;
if (loading) {
button.classList.add('loading');
} else {
button.classList.remove('loading');
}
}
}
// View Control
show() {
this.giteaView?.classList.remove('hidden');
this.giteaView?.classList.add('active');
this.updateModeView();
this.startAutoRefresh();
}
hide() {
this.giteaView?.classList.add('hidden');
this.giteaView?.classList.remove('active');
this.stopAutoRefresh();
}
startAutoRefresh() {
this.stopAutoRefresh();
// Status alle 30 Sekunden aktualisieren
this.refreshInterval = setInterval(() => {
if (this.isLoading) return;
if (this.currentMode === 'server') {
// Server-Modus: Server-Daten aktualisieren
this.loadServerGitData();
} else if (this.application?.configured) {
// Projekt-Modus: Projekt-Daten aktualisieren
this.loadGitData();
}
}, 30000);
}
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
}
}
const giteaManager = new GiteaManager();
export { giteaManager };
export default giteaManager;