Files
ForgeFlow/src/renderer/operations.js
T

212 lines
6.4 KiB
JavaScript

async function runOperation(
message,
operation,
successMessage,
{ refresh = true } = {},
) {
setLoading(true, message);
try {
const result = await operation();
if (successMessage) showToast("Done", successMessage, "success");
if (refresh) await refreshRepositories(false);
return result;
} catch (error) {
const pushAfterCommit = error.code === "PUSH_AFTER_COMMIT_FAILED";
showToast(
pushAfterCommit
? "Commit saved locally; push failed"
: "Operation failed",
error.message,
"error",
);
// Always reload the real Git state. A failed stage must keep changes visible, while a
// failed push after a successful commit must immediately surface as an ahead branch.
await refreshRepositories(false, true);
if (pushAfterCommit) {
ui.selectedFiles.clear();
ui.selectedFile = null;
ui.diff = "";
ui.commitMessage = "";
render();
}
return null;
} finally {
setLoading(false);
}
}
async function executeDeployment(profileId) {
const repository = selectedRepository();
const profile =
repository?.deploymentProfiles?.find((item) => item.id === profileId) ||
selectedProfile(repository);
if (!repository || !profile) return;
const targetSha = deploymentTargetSha(repository, profile);
if (!targetSha) {
showToast("Refresh required", "Refresh Gitea and server truth before deploying this environment.", "error");
return;
}
const deploymentOptions = {
note: document.querySelector("#deployment-note")?.value.trim() || "",
override: document.querySelector("#deployment-override")?.checked === true,
overrideReason:
document.querySelector("#deployment-override-reason")?.value.trim() || "",
};
ui.modal = null;
setLoading(
true,
profile.provider === "ssh-unraid"
? `Deploying ${repository.name} to ${profile.remoteFolder} over SSH…`
: `Dispatching ${profile.name} workflow…`,
);
try {
ui.activeDeployment = await window.forgeflow.deploy(
repository,
profile.id,
targetSha,
deploymentOptions,
);
updateOperationInState(ui.activeDeployment);
ui.currentView = "deployment-run";
showToast(
"Deployment started",
`${repository.name} ${shortSha(targetSha)}${profile.environment}`,
"success",
);
startOperationPolling();
} catch (error) {
if (profile.provider === "ssh-unraid" && isSshCredentialError(error)) {
ui.modal = {
type: "server-password",
serverId: profile.serverId,
retry: {
type: "deploy",
repositoryFullName: repository.fullName,
profileId: profile.id,
},
};
showToast("SSH key rejected", "Enter the Unraid server password once; ForgeFlow will retry the direct desktop → Unraid connection.", "error");
render();
} else {
showToast("Deployment failed to start", error.message, "error");
}
}
setLoading(false);
}
async function executeRollback(profileId) {
const repository = selectedRepository();
const profile = repository?.deploymentProfiles?.find(
(item) => item.id === profileId,
);
const target = profile?.state?.previousSha;
if (!repository || !profile || !target) return;
ui.modal = null;
setLoading(
true,
profile?.provider === "ssh-unraid"
? `Rolling back ${profile.remoteFolder} over SSH…`
: `Dispatching rollback to ${shortSha(target)}…`,
);
try {
ui.activeDeployment = await window.forgeflow.rollback(
repository,
profile.id,
target,
);
updateOperationInState(ui.activeDeployment);
ui.currentView = "deployment-run";
showToast(
"Rollback requested",
`${profile.environment}${shortSha(target)}`,
"success",
);
} catch (error) {
showToast("Rollback failed to start", error.message, "error");
}
setLoading(false);
}
async function loadGitTools(repository) {
if (!repository?.localPath) return;
setLoading(true, "Loading branches and stashes…");
try {
[ui.branches, ui.stashes, ui.gitRecovery] = await Promise.all([
window.forgeflow.branches(repository.localPath),
window.forgeflow.stashList(repository.localPath),
window.forgeflow.gitRecoveryStatus(repository.localPath),
]);
ui.repositoryTab = "gittools";
} catch (error) {
showToast("Git tools unavailable", error.message, "error");
}
setLoading(false);
}
function profileRepository(profileId) {
return ui.repositories.find((repository) =>
repository.deploymentProfiles?.some((profile) => profile.id === profileId),
);
}
async function runSystemPreflight({ setup = false } = {}) {
setLoading(true, "Checking local readiness…");
try {
ui.systemPreflight = await window.forgeflow.setupPreflight({
baseUrl: ui.setupDraft.baseUrl,
token: ui.setupDraft.token,
roots: setup ? ui.setupDraft.roots : ui.boot.state.workspaceRoots,
});
if (!setup)
ui.diagnosticsStatus = await window.forgeflow.diagnosticsStatus();
showToast(
ui.systemPreflight.summary.ready
? "Readiness checks passed"
: "Readiness needs attention",
ui.systemPreflight.summary.ready
? `${ui.systemPreflight.summary.counts.pass} checks passed.`
: `${ui.systemPreflight.summary.blocking.length} blocking check(s) must be resolved.`,
ui.systemPreflight.summary.ready ? "success" : "error",
);
return ui.systemPreflight;
} catch (error) {
showToast("Readiness check failed", error.message, "error");
return null;
} finally {
setLoading(false);
}
}
async function runDeploymentPreflight(
repository,
profileId,
{ showModal = true } = {},
) {
if (!repository || !profileId) return null;
if (String(repository.id) !== String(ui.selectedRepoId))
selectRepository(repository.id, false);
ui.selectedProfileId = profileId;
ui.deploymentPreflight = null;
setLoading(true, "Verifying repository, workflow and server…");
try {
const report = await window.forgeflow.deploymentPreflight(
repository,
profileId,
);
ui.deploymentPreflight = report;
if (showModal)
ui.modal = {
type: "deployment-preflight",
profileId,
repositoryFullName: repository.fullName,
};
return report;
} catch (error) {
showToast("Deployment preflight failed", error.message, "error");
return null;
} finally {
setLoading(false);
}
}