mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
Merge release/v3.8.8 into refactor/pages-v3-14 (CLI pages redesign)
Conflicts: CLAUDE.md base; i18n deep-merge (costsSection=Custos); .source regenerated (fumadocs-mdx, +1 doc); openapi regenerated. CLIToolsPageClient.tsx: accepted #2839 deletion (redesign replaced cli-tools/ with cli-code/cli-agents/acp-agents; base #2858 only removed obsolete MITM cards; AgentBridge reachable via sidebar; 0 orphan refs). sidebar-visibility test passes (cli items + agent-bridge merged).
This commit is contained in:
@@ -7,10 +7,18 @@ import Button from "./Button";
|
||||
import Input from "./Input";
|
||||
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
||||
|
||||
const GOOGLE_OAUTH_PROVIDERS = new Set(["antigravity", "gemini-cli"]);
|
||||
const GOOGLE_OAUTH_PROVIDERS = new Set(["antigravity", "agy", "gemini-cli"]);
|
||||
|
||||
/** Providers that use a local callback server on a random port (PKCE browser flow). */
|
||||
const PKCE_CALLBACK_SERVER_PROVIDERS = new Set(["codex", "windsurf", "devin-cli"]);
|
||||
const PKCE_CALLBACK_SERVER_PROVIDERS = new Set(["codex"]);
|
||||
|
||||
/**
|
||||
* Phase 1 hotfix (2026-05-29): windsurf & devin-cli only support import-token.
|
||||
* Their PKCE flow targeting app.devin.ai/editor/signin returned 404 post-rebrand.
|
||||
* Phase 2 will reintroduce browser login via Firebase OAuth + RegisterUser.
|
||||
* Spec: docs/superpowers/specs/2026-05-29-windsurf-login-fix-design.md.
|
||||
*/
|
||||
const IMPORT_TOKEN_ONLY_PROVIDERS = new Set(["windsurf", "devin-cli"]);
|
||||
|
||||
type OAuthModalProps = {
|
||||
isOpen: boolean;
|
||||
@@ -45,11 +53,16 @@ export default function OAuthModal({
|
||||
const [deviceData, setDeviceData] = useState(null);
|
||||
const [polling, setPolling] = useState(false);
|
||||
// API-key paste mode: for providers that accept a token directly (windsurf, devin-cli)
|
||||
const [showPasteToken, setShowPasteToken] = useState(false);
|
||||
const [showPasteToken, setShowPasteToken] = useState(
|
||||
provider === "windsurf" || provider === "devin-cli"
|
||||
);
|
||||
const [pasteToken, setPasteToken] = useState("");
|
||||
const [savingToken, setSavingToken] = useState(false);
|
||||
|
||||
const supportsTokenPaste = provider === "windsurf" || provider === "devin-cli";
|
||||
// Phase 1 hotfix (2026-05-29): windsurf/devin-cli are import-token-only.
|
||||
// Hide the "Browser Login" tab — Phase 2 will restore it via Firebase OAuth.
|
||||
const importTokenOnly = IMPORT_TOKEN_ONLY_PROVIDERS.has(provider);
|
||||
const popupRef = useRef(null);
|
||||
const { copied, copy } = useCopyToClipboard();
|
||||
const deviceVerificationUrl =
|
||||
@@ -695,8 +708,10 @@ export default function OAuthModal({
|
||||
size="lg"
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Paste-token tab toggle (Windsurf / Devin CLI only) */}
|
||||
{supportsTokenPaste && step !== "success" && (
|
||||
{/* Paste-token tab toggle (Windsurf / Devin CLI only).
|
||||
Phase 1 hotfix: when importTokenOnly is true, hide the entire toggle —
|
||||
there is no "Browser Login" tab to switch to until Phase 2 ships. */}
|
||||
{supportsTokenPaste && !importTokenOnly && step !== "success" && (
|
||||
<div className="flex gap-2 border-b border-border pb-3">
|
||||
<button
|
||||
className={`text-sm px-3 py-1 rounded-t ${!showPasteToken ? "font-semibold border-b-2 border-primary text-primary" : "text-text-muted"}`}
|
||||
|
||||
@@ -94,10 +94,10 @@ export default function ProxyLogDetail({ log, onClose }) {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">
|
||||
Public IP
|
||||
Client IP
|
||||
</div>
|
||||
<div className="text-sm font-medium font-mono text-emerald-400">
|
||||
{log.publicIp || "—"}
|
||||
{log.clientIp || "—"}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function ProxyLogger() {
|
||||
{ key: "provider", label: t("colProvider") },
|
||||
{ key: "target", label: t("colTarget") },
|
||||
{ key: "latency", label: t("colLatency") },
|
||||
{ key: "ip", label: t("colPublicIp") },
|
||||
{ key: "ip", label: t("colClientIp") },
|
||||
{ key: "time", label: t("colTime") },
|
||||
],
|
||||
[t]
|
||||
@@ -435,7 +435,7 @@ export default function ProxyLogger() {
|
||||
)}
|
||||
{visibleColumns.ip && (
|
||||
<th className="px-3 py-2.5 font-semibold text-text-muted uppercase tracking-wider text-[10px]">
|
||||
{t("colPublicIp")}
|
||||
{t("colClientIp")}
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.time && (
|
||||
@@ -552,7 +552,7 @@ export default function ProxyLogger() {
|
||||
)}
|
||||
{visibleColumns.ip && (
|
||||
<td className="px-3 py-2 font-mono text-[11px] text-emerald-400">
|
||||
{log.publicIp || "—"}
|
||||
{log.clientIp || "—"}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.time && (
|
||||
|
||||
81
src/shared/components/RiskNoticeModal.tsx
Normal file
81
src/shared/components/RiskNoticeModal.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Button from "@/shared/components/Button";
|
||||
|
||||
export interface RiskNoticeModalProps {
|
||||
open: boolean;
|
||||
title: string;
|
||||
body: string;
|
||||
dontShowAgainKey: string;
|
||||
onAccept: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic risk notice modal (D16).
|
||||
* Persists "don't show again" preference to localStorage using `dontShowAgainKey`.
|
||||
*/
|
||||
export function RiskNoticeModal({
|
||||
open,
|
||||
title,
|
||||
body,
|
||||
dontShowAgainKey,
|
||||
onAccept,
|
||||
onCancel,
|
||||
}: RiskNoticeModalProps) {
|
||||
const t = useTranslations("common");
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onCancel();
|
||||
};
|
||||
document.addEventListener("keydown", handler);
|
||||
return () => document.removeEventListener("keydown", handler);
|
||||
}, [open, onCancel]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const handleAccept = () => {
|
||||
try {
|
||||
localStorage.setItem(dontShowAgainKey, "true");
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
onAccept();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="risk-modal-title"
|
||||
>
|
||||
<div className="w-full max-w-md rounded-xl border border-amber-500/30 bg-card p-6 shadow-xl">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-amber-500/10 text-amber-500 shrink-0">
|
||||
<span className="material-symbols-outlined text-[20px]">warning</span>
|
||||
</div>
|
||||
<h2 id="risk-modal-title" className="text-base font-semibold text-text-main pt-1">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-text-muted mb-6 leading-relaxed">{body}</p>
|
||||
|
||||
<div className="flex gap-3 justify-end">
|
||||
<Button variant="ghost" onClick={onCancel}>
|
||||
{t("cancel") || "Cancel"}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleAccept}>
|
||||
<span className="material-symbols-outlined text-[14px] mr-1">check</span>
|
||||
{t("understand") || "I understand"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -301,6 +301,7 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
"amazon-q": "Aws",
|
||||
anthropic: "Anthropic",
|
||||
antigravity: "Antigravity",
|
||||
agy: "Antigravity", // Antigravity CLI — same brand icon as the antigravity provider
|
||||
assemblyai: "AssemblyAI",
|
||||
"aws-polly": "Aws",
|
||||
azure: "Azure",
|
||||
|
||||
Reference in New Issue
Block a user