Przeglądaj źródła

Fix: Install page redirects to signin if Dify finished setup. (#762)

Matri 1 rok temu
rodzic
commit
6242e91a6b

+ 9 - 8
api/controllers/console/setup.py

@@ -19,15 +19,16 @@ from .wraps import only_edition_self_hosted
 
 class SetupApi(Resource):
 
-    @only_edition_self_hosted
     def get(self):
-        setup_status = get_setup_status()
-        if setup_status:
-            return {
-                'step': 'finished',
-                'setup_at': setup_status.setup_at.isoformat()
-            }  
-        return {'step': 'not_start'}
+        if current_app.config['EDITION'] == 'SELF_HOSTED':
+            setup_status = get_setup_status()
+            if setup_status:
+                return {
+                    'step': 'finished',
+                    'setup_at': setup_status.setup_at.isoformat()
+                }
+            return {'step': 'not_start'}
+        return {'step': 'finished'}
 
     @only_edition_self_hosted
     def post(self):

+ 94 - 78
web/app/install/installForm.tsx

@@ -1,13 +1,15 @@
 'use client'
-import React from 'react'
+import React, { useEffect } from 'react'
 import { useTranslation } from 'react-i18next'
 import Link from 'next/link'
 import { useRouter } from 'next/navigation'
 import { useContext } from 'use-context-selector'
 import Toast from '../components/base/toast'
+import Loading from '../components/base/loading'
 import Button from '@/app/components/base/button'
-import { setup } from '@/service/common'
 import I18n from '@/context/i18n'
+import { fetchSetupStatus, setup } from '@/service/common'
+import type { SetupStatusResponse } from '@/models/common'
 
 const validEmailReg = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/
 const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
@@ -21,6 +23,8 @@ const InstallForm = () => {
   const [name, setName] = React.useState('')
   const [password, setPassword] = React.useState('')
   const [showPassword, setShowPassword] = React.useState(false)
+  const [loading, setLoading] = React.useState(true)
+
   const showErrorMessage = (message: string) => {
     Toast.notify({
       type: 'error',
@@ -61,78 +65,90 @@ const InstallForm = () => {
     })
     router.push('/signin')
   }
+
+  useEffect(() => {
+    fetchSetupStatus().then((res: SetupStatusResponse) => {
+      if (res.step === 'finished')
+        window.location.href = '/signin'
+      else
+        setLoading(false)
+    })
+  }, [])
+
   return (
-    <>
-      <div className="sm:mx-auto sm:w-full sm:max-w-md">
-        <h2 className="text-[32px] font-bold text-gray-900">{t('login.setAdminAccount')}</h2>
-        <p className='
+    loading
+      ? <Loading />
+      : <>
+        <div className="sm:mx-auto sm:w-full sm:max-w-md">
+          <h2 className="text-[32px] font-bold text-gray-900">{t('login.setAdminAccount')}</h2>
+          <p className='
           mt-1 text-sm text-gray-600
         '>{t('login.setAdminAccountDesc')}</p>
-      </div>
+        </div>
 
-      <div className="grow mt-8 sm:mx-auto sm:w-full sm:max-w-md">
-        <div className="bg-white ">
-          <form onSubmit={() => { }}>
-            <div className='mb-5'>
-              <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
-                {t('login.email')}
-              </label>
-              <div className="mt-1">
-                <input
-                  id="email"
-                  type="email"
-                  value={email}
-                  onChange={e => setEmail(e.target.value)}
-                  placeholder={t('login.emailPlaceholder') || ''}
-                  className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm'}
-                />
+        <div className="grow mt-8 sm:mx-auto sm:w-full sm:max-w-md">
+          <div className="bg-white ">
+            <form onSubmit={() => { }}>
+              <div className='mb-5'>
+                <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
+                  {t('login.email')}
+                </label>
+                <div className="mt-1">
+                  <input
+                    id="email"
+                    type="email"
+                    value={email}
+                    onChange={e => setEmail(e.target.value)}
+                    placeholder={t('login.emailPlaceholder') || ''}
+                    className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm'}
+                  />
+                </div>
               </div>
-            </div>
 
-            <div className='mb-5'>
-              <label htmlFor="name" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
-                {t('login.name')}
-              </label>
-              <div className="mt-1 relative rounded-md shadow-sm">
-                <input
-                  id="name"
-                  type="text"
-                  value={name}
-                  onChange={e => setName(e.target.value)}
-                  placeholder={t('login.namePlaceholder') || ''}
-                  className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'}
-                />
+              <div className='mb-5'>
+                <label htmlFor="name" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
+                  {t('login.name')}
+                </label>
+                <div className="mt-1 relative rounded-md shadow-sm">
+                  <input
+                    id="name"
+                    type="text"
+                    value={name}
+                    onChange={e => setName(e.target.value)}
+                    placeholder={t('login.namePlaceholder') || ''}
+                    className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'}
+                  />
+                </div>
               </div>
-            </div>
 
-            <div className='mb-5'>
-              <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
-                {t('login.password')}
-              </label>
-              <div className="mt-1 relative rounded-md shadow-sm">
-                <input
-                  id="password"
-                  type={showPassword ? 'text' : 'password'}
-                  value={password}
-                  onChange={e => setPassword(e.target.value)}
-                  placeholder={t('login.passwordPlaceholder') || ''}
-                  className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'}
-                />
-                <div className="absolute inset-y-0 right-0 flex items-center pr-3">
-                  <button
-                    type="button"
-                    onClick={() => setShowPassword(!showPassword)}
-                    className="text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500"
-                  >
-                    {showPassword ? '👀' : '😝'}
-                  </button>
+              <div className='mb-5'>
+                <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
+                  {t('login.password')}
+                </label>
+                <div className="mt-1 relative rounded-md shadow-sm">
+                  <input
+                    id="password"
+                    type={showPassword ? 'text' : 'password'}
+                    value={password}
+                    onChange={e => setPassword(e.target.value)}
+                    placeholder={t('login.passwordPlaceholder') || ''}
+                    className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'}
+                  />
+                  <div className="absolute inset-y-0 right-0 flex items-center pr-3">
+                    <button
+                      type="button"
+                      onClick={() => setShowPassword(!showPassword)}
+                      className="text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500"
+                    >
+                      {showPassword ? '👀' : '😝'}
+                    </button>
+                  </div>
                 </div>
-              </div>
-              <div className='mt-1 text-xs text-gray-500'>{t('login.error.passwordInvalid')}</div>
+                <div className='mt-1 text-xs text-gray-500'>{t('login.error.passwordInvalid')}</div>
 
-            </div>
+              </div>
 
-            {/* <div className="flex items-center justify-between">
+              {/* <div className="flex items-center justify-between">
               <div className="text-sm">
                 <div className="flex items-center mb-4">
                   <input id="default-checkbox" type="checkbox" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 rounded" />
@@ -140,24 +156,24 @@ const InstallForm = () => {
                 </div>
               </div>
             </div> */}
-            <div>
-              <Button type='primary' className='w-full !fone-medium !text-sm' onClick={handleSetting}>
-                {t('login.installBtn')}
-              </Button>
-            </div>
-          </form>
-          <div className="block w-hull mt-2 text-xs text-gray-600">
-            {t('login.license.tip')}
+              <div>
+                <Button type='primary' className='w-full !fone-medium !text-sm' onClick={handleSetting}>
+                  {t('login.installBtn')}
+                </Button>
+              </div>
+            </form>
+            <div className="block w-hull mt-2 text-xs text-gray-600">
+              {t('login.license.tip')}
             &nbsp;
-            <Link
-              className='text-primary-600'
-              target={'_blank'}
-              href={`https://docs.dify.ai/${locale === 'en' ? '' : `v/${locale.toLowerCase()}/`}community/open-source`}
-            >{t('login.license.link')}</Link>
+              <Link
+                className='text-primary-600'
+                target={'_blank'}
+                href={`https://docs.dify.ai/${locale === 'en' ? '' : `v/${locale.toLowerCase()}/`}community/open-source`}
+              >{t('login.license.link')}</Link>
+            </div>
           </div>
         </div>
-      </div>
-    </>
+      </>
   )
 }
 

+ 22 - 26
web/app/install/page.tsx

@@ -1,36 +1,32 @@
 import React from 'react'
-import InstallForm from './installForm'
+import classNames from 'classnames'
 import Header from '../signin/_header'
 import style from '../signin/page.module.css'
-import classNames from 'classnames'
-
-const SignIn = () => {
+import InstallForm from './installForm'
 
+const Install = () => {
   return (
-    <>
-      <div className={classNames(
-        style.background,
-        'flex w-full min-h-screen',
-        'p-4 lg:p-8',
-        'gap-x-20',
-        'justify-center lg:justify-start'
-      )}>
-        <div className={
-          classNames(
-            'flex w-full flex-col bg-white shadow rounded-2xl shrink-0',
-            'md:w-[608px] space-between'
-          )
-        }>
-          <Header />
-          <InstallForm />
-          <div className='px-8 py-6 text-sm font-normal text-gray-500'>
-            © {new Date().getFullYear()} Dify, Inc. All rights reserved.
-          </div>
+    <div className={classNames(
+      style.background,
+      'flex w-full min-h-screen',
+      'p-4 lg:p-8',
+      'gap-x-20',
+      'justify-center lg:justify-start',
+    )}>
+      <div className={
+        classNames(
+          'flex w-full flex-col bg-white shadow rounded-2xl shrink-0',
+          'md:w-[608px] space-between',
+        )
+      }>
+        <Header />
+        <InstallForm />
+        <div className='px-8 py-6 text-sm font-normal text-gray-500'>
+          © {new Date().getFullYear()} Dify, Inc. All rights reserved.
         </div>
       </div>
-
-    </>
+    </div>
   )
 }
 
-export default SignIn
+export default Install

+ 5 - 0
web/models/common.ts

@@ -6,6 +6,11 @@ export type OauthResponse = {
   redirect_url: string
 }
 
+export type SetupStatusResponse = {
+  step: 'finished' | 'not_started'
+  setup_at?: Date
+}
+
 export type UserProfileResponse = {
   id: string
   name: string

+ 6 - 2
web/service/common.ts

@@ -3,8 +3,8 @@ import { del, get, patch, post, put } from './base'
 import type {
   AccountIntegrate, CommonResponse, DataSourceNotion,
   IWorkspace, LangGeniusVersionResponse, Member,
-  OauthResponse, PluginProvider, Provider, ProviderAnthropicToken, ProviderAzureToken, TenantInfoResponse,
-  UserProfileOriginResponse,
+  OauthResponse, PluginProvider, Provider, ProviderAnthropicToken, ProviderAzureToken,
+  SetupStatusResponse, TenantInfoResponse, UserProfileOriginResponse,
 } from '@/models/common'
 import type {
   UpdateOpenAIKeyResponse,
@@ -19,6 +19,10 @@ export const setup: Fetcher<CommonResponse, { body: Record<string, any> }> = ({
   return post('/setup', { body }) as Promise<CommonResponse>
 }
 
+export const fetchSetupStatus = () => {
+  return get('/setup') as Promise<SetupStatusResponse>
+}
+
 export const fetchUserProfile: Fetcher<UserProfileOriginResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
   return get(url, params, { needAllResponseContent: true }) as Promise<UserProfileOriginResponse>
 }