From f6ed4f1e2e8691bbcd80cd8553bec63989973864 Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 30 Aug 2026 12:55:20 +0200 Subject: [PATCH] feat(admin-panel): update url feat(admin-panel): update some of the ui feat(oauth-screen): make oauth success screen match the rest of the app --- backend/internal/api/server_middleware.go | 12 ++++--- backend/internal/api/server_routes.go | 11 +++---- backend/internal/api/server_test.go | 18 +++++----- frontend/package.json | 2 +- frontend/src/App.tsx | 13 ++++---- frontend/src/OAuthSuccess.tsx | 40 +++++++++++++++++++++++ frontend/src/main.tsx | 3 +- pnpm-lock.yaml | 14 ++++---- 8 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 frontend/src/OAuthSuccess.tsx diff --git a/backend/internal/api/server_middleware.go b/backend/internal/api/server_middleware.go index 00b1fc3..64119c0 100644 --- a/backend/internal/api/server_middleware.go +++ b/backend/internal/api/server_middleware.go @@ -33,8 +33,8 @@ var corsExposedResponseHeaders = []string{ var corsExposedResponseHeadersJoined = strings.Join(corsExposedResponseHeaders, ", ") const ( - exampleAPIKeyManagementPath = "/management.html" - exampleAPIKeyManagementURL = "/management.html?safe-mode=configure" + exampleAPIKeyManagementPath = "/admin" + exampleAPIKeyManagementURL = "/admin?safe-mode=configure" ) func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc { @@ -45,7 +45,11 @@ func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc { } if c != nil && c.Request != nil { path := c.Request.URL.Path - if strings.HasPrefix(path, "/v0/management/") || path == "/v0/management" || strings.HasPrefix(path, "/v0/resource/plugins/") || path == "/management.html" { + if strings.HasPrefix(path, "/v0/management/") || + path == "/v0/management" || + strings.HasPrefix(path, "/v0/resource/plugins/") || + path == exampleAPIKeyManagementPath || + strings.HasPrefix(path, exampleAPIKeyManagementPath+"/") { c.Next() return } @@ -87,7 +91,7 @@ func (s *Server) exampleAPIKeySafeModeMiddleware() gin.HandlerFunc { c.Header("X-CPA-SAFE-MODE", "example-api-key") c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "error": "unsafe_example_api_key", - "message": "Proxy API endpoints are disabled because api-keys contains template values. Open /management.html?safe-mode=configure, update api-keys in Management, then retry.", + "message": "Proxy API endpoints are disabled because api-keys contains template values. Open /admin?safe-mode=configure, update api-keys in Management, then retry.", }) } } diff --git a/backend/internal/api/server_routes.go b/backend/internal/api/server_routes.go index e38e5b2..052f1e1 100644 --- a/backend/internal/api/server_routes.go +++ b/backend/internal/api/server_routes.go @@ -8,8 +8,6 @@ import ( "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai" ) -const oauthCallbackSuccessHTML = `Authentication successful

Authentication successful!

You can close this window.

This window will close automatically in 5 seconds.

` - func (s *Server) setupRoutes() { healthzHandler := func(c *gin.Context) { if c.Request.Method == http.MethodHead { @@ -21,8 +19,10 @@ func (s *Server) setupRoutes() { s.engine.GET("/healthz", healthzHandler) s.engine.HEAD("/healthz", healthzHandler) - s.engine.GET("/management.html", s.serveManagementControlPanel) - s.engine.HEAD("/management.html", s.serveManagementControlPanel) + s.engine.GET("/admin", s.serveManagementControlPanel) + s.engine.HEAD("/admin", s.serveManagementControlPanel) + s.engine.GET("/admin/oauth-success", s.serveManagementControlPanel) + s.engine.HEAD("/admin/oauth-success", s.serveManagementControlPanel) s.engine.GET("/management-assets/*filepath", s.serveManagementAsset) s.engine.HEAD("/management-assets/*filepath", s.serveManagementAsset) @@ -59,7 +59,6 @@ func (s *Server) setupRoutes() { if state != "" { _, _ = managementHandlers.WriteOAuthCallbackFileForPendingSession(s.cfg.AuthDir, "codex", state, code, errStr) } - c.Header("Content-Type", "text/html; charset=utf-8") - c.String(http.StatusOK, oauthCallbackSuccessHTML) + c.Redirect(http.StatusSeeOther, "/admin/oauth-success") }) } diff --git a/backend/internal/api/server_test.go b/backend/internal/api/server_test.go index a5c87a1..2796b47 100644 --- a/backend/internal/api/server_test.go +++ b/backend/internal/api/server_test.go @@ -1529,7 +1529,7 @@ func TestHomeEnabledHidesManagementEndpointsAndControlPanel(t *testing.T) { }) t.Run("management control panel returns 404", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/management.html", nil) + req := httptest.NewRequest(http.MethodGet, "/admin", nil) rr := httptest.NewRecorder() server.engine.ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { @@ -1566,27 +1566,27 @@ func TestExampleAPIKeySafeModeShowsWarningAndKeepsManagement(t *testing.T) { t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String()) } body := rr.Body.String() - for _, want := range []string{"Example API key detected", "Open Management", `href="/management.html?safe-mode=configure"`} { + for _, want := range []string{"Example API key detected", "Open Management", `href="/admin?safe-mode=configure"`} { if !strings.Contains(body, want) { t.Fatalf("warning page missing %q: %s", want, body) } } }) - t.Run("management html defaults to warning page", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/management.html", nil) + t.Run("admin defaults to warning page", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/admin", nil) rr := httptest.NewRecorder() server.engine.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String()) } if !strings.Contains(rr.Body.String(), "Example API key detected") { - t.Fatalf("management.html did not show warning page: %s", rr.Body.String()) + t.Fatalf("admin page did not show warning page: %s", rr.Body.String()) } }) - t.Run("management html head stops at warning page", func(t *testing.T) { - req := httptest.NewRequest(http.MethodHead, "/management.html", nil) + t.Run("admin head stops at warning page", func(t *testing.T) { + req := httptest.NewRequest(http.MethodHead, "/admin", nil) rr := httptest.NewRecorder() server.engine.ServeHTTP(rr, req) if rr.Code != http.StatusOK { @@ -1601,7 +1601,7 @@ func TestExampleAPIKeySafeModeShowsWarningAndKeepsManagement(t *testing.T) { }) t.Run("management button query opens control panel", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/management.html?safe-mode=configure", nil) + req := httptest.NewRequest(http.MethodGet, "/admin?safe-mode=configure", nil) rr := httptest.NewRecorder() server.engine.ServeHTTP(rr, req) if rr.Code != http.StatusOK { @@ -1643,7 +1643,7 @@ func TestExampleAPIKeySafeModeShowsWarningAndKeepsManagement(t *testing.T) { if strings.Contains(rr.Body.String(), "management_url") { t.Fatalf("body should not include management_url field: %s", rr.Body.String()) } - if !strings.Contains(rr.Body.String(), "/management.html?safe-mode=configure") { + if !strings.Contains(rr.Body.String(), "/admin?safe-mode=configure") { t.Fatalf("body missing management link in message: %s", rr.Body.String()) } if got := rr.Header().Get(internallogging.CPATraceIDHeader); got != "" { diff --git a/frontend/package.json b/frontend/package.json index 5c286c0..11f7bc3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz", + "@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.32/methanium-ui.tgz", "lucide-react": "^0.542.0", "react": "^19.2.7", "react-dom": "^19.2.7" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 85df36d..301b274 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -170,7 +170,7 @@ function Header({ loggedIn, onLogout }: { loggedIn: boolean; onLogout: () => voi size="sm" className="mr-1 shrink-0 px-[13px]! text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground" nativeButton={false} - render={} + render={} > {methaniumLogo && } @@ -180,7 +180,7 @@ function Header({ loggedIn, onLogout }: { loggedIn: boolean; onLogout: () => voi size="sm" className="h-9 text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground" nativeButton={false} - render={} + render={} > Vibe Proxy @@ -307,6 +307,7 @@ function AccountCard({ }; const [emailHidden, setEmailHidden] = useState(true); + const { themePolarity } = useTheme(); return ( @@ -341,7 +342,7 @@ function AccountCard({ {statusProblem ? ( account.status_message || ) : ( - + )} @@ -358,7 +359,7 @@ function AccountCard({ -
+
Quota
@@ -396,7 +397,7 @@ function AccountCard({ )} {cachedQuota && windows.length > 0 && ( -
+
{windows.map((window) => (
@@ -420,7 +421,7 @@ function AccountCard({ )}
-
+
+ + + + ); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 54118e9..fe3b314 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { App } from './App'; +import { OAuthSuccess } from './OAuthSuccess'; const methaniumTheme = BUILT_IN_THEMES.filter((theme) => theme.id === 'methanium'); @@ -23,7 +24,7 @@ createRoot(document.getElementById('root')!).render( parentThemeStorageKey={null} designStorageKey={null} > - + {window.location.pathname === '/admin/oauth-success' ? : } , ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b263bb8..50ee2a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: frontend: dependencies: '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@8.1.1)(typescript@6.0.3) + specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.32/methanium-ui.tgz + version: https://git.methanium.net/methanium/ui/releases/download/0.0.32/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@8.1.1)(typescript@6.0.3) lucide-react: specifier: ^0.542.0 version: 0.542.0(react@19.2.8) @@ -398,9 +398,9 @@ packages: '@marijn/find-cluster-break@1.0.4': resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==} - '@methanium/ui@https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz': - resolution: {integrity: sha512-ij9zo/PdP5l/F/70t7a1o+OFZbMFRNNseNc1dlqddUFqIkAy+71VL6ptrXNpeVtzKFq8LuycwzXGsGihuViVig==, tarball: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz} - version: 0.0.29 + '@methanium/ui@https://git.methanium.net/methanium/ui/releases/download/0.0.32/methanium-ui.tgz': + resolution: {integrity: sha512-MLQ3xCLz4cBbrpioT5VMC7yEDvGTaIB3Mk14woe7BEsI2AJY0CFy2GXf4xrcZdfzZucuTboGe5M0aZwSA9TC3Q==, tarball: https://git.methanium.net/methanium/ui/releases/download/0.0.32/methanium-ui.tgz} + version: 0.0.32 peerDependencies: react: ^19.2.7 react-dom: ^19.2.7 @@ -3911,7 +3911,7 @@ snapshots: '@marijn/find-cluster-break@1.0.4': {} - '@methanium/ui@https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@8.1.1)(typescript@6.0.3)': + '@methanium/ui@https://git.methanium.net/methanium/ui/releases/download/0.0.32/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@8.1.1)(typescript@6.0.3)': dependencies: '@base-ui/react': 1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@codemirror/autocomplete': 6.20.3 @@ -4200,7 +4200,7 @@ snapshots: immer: 11.1.18 redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.2.0 + reselect: 5.3.0 optionalDependencies: react: 19.2.8 react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1)