Dieser Commit ist enthalten in:
HG
2025-12-30 19:17:07 +00:00
committet von Server Deploy
Ursprung c8707d6cf4
Commit 15627cce99
14 geänderte Dateien mit 2456 neuen und 4 gelöschten Zeilen

Datei anzeigen

@ -31,6 +31,29 @@ class GiteaManager {
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() {
@ -51,8 +74,12 @@ class GiteaManager {
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;
}
@ -143,14 +170,19 @@ class GiteaManager {
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
// Projekt-Modus (Browser-Upload)
this.serverModeSection?.classList.add('hidden');
this.loadApplication();
this.browserUploadSection?.classList.remove('hidden');
this.noProjectSection?.classList.add('hidden');
this.configSection?.classList.add('hidden');
this.mainSection?.classList.add('hidden');
this.initBrowserUpload();
}
}
@ -524,6 +556,402 @@ class GiteaManager {
}
}
// =====================
// 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 = '<option value="">-- Repository wählen --</option>';
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 => `
<div class="upload-file-item">
<span class="file-icon">
<svg viewBox="0 0 24 24" width="16" height="16"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="2" fill="none"/><polyline points="14 2 14 8 20 8" stroke="currentColor" stroke-width="2" fill="none"/></svg>
</span>
<span class="file-path">${escapeHtml(f.relativePath)}</span>
<span class="file-size">${this.formatFileSizeUpload(f.size)}</span>
</div>
`).join('');
if (this.browserUploadFiles.length > 100) {
filesList.innerHTML += `
<div class="upload-file-item" style="justify-content: center; color: var(--text-tertiary);">
... und ${this.browserUploadFiles.length - 100} weitere Dateien
</div>
`;
}
}
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 = '<span class="spinner"></span> 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 = `
<svg viewBox="0 0 24 24" width="18" height="18"><path d="M12 19V5M5 12l7-7 7 7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
Commit & Push
`;
}
if (cancelBtn) cancelBtn.disabled = false;
progressContainer?.classList.add('hidden');
this.browserUploadSessionId = null;
}
}
// =====================
// PROJEKT-MODUS METHODEN
// =====================