diff --git a/ee/tabby-ui/app/auth/signup/components/signup.tsx b/ee/tabby-ui/app/auth/signup/components/signup.tsx
index 4b53c70..c756eaf 100644
--- a/ee/tabby-ui/app/auth/signup/components/signup.tsx
+++ b/ee/tabby-ui/app/auth/signup/components/signup.tsx
@@ -11,7 +11,7 @@ export default function Signup() {
const title = isAdmin ? 'Create an admin account' : 'Create an account'
const description = isAdmin
- ? 'The admin account has access to invite collaborators and manage Tabby configuration'
+ ? 'After creating an admin account, your instance is secured, and only registered users can access it.'
: 'Fill form below to create your account'
if (isAdmin || invitationCode) {
diff --git a/ee/tabby-ui/components/header.tsx b/ee/tabby-ui/components/header.tsx
index e0a682a..28f59f7 100644
--- a/ee/tabby-ui/components/header.tsx
+++ b/ee/tabby-ui/components/header.tsx
@@ -12,14 +12,8 @@ import { useWorkers } from '@/lib/hooks/use-workers'
import { WorkerKind } from '@/lib/gql/generates/graphql'
import { has } from 'lodash-es'
import { ThemeToggle } from './theme-toggle'
-import { useSession } from '@/lib/tabby/auth'
-import { useRouter } from 'next/navigation'
-import { graphql } from '@/lib/gql/generates'
-import { useGraphQLQuery } from '@/lib/tabby/gql'
export function Header() {
- useRequireAuth()
-
const { data } = useHealth()
const workers = useWorkers(data)
const isChatEnabled = has(workers, WorkerKind.Chat)
@@ -80,30 +74,3 @@ function isNewVersionAvailable(version?: string, latestRelease?: ReleaseInfo) {
return true
}
}
-
-export const getIsAdminInitialized = graphql(/* GraphQL */ `
- query GetIsAdminInitialized {
- isAdminInitialized
- }
-`)
-
-function useRequireAuth() {
- const { data, isLoading } = useGraphQLQuery(
- getIsAdminInitialized,
- undefined,
- {}
- )
- const router = useRouter()
- const { status } = useSession()
-
- React.useEffect(() => {
- if (isLoading) return
- if (status !== 'unauthenticated') return
-
- if (data!.isAdminInitialized) {
- router.replace('/auth/signin')
- } else {
- router.replace('/auth/signup?isAdmin=true')
- }
- }, [data, isLoading, status])
-}
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index dd4de39..eaad117 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -594,6 +594,27 @@ function IconLogout({ className, ...props }: React.ComponentProps<'svg'>) {
)
}
+function IconUnlock({ className, ...props }: React.ComponentProps<'svg'>) {
+ return (
+
+ )
+}
+
export {
IconEdit,
IconNextChat,
@@ -625,5 +646,6 @@ export {
IconSlack,
IconNotice,
IconSymbolFunction,
- IconLogout
+ IconLogout,
+ IconUnlock
}
diff --git a/ee/tabby-ui/components/user-panel.tsx b/ee/tabby-ui/components/user-panel.tsx
index a5953bb..9a7b111 100644
--- a/ee/tabby-ui/components/user-panel.tsx
+++ b/ee/tabby-ui/components/user-panel.tsx
@@ -1,20 +1,53 @@
-import { useSession, useSignOut } from '@/lib/tabby/auth'
+import {
+ useAuthenticatedSession,
+ useIsAdminInitialized,
+ useSession,
+ useSignOut
+} from '@/lib/tabby/auth'
import { cn } from '@/lib/utils'
-import { IconLogout } from './ui/icons'
+import { IconLogout, IconUnlock } from './ui/icons'
+import Link from 'next/link'
+import React from 'react'
export default function UserPanel() {
- const { data: session, status } = useSession()
- const signOut = useSignOut()
+ const isAdminInitialized = useIsAdminInitialized()
+
+ const Component = isAdminInitialized ? UserInfoPanel : EnableAdminPanel
- if (status !== 'authenticated') return
return (
-
+
+
+ )
+}
+
+function UserInfoPanel({ className }: React.ComponentProps<'span'>) {
+ const session = useAuthenticatedSession()
+ const signOut = useSignOut()
+
+ return (
+ session && (
+
{session.email}
-
+ )
+ )
+}
+
+function EnableAdminPanel({ className }: React.ComponentProps<'span'>) {
+ return (
+
+ Secure Access
+
)
}
diff --git a/ee/tabby-ui/lib/gql/generates/gql.ts b/ee/tabby-ui/lib/gql/generates/gql.ts
index 8b0d1b5..5a44e75 100644
--- a/ee/tabby-ui/lib/gql/generates/gql.ts
+++ b/ee/tabby-ui/lib/gql/generates/gql.ts
@@ -19,12 +19,12 @@ const documents = {
types.TokenAuthDocument,
'\n mutation register(\n $email: String!\n $password1: String!\n $password2: String!\n $invitationCode: String\n ) {\n register(\n email: $email\n password1: $password1\n password2: $password2\n invitationCode: $invitationCode\n ) {\n accessToken\n refreshToken\n }\n }\n':
types.RegisterDocument,
- '\n query GetIsAdminInitialized {\n isAdminInitialized\n }\n':
- types.GetIsAdminInitializedDocument,
'\n query GetWorkers {\n workers {\n kind\n name\n addr\n device\n arch\n cpuInfo\n cpuCount\n cudaDevices\n }\n }\n':
types.GetWorkersDocument,
'\n mutation refreshToken($refreshToken: String!) {\n refreshToken(refreshToken: $refreshToken) {\n accessToken\n refreshToken\n }\n }\n':
- types.RefreshTokenDocument
+ types.RefreshTokenDocument,
+ '\n query GetIsAdminInitialized {\n isAdminInitialized\n }\n':
+ types.GetIsAdminInitializedDocument
}
/**
@@ -59,12 +59,6 @@ export function graphql(
export function graphql(
source: '\n mutation register(\n $email: String!\n $password1: String!\n $password2: String!\n $invitationCode: String\n ) {\n register(\n email: $email\n password1: $password1\n password2: $password2\n invitationCode: $invitationCode\n ) {\n accessToken\n refreshToken\n }\n }\n'
): (typeof documents)['\n mutation register(\n $email: String!\n $password1: String!\n $password2: String!\n $invitationCode: String\n ) {\n register(\n email: $email\n password1: $password1\n password2: $password2\n invitationCode: $invitationCode\n ) {\n accessToken\n refreshToken\n }\n }\n']
-/**
- * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
- */
-export function graphql(
- source: '\n query GetIsAdminInitialized {\n isAdminInitialized\n }\n'
-): (typeof documents)['\n query GetIsAdminInitialized {\n isAdminInitialized\n }\n']
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -77,6 +71,12 @@ export function graphql(
export function graphql(
source: '\n mutation refreshToken($refreshToken: String!) {\n refreshToken(refreshToken: $refreshToken) {\n accessToken\n refreshToken\n }\n }\n'
): (typeof documents)['\n mutation refreshToken($refreshToken: String!) {\n refreshToken(refreshToken: $refreshToken) {\n accessToken\n refreshToken\n }\n }\n']
+/**
+ * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
+ */
+export function graphql(
+ source: '\n query GetIsAdminInitialized {\n isAdminInitialized\n }\n'
+): (typeof documents)['\n query GetIsAdminInitialized {\n isAdminInitialized\n }\n']
export function graphql(source: string) {
return (documents as any)[source] ?? {}
diff --git a/ee/tabby-ui/lib/gql/generates/graphql.ts b/ee/tabby-ui/lib/gql/generates/graphql.ts
index 1d821bd..6b9d6c2 100644
--- a/ee/tabby-ui/lib/gql/generates/graphql.ts
+++ b/ee/tabby-ui/lib/gql/generates/graphql.ts
@@ -176,15 +176,6 @@ export type RegisterMutation = {
}
}
-export type GetIsAdminInitializedQueryVariables = Exact<{
- [key: string]: never
-}>
-
-export type GetIsAdminInitializedQuery = {
- __typename?: 'Query'
- isAdminInitialized: boolean
-}
-
export type GetWorkersQueryVariables = Exact<{ [key: string]: never }>
export type GetWorkersQuery = {
@@ -215,6 +206,15 @@ export type RefreshTokenMutation = {
}
}
+export type GetIsAdminInitializedQueryVariables = Exact<{
+ [key: string]: never
+}>
+
+export type GetIsAdminInitializedQuery = {
+ __typename?: 'Query'
+ isAdminInitialized: boolean
+}
+
export const GetRegistrationTokenDocument = {
kind: 'Document',
definitions: [
@@ -405,25 +405,6 @@ export const RegisterDocument = {
}
]
} as unknown as DocumentNode
-export const GetIsAdminInitializedDocument = {
- kind: 'Document',
- definitions: [
- {
- kind: 'OperationDefinition',
- operation: 'query',
- name: { kind: 'Name', value: 'GetIsAdminInitialized' },
- selectionSet: {
- kind: 'SelectionSet',
- selections: [
- { kind: 'Field', name: { kind: 'Name', value: 'isAdminInitialized' } }
- ]
- }
- }
- ]
-} as unknown as DocumentNode<
- GetIsAdminInitializedQuery,
- GetIsAdminInitializedQueryVariables
->
export const GetWorkersDocument = {
kind: 'Document',
definitions: [
@@ -508,3 +489,22 @@ export const RefreshTokenDocument = {
RefreshTokenMutation,
RefreshTokenMutationVariables
>
+export const GetIsAdminInitializedDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'query',
+ name: { kind: 'Name', value: 'GetIsAdminInitialized' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'isAdminInitialized' } }
+ ]
+ }
+ }
+ ]
+} as unknown as DocumentNode<
+ GetIsAdminInitializedQuery,
+ GetIsAdminInitializedQueryVariables
+>
diff --git a/ee/tabby-ui/lib/tabby/auth.tsx b/ee/tabby-ui/lib/tabby/auth.tsx
index 5d55c67..b627c5a 100644
--- a/ee/tabby-ui/lib/tabby/auth.tsx
+++ b/ee/tabby-ui/lib/tabby/auth.tsx
@@ -1,8 +1,9 @@
import * as React from 'react'
import { graphql } from '@/lib/gql/generates'
import useInterval from '@/lib/hooks/use-interval'
-import { gqlClient } from '@/lib/tabby/gql'
+import { gqlClient, useGraphQLQuery } from '@/lib/tabby/gql'
import { jwtDecode } from 'jwt-decode'
+import { useRouter } from 'next/navigation'
interface AuthData {
accessToken: string
@@ -241,6 +242,40 @@ function useSession(): Session {
}
}
+export const getIsAdminInitialized = graphql(/* GraphQL */ `
+ query GetIsAdminInitialized {
+ isAdminInitialized
+ }
+`)
+
+function useIsAdminInitialized() {
+ const { data } = useGraphQLQuery(getIsAdminInitialized)
+ return data?.isAdminInitialized
+}
+
+function useAuthenticatedSession() {
+ const { data } = useGraphQLQuery(getIsAdminInitialized)
+ const router = useRouter()
+ const { data: session, status } = useSession()
+
+ React.useEffect(() => {
+ if (!data?.isAdminInitialized) return
+
+ if (status === 'unauthenticated') {
+ router.replace('/auth/signin')
+ }
+ }, [data, status])
+
+ return session
+}
+
export type { AuthStore, User, Session }
-export { AuthProvider, useSignIn, useSignOut, useSession }
+export {
+ AuthProvider,
+ useSignIn,
+ useSignOut,
+ useSession,
+ useIsAdminInitialized,
+ useAuthenticatedSession
+}