refactor(auth): replace dashboard sessions with stateless tokens and session control (#6329)
* refactor(auth): replace dashboard sessions with stateless tokens * feat(auth): harden session issuance and distributed enforcement * fix(proxy): preserve trusted proxy compatibility defaults * refactor: address dashboard auth review feedback * refactor: remove classic frontend and flatten web app
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { ForgotPassword } from '@/features/auth/forgot-password'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/forgot-password')({
|
||||
component: ForgotPassword,
|
||||
})
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
|
||||
import i18next from 'i18next'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { wechatLoginByCode } from '@/features/auth/api'
|
||||
import { sanitizeAuthRedirect } from '@/features/auth/lib/auth-redirect'
|
||||
import { applyAuthBundle, isAuthBundle } from '@/lib/api'
|
||||
import { getServerErrorMessageKey } from '@/lib/server-error-message'
|
||||
|
||||
function OAuthComponent() {
|
||||
const navigate = useNavigate()
|
||||
const search = useSearch({ from: '/(auth)/oauth' }) as {
|
||||
redirect?: string
|
||||
provider?: 'github' | 'discord' | 'oidc' | 'linuxdo' | 'telegram' | 'wechat'
|
||||
code?: string
|
||||
state?: string
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
try {
|
||||
if (search?.provider === 'wechat' && search.code) {
|
||||
const res = await wechatLoginByCode(search.code)
|
||||
if (res?.success && isAuthBundle(res.data)) {
|
||||
applyAuthBundle(res.data)
|
||||
const target =
|
||||
sanitizeAuthRedirect(search?.redirect, window.location.origin) ??
|
||||
'/dashboard'
|
||||
navigate({ href: target, replace: true })
|
||||
return
|
||||
}
|
||||
if (getServerErrorMessageKey(res)) {
|
||||
navigate({ to: '/sign-in', replace: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (getServerErrorMessageKey(error)) {
|
||||
navigate({ to: '/sign-in', replace: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
toast.error(i18next.t('OAuth failed'))
|
||||
navigate({ to: '/sign-in', replace: true })
|
||||
})()
|
||||
}, [navigate, search])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/(auth)/oauth')({
|
||||
component: OAuthComponent,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { Otp } from '@/features/auth/otp'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/otp')({
|
||||
component: Otp,
|
||||
})
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/register')({
|
||||
beforeLoad: ({ location }) => {
|
||||
throw redirect({
|
||||
to: '/sign-up',
|
||||
search: location.search,
|
||||
replace: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
||||
|
||||
import {
|
||||
ResetPasswordConfirm,
|
||||
type ResetPasswordSearchParams,
|
||||
} from '@/features/auth/reset-password-confirm'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/reset')({
|
||||
component: ResetPassword,
|
||||
})
|
||||
|
||||
function ResetPassword() {
|
||||
const search = useSearch({
|
||||
from: '/(auth)/reset',
|
||||
}) as ResetPasswordSearchParams
|
||||
return <ResetPasswordConfirm email={search?.email} token={search?.token} />
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(auth)')({})
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { sanitizeAuthRedirect } from '@/features/auth/lib/auth-redirect'
|
||||
import { SignIn } from '@/features/auth/sign-in'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const searchSchema = z.object({
|
||||
redirect: z.string().optional(),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/(auth)/sign-in')({
|
||||
component: SignIn,
|
||||
validateSearch: searchSchema,
|
||||
beforeLoad: async ({ search }) => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
// 如果已经有用户信息,说明已登录
|
||||
if (auth.user) {
|
||||
const target =
|
||||
sanitizeAuthRedirect(search?.redirect, window.location.origin) ??
|
||||
'/dashboard'
|
||||
throw redirect({ href: target, replace: true })
|
||||
}
|
||||
},
|
||||
})
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SignUp } from '@/features/auth/sign-up'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/sign-up')({
|
||||
component: SignUp,
|
||||
beforeLoad: async () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
// 如果已经有用户信息,说明已登录,注册页对其无意义,跳转到 dashboard
|
||||
if (auth.user) {
|
||||
throw redirect({ to: '/dashboard' })
|
||||
}
|
||||
},
|
||||
})
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
||||
|
||||
import {
|
||||
ResetPasswordConfirm,
|
||||
type ResetPasswordSearchParams,
|
||||
} from '@/features/auth/reset-password-confirm'
|
||||
|
||||
export const Route = createFileRoute('/(auth)/user/reset')({
|
||||
component: UserResetPassword,
|
||||
})
|
||||
|
||||
function UserResetPassword() {
|
||||
const search = useSearch({
|
||||
from: '/(auth)/user/reset',
|
||||
}) as ResetPasswordSearchParams
|
||||
|
||||
return <ResetPasswordConfirm email={search?.email} token={search?.token} />
|
||||
}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { UnauthorisedError } from '@/features/errors/unauthorized-error'
|
||||
|
||||
export const Route = createFileRoute('/(errors)/401')({
|
||||
component: UnauthorisedError,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { ForbiddenError } from '@/features/errors/forbidden'
|
||||
|
||||
export const Route = createFileRoute('/(errors)/403')({
|
||||
component: ForbiddenError,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { NotFoundError } from '@/features/errors/not-found-error'
|
||||
|
||||
export const Route = createFileRoute('/(errors)/404')({
|
||||
component: NotFoundError,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { GeneralError } from '@/features/errors/general-error'
|
||||
|
||||
export const Route = createFileRoute('/(errors)/500')({
|
||||
component: GeneralError,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { MaintenanceError } from '@/features/errors/maintenance-error'
|
||||
|
||||
export const Route = createFileRoute('/(errors)/503')({
|
||||
component: MaintenanceError,
|
||||
})
|
||||
Vendored
+182
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useQueryClient, type QueryClient } from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import {
|
||||
createRootRouteWithContext,
|
||||
Outlet,
|
||||
redirect,
|
||||
useNavigate,
|
||||
} from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { NavigationProgress } from '@/components/navigation-progress'
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
import { ThemeCustomizationProvider } from '@/context/theme-customization-provider'
|
||||
import { saveAffiliateCode } from '@/features/auth/lib/storage'
|
||||
import { GeneralError } from '@/features/errors/general-error'
|
||||
import { NotFoundError } from '@/features/errors/not-found-error'
|
||||
import { getSetupStatus } from '@/features/setup/api'
|
||||
import { useSystemConfig } from '@/hooks/use-system-config'
|
||||
import {
|
||||
bootstrapAuthentication,
|
||||
clearAuthenticatedClientState,
|
||||
clearAuthentication,
|
||||
} from '@/lib/auth-session'
|
||||
import { subscribeAuthSessionEvents } from '@/lib/auth-session-sync'
|
||||
import { resolveLegacyRoute } from '@/lib/legacy-route'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
function RootComponent() {
|
||||
const navigate = useNavigate()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
// Load system configuration (logo, system name, etc.) from backend
|
||||
useSystemConfig({ autoLoad: true })
|
||||
|
||||
useEffect(() => {
|
||||
const aff = new URLSearchParams(window.location.search).get('aff')?.trim()
|
||||
if (aff) {
|
||||
saveAffiliateCode(aff)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
useAuthStore.subscribe((state, previousState) => {
|
||||
const sid = state.auth.session?.sid
|
||||
const previousSID = previousState.auth.session?.sid
|
||||
if (sid !== previousSID) {
|
||||
queryClient.clear()
|
||||
}
|
||||
}),
|
||||
[queryClient]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
subscribeAuthSessionEvents((event) => {
|
||||
const currentSID = useAuthStore.getState().auth.session?.sid
|
||||
|
||||
if (event.kind === 'authenticated') {
|
||||
if (event.sid === currentSID) return
|
||||
if (currentSID) {
|
||||
clearAuthentication(false)
|
||||
}
|
||||
window.location.reload()
|
||||
return
|
||||
}
|
||||
|
||||
if (currentSID && event.sid === currentSID) {
|
||||
clearAuthenticatedClientState(queryClient, false)
|
||||
void navigate({ to: '/sign-in', replace: true })
|
||||
}
|
||||
}),
|
||||
[navigate, queryClient]
|
||||
)
|
||||
|
||||
return (
|
||||
<ThemeCustomizationProvider>
|
||||
<NavigationProgress />
|
||||
<Outlet />
|
||||
<Toaster closeButton duration={5000} position='top-center' richColors />
|
||||
{import.meta.env.MODE === 'development' && (
|
||||
<>
|
||||
<ReactQueryDevtools buttonPosition='bottom-left' />
|
||||
<TanStackRouterDevtools position='bottom-right' />
|
||||
</>
|
||||
)}
|
||||
</ThemeCustomizationProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// 缓存 setup 状态检查结果,避免每次导航都重复调用 API
|
||||
// 使用 localStorage 持久化,避免页面刷新后重复检查
|
||||
const SETUP_CHECKED_KEY = 'setup_status_checked'
|
||||
|
||||
function getSetupStatusFromCache(): boolean {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.localStorage.getItem(SETUP_CHECKED_KEY) === 'true'
|
||||
}
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function setSetupStatusCache(value: boolean): void {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (value) {
|
||||
window.localStorage.setItem(SETUP_CHECKED_KEY, 'true')
|
||||
} else {
|
||||
window.localStorage.removeItem(SETUP_CHECKED_KEY)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
// 内存中的标记,避免同一会话中重复检查
|
||||
let setupStatusChecked = getSetupStatusFromCache()
|
||||
|
||||
export const Route = createRootRouteWithContext<{
|
||||
queryClient: QueryClient
|
||||
}>()({
|
||||
// 应用初始化与路由解析前统一校验会话
|
||||
beforeLoad: async ({ location }) => {
|
||||
const legacyTarget = resolveLegacyRoute(location.href)
|
||||
if (legacyTarget) {
|
||||
throw redirect({ href: legacyTarget, replace: true })
|
||||
}
|
||||
|
||||
const pathname = location?.pathname || ''
|
||||
const needsSetupCheck =
|
||||
!setupStatusChecked && !pathname.startsWith('/setup')
|
||||
const authBootstrap = bootstrapAuthentication()
|
||||
|
||||
// 只检查 setup 状态(如果需要)
|
||||
if (needsSetupCheck) {
|
||||
const [status] = await Promise.all([
|
||||
getSetupStatus().catch((error) => {
|
||||
if (import.meta.env.DEV) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[root.beforeLoad] setup status check failed', error)
|
||||
}
|
||||
return null
|
||||
}),
|
||||
authBootstrap,
|
||||
])
|
||||
|
||||
if (status?.success && status.data && !status.data.status) {
|
||||
throw redirect({ to: '/setup' })
|
||||
}
|
||||
setupStatusChecked = true
|
||||
setSetupStatusCache(true)
|
||||
} else {
|
||||
await authBootstrap
|
||||
}
|
||||
},
|
||||
component: RootComponent,
|
||||
notFoundComponent: NotFoundError,
|
||||
errorComponent: GeneralError,
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { Channels } from '@/features/channels'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const channelsSearchSchema = z.object({
|
||||
page: z.number().optional().catch(1),
|
||||
pageSize: z.number().optional().catch(undefined),
|
||||
filter: z.string().optional().catch(''),
|
||||
status: z.array(z.string()).optional().catch([]),
|
||||
type: z.array(z.string()).optional().catch([]),
|
||||
group: z.array(z.string()).optional().catch([]),
|
||||
model: z.string().optional().catch(''),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/channels/')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (!auth.user || auth.user.role < ROLE.ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
},
|
||||
validateSearch: channelsSearchSchema,
|
||||
component: Channels,
|
||||
})
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { Link, createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import { Loader2, MessageCircleWarning } from 'lucide-react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useActiveChatKey } from '@/features/chat/hooks/use-active-chat-key'
|
||||
import { useChatPresets } from '@/features/chat/hooks/use-chat-presets'
|
||||
import {
|
||||
chatLinkRequiresApiKey,
|
||||
resolveChatUrl,
|
||||
} from '@/features/chat/lib/chat-links'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/chat/$chatId')({
|
||||
loader: async ({ params }) => {
|
||||
if (!Number.isInteger(Number(params.chatId))) {
|
||||
throw redirect({ to: '/dashboard' })
|
||||
}
|
||||
},
|
||||
component: ChatRouteComponent,
|
||||
})
|
||||
|
||||
function ChatRouteComponent() {
|
||||
const { t } = useTranslation()
|
||||
const { chatId } = Route.useParams()
|
||||
const { chatPresets, serverAddress } = useChatPresets()
|
||||
const preset = useMemo(() => {
|
||||
const index = Number(chatId)
|
||||
if (!Number.isInteger(index)) return undefined
|
||||
return chatPresets[index]
|
||||
}, [chatId, chatPresets])
|
||||
|
||||
const isWebLink = preset?.type === 'web'
|
||||
|
||||
const requiresActiveKey = useMemo(() => {
|
||||
if (!preset || !isWebLink) return false
|
||||
return chatLinkRequiresApiKey(preset.url ?? '')
|
||||
}, [isWebLink, preset])
|
||||
|
||||
const {
|
||||
data: activeKey,
|
||||
isPending,
|
||||
isError,
|
||||
error,
|
||||
} = useActiveChatKey(Boolean(preset && requiresActiveKey))
|
||||
|
||||
const iframeSrc = useMemo(() => {
|
||||
if (!preset || !isWebLink) return ''
|
||||
if (requiresActiveKey && !activeKey) return ''
|
||||
return resolveChatUrl({
|
||||
template: preset.url,
|
||||
apiKey: requiresActiveKey ? activeKey : undefined,
|
||||
serverAddress,
|
||||
})
|
||||
}, [activeKey, isWebLink, preset, requiresActiveKey, serverAddress])
|
||||
|
||||
if (!preset) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center gap-4 p-6 text-center'>
|
||||
<MessageCircleWarning className='text-muted-foreground h-12 w-12' />
|
||||
<div className='space-y-1'>
|
||||
<h2 className='text-lg font-semibold'>
|
||||
{t('Chat preset not found')}
|
||||
</h2>
|
||||
<p className='text-muted-foreground'>
|
||||
{t('The requested chat preset does not exist or has been removed.')}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant='outline' render={<Link to='/dashboard' />}>
|
||||
{t('Return to dashboard')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isWebLink) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center gap-4 p-6 text-center'>
|
||||
<MessageCircleWarning className='text-muted-foreground h-12 w-12' />
|
||||
<div className='space-y-1'>
|
||||
<h2 className='text-lg font-semibold'>{t('Use sidebar shortcut')}</h2>
|
||||
<p className='text-muted-foreground'>
|
||||
{preset.name}{' '}
|
||||
{t(
|
||||
'opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant='outline' render={<Link to='/dashboard' />}>
|
||||
{t('Return to dashboard')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (requiresActiveKey && isPending) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center gap-4'>
|
||||
<Loader2 className='text-muted-foreground h-8 w-8 animate-spin' />
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('Preparing your chat link…')}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (requiresActiveKey && (isError || !activeKey || !iframeSrc)) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unable to generate chat link. Please check your API keys.'
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center p-6'>
|
||||
<Alert variant='destructive' className='max-w-xl'>
|
||||
<AlertTitle>{t('Unable to open chat')}</AlertTitle>
|
||||
<AlertDescription>{message}</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!requiresActiveKey && !iframeSrc) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center p-6'>
|
||||
<Alert variant='destructive' className='max-w-xl'>
|
||||
<AlertTitle>{t('Unable to open chat')}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
'Unable to generate chat link. Please contact your administrator.'
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<iframe
|
||||
src={iframeSrc}
|
||||
key={iframeSrc}
|
||||
className='h-full w-full border-0'
|
||||
allow='camera; microphone'
|
||||
title={`Chat preset: ${preset.name}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { useActiveChatKey } from '@/features/chat/hooks/use-active-chat-key'
|
||||
import { useChatPresets } from '@/features/chat/hooks/use-chat-presets'
|
||||
import { resolveChatUrl } from '@/features/chat/lib/chat-links'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/chat2link')({
|
||||
component: Chat2LinkPage,
|
||||
})
|
||||
|
||||
function Chat2LinkPage() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { chatPresets, serverAddress } = useChatPresets()
|
||||
|
||||
const firstWebPreset = useMemo(
|
||||
() => chatPresets.find((p) => p.type === 'web'),
|
||||
[chatPresets]
|
||||
)
|
||||
|
||||
const { data: activeKey, error: keyError } = useActiveChatKey(
|
||||
Boolean(firstWebPreset)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstWebPreset) {
|
||||
if (chatPresets.length > 0) {
|
||||
toast.error(t('No available Web chat links'))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (activeKey === undefined && !keyError) return
|
||||
|
||||
if (keyError || !activeKey) {
|
||||
const message =
|
||||
keyError instanceof Error
|
||||
? keyError.message
|
||||
: t('No enabled tokens available')
|
||||
toast.error(message)
|
||||
navigate({ to: '/keys' })
|
||||
return
|
||||
}
|
||||
|
||||
const url = resolveChatUrl({
|
||||
template: firstWebPreset.url,
|
||||
apiKey: activeKey,
|
||||
serverAddress,
|
||||
})
|
||||
|
||||
if (url) {
|
||||
window.location.href = url
|
||||
}
|
||||
}, [
|
||||
firstWebPreset,
|
||||
activeKey,
|
||||
keyError,
|
||||
serverAddress,
|
||||
chatPresets.length,
|
||||
navigate,
|
||||
t,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center gap-3'>
|
||||
<Loader2 className='text-muted-foreground h-8 w-8 animate-spin' />
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('Redirecting to chat page...')}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { Dashboard } from '@/features/dashboard'
|
||||
import {
|
||||
DASHBOARD_SECTION_IDS,
|
||||
DASHBOARD_DEFAULT_SECTION,
|
||||
} from '@/features/dashboard/section-registry'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/dashboard/$section')({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = DASHBOARD_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/dashboard/$section',
|
||||
params: { section: DASHBOARD_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: Dashboard,
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { DASHBOARD_DEFAULT_SECTION } from '@/features/dashboard/section-registry'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/dashboard/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/dashboard/$section',
|
||||
params: { section: DASHBOARD_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { ConfigDrawer } from '@/components/config-drawer'
|
||||
import { Header } from '@/components/layout'
|
||||
import { ProfileDropdown } from '@/components/profile-dropdown'
|
||||
import { Search } from '@/components/search'
|
||||
import { ThemeSwitch } from '@/components/theme-switch'
|
||||
import { ForbiddenError } from '@/features/errors/forbidden'
|
||||
import { GeneralError } from '@/features/errors/general-error'
|
||||
import { MaintenanceError } from '@/features/errors/maintenance-error'
|
||||
import { NotFoundError } from '@/features/errors/not-found-error'
|
||||
import { UnauthorisedError } from '@/features/errors/unauthorized-error'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/errors/$error')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const { error } = Route.useParams()
|
||||
|
||||
const errorMap: Record<string, React.ComponentType> = {
|
||||
unauthorized: UnauthorisedError,
|
||||
forbidden: ForbiddenError,
|
||||
'not-found': NotFoundError,
|
||||
'internal-server-error': GeneralError,
|
||||
'maintenance-error': MaintenanceError,
|
||||
}
|
||||
const ErrorComponent = errorMap[error] || NotFoundError
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<Search />
|
||||
<div className='ms-auto flex items-center md:space-x-4'>
|
||||
<ThemeSwitch />
|
||||
<ConfigDrawer />
|
||||
<ProfileDropdown />
|
||||
</div>
|
||||
</Header>
|
||||
<div className='flex-1 [&>div]:h-full'>
|
||||
<ErrorComponent />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { ApiKeys } from '@/features/keys'
|
||||
import { API_KEY_STATUS_OPTIONS } from '@/features/keys/constants'
|
||||
|
||||
const apiKeySearchSchema = z.object({
|
||||
page: z.number().optional().catch(1),
|
||||
pageSize: z.number().optional().catch(undefined),
|
||||
status: z
|
||||
.array(z.enum(API_KEY_STATUS_OPTIONS.map((s) => s.value as `${number}`)))
|
||||
.optional()
|
||||
.catch([]),
|
||||
filter: z.string().optional().catch(''),
|
||||
token: z.string().optional().catch(''),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/keys/')({
|
||||
validateSearch: apiKeySearchSchema,
|
||||
component: ApiKeys,
|
||||
})
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { Models } from '@/features/models'
|
||||
import {
|
||||
MODELS_SECTION_IDS,
|
||||
MODELS_DEFAULT_SECTION,
|
||||
} from '@/features/models/section-registry'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const modelsSearchSchema = z.object({
|
||||
page: z.number().optional().catch(1),
|
||||
pageSize: z.number().optional().catch(10),
|
||||
filter: z.string().optional().catch(''),
|
||||
vendor: z.array(z.string()).optional().catch([]),
|
||||
status: z.array(z.string()).optional().catch([]),
|
||||
sync: z.array(z.string()).optional().catch([]),
|
||||
dPage: z.number().optional().catch(1),
|
||||
dPageSize: z.number().optional().catch(10),
|
||||
dFilter: z.string().optional().catch(''),
|
||||
dStatus: z.array(z.string()).optional().catch([]),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/models/$section')({
|
||||
beforeLoad: ({ params }) => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (!auth.user || auth.user.role < ROLE.ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
|
||||
const validSections = MODELS_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/models/$section',
|
||||
params: { section: MODELS_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
validateSearch: modelsSearchSchema,
|
||||
component: Models,
|
||||
})
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { MODELS_DEFAULT_SECTION } from '@/features/models/section-registry'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/models/')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (!auth.user || auth.user.role < ROLE.ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
|
||||
throw redirect({
|
||||
to: '/models/$section',
|
||||
params: { section: MODELS_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { Main } from '@/components/layout'
|
||||
import { Playground } from '@/features/playground'
|
||||
import { isSidebarModuleEnabled } from '@/lib/nav-modules'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/playground/')({
|
||||
beforeLoad: () => {
|
||||
if (!isSidebarModuleEnabled('chat', 'playground')) {
|
||||
throw redirect({ to: '/dashboard' })
|
||||
}
|
||||
},
|
||||
component: PlaygroundPage,
|
||||
})
|
||||
|
||||
function PlaygroundPage() {
|
||||
return (
|
||||
<Main className='p-0'>
|
||||
<Playground />
|
||||
</Main>
|
||||
)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { Profile } from '@/features/profile'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/profile/')({
|
||||
component: Profile,
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { Redemptions } from '@/features/redemption-codes'
|
||||
import { REDEMPTION_FILTER_VALUES } from '@/features/redemption-codes/constants'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const redemptionsSearchSchema = z.object({
|
||||
page: z.number().optional().catch(1),
|
||||
pageSize: z.number().optional().catch(10),
|
||||
filter: z.string().optional().catch(''),
|
||||
status: z.array(z.enum(REDEMPTION_FILTER_VALUES)).optional().catch([]),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/redemption-codes/')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (!auth.user || auth.user.role < ROLE.ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
},
|
||||
validateSearch: redemptionsSearchSchema,
|
||||
component: Redemptions,
|
||||
})
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { AuthenticatedLayout } from '@/components/layout'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated')({
|
||||
beforeLoad: ({ location }) => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (!auth.user || !auth.accessToken) {
|
||||
throw redirect({
|
||||
to: '/sign-in',
|
||||
search: { redirect: location.href },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: AuthenticatedLayout,
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { Subscriptions } from '@/features/subscriptions'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/subscriptions/')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
if (!auth.user || auth.user.role < ROLE.ADMIN) {
|
||||
throw redirect({ to: '/403' })
|
||||
}
|
||||
},
|
||||
component: Subscriptions,
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SystemInfo } from '@/features/system-info'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/system-info/')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (auth.user?.role !== ROLE.SUPER_ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
},
|
||||
component: SystemInfo,
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { AuthSettings } from '@/features/system-settings/auth'
|
||||
import {
|
||||
AUTH_DEFAULT_SECTION,
|
||||
AUTH_SECTION_IDS,
|
||||
} from '@/features/system-settings/auth/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/auth/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = AUTH_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/auth/$section',
|
||||
params: { section: AUTH_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: AuthSettings,
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { AUTH_DEFAULT_SECTION } from '@/features/system-settings/auth/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/system-settings/auth/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/auth/$section',
|
||||
params: { section: AUTH_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { BillingSettings } from '@/features/system-settings/billing'
|
||||
import {
|
||||
BILLING_DEFAULT_SECTION,
|
||||
BILLING_SECTION_IDS,
|
||||
} from '@/features/system-settings/billing/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/billing/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = BILLING_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/billing/$section',
|
||||
params: { section: BILLING_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: BillingSettings,
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { BILLING_DEFAULT_SECTION } from '@/features/system-settings/billing/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/billing/'
|
||||
)({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/billing/$section',
|
||||
params: { section: BILLING_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { ContentSettings } from '@/features/system-settings/content'
|
||||
import {
|
||||
CONTENT_DEFAULT_SECTION,
|
||||
CONTENT_SECTION_IDS,
|
||||
} from '@/features/system-settings/content/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/content/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = CONTENT_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/content/$section',
|
||||
params: { section: CONTENT_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: ContentSettings,
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { CONTENT_DEFAULT_SECTION } from '@/features/system-settings/content/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/content/'
|
||||
)({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/content/$section',
|
||||
params: { section: CONTENT_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/system-settings/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/site',
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { ModelSettings } from '@/features/system-settings/models'
|
||||
import {
|
||||
MODELS_DEFAULT_SECTION,
|
||||
MODELS_SECTION_IDS,
|
||||
} from '@/features/system-settings/models/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/models/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = MODELS_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/models/$section',
|
||||
params: { section: MODELS_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: ModelSettings,
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { MODELS_DEFAULT_SECTION } from '@/features/system-settings/models/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/system-settings/models/')(
|
||||
{
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/models/$section',
|
||||
params: { section: MODELS_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { OperationsSettings } from '@/features/system-settings/operations'
|
||||
import {
|
||||
OPERATIONS_DEFAULT_SECTION,
|
||||
OPERATIONS_SECTION_IDS,
|
||||
} from '@/features/system-settings/operations/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/operations/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
if (params.section === 'monitoring') {
|
||||
throw redirect({
|
||||
to: '/system-settings/models/$section',
|
||||
params: { section: 'routing-reliability' },
|
||||
})
|
||||
}
|
||||
|
||||
const validSections = OPERATIONS_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/operations/$section',
|
||||
params: { section: OPERATIONS_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: OperationsSettings,
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { OPERATIONS_DEFAULT_SECTION } from '@/features/system-settings/operations/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/operations/'
|
||||
)({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/operations/$section',
|
||||
params: { section: OPERATIONS_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SystemSettings } from '@/features/system-settings'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/system-settings')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (auth.user?.role !== ROLE.SUPER_ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
},
|
||||
component: SystemSettings,
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SecuritySettings } from '@/features/system-settings/security'
|
||||
import {
|
||||
SECURITY_DEFAULT_SECTION,
|
||||
SECURITY_SECTION_IDS,
|
||||
} from '@/features/system-settings/security/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/security/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = SECURITY_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/security/$section',
|
||||
params: { section: SECURITY_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: SecuritySettings,
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SECURITY_DEFAULT_SECTION } from '@/features/system-settings/security/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/security/'
|
||||
)({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/security/$section',
|
||||
params: { section: SECURITY_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SiteSettings } from '@/features/system-settings/site'
|
||||
import {
|
||||
SITE_DEFAULT_SECTION,
|
||||
SITE_SECTION_IDS,
|
||||
} from '@/features/system-settings/site/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticated/system-settings/site/$section'
|
||||
)({
|
||||
beforeLoad: ({ params }) => {
|
||||
const validSections = SITE_SECTION_IDS as unknown as string[]
|
||||
if (!validSections.includes(params.section)) {
|
||||
throw redirect({
|
||||
to: '/system-settings/site/$section',
|
||||
params: { section: SITE_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
},
|
||||
component: SiteSettings,
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SITE_DEFAULT_SECTION } from '@/features/system-settings/site/section-registry.tsx'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/system-settings/site/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/system-settings/site/$section',
|
||||
params: { section: SITE_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { UsageLogs } from '@/features/usage-logs'
|
||||
import {
|
||||
isUsageLogsSectionId,
|
||||
USAGE_LOGS_DEFAULT_SECTION,
|
||||
} from '@/features/usage-logs/section-registry'
|
||||
|
||||
const logTypeValues = ['0', '1', '2', '3', '4', '5', '6', '7'] as const
|
||||
const logTypeSearchSchema = z
|
||||
.preprocess((value) => {
|
||||
if (value == null || value === '') return undefined
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}, z.array(z.enum(logTypeValues)).optional())
|
||||
.catch([])
|
||||
|
||||
const usageLogsSearchSchema = z.object({
|
||||
page: z.number().optional().catch(1),
|
||||
pageSize: z.number().optional().catch(undefined),
|
||||
type: logTypeSearchSchema.optional(),
|
||||
filter: z.string().optional().catch(''),
|
||||
model: z.string().optional().catch(''),
|
||||
token: z.string().optional().catch(''),
|
||||
channel: z.string().optional().catch(''),
|
||||
group: z.string().optional().catch(''),
|
||||
username: z.string().optional().catch(''),
|
||||
requestId: z.string().optional().catch(''),
|
||||
upstreamRequestId: z.string().optional().catch(''),
|
||||
startTime: z.number().optional(),
|
||||
endTime: z.number().optional(),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/usage-logs/$section')({
|
||||
beforeLoad: ({ params, search }) => {
|
||||
if (!isUsageLogsSectionId(params.section)) {
|
||||
throw redirect({
|
||||
to: '/usage-logs/$section',
|
||||
params: { section: USAGE_LOGS_DEFAULT_SECTION },
|
||||
})
|
||||
}
|
||||
// type 仅 common 使用,非 common 时清掉 URL 里的 type
|
||||
const hasTypeSearch = Array.isArray(search?.type)
|
||||
? search.type.length > 0
|
||||
: search?.type != null && search.type !== ''
|
||||
if (params.section !== 'common' && hasTypeSearch) {
|
||||
throw redirect({
|
||||
to: '/usage-logs/$section',
|
||||
params: { section: params.section },
|
||||
search: { ...search, type: undefined },
|
||||
replace: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
validateSearch: usageLogsSearchSchema,
|
||||
component: UsageLogs,
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { USAGE_LOGS_DEFAULT_SECTION } from '@/features/usage-logs/section-registry'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/usage-logs/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({
|
||||
to: '/usage-logs/$section',
|
||||
params: { section: USAGE_LOGS_DEFAULT_SECTION },
|
||||
})
|
||||
},
|
||||
})
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { Users } from '@/features/users'
|
||||
import { ROLE } from '@/lib/roles'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const usersSearchSchema = z.object({
|
||||
page: z.number().optional().catch(1),
|
||||
pageSize: z.number().optional().catch(undefined),
|
||||
filter: z.string().optional().catch(''),
|
||||
status: z
|
||||
.array(z.enum(['-1', '1', '2']))
|
||||
.optional()
|
||||
.catch([]),
|
||||
role: z
|
||||
.array(z.enum(['1', '10', '100']))
|
||||
.optional()
|
||||
.catch([]),
|
||||
group: z.string().optional().catch(''),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/users/')({
|
||||
beforeLoad: () => {
|
||||
const { auth } = useAuthStore.getState()
|
||||
|
||||
if (!auth.user || auth.user.role < ROLE.ADMIN) {
|
||||
throw redirect({
|
||||
to: '/403',
|
||||
})
|
||||
}
|
||||
},
|
||||
validateSearch: usersSearchSchema,
|
||||
component: Users,
|
||||
})
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { Wallet } from '@/features/wallet'
|
||||
|
||||
const walletSearchSchema = z.object({
|
||||
show_history: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/wallet/')({
|
||||
component: RouteComponent,
|
||||
validateSearch: walletSearchSchema,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const { show_history } = Route.useSearch()
|
||||
return <Wallet initialShowHistory={show_history} />
|
||||
}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { About } from '@/features/about'
|
||||
|
||||
export const Route = createFileRoute('/about/')({
|
||||
component: About,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { Home } from '@/features/home'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
})
|
||||
Vendored
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import {
|
||||
createFileRoute,
|
||||
useNavigate,
|
||||
useParams,
|
||||
useSearch,
|
||||
} from '@tanstack/react-router'
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import i18next from 'i18next'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { OAuthCallbackScreen } from '@/features/auth/components/oauth-callback-screen'
|
||||
import {
|
||||
OAUTH_BIND_CALLBACK_MESSAGE,
|
||||
OAUTH_BIND_RESULT_MESSAGE,
|
||||
} from '@/features/auth/constants'
|
||||
import { sanitizeAuthRedirect } from '@/features/auth/lib/auth-redirect'
|
||||
import {
|
||||
parseTelegramBindCallback,
|
||||
postTelegramBindResult,
|
||||
startOAuthBindResponseDeadline,
|
||||
} from '@/features/auth/lib/oauth-bind-window'
|
||||
import { api, applyAuthBundle, isAuthBundle } from '@/lib/api'
|
||||
import { getServerErrorMessageKey } from '@/lib/server-error-message'
|
||||
|
||||
type OAuthRequestConfig = AxiosRequestConfig & {
|
||||
skipBusinessError?: boolean
|
||||
}
|
||||
|
||||
interface OAuthBindingResult {
|
||||
type: typeof OAUTH_BIND_RESULT_MESSAGE
|
||||
provider: string
|
||||
state: string
|
||||
success: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
function OAuthCallback() {
|
||||
const navigate = useNavigate()
|
||||
const { provider } = useParams({ from: '/oauth/$provider' }) as {
|
||||
provider: string
|
||||
}
|
||||
const search = useSearch({ from: '/oauth/$provider' }) as {
|
||||
code?: string
|
||||
state?: string
|
||||
error?: string
|
||||
error_description?: string
|
||||
redirect?: string
|
||||
telegram_bind?: string
|
||||
flow_token?: string
|
||||
error_code?: string
|
||||
}
|
||||
const mode: 'login' | 'bind' =
|
||||
typeof window !== 'undefined' && window.opener ? 'bind' : 'login'
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const code = search.code ?? ''
|
||||
const state = search.state ?? ''
|
||||
const telegramCallback =
|
||||
provider === 'telegram'
|
||||
? parseTelegramBindCallback({
|
||||
telegram_bind: search.telegram_bind,
|
||||
flow_token: search.flow_token,
|
||||
error_code: search.error_code,
|
||||
})
|
||||
: null
|
||||
if (telegramCallback) {
|
||||
const opener = window.opener
|
||||
if (
|
||||
!postTelegramBindResult(
|
||||
telegramCallback,
|
||||
opener,
|
||||
window.location.origin
|
||||
)
|
||||
) {
|
||||
toast.error(i18next.t('Telegram binding failed. Please try again.'))
|
||||
const closeTimeout = window.setTimeout(() => window.close(), 1500)
|
||||
return () => window.clearTimeout(closeTimeout)
|
||||
}
|
||||
window.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (mode === 'bind') {
|
||||
const opener = window.opener
|
||||
if (!opener || opener.closed) {
|
||||
toast.error(i18next.t('OAuth binding window is no longer available'))
|
||||
return
|
||||
}
|
||||
|
||||
let cancelResultTimeout: () => void = () => undefined
|
||||
let delayedClose: number | undefined
|
||||
const handleBindingResult = (event: MessageEvent<unknown>) => {
|
||||
if (
|
||||
event.origin !== window.location.origin ||
|
||||
event.source !== opener
|
||||
) {
|
||||
return
|
||||
}
|
||||
const result = event.data as Partial<OAuthBindingResult> | null
|
||||
if (
|
||||
!result ||
|
||||
result.type !== OAUTH_BIND_RESULT_MESSAGE ||
|
||||
result.provider !== provider ||
|
||||
result.state !== state
|
||||
) {
|
||||
return
|
||||
}
|
||||
cancelResultTimeout()
|
||||
if (result.success) {
|
||||
toast.success(i18next.t('Binding successful!'))
|
||||
window.close()
|
||||
return
|
||||
}
|
||||
toast.error(result.message || i18next.t('OAuth failed'))
|
||||
delayedClose = window.setTimeout(() => window.close(), 1500)
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleBindingResult)
|
||||
cancelResultTimeout = startOAuthBindResponseDeadline(() => {
|
||||
toast.error(i18next.t('OAuth binding timed out. Please try again.'))
|
||||
delayedClose = window.setTimeout(() => window.close(), 1500)
|
||||
})
|
||||
opener.postMessage(
|
||||
{
|
||||
type: OAUTH_BIND_CALLBACK_MESSAGE,
|
||||
provider,
|
||||
code,
|
||||
state,
|
||||
error: search.error,
|
||||
errorDescription: search.error_description,
|
||||
},
|
||||
window.location.origin
|
||||
)
|
||||
return () => {
|
||||
window.removeEventListener('message', handleBindingResult)
|
||||
cancelResultTimeout()
|
||||
if (delayedClose !== undefined) window.clearTimeout(delayedClose)
|
||||
}
|
||||
}
|
||||
|
||||
const safeNavigate = (target: unknown, fallback = '/dashboard') => {
|
||||
const href =
|
||||
sanitizeAuthRedirect(target, window.location.origin) ?? fallback
|
||||
void navigate({ href, replace: true })
|
||||
}
|
||||
|
||||
if (!code && !search.error) {
|
||||
toast.error(i18next.t('Missing code'))
|
||||
safeNavigate('/sign-in', '/sign-in')
|
||||
return
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const config: OAuthRequestConfig = {
|
||||
params: {
|
||||
code: code || undefined,
|
||||
state,
|
||||
error: search.error,
|
||||
error_description: search.error_description,
|
||||
},
|
||||
skipBusinessError: true,
|
||||
}
|
||||
const response = await api.get(`/api/oauth/${provider}`, config)
|
||||
if (response.data?.success && isAuthBundle(response.data?.data)) {
|
||||
applyAuthBundle(response.data.data)
|
||||
safeNavigate(search.redirect)
|
||||
toast.success(i18next.t('Signed in successfully!'))
|
||||
return
|
||||
}
|
||||
const messageKey = getServerErrorMessageKey(response.data)
|
||||
toast.error(
|
||||
messageKey
|
||||
? i18next.t(messageKey)
|
||||
: response.data?.message || i18next.t('OAuth failed')
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
const messageKey = getServerErrorMessageKey(error)
|
||||
const responseMessage = (
|
||||
error as { response?: { data?: { message?: string } } }
|
||||
).response?.data?.message
|
||||
if (!messageKey) {
|
||||
toast.error(
|
||||
responseMessage ||
|
||||
(error instanceof Error
|
||||
? error.message
|
||||
: i18next.t('OAuth failed'))
|
||||
)
|
||||
}
|
||||
}
|
||||
safeNavigate('/sign-in', '/sign-in')
|
||||
})()
|
||||
}, [
|
||||
mode,
|
||||
navigate,
|
||||
provider,
|
||||
search.code,
|
||||
search.error,
|
||||
search.error_code,
|
||||
search.error_description,
|
||||
search.flow_token,
|
||||
search.redirect,
|
||||
search.state,
|
||||
search.telegram_bind,
|
||||
])
|
||||
|
||||
return <OAuthCallbackScreen provider={provider} mode={mode} />
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/oauth/$provider')({
|
||||
component: OAuthCallback,
|
||||
})
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { ModelDetails } from '@/features/pricing/components/model-details'
|
||||
import { getFreshModuleAccess } from '@/lib/nav-modules'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const modelDetailsSearchSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
sort: z.string().optional(),
|
||||
vendor: z.string().optional(),
|
||||
group: z.string().optional(),
|
||||
quotaType: z.string().optional(),
|
||||
endpointType: z.string().optional(),
|
||||
tag: z.string().optional(),
|
||||
tokenUnit: z.enum(['M', 'K']).optional(),
|
||||
view: z.enum(['card', 'table']).optional().catch(undefined),
|
||||
rechargePrice: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/pricing/$modelId/')({
|
||||
validateSearch: modelDetailsSearchSchema,
|
||||
beforeLoad: async ({ location }) => {
|
||||
const access = await getFreshModuleAccess('pricing')
|
||||
if (!access.enabled) {
|
||||
throw redirect({ to: '/' })
|
||||
}
|
||||
if (access.requireAuth) {
|
||||
const { auth } = useAuthStore.getState()
|
||||
if (!auth.user) {
|
||||
throw redirect({
|
||||
to: '/sign-in',
|
||||
search: { redirect: location.href },
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
component: ModelDetails,
|
||||
})
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { Pricing } from '@/features/pricing'
|
||||
import { getFreshModuleAccess } from '@/lib/nav-modules'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const pricingSearchSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
sort: z.string().optional(),
|
||||
vendor: z.string().optional(),
|
||||
group: z.string().optional(),
|
||||
quotaType: z.string().optional(),
|
||||
endpointType: z.string().optional(),
|
||||
tag: z.string().optional(),
|
||||
tokenUnit: z.enum(['M', 'K']).optional(),
|
||||
view: z.enum(['card', 'table']).optional().catch(undefined),
|
||||
rechargePrice: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/pricing/')({
|
||||
validateSearch: pricingSearchSchema,
|
||||
beforeLoad: async ({ location }) => {
|
||||
const access = await getFreshModuleAccess('pricing')
|
||||
if (!access.enabled) {
|
||||
throw redirect({ to: '/' })
|
||||
}
|
||||
if (access.requireAuth) {
|
||||
const { auth } = useAuthStore.getState()
|
||||
if (!auth.user) {
|
||||
throw redirect({
|
||||
to: '/sign-in',
|
||||
search: { redirect: location.href },
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
component: Pricing,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { PrivacyPolicy } from '@/features/legal'
|
||||
|
||||
export const Route = createFileRoute('/privacy-policy')({
|
||||
component: PrivacyPolicy,
|
||||
})
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import z from 'zod'
|
||||
|
||||
import { Rankings } from '@/features/rankings'
|
||||
import { getFreshModuleAccess } from '@/lib/nav-modules'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
const rankingsSearchSchema = z.object({
|
||||
period: z
|
||||
.enum(['today', 'week', 'month', 'year'])
|
||||
.optional()
|
||||
.catch(undefined),
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/rankings/')({
|
||||
validateSearch: rankingsSearchSchema,
|
||||
beforeLoad: async ({ location }) => {
|
||||
const access = await getFreshModuleAccess('rankings')
|
||||
if (!access.enabled) {
|
||||
throw redirect({ to: '/' })
|
||||
}
|
||||
if (access.requireAuth) {
|
||||
const { auth } = useAuthStore.getState()
|
||||
if (!auth.user) {
|
||||
throw redirect({
|
||||
to: '/sign-in',
|
||||
search: { redirect: location.href },
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
component: Rankings,
|
||||
})
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
import { SetupWizard } from '@/features/setup'
|
||||
import { getSetupStatus } from '@/features/setup/api'
|
||||
|
||||
export const Route = createFileRoute('/setup/')({
|
||||
beforeLoad: async () => {
|
||||
const status = await getSetupStatus().catch((error) => {
|
||||
if (import.meta.env.DEV) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[setup.beforeLoad] failed to fetch setup status', error)
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
if (status?.success && status.data?.status) {
|
||||
throw redirect({ to: '/' })
|
||||
}
|
||||
},
|
||||
component: SetupWizard,
|
||||
})
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
import { UserAgreement } from '@/features/legal'
|
||||
|
||||
export const Route = createFileRoute('/user-agreement')({
|
||||
component: UserAgreement,
|
||||
})
|
||||
Reference in New Issue
Block a user