f514602697
Part A — a fetch/pull/push against a remote that needs a login no longer dies on "terminal prompts disabled": the server answers 409 EGITAUTH with the host, the git bar asks for a username + password/access token and resends the same action. AgentHub keeps its OWN credential store (<CLAUDE_HOME>/agenthub-git-credentials/, 0700/0600), one login per host, shared by every user of the install, managed from Settings -> Git credentials. A login is only sent to one of the repo's own remote hosts, saved only after git accepted it, never returned by any route, and reaches git through an exclusive credential helper (never argv). Part B — merge a branch (or the diverged upstream) from the git bar; a merge that stops on conflicts lists them in the Changes panel with Keep mine / Take theirs / Mark resolved, then Complete merge or Abort merge (confirm gate). Clean tree, repo-root lane and an existing ref only; pull/switch/hunk actions are blocked mid-merge; staging a conflicted file with markers left is refused. Rebase/reset/stash stay out.
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
/**
|
|
* @file SettingsHub.tsx
|
|
* @description Tabbed shell for /settings that hosts the dashboard's own settings plus one configuration panel per AI tool (Claude Code and Codex) plus the AI Router profiles they can be pointed at and the git logins dashboard fetch/pull/push use, with the active tab kept in the `?tool=` query param.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
import type { ComponentType } from "react";
|
|
import { useSearchParams } from "react-router-dom";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Boxes, KeyRound, Router, Settings as SettingsIcon, SquareTerminal } from "lucide-react";
|
|
import { PageTabs } from "../components/PageTabs";
|
|
import { Settings } from "./Settings";
|
|
import { CcConfig } from "./CcConfig";
|
|
import { CodexConfig } from "./CodexConfig";
|
|
import { AiRouterConfig } from "./AiRouterConfig";
|
|
import { GitCredentialsConfig } from "./GitCredentialsConfig";
|
|
|
|
interface ToolTab {
|
|
id: string;
|
|
labelKey: string;
|
|
Icon: ComponentType<{ className?: string }>;
|
|
Panel: ComponentType;
|
|
}
|
|
|
|
const DASHBOARD_TAB: ToolTab = {
|
|
id: "dashboard",
|
|
labelKey: "tools.dashboard",
|
|
Icon: SettingsIcon,
|
|
Panel: Settings,
|
|
};
|
|
|
|
/** Add a tool here to give it a tab; that is the whole extension point. */
|
|
const TOOL_TABS: ToolTab[] = [
|
|
DASHBOARD_TAB,
|
|
{ id: "claude-code", labelKey: "tools.claudeCode", Icon: Boxes, Panel: CcConfig },
|
|
{ id: "codex", labelKey: "tools.codex", Icon: SquareTerminal, Panel: CodexConfig },
|
|
{ id: "ai-router", labelKey: "tools.aiRouter", Icon: Router, Panel: AiRouterConfig },
|
|
{
|
|
id: "git-credentials",
|
|
labelKey: "tools.gitCredentials",
|
|
Icon: KeyRound,
|
|
Panel: GitCredentialsConfig,
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Settings page shell with one tab per configuration surface.
|
|
* @returns The tab bar plus the active tab's panel.
|
|
*/
|
|
export function SettingsHub() {
|
|
const { t } = useTranslation("settings");
|
|
const [params, setParams] = useSearchParams();
|
|
const requested = params.get("tool");
|
|
const active = TOOL_TABS.find((tab) => tab.id === requested) ?? DASHBOARD_TAB;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageTabs
|
|
ariaLabel={t("tools.label", "Tools")}
|
|
activeId={active.id}
|
|
onSelect={(id) => setParams(id === DASHBOARD_TAB.id ? {} : { tool: id }, { replace: true })}
|
|
tabs={TOOL_TABS.map(({ id, labelKey, Icon }) => ({
|
|
id,
|
|
Icon,
|
|
label: t(labelKey),
|
|
}))}
|
|
/>
|
|
|
|
<active.Panel />
|
|
</div>
|
|
);
|
|
}
|