diff --git a/ui/config-overrides.js b/ui/config-overrides.js index ac52fa63..35510f12 100644 --- a/ui/config-overrides.js +++ b/ui/config-overrides.js @@ -22,6 +22,11 @@ module.exports = { changeOrigin: true, secure: false, }, + '/installation': { + target: 'http://10.0.10.98:2060', + changeOrigin: true, + secure: false, + }, }; return config; }; diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 98f7c046..a4c7dcd2 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -258,6 +258,8 @@ export interface AdminSettingsGeneral { name: string; short_description: string; description: string; + site_url: string; + contact_email: string; } export interface AdminSettingsInterface { diff --git a/ui/src/components/QueryGroup/index.tsx b/ui/src/components/QueryGroup/index.tsx index fae01621..15c3ad49 100644 --- a/ui/src/components/QueryGroup/index.tsx +++ b/ui/src/components/QueryGroup/index.tsx @@ -1,6 +1,6 @@ import { FC, memo } from 'react'; import { ButtonGroup, Button, DropdownButton, Dropdown } from 'react-bootstrap'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; @@ -11,6 +11,7 @@ interface Props { currentSort: string; sortKey?: string; className?: string; + pathname?: string; } const MAX_BUTTON_COUNT = 3; const Index: FC = ({ @@ -19,8 +20,10 @@ const Index: FC = ({ sortKey = 'order', i18nKeyPrefix = '', className = '', + pathname = '', }) => { const [searchParams, setUrlSearchParams] = useSearchParams(); + const navigate = useNavigate(); const { t } = useTranslation('translation', { keyPrefix: i18nKeyPrefix, @@ -36,7 +39,11 @@ const Index: FC = ({ const handleClick = (e, type) => { e.preventDefault(); const str = handleParams(type); - setUrlSearchParams(str); + if (pathname) { + navigate(`${pathname}${str}`); + } else { + setUrlSearchParams(str); + } }; const filteredData = data.filter((_, index) => index > MAX_BUTTON_COUNT - 2); @@ -69,7 +76,9 @@ const Index: FC = ({ } : {} } - href={handleParams(key)} + href={ + pathname ? `${pathname}${handleParams(key)}` : handleParams(key) + } onClick={(evt) => handleClick(evt, key)}> {t(name)} @@ -95,7 +104,11 @@ const Index: FC = ({ 'd-block d-md-none', className, )} - href={handleParams(key)} + href={ + pathname + ? `${pathname}${handleParams(key)}` + : handleParams(key) + } onClick={(evt) => handleClick(evt, key)}> {t(name)} diff --git a/ui/src/components/QuestionList/index.tsx b/ui/src/components/QuestionList/index.tsx index a353d9bd..469a6e58 100644 --- a/ui/src/components/QuestionList/index.tsx +++ b/ui/src/components/QuestionList/index.tsx @@ -115,6 +115,7 @@ const QuestionList: FC = ({ source }) => { diff --git a/ui/src/i18n/locales/en.json b/ui/src/i18n/locales/en.json index 19422b24..4f0de90b 100644 --- a/ui/src/i18n/locales/en.json +++ b/ui/src/i18n/locales/en.json @@ -744,6 +744,7 @@ "title": "Answer", "next": "Next", "done": "Done", + "config_yaml_error": "Can’t create the config.yaml file.", "lang": { "label": "Please choose a language" }, @@ -784,22 +785,42 @@ "site_information": "Site Information", "admin_account": "Admin Account", "site_name": { - "label": "Site Name" + "label": "Site Name", + "msg": "Site Name cannot be empty." + }, + "site_url": { + "label": "Site URL", + "text": "The address of your site.", + "msg": { + "empty": "Site URL cannot be empty.", + "incorrect": "Site URL incorrect format." + } }, "contact_email": { "label": "Contact Email", - "text": "Email address of key contact responsible for this site." + "text": "Email address of key contact responsible for this site.", + "msg": { + "empty": "Contact Email cannot be empty.", + "incorrect": "Contact Email incorrect format." + } + }, "admin_name": { - "label": "Name" + "label": "Name", + "msg": "Name cannot be empty." }, "admin_password": { "label": "Password", - "text": "You will need this password to log in. Please store it in a secure location." + "text": "You will need this password to log in. Please store it in a secure location.", + "msg": "Password cannot be empty." }, "admin_email": { "label": "Email", - "text": "You will need this email to log in." + "text": "You will need this email to log in.", + "msg": { + "empty": "Email cannot be empty.", + "incorrect": "Email incorrect format." + } }, "ready_title": "Your Answer is Ready!", "ready_description": "If you ever feel like changing more settings, visit <1>admin section; find it in the site menu.", @@ -976,6 +997,11 @@ "msg": "Site name cannot be empty.", "text": "The name of this site, as used in the title tag." }, + "site_url": { + "label": "Site URL", + "msg": "Site url cannot be empty.", + "text": "The address of your site." + }, "short_description": { "label": "Short Site Description (optional)", "msg": "Short site description cannot be empty.", @@ -985,6 +1011,11 @@ "label": "Site Description (optional)", "msg": "Site description cannot be empty.", "text": "Describe this site in one sentence, as used in the meta description tag." + }, + "contact_email": { + "label": "Contact Email", + "msg": "Contact email cannot be empty.", + "text": "Email address of key contact responsible for this site." } }, "interface": { @@ -1004,7 +1035,7 @@ "msg": "Interface language cannot be empty.", "text": "User interface language. It will change when you refresh the page." }, - "timezone": { + "time_zone": { "label": "Timezone", "msg": "Timezone cannot be empty.", "text": "Choose a UTC (Coordinated Universal Time) time offset." diff --git a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx index 9e6c979e..af798c8e 100644 --- a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx +++ b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx @@ -1,6 +1,7 @@ import { FC } from 'react'; import { Card, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; import type * as Type from '@/common/interface'; @@ -38,9 +39,9 @@ const Statistics: FC = ({ data }) => { {t('flags')} {data.report_count} - + {t('review')} - + diff --git a/ui/src/pages/Admin/General/index.tsx b/ui/src/pages/Admin/General/index.tsx index 14b95978..cdce67bb 100644 --- a/ui/src/pages/Admin/General/index.tsx +++ b/ui/src/pages/Admin/General/index.tsx @@ -23,6 +23,11 @@ const General: FC = () => { isInvalid: false, errorMsg: '', }, + site_url: { + value: '', + isInvalid: false, + errorMsg: '', + }, short_description: { value: '', isInvalid: false, @@ -33,10 +38,15 @@ const General: FC = () => { isInvalid: false, errorMsg: '', }, + contact_email: { + value: '', + isInvalid: false, + errorMsg: '', + }, }); const checkValidated = (): boolean => { let ret = true; - const { name } = formData; + const { name, site_url, contact_email } = formData; if (!name.value) { ret = false; formData.name = { @@ -45,6 +55,22 @@ const General: FC = () => { errorMsg: t('name.msg'), }; } + if (!site_url.value) { + ret = false; + formData.site_url = { + value: '', + isInvalid: true, + errorMsg: t('site_url.msg'), + }; + } + if (!contact_email.value) { + ret = false; + formData.contact_email = { + value: '', + isInvalid: true, + errorMsg: t('contact_email.msg'), + }; + } setFormData({ ...formData, }); @@ -61,6 +87,8 @@ const General: FC = () => { name: formData.name.value, description: formData.description.value, short_description: formData.short_description.value, + site_url: formData.site_url.value, + contact_email: formData.contact_email.value, }; updateGeneralSetting(reqParams) @@ -100,7 +128,7 @@ const General: FC = () => { Object.keys(setting).forEach((k) => { formMeta[k] = { ...formData[k], value: setting[k] }; }); - setFormData(formMeta); + setFormData({ ...formData, ...formMeta }); }, [setting]); return ( <> @@ -120,6 +148,20 @@ const General: FC = () => { {formData.name.errorMsg} + + {t('site_url.label')} + onFieldChange('site_url', evt.target.value)} + /> + {t('site_url.text')} + + {formData.site_url.errorMsg} + + {t('short_description.label')} { {formData.description.errorMsg} + + {t('contact_email.label')} + onFieldChange('contact_email', evt.target.value)} + /> + {t('contact_email.text')} + + {formData.contact_email.errorMsg} + + + ); diff --git a/ui/src/pages/Install/components/FirstStep/index.tsx b/ui/src/pages/Install/components/FirstStep/index.tsx index 4f84e3b5..78b4aac8 100644 --- a/ui/src/pages/Install/components/FirstStep/index.tsx +++ b/ui/src/pages/Install/components/FirstStep/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import type { LangsType, FormValue, FormDataType } from '@/common/interface'; import Progress from '../Progress'; -import { getLanguageOptions } from '@/services'; +import { getInstallLangOptions } from '@/services'; interface Props { data: FormValue; @@ -18,8 +18,15 @@ const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { const [langs, setLangs] = useState(); const getLangs = async () => { - const res: LangsType[] = await getLanguageOptions(); + const res: LangsType[] = await getInstallLangOptions(); setLangs(res); + changeCallback({ + lang: { + value: res[0].value, + isInvalid: false, + errorMsg: '', + }, + }); }; const handleSubmit = () => { diff --git a/ui/src/pages/Install/components/FourthStep/index.tsx b/ui/src/pages/Install/components/FourthStep/index.tsx index ccd44be2..fae0c53c 100644 --- a/ui/src/pages/Install/components/FourthStep/index.tsx +++ b/ui/src/pages/Install/components/FourthStep/index.tsx @@ -18,6 +18,7 @@ const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { let bol = true; const { site_name, + site_url, contact_email, admin_name, admin_password, @@ -33,12 +34,40 @@ const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { }; } + if (!site_url.value) { + bol = false; + data.site_url = { + value: '', + isInvalid: true, + errorMsg: t('site_name.msg.empty'), + }; + } + const reg = /^(http|https):\/\//g; + if (site_url.value && !site_url.value.match(reg)) { + bol = false; + data.site_url = { + value: site_url.value, + isInvalid: true, + errorMsg: t('site_url.msg.incorrect'), + }; + } + if (!contact_email.value) { bol = false; data.contact_email = { value: '', isInvalid: true, - errorMsg: t('contact_email.msg'), + errorMsg: t('contact_email.msg.empty'), + }; + } + + const mailReg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; + if (contact_email.value && !contact_email.value.match(mailReg)) { + bol = false; + data.contact_email = { + value: contact_email.value, + isInvalid: true, + errorMsg: t('contact_email.msg.incorrect'), }; } @@ -65,7 +94,16 @@ const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { data.admin_email = { value: '', isInvalid: true, - errorMsg: t('admin_email.msg'), + errorMsg: t('admin_email.msg.empty'), + }; + } + + if (admin_email.value && !admin_email.value.match(mailReg)) { + bol = false; + data.admin_email = { + value: '', + isInvalid: true, + errorMsg: t('admin_email.msg.incorrect'), }; } @@ -108,6 +146,27 @@ const Index: FC = ({ visible, data, changeCallback, nextCallback }) => { {data.site_name.errorMsg} + + {t('site_url.label')} + { + changeCallback({ + site_url: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }); + }} + /> + {t('site_url.text')} + + {data.site_url.errorMsg} + + {t('contact_email.label')} void; } -const Index: FC = ({ visible, nextCallback }) => { +const Index: FC = ({ visible, errorMsg, nextCallback }) => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); if (!visible) return null; return (
{t('config_yaml.title')}
-
{t('config_yaml.label')}
-
-

- }} - /> -

-
- - - -
{t('config_yaml.info')}
+ + {errorMsg?.msg?.length > 0 ? ( + <> +
+

+ }} + /> +

+
+ + + +
{t('config_yaml.info')}
+ + ) : ( +
{t('config_yaml.label')}
+ )} +
diff --git a/ui/src/pages/Install/index.tsx b/ui/src/pages/Install/index.tsx index e8c40d5a..287f4f11 100644 --- a/ui/src/pages/Install/index.tsx +++ b/ui/src/pages/Install/index.tsx @@ -3,8 +3,13 @@ import { Container, Row, Col, Card, Alert } from 'react-bootstrap'; import { useTranslation, Trans } from 'react-i18next'; import type { FormDataType } from '@/common/interface'; -import { Storage } from '@/utils'; import { PageTitle } from '@/components'; +import { + dbCheck, + installInit, + installBaseInfo, + checkConfigFileExists, +} from '@/services'; import { FirstStep, @@ -17,7 +22,11 @@ import { const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'install' }); const [step, setStep] = useState(1); - const [showError] = useState(false); + const [loading, setLoading] = useState(true); + const [errorData, setErrorData] = useState<{ [propName: string]: any }>({ + msg: '', + }); + const [tableExist, setTableExist] = useState(false); const [formData, setFormData] = useState({ lang: { @@ -26,7 +35,7 @@ const Index: FC = () => { errorMsg: '', }, db_type: { - value: '', + value: 'mysql', isInvalid: false, errorMsg: '', }, @@ -55,12 +64,16 @@ const Index: FC = () => { isInvalid: false, errorMsg: '', }, - site_name: { value: '', isInvalid: false, errorMsg: '', }, + site_url: { + value: '', + isInvalid: false, + errorMsg: '', + }, contact_email: { value: '', isInvalid: false, @@ -88,33 +101,107 @@ const Index: FC = () => { setFormData({ ...formData, ...params }); }; - const handleStep = () => { + const handleErr = (data) => { + window.scrollTo(0, 0); + setErrorData(data); + }; + + const handleNext = async () => { + setErrorData({ + msg: '', + }); setStep((pre) => pre + 1); }; - // const handleSubmit = () => { - // const params = { - // lang: formData.lang.value, - // db_type: formData.db_type.value, - // db_username: formData.db_username.value, - // db_password: formData.db_password.value, - // db_host: formData.db_host.value, - // db_name: formData.db_name.value, - // db_file: formData.db_file.value, - // site_name: formData.site_name.value, - // contact_email: formData.contact_email.value, - // admin_name: formData.admin_name.value, - // admin_password: formData.admin_password.value, - // admin_email: formData.admin_email.value, - // }; + const submitDatabaseForm = () => { + const params = { + lang: formData.lang.value, + db_type: formData.db_type.value, + db_username: formData.db_username.value, + db_password: formData.db_password.value, + db_host: formData.db_host.value, + db_name: formData.db_name.value, + db_file: formData.db_file.value, + }; + dbCheck(params) + .then(() => { + handleNext(); + }) + .catch((err) => { + console.log(err); + handleErr(err); + }); + }; - // console.log(params); - // }; + const checkInstall = () => { + installInit() + .then(() => { + handleNext(); + }) + .catch((err) => { + handleErr(err); + }); + }; + + const submitSiteConfig = () => { + const params = { + site_name: formData.site_name.value, + contact_email: formData.contact_email.value, + admin_name: formData.admin_name.value, + admin_password: formData.admin_password.value, + admin_email: formData.admin_email.value, + }; + installBaseInfo(params) + .then(() => { + handleNext(); + }) + .catch((err) => { + handleErr(err); + }); + }; + + const handleStep = () => { + if (step === 2) { + submitDatabaseForm(); + } else if (step === 3) { + checkInstall(); + } else if (step === 4) { + submitSiteConfig(); + } else { + handleNext(); + } + }; + + const handleInstallNow = (e) => { + e.preventDefault(); + if (tableExist) { + setStep(7); + } else { + setStep(4); + } + }; + + const configYmlCheck = () => { + checkConfigFileExists() + .then((res) => { + setTableExist(res?.db_table_exist); + if (res && res.config_file_exist) { + setStep(5); + } + }) + .finally(() => { + setLoading(false); + }); + }; useEffect(() => { - console.log('step===', Storage.get('INSTALL_STEP')); + configYmlCheck(); }, []); + if (loading) { + return
; + } + return (
@@ -124,7 +211,9 @@ const Index: FC = () => {

{t('title')}

- {showError && show error msg } + {errorData?.msg && ( + {errorData?.msg} + )} { nextCallback={handleStep} /> - + { nextCallback={handleStep} /> - + {step === 6 && (
{t('warning')}
@@ -158,7 +251,10 @@ const Index: FC = () => { The file config.yaml already exists. If you need to reset any of the configuration items in this file, please delete it first. You may try{' '} - installing now. + handleInstallNow(e)}> + installing now + + .

diff --git a/ui/src/pages/Upgrade/index.tsx b/ui/src/pages/Upgrade/index.tsx index c65c9098..c264e46c 100644 --- a/ui/src/pages/Upgrade/index.tsx +++ b/ui/src/pages/Upgrade/index.tsx @@ -1,17 +1,20 @@ import { useState } from 'react'; -import { Container, Row, Col, Card, Button } from 'react-bootstrap'; +import { Container, Row, Col, Card, Button, Spinner } from 'react-bootstrap'; import { useTranslation, Trans } from 'react-i18next'; import { PageTitle } from '@/components'; +import { upgradSystem } from '@/services'; const Index = () => { const { t } = useTranslation('translation', { keyPrefix: 'upgrade', }); - const [step, setStep] = useState(1); + const [step] = useState(1); + const [loading, setLoading] = useState(false); - const handleUpdate = () => { - setStep(2); + const handleUpdate = async () => { + await upgradSystem(); + setLoading(true); }; return (
@@ -29,9 +32,22 @@ const Index = () => { i18nKey="upgrade.update_description" components={{ 1:

}} /> - + {loading ? ( + + ) : ( + + )} )} @@ -39,7 +55,9 @@ const Index = () => { <>

{t('done_title')}

{t('done_desscription')}

- + )} diff --git a/ui/src/services/common.ts b/ui/src/services/common.ts index 1ddc7f18..7b4db720 100644 --- a/ui/src/services/common.ts +++ b/ui/src/services/common.ts @@ -248,3 +248,7 @@ export const changeEmailVerify = (params: { code: string }) => { export const getAppSettings = () => { return request.get('/answer/api/v1/siteinfo'); }; + +export const upgradSystem = () => { + return request.post('/answer/api/v1/upgradation'); +}; diff --git a/ui/src/services/index.ts b/ui/src/services/index.ts index a6923bcf..a3e936ec 100644 --- a/ui/src/services/index.ts +++ b/ui/src/services/index.ts @@ -1,3 +1,4 @@ export * from './admin'; export * from './common'; export * from './client'; +export * from './install'; diff --git a/ui/src/services/install/index.ts b/ui/src/services/install/index.ts new file mode 100644 index 00000000..31d50690 --- /dev/null +++ b/ui/src/services/install/index.ts @@ -0,0 +1,21 @@ +import request from '@/utils/request'; + +export const checkConfigFileExists = () => { + return request.post('/installation/config-file/check'); +}; + +export const dbCheck = (params) => { + return request.post('/installation/db/check', params); +}; + +export const installInit = () => { + return request.post('/installation/init'); +}; + +export const installBaseInfo = (params) => { + return request.post('/installation/base-info', params); +}; + +export const getInstallLangOptions = () => { + return request.get('/installation/language/options'); +}; diff --git a/ui/src/utils/request.ts b/ui/src/utils/request.ts index 4e878cb3..8167448d 100644 --- a/ui/src/utils/request.ts +++ b/ui/src/utils/request.ts @@ -73,7 +73,14 @@ class Request { }); } - if (data.type === 'modal') { + if (data.err_type === 'alert') { + return Promise.reject({ + msg, + ...data, + }); + } + + if (data.err_type === 'modal') { // modal error message Modal.confirm({ content: msg,