Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ed561fe78 | ||
|
|
fe6c31793e | ||
|
|
4bb948d665 | ||
|
|
5c792eb1a8 | ||
|
|
309babf849 |
53
main.js
53
main.js
@@ -283,7 +283,7 @@ function tryReadWrappedCredentials(raw) {
|
|||||||
credentials: null,
|
credentials: null,
|
||||||
needsRewrite: false,
|
needsRewrite: false,
|
||||||
reason: 'safeStorage-unavailable',
|
reason: 'safeStorage-unavailable',
|
||||||
message: 'Gespeicherte Zugangsdaten koennen in dieser Sitzung nicht gelesen werden. Bitte neu anmelden.'
|
message: 'Gespeicherte Zugangsdaten können in dieser Sitzung nicht gelesen werden. Bitte neu anmelden.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ function tryReadWrappedCredentials(raw) {
|
|||||||
credentials: null,
|
credentials: null,
|
||||||
needsRewrite: false,
|
needsRewrite: false,
|
||||||
reason: 'safeStorage-decrypt-failed',
|
reason: 'safeStorage-decrypt-failed',
|
||||||
message: 'Gespeicherte Zugangsdaten konnten nicht entschluesselt werden. Bitte Token neu eingeben.'
|
message: 'Gespeicherte Zugangsdaten konnten nicht entschlüsselt werden. Bitte Token neu eingeben.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +349,7 @@ function readCredentialsFromFile(filePath) {
|
|||||||
credentials: null,
|
credentials: null,
|
||||||
needsRewrite: false,
|
needsRewrite: false,
|
||||||
reason: 'legacy-decrypt-failed',
|
reason: 'legacy-decrypt-failed',
|
||||||
message: 'Gespeicherte Zugangsdaten waren unlesbar und wurden zurueckgesetzt. Bitte Zugangsdaten neu eingeben.',
|
message: 'Gespeicherte Zugangsdaten waren unlesbar und wurden zurückgesetzt. Bitte Zugangsdaten neu eingeben.',
|
||||||
filePath
|
filePath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -678,7 +678,7 @@ function createWindow() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Externe Fenster immer blockieren und nur erlaubte URLs explizit im System-Browser oeffnen.
|
// Externe Fenster immer blockieren und nur erlaubte URLs explizit im System-Browser öffnen.
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
if (isAllowedExternalUrl(url)) {
|
if (isAllowedExternalUrl(url)) {
|
||||||
shell.openExternal(url).catch(() => {});
|
shell.openExternal(url).catch(() => {});
|
||||||
@@ -833,7 +833,7 @@ function mapIpcError(errorLike) {
|
|||||||
if (raw.includes('timeout') || raw.includes('econnaborted')) {
|
if (raw.includes('timeout') || raw.includes('econnaborted')) {
|
||||||
return 'Zeitüberschreitung bei der Verbindung. Bitte Netzwerk oder Server prüfen.';
|
return 'Zeitüberschreitung bei der Verbindung. Bitte Netzwerk oder Server prüfen.';
|
||||||
}
|
}
|
||||||
if (raw.includes('http://') || raw.includes('https://') || raw.includes('ungueltige gitea url') || raw.includes('ungültige gitea url') || raw.includes('invalid')) {
|
if (raw.includes('http://') || raw.includes('https://') || raw.includes('ungültige gitea url') || raw.includes('ungültige gitea url') || raw.includes('invalid')) {
|
||||||
return 'Ungültige URL. Beispiel für IPv6: http://[2001:db8::1]:3000';
|
return 'Ungültige URL. Beispiel für IPv6: http://[2001:db8::1]:3000';
|
||||||
}
|
}
|
||||||
return String(errorLike && errorLike.message ? errorLike.message : errorLike);
|
return String(errorLike && errorLike.message ? errorLike.message : errorLike);
|
||||||
@@ -1714,7 +1714,7 @@ ipcMain.handle('deleteFile', async (event, data) => {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeDeleteBatch(files) {
|
async function executeDeleteBatch(files, concurrency = 2) {
|
||||||
const deleteOps = files.map(f =>
|
const deleteOps = files.map(f =>
|
||||||
async () => {
|
async () => {
|
||||||
const res = await giteaDeleteFile(f.path, f.sha);
|
const res = await giteaDeleteFile(f.path, f.sha);
|
||||||
@@ -1722,8 +1722,8 @@ ipcMain.handle('deleteFile', async (event, data) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Gitea: mit Concurrency=2 + Retry-Logik bei 409-Konflikten für bessere Performance
|
// Concurrency kann dynamisch reduziert werden, um API-Konflikte bei Nachloeschungen zu vermeiden
|
||||||
const deleteResults = await runParallel(deleteOps, 2);
|
const deleteResults = await runParallel(deleteOps, concurrency);
|
||||||
const failedEntries = deleteResults.filter(r => !r.ok || !(r.result && r.result.ok));
|
const failedEntries = deleteResults.filter(r => !r.ok || !(r.result && r.result.ok));
|
||||||
return { deleteResults, failedEntries };
|
return { deleteResults, failedEntries };
|
||||||
}
|
}
|
||||||
@@ -1741,38 +1741,31 @@ ipcMain.handle('deleteFile', async (event, data) => {
|
|||||||
return { ok: true, deleted: 0 };
|
return { ok: true, deleted: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete attempt
|
// Delete attempt (schnell) + sequenzieller Fallback bei Konflikten
|
||||||
let { failedEntries } = await executeDeleteBatch(filesToDelete);
|
let { failedEntries } = await executeDeleteBatch(filesToDelete, 2);
|
||||||
|
|
||||||
|
// Bei Teilfehlern sofort sequenziellen Retry fahren (stabiler bei parallelen Commits)
|
||||||
if (failedEntries.length > 0) {
|
if (failedEntries.length > 0) {
|
||||||
const failedFiles = failedEntries
|
const failedFiles = failedEntries
|
||||||
.map(r => (r.result && r.result.path) ? r.result.path : 'unknown')
|
.map(r => (r.result && r.result.path) ? r.result.path : 'unknown')
|
||||||
.join(', ');
|
.join(', ');
|
||||||
logger.error('deleteFile', `Failed to delete ${failedEntries.length} files`, { files: failedFiles });
|
logger.warn('deleteFile', `Initial delete had ${failedEntries.length} failures, retrying sequentially`, { files: failedFiles });
|
||||||
return { ok: false, error: `${failedEntries.length} von ${filesToDelete.length} Dateien konnten nicht gelöscht werden` };
|
}
|
||||||
|
|
||||||
|
// Verification + bis zu zwei sequenzielle Nachlöschversuche auf verbleibende Dateien
|
||||||
|
for (let attempt = 0; attempt < 2; attempt++) {
|
||||||
|
if (!(await pathStillExists(filePath))) break;
|
||||||
|
const remaining = await collectAllFiles(filePath);
|
||||||
|
if (remaining.length === 0) break;
|
||||||
|
({ failedEntries } = await executeDeleteBatch(remaining, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verification + one automatic retry on leftovers
|
|
||||||
if (await pathStillExists(filePath)) {
|
if (await pathStillExists(filePath)) {
|
||||||
const remaining = await collectAllFiles(filePath);
|
const remaining = await collectAllFiles(filePath);
|
||||||
if (remaining.length > 0) {
|
const remainingPreview = remaining.slice(0, 10).map(f => f.path).join(', ');
|
||||||
({ failedEntries } = await executeDeleteBatch(remaining));
|
|
||||||
if (failedEntries.length > 0) {
|
|
||||||
const failedFiles = failedEntries
|
|
||||||
.map(r => (r.result && r.result.path) ? r.result.path : 'unknown')
|
|
||||||
.join(', ');
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: `Nachloeschung fehlgeschlagen. ${failedEntries.length} Dateien konnten nicht gelöscht werden (${failedFiles}).`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await pathStillExists(filePath)) {
|
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: 'Löschen unvollständig: Der Ordnerpfad ist nach zwei Versuchen noch vorhanden.'
|
error: `Löschen unvollständig: ${remaining.length} Dateien sind noch vorhanden (${remainingPreview}${remaining.length > 10 ? ', ...' : ''}).`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3844,7 +3837,7 @@ ipcMain.handle('import-settings-bundle', async () => {
|
|||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
|
|
||||||
if (!parsed || parsed.bundleType !== 'git-manager-settings-backup') {
|
if (!parsed || parsed.bundleType !== 'git-manager-settings-backup') {
|
||||||
return { ok: false, error: 'Ungueltige Backup-Datei.' };
|
return { ok: false, error: 'Ungültige Backup-Datei.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = readCredentials() || {};
|
const existing = readCredentials() || {};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "git-manager-gui",
|
"name": "git-manager-gui",
|
||||||
"version": "2.1.3",
|
"version": "2.1.5",
|
||||||
"description": "Git Manager GUI - Verwaltung von Git Repositories",
|
"description": "Git Manager GUI - Verwaltung von Git Repositories",
|
||||||
"author": "M_Viper",
|
"author": "M_Viper",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
|||||||
@@ -944,7 +944,7 @@ async function toggleRepoVisibility(owner, repoName, currentPrivate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const targetPrivate = !currentPrivate;
|
const targetPrivate = !currentPrivate;
|
||||||
const actionText = targetPrivate ? 'privat' : 'oeffentlich';
|
const actionText = targetPrivate ? 'privat' : 'öffentlich';
|
||||||
showProgress(35, `Repository wird ${actionText} gesetzt...`);
|
showProgress(35, `Repository wird ${actionText} gesetzt...`);
|
||||||
const result = await window.electronAPI.updateGiteaRepoVisibility({
|
const result = await window.electronAPI.updateGiteaRepoVisibility({
|
||||||
token: creds.giteaToken,
|
token: creds.giteaToken,
|
||||||
@@ -1048,7 +1048,7 @@ function showTagsEditorModal(owner, repoName, seed, knownTopics) {
|
|||||||
|
|
||||||
const hint = document.createElement('div');
|
const hint = document.createElement('div');
|
||||||
hint.className = 'settings-inline-hint';
|
hint.className = 'settings-inline-hint';
|
||||||
hint.textContent = 'Vorschlaege kommen live von deiner Gitea-Seite. Neue Tags sind ebenfalls erlaubt.';
|
hint.textContent = 'Vorschläge kommen live von deiner Gitea-Seite. Neue Tags sind ebenfalls erlaubt.';
|
||||||
|
|
||||||
group.appendChild(repoLabel);
|
group.appendChild(repoLabel);
|
||||||
group.appendChild(selectedHostEl);
|
group.appendChild(selectedHostEl);
|
||||||
@@ -1130,7 +1130,7 @@ function showTagsEditorModal(owner, repoName, seed, knownTopics) {
|
|||||||
}
|
}
|
||||||
list = list.filter(t => !selected.some(s => s.toLowerCase() === t.toLowerCase())).slice(0, 50);
|
list = list.filter(t => !selected.some(s => s.toLowerCase() === t.toLowerCase())).slice(0, 50);
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
suggestionsHost.innerHTML = '<div style="padding:10px 12px;color:var(--text-muted);font-size:12px;">Keine Vorschlaege</div>';
|
suggestionsHost.innerHTML = '<div style="padding:10px 12px;color:var(--text-muted);font-size:12px;">Keine Vorschläge</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
suggestionsHost.innerHTML = '';
|
suggestionsHost.innerHTML = '';
|
||||||
@@ -1521,7 +1521,7 @@ async function buildGithubHeatmapFromRepoCommits(monthsBack) {
|
|||||||
dayMap.set(key, (dayMap.get(key) || 0) + 1);
|
dayMap.set(key, (dayMap.get(key) || 0) + 1);
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// einzelne Repos koennen fehlschlagen ohne den Gesamtprozess zu blockieren
|
// einzelne Repos können fehlschlagen, ohne den Gesamtprozess zu blockieren
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2742,9 +2742,9 @@ function renderTabs() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (tab.dirty) {
|
if (tab.dirty) {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Ungespeicherte Aenderungen',
|
title: 'Ungespeicherte Änderungen',
|
||||||
message: `${tab.name} hat ungespeicherte Aenderungen. Wirklich schliessen?`,
|
message: `${tab.name} hat ungespeicherte Änderungen. Wirklich schließen?`,
|
||||||
confirmText: 'Schliessen',
|
confirmText: 'Schließen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -3009,9 +3009,9 @@ async function closeFileEditor() {
|
|||||||
|
|
||||||
if (unsaved.length > 0) {
|
if (unsaved.length > 0) {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Ungespeicherte Aenderungen',
|
title: 'Ungespeicherte Änderungen',
|
||||||
message: `${unsaved.length} Datei(en) haben ungespeicherte Aenderungen. Wirklich schliessen?`,
|
message: `${unsaved.length} Datei(en) haben ungespeicherte Änderungen. Wirklich schließen?`,
|
||||||
confirmText: 'Schliessen',
|
confirmText: 'Schließen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -3049,7 +3049,7 @@ async function openFileEditor(filePath, fileName) {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
addTab(filePath, fileName, response.content);
|
addTab(filePath, fileName, response.content);
|
||||||
} else {
|
} else {
|
||||||
await showInfoModal('Datei konnte nicht geoeffnet werden', `Fehler: ${response.error || 'Unbekannter Fehler'}`, true);
|
await showInfoModal('Datei konnte nicht geöffnet werden', `Fehler: ${response.error || 'Unbekannter Fehler'}`, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3065,7 +3065,7 @@ async function openFileEditor(filePath, fileName) {
|
|||||||
console.log('✅ File opened');
|
console.log('✅ File opened');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error opening file:', error);
|
console.error('Error opening file:', error);
|
||||||
await showInfoModal('Datei konnte nicht geoeffnet werden', 'Fehler beim Oeffnen der Datei.', true);
|
await showInfoModal('Datei konnte nicht geöffnet werden', 'Fehler beim Öffnen der Datei.', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3110,7 +3110,7 @@ async function openGiteaFileInEditor(owner, repo, filePath, fileName) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error opening Gitea file:', error);
|
console.error('Error opening Gitea file:', error);
|
||||||
await showInfoModal('Datei konnte nicht geoeffnet werden', 'Fehler beim Oeffnen der Datei.', true);
|
await showInfoModal('Datei konnte nicht geöffnet werden', 'Fehler beim Öffnen der Datei.', true);
|
||||||
showError('Fehler');
|
showError('Fehler');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3277,7 +3277,7 @@ async function saveCurrentFile(isAutoSave = false) {
|
|||||||
|
|
||||||
// Prüfe ob es eine Bilddatei ist
|
// Prüfe ob es eine Bilddatei ist
|
||||||
if (/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(currentActiveTab)) {
|
if (/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(currentActiveTab)) {
|
||||||
await showInfoModal('Nicht bearbeitbar', 'Bilder koennen nicht bearbeitet werden.');
|
await showInfoModal('Nicht bearbeitbar', 'Bilder können nicht bearbeitet werden.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4374,7 +4374,7 @@ function showSyncConfirmModal({ title = 'Bestaetigen', message = '', confirmText
|
|||||||
|
|
||||||
const detailTitle = document.createElement('div');
|
const detailTitle = document.createElement('div');
|
||||||
detailTitle.style.cssText = 'font-size:12px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:#b9c9e8;margin-bottom:4px;';
|
detailTitle.style.cssText = 'font-size:12px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:#b9c9e8;margin-bottom:4px;';
|
||||||
detailTitle.textContent = 'Was wird uebernommen';
|
detailTitle.textContent = 'Was wird übernommen';
|
||||||
detailBlock.appendChild(detailTitle);
|
detailBlock.appendChild(detailTitle);
|
||||||
|
|
||||||
rows.forEach((d) => {
|
rows.forEach((d) => {
|
||||||
@@ -4600,7 +4600,7 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
|
|||||||
details: [
|
details: [
|
||||||
{ label: 'Richtung', value: 'Gitea -> GitHub' },
|
{ label: 'Richtung', value: 'Gitea -> GitHub' },
|
||||||
{ label: 'Repository', value: `${owner}/${repoName}` },
|
{ label: 'Repository', value: `${owner}/${repoName}` },
|
||||||
{ label: 'Sichtbarkeit', value: isPrivate ? 'Privat' : 'Oeffentlich' },
|
{ label: 'Sichtbarkeit', value: isPrivate ? 'Privat' : 'Öffentlich' },
|
||||||
{ label: 'Beschreibung', value: (repoMeta?.description || '-').slice(0, 160) },
|
{ label: 'Beschreibung', value: (repoMeta?.description || '-').slice(0, 160) },
|
||||||
{ label: 'Topics', value: (Array.isArray(repoMeta?.topics) && repoMeta.topics.length > 0) ? repoMeta.topics.join(', ') : '-' }
|
{ label: 'Topics', value: (Array.isArray(repoMeta?.topics) && repoMeta.topics.length > 0) ? repoMeta.topics.join(', ') : '-' }
|
||||||
]
|
]
|
||||||
@@ -4633,7 +4633,7 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
|
|||||||
details: [
|
details: [
|
||||||
{ label: 'Richtung', value: 'GitHub -> Gitea' },
|
{ label: 'Richtung', value: 'GitHub -> Gitea' },
|
||||||
{ label: 'Repository', value: `${owner}/${repoName}` },
|
{ label: 'Repository', value: `${owner}/${repoName}` },
|
||||||
{ label: 'Sichtbarkeit', value: isPrivate ? 'Privat' : 'Oeffentlich' },
|
{ label: 'Sichtbarkeit', value: isPrivate ? 'Privat' : 'Öffentlich' },
|
||||||
{ label: 'Beschreibung', value: (repoMeta?.description || '-').slice(0, 160) },
|
{ label: 'Beschreibung', value: (repoMeta?.description || '-').slice(0, 160) },
|
||||||
{ label: 'Topics', value: (Array.isArray(repoMeta?.topics) && repoMeta.topics.length > 0) ? repoMeta.topics.join(', ') : '-' }
|
{ label: 'Topics', value: (Array.isArray(repoMeta?.topics) && repoMeta.topics.length > 0) ? repoMeta.topics.join(', ') : '-' }
|
||||||
]
|
]
|
||||||
@@ -4757,9 +4757,9 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
|
|||||||
const deleteItem = createMenuItem('🗑️', 'Repo löschen', async () => {
|
const deleteItem = createMenuItem('🗑️', 'Repo löschen', async () => {
|
||||||
menu.remove();
|
menu.remove();
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Repository loeschen',
|
title: 'Repository löschen',
|
||||||
message: `Delete ${repoName}?`,
|
message: `Repository ${repoName} wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -4767,13 +4767,13 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
|
|||||||
const res = await window.electronAPI.deleteGiteaRepo({ owner, repo: repoName, platform: currentState.platform });
|
const res = await window.electronAPI.deleteGiteaRepo({ owner, repo: repoName, platform: currentState.platform });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
element.remove();
|
element.remove();
|
||||||
showSuccess('Repository deleted');
|
showSuccess('Repository gelöscht');
|
||||||
} else {
|
} else {
|
||||||
showError('Delete failed: ' + res.error);
|
showError('Löschen fehlgeschlagen: ' + res.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete error:', error);
|
console.error('Delete error:', error);
|
||||||
showError('Delete failed');
|
showError('Löschen fehlgeschlagen');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, '#ef4444');
|
}, '#ef4444');
|
||||||
@@ -4832,9 +4832,9 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
|
|||||||
|
|
||||||
addItem('🗑️', `Alle ${selectedItems.size} löschen`, async () => {
|
addItem('🗑️', `Alle ${selectedItems.size} löschen`, async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Mehrfach-Loeschen',
|
title: 'Mehrfach-Löschen',
|
||||||
message: `${selectedItems.size} Elemente wirklich loeschen?`,
|
message: `${selectedItems.size} Elemente wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -4911,10 +4911,10 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
|
|||||||
|
|
||||||
// --- LÖSCHEN ---
|
// --- LÖSCHEN ---
|
||||||
addItem('🗑️', 'Löschen', async () => {
|
addItem('🗑️', 'Löschen', async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Element loeschen',
|
title: 'Element löschen',
|
||||||
message: `"${item.name}" wirklich loeschen?`,
|
message: `"${item.name}" wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -4927,7 +4927,7 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
|
|||||||
loadRepoContents(owner, repo, currentState.path);
|
loadRepoContents(owner, repo, currentState.path);
|
||||||
} else {
|
} else {
|
||||||
showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
|
showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
|
||||||
await showInfoModal('Loeschen fehlgeschlagen', 'Loeschen fehlgeschlagen:\n' + (res?.error || 'Unbekannter Fehler'), true);
|
await showInfoModal('Löschen fehlgeschlagen', 'Löschen fehlgeschlagen:\n' + (res?.error || 'Unbekannter Fehler'), true);
|
||||||
}
|
}
|
||||||
}, '#ef4444');
|
}, '#ef4444');
|
||||||
|
|
||||||
@@ -4980,9 +4980,9 @@ function showLocalItemContextMenu(ev, node) {
|
|||||||
|
|
||||||
addItem('🗑️', `Alle ${selectedItems.size} löschen`, async () => {
|
addItem('🗑️', `Alle ${selectedItems.size} löschen`, async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Mehrfach-Loeschen',
|
title: 'Mehrfach-Löschen',
|
||||||
message: `${selectedItems.size} Elemente wirklich loeschen?`,
|
message: `${selectedItems.size} Elemente wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -5041,10 +5041,10 @@ function showLocalItemContextMenu(ev, node) {
|
|||||||
|
|
||||||
// --- LÖSCHEN ---
|
// --- LÖSCHEN ---
|
||||||
addItem('🗑️', 'Löschen', async () => {
|
addItem('🗑️', 'Löschen', async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Element loeschen',
|
title: 'Element löschen',
|
||||||
message: `"${node.name}" wirklich loeschen?`,
|
message: `"${node.name}" wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -6807,9 +6807,9 @@ function createReleaseCard(release, isLatest) {
|
|||||||
`;
|
`;
|
||||||
deleteAssetBtn.onclick = async () => {
|
deleteAssetBtn.onclick = async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Asset loeschen',
|
title: 'Asset löschen',
|
||||||
message: `Delete asset "${asset.name}"?`,
|
message: `Asset "${asset.name}" wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -6820,7 +6820,7 @@ function createReleaseCard(release, isLatest) {
|
|||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
assetItem.remove();
|
assetItem.remove();
|
||||||
setStatus('Asset deleted');
|
setStatus('Asset gelöscht');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -6910,7 +6910,7 @@ function createReleaseCard(release, isLatest) {
|
|||||||
addAssetBtn.onclick = () => showUploadAssetDialog(release);
|
addAssetBtn.onclick = () => showUploadAssetDialog(release);
|
||||||
|
|
||||||
const deleteBtn = document.createElement('button');
|
const deleteBtn = document.createElement('button');
|
||||||
deleteBtn.textContent = '🗑️ Delete';
|
deleteBtn.textContent = '🗑️ Löschen';
|
||||||
deleteBtn.style.cssText = `
|
deleteBtn.style.cssText = `
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
@@ -6923,9 +6923,9 @@ function createReleaseCard(release, isLatest) {
|
|||||||
`;
|
`;
|
||||||
deleteBtn.onclick = async () => {
|
deleteBtn.onclick = async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Release loeschen',
|
title: 'Release löschen',
|
||||||
message: `Delete release "${release.name || release.tag_name}"?`,
|
message: `Release "${release.name || release.tag_name}" wirklich löschen?`,
|
||||||
confirmText: 'Loeschen',
|
confirmText: 'Löschen',
|
||||||
danger: true
|
danger: true
|
||||||
});
|
});
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -7093,7 +7093,7 @@ function showCreateReleaseModal(owner, repo) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Auswählen der Datei:', error);
|
console.error('Fehler beim Auswählen der Datei:', error);
|
||||||
await showInfoModal('Dateidialog', 'Konnte Dateidialog nicht oeffnen.', true);
|
await showInfoModal('Dateidialog', 'Konnte Dateidialog nicht öffnen.', true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8103,7 +8103,7 @@ async function initUpdater() {
|
|||||||
$('btnImportSettingsBundle').onclick = async () => {
|
$('btnImportSettingsBundle').onclick = async () => {
|
||||||
const ok = await showActionConfirmModal({
|
const ok = await showActionConfirmModal({
|
||||||
title: 'Backup importieren',
|
title: 'Backup importieren',
|
||||||
message: 'Vorhandene Einstellungen koennen ueberschrieben werden. Fortfahren?',
|
message: 'Vorhandene Einstellungen können überschrieben werden. Fortfahren?',
|
||||||
confirmText: 'Import starten',
|
confirmText: 'Import starten',
|
||||||
danger: false
|
danger: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function normalizeAndValidateBaseUrl(rawUrl) {
|
|||||||
try {
|
try {
|
||||||
parsed = new URL(value);
|
parsed = new URL(value);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
throw new Error('Ungueltige Gitea URL. Beispiel fuer IPv6: http://[2001:db8::1]:3000');
|
throw new Error('Ungültige Gitea-URL. Beispiel für IPv6: http://[2001:db8::1]:3000');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||||
@@ -1293,7 +1293,7 @@ async function updateGiteaRepoTopics({ token, url, owner, repo, topics }) {
|
|||||||
);
|
);
|
||||||
return res.data;
|
return res.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Fallback fuer aeltere/abweichende Gitea-Versionen
|
// Fallback für ältere/abweichende Gitea-Versionen
|
||||||
const res = await axiosInstance.patch(
|
const res = await axiosInstance.patch(
|
||||||
`${base}/api/v1/repos/${repoPath}`,
|
`${base}/api/v1/repos/${repoPath}`,
|
||||||
{ topics: safeTopics },
|
{ topics: safeTopics },
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class Updater {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Updater] Fehler beim Update-Check:', error);
|
console.error('[Updater] Fehler beim Update-Check:', error);
|
||||||
this.emitUpdateError('Update-Pruefung fehlgeschlagen.', {
|
this.emitUpdateError('Update-Prüfung fehlgeschlagen.', {
|
||||||
phase: 'check',
|
phase: 'check',
|
||||||
silent: !!silent,
|
silent: !!silent,
|
||||||
error: String(error?.message || error)
|
error: String(error?.message || error)
|
||||||
@@ -325,7 +325,7 @@ class Updater {
|
|||||||
} catch (verifyErr) {
|
} catch (verifyErr) {
|
||||||
console.error('[Updater] Checksum-Validierung konnte nicht ausgeführt werden:', verifyErr?.message || verifyErr);
|
console.error('[Updater] Checksum-Validierung konnte nicht ausgeführt werden:', verifyErr?.message || verifyErr);
|
||||||
fs.unlink(tempPath, () => {});
|
fs.unlink(tempPath, () => {});
|
||||||
this.emitUpdateError('Update-Download fehlgeschlagen: Checksum-Validierung nicht moeglich.', {
|
this.emitUpdateError('Update-Download fehlgeschlagen: Checksum-Validierung nicht möglich.', {
|
||||||
phase: 'download',
|
phase: 'download',
|
||||||
reason: 'checksum-verify-failed',
|
reason: 'checksum-verify-failed',
|
||||||
error: String(verifyErr?.message || verifyErr)
|
error: String(verifyErr?.message || verifyErr)
|
||||||
|
|||||||
Reference in New Issue
Block a user