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
This commit is contained in:
parent
3db5ec25c2
commit
f6ed4f1e2e
8 changed files with 79 additions and 34 deletions
|
|
@ -33,8 +33,8 @@ var corsExposedResponseHeaders = []string{
|
||||||
var corsExposedResponseHeadersJoined = strings.Join(corsExposedResponseHeaders, ", ")
|
var corsExposedResponseHeadersJoined = strings.Join(corsExposedResponseHeaders, ", ")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
exampleAPIKeyManagementPath = "/management.html"
|
exampleAPIKeyManagementPath = "/admin"
|
||||||
exampleAPIKeyManagementURL = "/management.html?safe-mode=configure"
|
exampleAPIKeyManagementURL = "/admin?safe-mode=configure"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc {
|
func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc {
|
||||||
|
|
@ -45,7 +45,11 @@ func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
if c != nil && c.Request != nil {
|
if c != nil && c.Request != nil {
|
||||||
path := c.Request.URL.Path
|
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()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +91,7 @@ func (s *Server) exampleAPIKeySafeModeMiddleware() gin.HandlerFunc {
|
||||||
c.Header("X-CPA-SAFE-MODE", "example-api-key")
|
c.Header("X-CPA-SAFE-MODE", "example-api-key")
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||||
"error": "unsafe_example_api_key",
|
"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.",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai"
|
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai"
|
||||||
)
|
)
|
||||||
|
|
||||||
const oauthCallbackSuccessHTML = `<html><head><meta charset="utf-8"><title>Authentication successful</title><script>setTimeout(function(){window.close();},5000);</script></head><body><h1>Authentication successful!</h1><p>You can close this window.</p><p>This window will close automatically in 5 seconds.</p></body></html>`
|
|
||||||
|
|
||||||
func (s *Server) setupRoutes() {
|
func (s *Server) setupRoutes() {
|
||||||
healthzHandler := func(c *gin.Context) {
|
healthzHandler := func(c *gin.Context) {
|
||||||
if c.Request.Method == http.MethodHead {
|
if c.Request.Method == http.MethodHead {
|
||||||
|
|
@ -21,8 +19,10 @@ func (s *Server) setupRoutes() {
|
||||||
s.engine.GET("/healthz", healthzHandler)
|
s.engine.GET("/healthz", healthzHandler)
|
||||||
s.engine.HEAD("/healthz", healthzHandler)
|
s.engine.HEAD("/healthz", healthzHandler)
|
||||||
|
|
||||||
s.engine.GET("/management.html", s.serveManagementControlPanel)
|
s.engine.GET("/admin", s.serveManagementControlPanel)
|
||||||
s.engine.HEAD("/management.html", 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.GET("/management-assets/*filepath", s.serveManagementAsset)
|
||||||
s.engine.HEAD("/management-assets/*filepath", s.serveManagementAsset)
|
s.engine.HEAD("/management-assets/*filepath", s.serveManagementAsset)
|
||||||
|
|
||||||
|
|
@ -59,7 +59,6 @@ func (s *Server) setupRoutes() {
|
||||||
if state != "" {
|
if state != "" {
|
||||||
_, _ = managementHandlers.WriteOAuthCallbackFileForPendingSession(s.cfg.AuthDir, "codex", state, code, errStr)
|
_, _ = managementHandlers.WriteOAuthCallbackFileForPendingSession(s.cfg.AuthDir, "codex", state, code, errStr)
|
||||||
}
|
}
|
||||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
c.Redirect(http.StatusSeeOther, "/admin/oauth-success")
|
||||||
c.String(http.StatusOK, oauthCallbackSuccessHTML)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1529,7 +1529,7 @@ func TestHomeEnabledHidesManagementEndpointsAndControlPanel(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("management control panel returns 404", func(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()
|
rr := httptest.NewRecorder()
|
||||||
server.engine.ServeHTTP(rr, req)
|
server.engine.ServeHTTP(rr, req)
|
||||||
if rr.Code != http.StatusNotFound {
|
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())
|
t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
|
||||||
}
|
}
|
||||||
body := 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) {
|
if !strings.Contains(body, want) {
|
||||||
t.Fatalf("warning page missing %q: %s", want, body)
|
t.Fatalf("warning page missing %q: %s", want, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("management html defaults to warning page", func(t *testing.T) {
|
t.Run("admin defaults to warning page", func(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/management.html", nil)
|
req := httptest.NewRequest(http.MethodGet, "/admin", nil)
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
server.engine.ServeHTTP(rr, req)
|
server.engine.ServeHTTP(rr, req)
|
||||||
if rr.Code != http.StatusOK {
|
if rr.Code != http.StatusOK {
|
||||||
t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusOK, rr.Body.String())
|
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") {
|
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) {
|
t.Run("admin head stops at warning page", func(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodHead, "/management.html", nil)
|
req := httptest.NewRequest(http.MethodHead, "/admin", nil)
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
server.engine.ServeHTTP(rr, req)
|
server.engine.ServeHTTP(rr, req)
|
||||||
if rr.Code != http.StatusOK {
|
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) {
|
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()
|
rr := httptest.NewRecorder()
|
||||||
server.engine.ServeHTTP(rr, req)
|
server.engine.ServeHTTP(rr, req)
|
||||||
if rr.Code != http.StatusOK {
|
if rr.Code != http.StatusOK {
|
||||||
|
|
@ -1643,7 +1643,7 @@ func TestExampleAPIKeySafeModeShowsWarningAndKeepsManagement(t *testing.T) {
|
||||||
if strings.Contains(rr.Body.String(), "management_url") {
|
if strings.Contains(rr.Body.String(), "management_url") {
|
||||||
t.Fatalf("body should not include management_url field: %s", rr.Body.String())
|
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())
|
t.Fatalf("body missing management link in message: %s", rr.Body.String())
|
||||||
}
|
}
|
||||||
if got := rr.Header().Get(internallogging.CPATraceIDHeader); got != "" {
|
if got := rr.Header().Get(internallogging.CPATraceIDHeader); got != "" {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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",
|
"lucide-react": "^0.542.0",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7"
|
"react-dom": "^19.2.7"
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ function Header({ loggedIn, onLogout }: { loggedIn: boolean; onLogout: () => voi
|
||||||
size="sm"
|
size="sm"
|
||||||
className="mr-1 shrink-0 px-[13px]! text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
className="mr-1 shrink-0 px-[13px]! text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
render={<a href="/management.html" aria-label="Vibe Proxy management" />}
|
render={<a href="/admin" aria-label="Vibe Proxy management" />}
|
||||||
>
|
>
|
||||||
{methaniumLogo && <img src={methaniumLogo} alt="" className="w-[20.59px]!" />}
|
{methaniumLogo && <img src={methaniumLogo} alt="" className="w-[20.59px]!" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -180,7 +180,7 @@ function Header({ loggedIn, onLogout }: { loggedIn: boolean; onLogout: () => voi
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-9 text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
className="h-9 text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
render={<a href="/management.html" />}
|
render={<a href="/admin" />}
|
||||||
>
|
>
|
||||||
Vibe Proxy
|
Vibe Proxy
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -307,6 +307,7 @@ function AccountCard({
|
||||||
};
|
};
|
||||||
|
|
||||||
const [emailHidden, setEmailHidden] = useState(true);
|
const [emailHidden, setEmailHidden] = useState(true);
|
||||||
|
const { themePolarity } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
|
@ -341,7 +342,7 @@ function AccountCard({
|
||||||
{statusProblem ? (
|
{statusProblem ? (
|
||||||
account.status_message || <AlertTriangle color="orange" />
|
account.status_message || <AlertTriangle color="orange" />
|
||||||
) : (
|
) : (
|
||||||
<Check color="lightGreen" />
|
<Check color={themePolarity === 'dark' ? 'lightGreen' : 'green'} />
|
||||||
)}
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardAction>
|
</CardAction>
|
||||||
|
|
@ -358,7 +359,7 @@ function AccountCard({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-border pt-4 flex flex-col gap-4">
|
<div className="pt-4 flex flex-col gap-4">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">Quota</div>
|
<div className="font-medium">Quota</div>
|
||||||
|
|
@ -396,7 +397,7 @@ function AccountCard({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{cachedQuota && windows.length > 0 && (
|
{cachedQuota && windows.length > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
{windows.map((window) => (
|
{windows.map((window) => (
|
||||||
<div key={window.key}>
|
<div key={window.key}>
|
||||||
<Progress value={100 - window.usedPercent} max={100}>
|
<Progress value={100 - window.usedPercent} max={100}>
|
||||||
|
|
@ -420,7 +421,7 @@ function AccountCard({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end border-t border-border pt-4">
|
<div className="flex justify-end pt-4">
|
||||||
<Button variant="destructive" size="sm" onClick={() => setDeleteOpen(true)}>
|
<Button variant="destructive" size="sm" onClick={() => setDeleteOpen(true)}>
|
||||||
<Trash2 />
|
<Trash2 />
|
||||||
Delete account
|
Delete account
|
||||||
|
|
|
||||||
40
frontend/src/OAuthSuccess.tsx
Normal file
40
frontend/src/OAuthSuccess.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@methanium/ui';
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export function OAuthSuccess() {
|
||||||
|
const [secondsRemaining, setSecondsRemaining] = useState(5);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = 'Authentication successful | Vibe Proxy';
|
||||||
|
|
||||||
|
const closeTimer = window.setTimeout(() => window.close(), 5_000);
|
||||||
|
const countdownTimer = window.setInterval(() => {
|
||||||
|
setSecondsRemaining((current) => Math.max(0, current - 1));
|
||||||
|
}, 1_000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.clearTimeout(closeTimer);
|
||||||
|
window.clearInterval(countdownTimer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex min-h-screen items-center justify-center bg-background p-4 text-foreground">
|
||||||
|
<Card className="text-center p-6! py-8!">
|
||||||
|
<CardHeader className="items-center">
|
||||||
|
<CardTitle>Authentication successful</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col items-center gap-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
This window will close automatically in {secondsRemaining}{' '}
|
||||||
|
{secondsRemaining === 1 ? 'second' : 'seconds'}.
|
||||||
|
</p>
|
||||||
|
<Button type="button" variant="outline" onClick={() => window.close()}>
|
||||||
|
Close window
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { App } from './App';
|
import { App } from './App';
|
||||||
|
import { OAuthSuccess } from './OAuthSuccess';
|
||||||
|
|
||||||
const methaniumTheme = BUILT_IN_THEMES.filter((theme) => theme.id === 'methanium');
|
const methaniumTheme = BUILT_IN_THEMES.filter((theme) => theme.id === 'methanium');
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ createRoot(document.getElementById('root')!).render(
|
||||||
parentThemeStorageKey={null}
|
parentThemeStorageKey={null}
|
||||||
designStorageKey={null}
|
designStorageKey={null}
|
||||||
>
|
>
|
||||||
<App />
|
{window.location.pathname === '/admin/oauth-success' ? <OAuthSuccess /> : <App />}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
|
|
@ -18,8 +18,8 @@ importers:
|
||||||
frontend:
|
frontend:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@methanium/ui':
|
'@methanium/ui':
|
||||||
specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz
|
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.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)
|
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:
|
lucide-react:
|
||||||
specifier: ^0.542.0
|
specifier: ^0.542.0
|
||||||
version: 0.542.0(react@19.2.8)
|
version: 0.542.0(react@19.2.8)
|
||||||
|
|
@ -398,9 +398,9 @@ packages:
|
||||||
'@marijn/find-cluster-break@1.0.4':
|
'@marijn/find-cluster-break@1.0.4':
|
||||||
resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==}
|
resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==}
|
||||||
|
|
||||||
'@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':
|
||||||
resolution: {integrity: sha512-ij9zo/PdP5l/F/70t7a1o+OFZbMFRNNseNc1dlqddUFqIkAy+71VL6ptrXNpeVtzKFq8LuycwzXGsGihuViVig==, tarball: https://git.methanium.net/methanium/ui/releases/download/0.0.29/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.29
|
version: 0.0.32
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^19.2.7
|
react: ^19.2.7
|
||||||
react-dom: ^19.2.7
|
react-dom: ^19.2.7
|
||||||
|
|
@ -3911,7 +3911,7 @@ snapshots:
|
||||||
|
|
||||||
'@marijn/find-cluster-break@1.0.4': {}
|
'@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:
|
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)
|
'@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
|
'@codemirror/autocomplete': 6.20.3
|
||||||
|
|
@ -4200,7 +4200,7 @@ snapshots:
|
||||||
immer: 11.1.18
|
immer: 11.1.18
|
||||||
redux: 5.0.1
|
redux: 5.0.1
|
||||||
redux-thunk: 3.1.0(redux@5.0.1)
|
redux-thunk: 3.1.0(redux@5.0.1)
|
||||||
reselect: 5.2.0
|
reselect: 5.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1)
|
react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue