212 lines
6.4 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
|