356 lines
12 KiB
JavaScript
356 lines
12 KiB
JavaScript
async function handleRecoveryActions(event, target, action, repository) {
|
|
if (action === "repair-origin") {
|
|
if (!repository?.localPath || !repository.sshUrl) return;
|
|
if (
|
|
!confirm(
|
|
`Replace origin with ${repository.sshUrl}? Local files and commits are not changed.`,
|
|
)
|
|
)
|
|
return;
|
|
setLoading(true, "Updating Git origin…");
|
|
try {
|
|
await window.forgeflow.setOrigin(repository.localPath, repository.sshUrl);
|
|
await refreshRepositories(false);
|
|
showToast("Git origin updated", repository.sshUrl, "success");
|
|
} catch (error) {
|
|
showToast("Could not update origin", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "normalize-origins") {
|
|
if (
|
|
!confirm(
|
|
"Replace legacy origin URLs for every linked repository with the current Gitea SSH URL? Local files and commits are not changed.",
|
|
)
|
|
)
|
|
return;
|
|
setLoading(true, "Normalizing linked Git origins…");
|
|
try {
|
|
const result = await window.forgeflow.normalizeOrigins();
|
|
ui.repositories = result.repositories;
|
|
showToast(
|
|
"Git origins normalized",
|
|
`${result.changes.length} repository origin${result.changes.length === 1 ? "" : "s"} updated.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Could not normalize origins", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "scan-git-recovery") {
|
|
if (!repository?.localPath) return;
|
|
setLoading(true, "Scanning Git directory and active processes…");
|
|
try {
|
|
ui.gitRecovery = await window.forgeflow.gitRecoveryStatus(
|
|
repository.localPath,
|
|
);
|
|
ui.repositoryTab = "gittools";
|
|
showToast(
|
|
"Git health scan complete",
|
|
`${ui.gitRecovery.lockReport.locks.length} lock file(s) found.`,
|
|
ui.gitRecovery.lockReport.locks.length ? "info" : "success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Git health scan failed", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "repair-git-locks" || action === "repair-index-lock") {
|
|
if (
|
|
!repository?.localPath ||
|
|
!confirm(
|
|
"Repair stale Git lock files for this repository? ForgeFlow refuses while a matching Git process is active.",
|
|
)
|
|
)
|
|
return;
|
|
setLoading(true, "Safely repairing stale Git locks…");
|
|
try {
|
|
const result = await window.forgeflow.repairGitLocks(
|
|
repository.localPath,
|
|
false,
|
|
);
|
|
ui.gitRecovery = await window.forgeflow.gitRecoveryStatus(
|
|
repository.localPath,
|
|
);
|
|
await refreshRepositories(false);
|
|
showToast(
|
|
"Git locks repaired",
|
|
`${result.removed.length} stale lock file(s) removed.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
if (
|
|
error.code === "GIT_PROCESS_PROBE_UNAVAILABLE" &&
|
|
confirm(`${error.message}
|
|
|
|
Force repair after you have closed all Git tools for this repository?`)
|
|
) {
|
|
try {
|
|
const result = await window.forgeflow.repairGitLocks(
|
|
repository.localPath,
|
|
true,
|
|
);
|
|
showToast(
|
|
"Git locks force-repaired",
|
|
`${result.removed.length} lock file(s) removed.`,
|
|
"success",
|
|
);
|
|
await refreshRepositories(false);
|
|
} catch (forceError) {
|
|
showToast("Could not repair Git locks", forceError.message, "error");
|
|
}
|
|
} else showToast("Could not repair Git locks", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "reconcile-repository") {
|
|
if (!repository?.localPath) return;
|
|
setLoading(true, "Refreshing repository truth from Git…");
|
|
try {
|
|
ui.gitRecovery = await window.forgeflow.reconcileRepository(
|
|
repository.localPath,
|
|
);
|
|
await refreshRepositories(false);
|
|
showToast(
|
|
"Repository reconciled",
|
|
"Branch, upstream, lock and working-tree state were refreshed.",
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Could not reconcile repository", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "repair-repository-sync") {
|
|
if (!repository?.localPath) return;
|
|
const strategy = target.dataset.strategy;
|
|
const destructive = strategy === "backup-reset";
|
|
const message = destructive
|
|
? "Create a safety branch from the current HEAD and reset this branch to its upstream? Uncommitted changes are never discarded."
|
|
: `Run the repository-specific ${strategy} repair now?`;
|
|
if (!confirm(message)) return;
|
|
setLoading(
|
|
true,
|
|
destructive
|
|
? "Creating safety branch and repairing divergence…"
|
|
: "Repairing repository synchronization…",
|
|
);
|
|
try {
|
|
const result = await window.forgeflow.repairRepositorySync(
|
|
repository.localPath,
|
|
strategy,
|
|
);
|
|
ui.gitRecovery = await window.forgeflow.gitRecoveryStatus(
|
|
repository.localPath,
|
|
);
|
|
await refreshRepositories(false);
|
|
showToast(
|
|
"Repository synchronization repaired",
|
|
result.backupBranch
|
|
? `Safety branch created: ${result.backupBranch}`
|
|
: `Completed ${strategy}.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Synchronization repair failed", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "run-troubleshooter") {
|
|
setLoading(
|
|
true,
|
|
"Scanning repositories, Git operations and deployment servers…",
|
|
);
|
|
try {
|
|
ui.troubleshooter = await window.forgeflow.troubleshooterScan();
|
|
showToast(
|
|
"Troubleshooter completed",
|
|
ui.troubleshooter.summary.total
|
|
? `${ui.troubleshooter.summary.total} issue(s) found; ${ui.troubleshooter.summary.repairable} repairable.`
|
|
: "No problems were detected.",
|
|
ui.troubleshooter.summary.errors ? "error" : "success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Troubleshooter failed", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "troubleshooter-auto-repair") {
|
|
const safeIssues = (ui.troubleshooter?.issues || []).filter(
|
|
(item) => item.repairable && item.safe,
|
|
);
|
|
if (
|
|
!safeIssues.length ||
|
|
!confirm(
|
|
`Repair ${safeIssues.length} safe issue(s) now? ForgeFlow will not run destructive reset actions automatically.`,
|
|
)
|
|
)
|
|
return;
|
|
setLoading(true, "Applying safe one-click repairs…");
|
|
try {
|
|
const results =
|
|
await window.forgeflow.troubleshooterAutoRepair(safeIssues);
|
|
ui.troubleshooter = await window.forgeflow.troubleshooterScan();
|
|
await refreshRepositories(false);
|
|
const failed = results.filter((item) => !item.ok);
|
|
showToast(
|
|
failed.length
|
|
? "Repairs partially completed"
|
|
: "Safe repairs completed",
|
|
`${results.length - failed.length} repaired, ${failed.length} failed.`,
|
|
failed.length ? "error" : "success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Automatic repair failed", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "troubleshooter-repair") {
|
|
const issue =
|
|
ui.troubleshooter?.issues?.[Number(target.dataset.issueIndex)];
|
|
if (!issue) return;
|
|
const warning = issue.safe
|
|
? `Repair “${issue.title}” now?`
|
|
: `“${issue.title}” requires a safety branch or another potentially destructive change. Continue?`;
|
|
if (!confirm(warning)) return;
|
|
setLoading(true, `Repairing ${issue.title}…`);
|
|
try {
|
|
const result = await window.forgeflow.troubleshooterRepair(issue);
|
|
ui.troubleshooter = await window.forgeflow.troubleshooterScan();
|
|
await refreshRepositories(false);
|
|
showToast(
|
|
"Problem repaired",
|
|
result?.backupBranch
|
|
? `Safety branch created: ${result.backupBranch}`
|
|
: issue.title,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Repair failed", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
render();
|
|
} else if (action === "run-system-preflight") await runSystemPreflight();
|
|
else if (action === "load-audit-log") {
|
|
try {
|
|
ui.auditEvents = await window.forgeflow.listAuditEvents(500);
|
|
render();
|
|
} catch (error) {
|
|
showToast("Could not load audit log", error.message, "error");
|
|
}
|
|
} else if (action === "export-audit-json" || action === "export-audit-csv") {
|
|
try {
|
|
const result = await window.forgeflow.exportAuditLog(
|
|
action.endsWith("csv") ? "csv" : "json",
|
|
);
|
|
if (result)
|
|
showToast(
|
|
"Audit log exported",
|
|
`${result.count} records exported.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Could not export audit log", error.message, "error");
|
|
}
|
|
} else if (action === "save-diagnostics-preferences") {
|
|
const preferences = {
|
|
diagnosticsEnabled:
|
|
document.querySelector("#diagnostics-enabled").value === "true",
|
|
diagnosticLevel: document.querySelector("#diagnostic-level").value,
|
|
logRetentionDays: Number(
|
|
document.querySelector("#diagnostic-retention").value,
|
|
),
|
|
maxLogFileMb: Number(
|
|
document.querySelector("#diagnostic-max-file").value,
|
|
),
|
|
};
|
|
setLoading(true, "Saving diagnostic policy…");
|
|
try {
|
|
ui.boot.state = await window.forgeflow.setPreferences(preferences);
|
|
ui.diagnosticsStatus = await window.forgeflow.diagnosticsStatus();
|
|
showToast(
|
|
"Diagnostic policy saved",
|
|
"New events now use the updated retention and logging level.",
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Could not save diagnostics", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "export-diagnostics") {
|
|
const privacyMode =
|
|
document.querySelector("#diagnostic-privacy")?.value || "standard";
|
|
setLoading(true, "Creating redacted diagnostic bundle…");
|
|
try {
|
|
const bundle = await window.forgeflow.exportDiagnostics(privacyMode);
|
|
if (bundle) {
|
|
ui.lastDiagnosticBundle = bundle;
|
|
ui.diagnosticsStatus = await window.forgeflow.diagnosticsStatus();
|
|
showToast(
|
|
"Diagnostic bundle created",
|
|
`${bundle.size} · SHA-256 ${shortSha(bundle.sha256)}`,
|
|
"success",
|
|
);
|
|
}
|
|
} catch (error) {
|
|
showToast("Could not export diagnostics", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "show-diagnostic-bundle") {
|
|
if (!ui.lastDiagnosticBundle?.path) return;
|
|
await window.forgeflow
|
|
.showDiagnosticBundle(ui.lastDiagnosticBundle.path)
|
|
.catch((error) =>
|
|
showToast("Could not show bundle", error.message, "error"),
|
|
);
|
|
} else if (action === "open-diagnostics-folder")
|
|
await window.forgeflow
|
|
.openDiagnosticsFolder()
|
|
.catch((error) =>
|
|
showToast("Could not open diagnostic folder", error.message, "error"),
|
|
);
|
|
else if (action === "clear-diagnostics") {
|
|
if (
|
|
!confirm(
|
|
"Clear local ForgeFlow diagnostic logs? This does not affect repositories or configuration.",
|
|
)
|
|
)
|
|
return;
|
|
try {
|
|
ui.diagnosticsStatus = await window.forgeflow.clearDiagnostics();
|
|
showToast(
|
|
"Diagnostic logs cleared",
|
|
"A new session marker was created.",
|
|
"success",
|
|
);
|
|
render();
|
|
} catch (error) {
|
|
showToast("Could not clear logs", error.message, "error");
|
|
}
|
|
} else if (action === "reset-app") {
|
|
if (
|
|
!confirm(
|
|
"Reset ForgeFlow configuration? Your Git repositories and Gitea data are not modified.",
|
|
)
|
|
)
|
|
return;
|
|
ui.boot.state = await window.forgeflow.reset();
|
|
ui.repositories = [];
|
|
ui.setupStep = 0;
|
|
ui.setupValidation = null;
|
|
ui.systemPreflight = null;
|
|
ui.deploymentPreflight = null;
|
|
ui.lastDiagnosticBundle = null;
|
|
ui.setupDraft = {
|
|
baseUrl: "https://",
|
|
token: "",
|
|
user: null,
|
|
roots: [],
|
|
discovered: [],
|
|
};
|
|
render();
|
|
}
|
|
else return false;
|
|
return true;
|
|
}
|