refactor: split renderer ipc and unraid domains
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
async function handleShellActions(event, target, action, repository) {
|
||||
if (action === "navigate") {
|
||||
ui.currentView = target.dataset.view;
|
||||
ui.modal = null;
|
||||
render();
|
||||
if (ui.currentView === "deployments" && (ui.boot?.state?.servers || []).length && !(ui.serverDiscovery || []).length) {
|
||||
setLoading(true, "Reading Docker, Compose and DockerMan inventory from Unraid…");
|
||||
await refreshDeploymentTruth(true);
|
||||
setLoading(false);
|
||||
}
|
||||
} else if (action === "select-repo") selectRepository(target.dataset.id);
|
||||
else if (action === "refresh") {
|
||||
await refreshRepositories(true);
|
||||
await refreshActiveOperations(false);
|
||||
await refreshDeploymentTruth(false);
|
||||
} else if (action === "refresh-operations") {
|
||||
setLoading(true, "Refreshing deployment operations and live server state…");
|
||||
await refreshActiveOperations();
|
||||
await refreshDeploymentTruth(true);
|
||||
setLoading(false);
|
||||
} else if (action === "toggle-theme") {
|
||||
const appearance =
|
||||
document.documentElement.dataset.theme === "dark" ? "light" : "dark";
|
||||
applyTheme(appearance);
|
||||
ui.boot.state = await window.forgeflow.setAppearance(appearance);
|
||||
render();
|
||||
} else if (action === "open-palette") {
|
||||
ui.paletteQuery = "";
|
||||
ui.modal = { type: "command-palette" };
|
||||
render();
|
||||
} else if (action === "repo-tab") {
|
||||
ui.repositoryTab = target.dataset.tab;
|
||||
if (ui.repositoryTab === "gittools" && !ui.branches.length)
|
||||
await loadGitTools(repository);
|
||||
else if (ui.repositoryTab === "validator" && !ui.gitValidation) {
|
||||
setLoading(true, "Validating Git and Gitea best practices…");
|
||||
try {
|
||||
ui.gitValidation = await window.forgeflow.gitValidatorScan(
|
||||
repository.fullName,
|
||||
);
|
||||
} catch (error) {
|
||||
showToast("Git Validator failed", error.message, "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
render();
|
||||
} else if (ui.repositoryTab === "settings") {
|
||||
try {
|
||||
ui.pullRequests = await window.forgeflow.pullRequests(
|
||||
repository.fullName,
|
||||
"open",
|
||||
);
|
||||
} catch (error) {
|
||||
ui.pullRequests = [];
|
||||
showToast("Could not load pull requests", error.message, "error");
|
||||
}
|
||||
render();
|
||||
} else render();
|
||||
} else if (action === "git-validator-scan") {
|
||||
setLoading(true, "Validating Git and Gitea best practices…");
|
||||
try {
|
||||
ui.gitValidation = await window.forgeflow.gitValidatorScan(
|
||||
repository.fullName,
|
||||
);
|
||||
showToast(
|
||||
"Git validation complete",
|
||||
`${ui.gitValidation.score}/100 · ${ui.gitValidation.grade}`,
|
||||
ui.gitValidation.summary.errors ? "error" : "success",
|
||||
);
|
||||
} catch (error) {
|
||||
showToast("Git Validator failed", error.message, "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else if (action === "git-validator-repair") {
|
||||
const check = ui.gitValidation?.checks?.[Number(target.dataset.checkIndex)];
|
||||
if (!check?.fixAction) return;
|
||||
if (!check.safe && !confirm(check.confirmation || `Apply ${check.title}?`))
|
||||
return;
|
||||
setLoading(true, `Repairing ${check.title}…`);
|
||||
try {
|
||||
await window.forgeflow.gitValidatorRepair(repository.fullName, check);
|
||||
await refreshRepositories(false, true);
|
||||
ui.gitValidation = await window.forgeflow.gitValidatorScan(
|
||||
repository.fullName,
|
||||
);
|
||||
showToast("Git best practice repaired", check.title, "success");
|
||||
} catch (error) {
|
||||
showToast("Repair failed", error.message, "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else if (action === "git-validator-repair-safe") {
|
||||
const checks = (ui.gitValidation?.checks || []).filter(
|
||||
(check) => check.fixAction && check.safe,
|
||||
);
|
||||
setLoading(true, `Applying ${checks.length} safe Git fixes…`);
|
||||
let repaired = 0;
|
||||
try {
|
||||
for (const check of checks) {
|
||||
await window.forgeflow.gitValidatorRepair(repository.fullName, check);
|
||||
repaired += 1;
|
||||
}
|
||||
await refreshRepositories(false, true);
|
||||
ui.gitValidation = await window.forgeflow.gitValidatorScan(
|
||||
repository.fullName,
|
||||
);
|
||||
showToast("Safe Git fixes applied", `${repaired} repaired.`, "success");
|
||||
} catch (error) {
|
||||
showToast("Safe repair stopped", error.message, "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else if (action === "toggle-favorite") {
|
||||
ui.boot.state = await window.forgeflow.favoriteRepository(
|
||||
repository.fullName,
|
||||
!repository.favorite,
|
||||
);
|
||||
repository.favorite = !repository.favorite;
|
||||
render();
|
||||
} else if (action === "select-file") {
|
||||
if (event.target.matches("input[type=checkbox]")) return;
|
||||
ui.selectedFile = target.dataset.path;
|
||||
await loadDiff(repository, ui.selectedFile);
|
||||
} else if (action === "toggle-all-files") {
|
||||
const files = repository.localStatus?.files || [];
|
||||
ui.selectedFiles =
|
||||
ui.selectedFiles.size === files.length
|
||||
? new Set()
|
||||
: new Set(files.map((file) => file.path));
|
||||
render();
|
||||
} else if (action === "copy-diff") {
|
||||
await navigator.clipboard.writeText(ui.diff || "");
|
||||
showToast("Copied", "Diff copied to clipboard.", "success");
|
||||
} else if (action === "open-hunk-staging") {
|
||||
if (ui.diffHunks?.partialSupported) {
|
||||
ui.modal = { type: "hunk-staging" };
|
||||
render();
|
||||
}
|
||||
} else if (action === "stage-chosen-hunks") {
|
||||
const indexes = [
|
||||
...document.querySelectorAll("[data-hunk-index]:checked"),
|
||||
].map((input) => Number(input.dataset.hunkIndex));
|
||||
if (!indexes.length) return;
|
||||
const result = await runOperation(
|
||||
"Staging selected hunks…",
|
||||
() =>
|
||||
window.forgeflow.stageHunks(
|
||||
repository.localPath,
|
||||
ui.selectedFile,
|
||||
indexes,
|
||||
),
|
||||
"Selected hunks staged.",
|
||||
);
|
||||
if (result) {
|
||||
ui.modal = null;
|
||||
await loadDiff(selectedRepository(), ui.selectedFile);
|
||||
}
|
||||
} else if (action === "open-file-editor") {
|
||||
await window.forgeflow
|
||||
.openEditor(repository.localPath, ui.selectedFile)
|
||||
.catch((error) =>
|
||||
showToast("Could not open editor", error.message, "error"),
|
||||
);
|
||||
} else if (action === "open-editor") {
|
||||
await window.forgeflow
|
||||
.openEditor(repository.localPath)
|
||||
.catch((error) =>
|
||||
showToast("Could not open editor", error.message, "error"),
|
||||
);
|
||||
} else if (action === "open-terminal") {
|
||||
await window.forgeflow
|
||||
.openTerminal(repository.localPath)
|
||||
.catch((error) =>
|
||||
showToast("Could not open terminal", error.message, "error"),
|
||||
);
|
||||
} else if (action === "load-conflicts") {
|
||||
ui.conflictState = await window.forgeflow.conflictState(
|
||||
repository.localPath,
|
||||
);
|
||||
ui.modal = { type: "conflict-guide" };
|
||||
render();
|
||||
} else if (action === "resolve-conflict") {
|
||||
if (
|
||||
!ui.selectedFile ||
|
||||
!confirm(`Apply “${target.dataset.resolution}” to ${ui.selectedFile}?`)
|
||||
)
|
||||
return;
|
||||
ui.conflictState = await window.forgeflow.resolveConflict(
|
||||
repository.localPath,
|
||||
ui.selectedFile,
|
||||
target.dataset.resolution,
|
||||
);
|
||||
await refreshRepositories(false);
|
||||
ui.modal = { type: "conflict-guide" };
|
||||
render();
|
||||
} else if (action === "open-conflict-file") {
|
||||
await window.forgeflow.openEditor(
|
||||
repository.localPath,
|
||||
target.dataset.path,
|
||||
);
|
||||
} else if (action === "continue-git-operation") {
|
||||
ui.conflictState = await window.forgeflow.continueGitOperation(
|
||||
repository.localPath,
|
||||
);
|
||||
ui.modal = null;
|
||||
await refreshRepositories(false);
|
||||
showToast(
|
||||
"Git operation continued",
|
||||
"The repository operation completed.",
|
||||
"success",
|
||||
);
|
||||
} else if (action === "abort-git-operation") {
|
||||
if (
|
||||
!confirm(
|
||||
"Abort the active Git operation? Conflict-resolution work may be discarded.",
|
||||
)
|
||||
)
|
||||
return;
|
||||
await window.forgeflow.abortGitOperation(repository.localPath);
|
||||
ui.modal = null;
|
||||
await refreshRepositories(false);
|
||||
} else if (action === "check-branch-protection") {
|
||||
ui.branchProtection = await window.forgeflow.branchProtection(
|
||||
repository.fullName,
|
||||
repository.localStatus.branch.head,
|
||||
);
|
||||
showToast(
|
||||
ui.branchProtection.protected
|
||||
? "Protected branch"
|
||||
: "Branch is not protected",
|
||||
ui.branchProtection.protected
|
||||
? `${ui.branchProtection.requiredApprovals} approval(s) required.`
|
||||
: "Direct pushes are permitted by the reported branch rule.",
|
||||
ui.branchProtection.protected ? "info" : "success",
|
||||
);
|
||||
render();
|
||||
} else if (action === "open-pull-request") {
|
||||
const subject =
|
||||
ui.history[0]?.subject || repository.localStatus.branch.head;
|
||||
ui.modal = {
|
||||
type: "pull-request",
|
||||
title: subject,
|
||||
body: `## Summary\n\nChanges from ${repository.localStatus.branch.head}.`,
|
||||
};
|
||||
render();
|
||||
} else if (action === "load-pull-requests") {
|
||||
try {
|
||||
ui.pullRequests = await window.forgeflow.pullRequests(
|
||||
repository.fullName,
|
||||
"open",
|
||||
);
|
||||
render();
|
||||
} catch (error) {
|
||||
showToast("Could not load pull requests", error.message, "error");
|
||||
}
|
||||
} else if (action === "open-pull-request-url") {
|
||||
if (target.dataset.url)
|
||||
await window.forgeflow.openExternal(target.dataset.url);
|
||||
} else if (action === "create-pull-request") {
|
||||
setLoading(true, "Creating pull request…");
|
||||
try {
|
||||
const pull = await window.forgeflow.createPullRequest(
|
||||
repository.fullName,
|
||||
document.querySelector("#pr-title").value,
|
||||
document.querySelector("#pr-body").value,
|
||||
document.querySelector("#pr-base").value,
|
||||
);
|
||||
ui.modal = null;
|
||||
ui.pullRequests = await window.forgeflow
|
||||
.pullRequests(repository.fullName, "open")
|
||||
.catch(() => ui.pullRequests);
|
||||
showToast("Pull request created", `#${pull.number}`, "success");
|
||||
if (pull.html_url) await window.forgeflow.openExternal(pull.html_url);
|
||||
} catch (error) {
|
||||
showToast("Could not create pull request", error.message, "error");
|
||||
}
|
||||
setLoading(false);
|
||||
} else if (action === "copy-logs") {
|
||||
const text = (ui.activeDeployment?.logs || []).join("\n");
|
||||
await navigator.clipboard.writeText(text);
|
||||
showToast(
|
||||
"Copied",
|
||||
"Safe operation output copied. Open Gitea for raw runner logs.",
|
||||
"success",
|
||||
);
|
||||
} else if (action === "stage-selected") {
|
||||
if (!repository?.localPath || !ui.selectedFiles.size) return;
|
||||
await runOperation(
|
||||
"Staging selected files…",
|
||||
() =>
|
||||
window.forgeflow.stageFiles(repository.localPath, [
|
||||
...ui.selectedFiles,
|
||||
]),
|
||||
"Files staged.",
|
||||
);
|
||||
} else if (action === "unstage-selected") {
|
||||
if (!repository?.localPath || !ui.selectedFiles.size) return;
|
||||
await runOperation(
|
||||
"Unstaging selected files…",
|
||||
() =>
|
||||
window.forgeflow.unstageFiles(repository.localPath, [
|
||||
...ui.selectedFiles,
|
||||
]),
|
||||
"Files unstaged.",
|
||||
);
|
||||
} else if (action === "commit-push" || action === "commit-only") {
|
||||
if (
|
||||
!repository?.localPath ||
|
||||
!ui.commitMessage.trim() ||
|
||||
(!ui.selectedFiles.size && !repository.localStatus?.counts?.staged)
|
||||
)
|
||||
return;
|
||||
const selected = [...ui.selectedFiles];
|
||||
const result = await runOperation(
|
||||
action === "commit-push"
|
||||
? "Committing and pushing…"
|
||||
: "Creating local commit…",
|
||||
() =>
|
||||
action === "commit-push"
|
||||
? selected.length
|
||||
? window.forgeflow.commitAndPush(
|
||||
repository.localPath,
|
||||
ui.commitMessage,
|
||||
selected,
|
||||
)
|
||||
: window.forgeflow.commitStagedAndPush(
|
||||
repository.localPath,
|
||||
ui.commitMessage,
|
||||
)
|
||||
: selected.length
|
||||
? window.forgeflow.commit(
|
||||
repository.localPath,
|
||||
ui.commitMessage,
|
||||
selected,
|
||||
)
|
||||
: window.forgeflow.commitStaged(
|
||||
repository.localPath,
|
||||
ui.commitMessage,
|
||||
),
|
||||
action === "commit-push"
|
||||
? "Changes committed and pushed to Gitea."
|
||||
: "Local commit created.",
|
||||
);
|
||||
if (result) {
|
||||
ui.commitMessage = "";
|
||||
ui.selectedFiles.clear();
|
||||
ui.selectedFile = null;
|
||||
ui.diff = "";
|
||||
}
|
||||
} else if (action === "push")
|
||||
await runOperation(
|
||||
"Pushing local commits…",
|
||||
() => window.forgeflow.push(repository.localPath),
|
||||
"Push completed.",
|
||||
);
|
||||
else if (action === "fetch")
|
||||
await runOperation(
|
||||
"Fetching from Gitea…",
|
||||
() => window.forgeflow.fetch(repository.localPath),
|
||||
"Remote state refreshed.",
|
||||
);
|
||||
else if (action === "pull")
|
||||
await runOperation(
|
||||
"Synchronizing from Gitea…",
|
||||
() => window.forgeflow.pull(repository.localPath),
|
||||
"Local branch fast-forwarded.",
|
||||
);
|
||||
else if (action === "load-history") {
|
||||
setLoading(true, "Loading commit history…");
|
||||
try {
|
||||
ui.history = await window.forgeflow.history(repository.localPath, 50);
|
||||
} catch (error) {
|
||||
showToast("History unavailable", error.message, "error");
|
||||
}
|
||||
setLoading(false);
|
||||
} else if (action === "load-git-tools") await loadGitTools(repository);
|
||||
else if (action === "create-branch") {
|
||||
const branch = document.querySelector("#new-branch-name")?.value.trim();
|
||||
if (branch)
|
||||
await runOperation(
|
||||
`Creating ${branch}…`,
|
||||
() => window.forgeflow.createBranch(repository.localPath, branch),
|
||||
`Switched to ${branch}.`,
|
||||
);
|
||||
await loadGitTools(selectedRepository());
|
||||
} else if (action === "checkout-branch") {
|
||||
await runOperation(
|
||||
`Switching to ${target.dataset.branch}…`,
|
||||
() =>
|
||||
window.forgeflow.checkoutBranch(
|
||||
repository.localPath,
|
||||
target.dataset.branch,
|
||||
),
|
||||
`Switched to ${target.dataset.branch}.`,
|
||||
);
|
||||
await loadGitTools(selectedRepository());
|
||||
} else if (action === "stash-changes") {
|
||||
const result = await runOperation(
|
||||
"Stashing local changes…",
|
||||
() =>
|
||||
window.forgeflow.stash(
|
||||
repository.localPath,
|
||||
`ForgeFlow ${new Date().toLocaleString()}`,
|
||||
),
|
||||
"Local changes stashed.",
|
||||
);
|
||||
if (result) ui.stashes = result.stashes;
|
||||
} else if (action === "pop-stash") {
|
||||
const result = await runOperation(
|
||||
`Applying ${target.dataset.stashRef}…`,
|
||||
() =>
|
||||
window.forgeflow.popStash(
|
||||
repository.localPath,
|
||||
target.dataset.stashRef,
|
||||
),
|
||||
"Stash applied.",
|
||||
);
|
||||
if (result) ui.stashes = result.stashes;
|
||||
} else if (action === "open-path")
|
||||
await window.forgeflow
|
||||
.openPath(repository.localPath)
|
||||
.catch((error) =>
|
||||
showToast("Could not open folder", error.message, "error"),
|
||||
);
|
||||
else if (action === "open-gitea")
|
||||
await window.forgeflow
|
||||
.openExternal(repository.htmlUrl)
|
||||
.catch((error) =>
|
||||
showToast("Could not open Gitea", error.message, "error"),
|
||||
);
|
||||
else if (action === "link-repo") {
|
||||
const localPath = await window.forgeflow.selectDirectory({
|
||||
title: `Link local folder for ${repository.name}`,
|
||||
});
|
||||
if (localPath) {
|
||||
ui.repositories =
|
||||
(await runOperation(
|
||||
"Linking local repository…",
|
||||
() => window.forgeflow.linkRepository(repository.fullName, localPath),
|
||||
"Local folder linked.",
|
||||
{ refresh: false },
|
||||
)) || ui.repositories;
|
||||
selectRepository(repository.id);
|
||||
}
|
||||
} else if (action === "unlink-repo") {
|
||||
ui.repositories =
|
||||
(await runOperation(
|
||||
"Removing local link…",
|
||||
() => window.forgeflow.unlinkRepository(repository.fullName),
|
||||
"Repository link removed.",
|
||||
{ refresh: false },
|
||||
)) || ui.repositories;
|
||||
selectRepository(repository.id);
|
||||
} else if (action === "clone-repo" || action === "clone-repo-custom") {
|
||||
const mode = action === "clone-repo-custom" ? "custom" : "default";
|
||||
const clone = await runOperation(
|
||||
mode === "custom"
|
||||
? "Choosing location and cloning repository…"
|
||||
: `Cloning ${repository.name} into the default project root…`,
|
||||
() => window.forgeflow.cloneRepository(repository.fullName, mode),
|
||||
null,
|
||||
{ refresh: false },
|
||||
);
|
||||
if (clone?.cancelled) return;
|
||||
if (clone?.target) {
|
||||
if (clone.state) ui.boot.state = clone.state;
|
||||
ui.repositories =
|
||||
clone.repositories || (await window.forgeflow.refreshRepositories());
|
||||
selectRepository(repository.id);
|
||||
showToast(
|
||||
clone.reused ? "Existing repository linked" : "Repository cloned",
|
||||
clone.target,
|
||||
"success",
|
||||
);
|
||||
}
|
||||
}
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user