1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-08 09:30:06 +08:00

feat: Update Login Page (#7815)

This commit is contained in:
zhengkunwang 2025-02-07 18:15:20 +08:00 committed by GitHub
parent b2fa0aa2d4
commit 88684c7cf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 284 additions and 227 deletions

View File

@ -2,7 +2,6 @@ package v2
import ( import (
"encoding/base64" "encoding/base64"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper" "github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model" "github.com/1Panel-dev/1Panel/core/app/model"
@ -28,8 +27,8 @@ func (b *BaseApi) Login(c *gin.Context) {
} }
if req.AuthMethod != "jwt" && !req.IgnoreCaptcha { if req.AuthMethod != "jwt" && !req.IgnoreCaptcha {
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil { if errMsg := captcha.VerifyCode(req.CaptchaID, req.Captcha); errMsg != "" {
helper.InternalServer(c, err) helper.BadAuth(c, errMsg, nil)
return return
} }
} }
@ -39,8 +38,12 @@ func (b *BaseApi) Login(c *gin.Context) {
entrance, _ = base64.StdEncoding.DecodeString(entranceItem) entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
} }
user, err := authService.Login(c, req, string(entrance)) user, msgKey, err := authService.Login(c, req, string(entrance))
go saveLoginLogs(c, err) go saveLoginLogs(c, err)
if msgKey == "ErrAuth" {
helper.BadAuth(c, msgKey, err)
return
}
if err != nil { if err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
@ -67,7 +70,11 @@ func (b *BaseApi) MFALogin(c *gin.Context) {
entrance, _ = base64.StdEncoding.DecodeString(entranceItem) entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
} }
user, err := authService.MFALogin(c, req, string(entrance)) user, msgKey, err := authService.MFALogin(c, req, string(entrance))
if msgKey == "ErrAuth" {
helper.BadAuth(c, msgKey, err)
return
}
if err != nil { if err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
@ -141,16 +148,7 @@ func saveLoginLogs(c *gin.Context, err error) {
logs.Status = constant.StatusSuccess logs.Status = constant.StatusSuccess
} }
logs.IP = c.ClientIP() logs.IP = c.ClientIP()
//lang := c.GetHeader("Accept-Language")
//if lang == "" {
// lang = "zh"
//}
//address, err := geo.GetIPLocation(logs.IP, lang)
//if err != nil {
// global.LOG.Errorf("get ip location failed: %s", err)
//}
logs.Agent = c.GetHeader("User-Agent") logs.Agent = c.GetHeader("User-Agent")
//logs.Address = address
_ = logService.CreateLoginLog(logs) _ = logService.CreateLoginLog(logs)
} }

View File

@ -15,7 +15,7 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
Message: "", Message: "",
} }
if msgKey == "ErrCaptchaCode" || msgKey == "ErrAuth" { if msgKey == "ErrCaptchaCode" || msgKey == "ErrAuth" {
res.Code = 406 res.Code = 401
res.Message = msgKey res.Message = msgKey
} }
res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err}) res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err})

View File

@ -13,6 +13,7 @@ type ISettingRepo interface {
GetValueByKey(key string) (string, error) GetValueByKey(key string) (string, error)
Create(key, value string) error Create(key, value string) error
Update(key, value string) error Update(key, value string) error
UpdateOrCreate(key, value string) error
} }
func NewISettingRepo() ISettingRepo { func NewISettingRepo() ISettingRepo {
@ -58,3 +59,7 @@ func (u *SettingRepo) GetValueByKey(key string) (string, error) {
func (u *SettingRepo) Update(key, value string) error { func (u *SettingRepo) Update(key, value string) error {
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error
} }
func (u *SettingRepo) UpdateOrCreate(key, value string) error {
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Assign(model.Setting{Key: key, Value: value}).FirstOrCreate(&model.Setting{}).Error
}

View File

@ -21,9 +21,9 @@ type IAuthService interface {
CheckIsSafety(code string) (string, error) CheckIsSafety(code string) (string, error)
GetResponsePage() (string, error) GetResponsePage() (string, error)
VerifyCode(code string) (bool, error) VerifyCode(code string) (bool, error)
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error)
LogOut(c *gin.Context) error LogOut(c *gin.Context) error
MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error)
GetSecurityEntrance() string GetSecurityEntrance() string
IsLogin(c *gin.Context) bool IsLogin(c *gin.Context) bool
} }
@ -32,79 +32,86 @@ func NewIAuthService() IAuthService {
return &AuthService{} return &AuthService{}
} }
func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error) { func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error) {
nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) nameSetting, err := settingRepo.Get(repo.WithByKey("UserName"))
if err != nil { if err != nil {
return nil, buserr.New("ErrRecordNotFound") return nil, "", buserr.New("ErrRecordNotFound")
} }
passwordSetting, err := settingRepo.Get(repo.WithByKey("Password")) passwordSetting, err := settingRepo.Get(repo.WithByKey("Password"))
if err != nil { if err != nil {
return nil, buserr.New("ErrRecordNotFound") return nil, "", buserr.New("ErrRecordNotFound")
} }
pass, err := encrypt.StringDecrypt(passwordSetting.Value) pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil { if err != nil {
return nil, buserr.New("ErrAuth") return nil, "ErrAuth", nil
} }
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name { if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, buserr.New("ErrAuth") return nil, "ErrAuth", nil
} }
entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance"))
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return nil, buserr.New("ErrEntrance") return nil, "ErrEntrance", nil
} }
mfa, err := settingRepo.Get(repo.WithByKey("MFAStatus")) mfa, err := settingRepo.Get(repo.WithByKey("MFAStatus"))
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if err = settingRepo.Update("Language", info.Language); err != nil { if err = settingRepo.Update("Language", info.Language); err != nil {
return nil, err return nil, "", err
} }
if mfa.Value == constant.StatusEnable { if mfa.Value == constant.StatusEnable {
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, nil return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, "", nil
} }
return u.generateSession(c, info.Name, info.AuthMethod) res, err := u.generateSession(c, info.Name, info.AuthMethod)
if err != nil {
return nil, "", err
}
return res, "", nil
} }
func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error) { func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error) {
nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) nameSetting, err := settingRepo.Get(repo.WithByKey("UserName"))
if err != nil { if err != nil {
return nil, buserr.New("ErrRecordNotFound") return nil, "", buserr.New("ErrRecordNotFound")
} }
passwordSetting, err := settingRepo.Get(repo.WithByKey("Password")) passwordSetting, err := settingRepo.Get(repo.WithByKey("Password"))
if err != nil { if err != nil {
return nil, buserr.New("ErrRecordNotFound") return nil, "", buserr.New("ErrRecordNotFound")
} }
pass, err := encrypt.StringDecrypt(passwordSetting.Value) pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name { if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, buserr.New("ErrAuth") return nil, "ErrAuth", nil
} }
entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance"))
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return nil, buserr.New("ErrEntrance") return nil, "", buserr.New("ErrEntrance")
} }
mfaSecret, err := settingRepo.Get(repo.WithByKey("MFASecret")) mfaSecret, err := settingRepo.Get(repo.WithByKey("MFASecret"))
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
mfaInterval, err := settingRepo.Get(repo.WithByKey("MFAInterval")) mfaInterval, err := settingRepo.Get(repo.WithByKey("MFAInterval"))
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value) success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value)
if !success { if !success {
return nil, buserr.New("ErrAuth") return nil, "ErrAuth", nil
} }
res, err := u.generateSession(c, info.Name, info.AuthMethod)
return u.generateSession(c, info.Name, info.AuthMethod) if err != nil {
return nil, "", err
}
return res, "", nil
} }
func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (*dto.UserLoginInfo, error) { func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (*dto.UserLoginInfo, error) {

View File

@ -4,24 +4,23 @@ import (
"strings" "strings"
"github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/mojocn/base64Captcha" "github.com/mojocn/base64Captcha"
) )
var store = base64Captcha.DefaultMemStore var store = base64Captcha.DefaultMemStore
func VerifyCode(codeID string, code string) error { func VerifyCode(codeID string, code string) string {
if codeID == "" { if codeID == "" {
return buserr.New("ErrCaptchaCode") return "ErrCaptchaCode"
} }
vv := store.Get(codeID, true) vv := store.Get(codeID, true)
vv = strings.TrimSpace(vv) vv = strings.TrimSpace(vv)
code = strings.TrimSpace(code) code = strings.TrimSpace(code)
if strings.EqualFold(vv, code) { if strings.EqualFold(vv, code) {
return nil return ""
} }
return buserr.New("ErrCaptchaCode") return "ErrCaptchaCode"
} }
func CreateCaptcha() (*dto.CaptchaResponse, error) { func CreateCaptcha() (*dto.CaptchaResponse, error) {

View File

@ -1,5 +1,5 @@
<template> <template>
<el-drawer v-model="localOpenPage" :destroy-on-close="true" :size="size"> <el-drawer v-model="localOpenPage" :destroy-on-close="true" :size="size" :close-on-press-escape="true">
<template #header> <template #header>
<el-page-header @back="handleBack"> <el-page-header @back="handleBack">
<template #content> <template #content>

View File

@ -177,8 +177,7 @@ const message = {
mfaTitle: 'MFA Certification', mfaTitle: 'MFA Certification',
mfaCode: 'MFA verification code', mfaCode: 'MFA verification code',
title: 'Linux Server Management Panel', title: 'Linux Server Management Panel',
licenseHelper: licenseHelper: '<Community License Agreement>',
'Agree &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank">Community License Agreement</a> &raquo;',
errorAgree: 'Click to agree to the Community Software License', errorAgree: 'Click to agree to the Community Software License',
logout: 'Logout', logout: 'Logout',
agreeTitle: 'Agreement', agreeTitle: 'Agreement',

View File

@ -167,8 +167,7 @@ const message = {
mfaTitle: 'MFA認定', mfaTitle: 'MFA認定',
mfaCode: 'MFA検証コード', mfaCode: 'MFA検証コード',
title: 'Linuxサーバー管理パネル', title: 'Linuxサーバー管理パネル',
licenseHelper: licenseHelper: '<コミュニティライセンス契約>',
'同意laquo;<a href = "https://www.fit2cloud.com/legal/licenses.html" target="_blank">コミュニティライセンス契約</a>raquo;',
errorAgree: 'クリックしてコミュニティソフトウェアライセンスに同意します', errorAgree: 'クリックしてコミュニティソフトウェアライセンスに同意します',
logout: 'ログアウト', logout: 'ログアウト',
agreeTitle: '合意', agreeTitle: '合意',

View File

@ -166,8 +166,7 @@ const message = {
mfaTitle: 'MFA 인증', mfaTitle: 'MFA 인증',
mfaCode: 'MFA 인증 코드', mfaCode: 'MFA 인증 코드',
title: 'Linux 서버 관리 패널', title: 'Linux 서버 관리 패널',
licenseHelper: licenseHelper: '<커뮤니티 라이선스 계약>',
'&laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank">커뮤니티 라이선스 계약</a> &raquo; 동의합니다',
errorAgree: '커뮤니티 소프트웨어 라이선스에 동의하려면 클릭하세요', errorAgree: '커뮤니티 소프트웨어 라이선스에 동의하려면 클릭하세요',
logout: '로그아웃', logout: '로그아웃',
agreeTitle: '동의', agreeTitle: '동의',

View File

@ -169,8 +169,7 @@ const message = {
mfaTitle: 'Pengesahan MFA', mfaTitle: 'Pengesahan MFA',
mfaCode: 'Kod pengesahan MFA', mfaCode: 'Kod pengesahan MFA',
title: 'Panel Pengurusan Pelayan Linux', title: 'Panel Pengurusan Pelayan Linux',
licenseHelper: licenseHelper: '<Perjanjian Lesen Komuniti>',
'Setuju &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank">Perjanjian Lesen Komuniti</a> &raquo;',
errorAgree: 'Klik untuk bersetuju dengan Lesen Perisian Komuniti', errorAgree: 'Klik untuk bersetuju dengan Lesen Perisian Komuniti',
logout: 'Log keluar', logout: 'Log keluar',
agreeTitle: 'Agreement', agreeTitle: 'Agreement',

View File

@ -168,8 +168,7 @@ const message = {
mfaTitle: 'Autenticação MFA', mfaTitle: 'Autenticação MFA',
mfaCode: 'Código de verificação MFA', mfaCode: 'Código de verificação MFA',
title: 'Painel de Gerenciamento de Servidores Linux', title: 'Painel de Gerenciamento de Servidores Linux',
licenseHelper: licenseHelper: '<Acordo de Licença Comunitária>',
'Concordar com &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank">Acordo de Licença Comunitária</a> &raquo;',
errorAgree: 'Clique para concordar com o Acordo de Licença de Software Comunitário', errorAgree: 'Clique para concordar com o Acordo de Licença de Software Comunitário',
logout: 'Sair', logout: 'Sair',
agreeTitle: 'Termo de Aceite', agreeTitle: 'Termo de Aceite',

View File

@ -167,8 +167,7 @@ const message = {
mfaTitle: 'MFA Сертификация', mfaTitle: 'MFA Сертификация',
mfaCode: 'MFA код подтверждения', mfaCode: 'MFA код подтверждения',
title: 'Панель управления Linux сервером', title: 'Панель управления Linux сервером',
licenseHelper: licenseHelper: '<Лицензионное соглашение сообщества>',
'Согласен &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank">Лицензионное соглашение сообщества</a> &raquo;',
errorAgree: 'Нажмите, чтобы согласиться с Лицензией программного обеспечения сообщества', errorAgree: 'Нажмите, чтобы согласиться с Лицензией программного обеспечения сообщества',
logout: 'Выход', logout: 'Выход',
agreeTitle: 'Соглашение', agreeTitle: 'Соглашение',

View File

@ -175,8 +175,7 @@ const message = {
mfaTitle: 'MFA 認證', mfaTitle: 'MFA 認證',
mfaCode: 'MFA 驗證碼', mfaCode: 'MFA 驗證碼',
title: 'Linux 服務器運維管理面板', title: 'Linux 服務器運維管理面板',
licenseHelper: licenseHelper: '飛致雲社區軟件許可協議',
'同意 &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank"> 飛致雲社區軟件許可協議</a> &raquo;',
errorAgree: '請點擊同意社區軟件許可協議', errorAgree: '請點擊同意社區軟件許可協議',
agreeTitle: '服務協議及隱私保護', agreeTitle: '服務協議及隱私保護',
agreeContent: agreeContent:

View File

@ -175,8 +175,7 @@ const message = {
mfaTitle: 'MFA 认证', mfaTitle: 'MFA 认证',
mfaCode: 'MFA 验证码', mfaCode: 'MFA 验证码',
title: 'Linux 服务器运维管理面板', title: 'Linux 服务器运维管理面板',
licenseHelper: licenseHelper: '飞致云社区软件许可协议',
'同意 &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank"> 飞致云社区软件许可协议</a> &raquo;',
errorAgree: '请点击同意社区软件许可协议', errorAgree: '请点击同意社区软件许可协议',
agreeTitle: '服务协议及隐私保护', agreeTitle: '服务协议及隐私保护',
agreeContent: agreeContent:

View File

@ -386,7 +386,7 @@ import AppIgnore from './ignore/index.vue';
import ComposeLogs from '@/components/compose-log/index.vue'; import ComposeLogs from '@/components/compose-log/index.vue';
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import Status from '@/components/status/index.vue'; import Status from '@/components/status/index.vue';
import { getAge, getLanguage } from '@/utils/util'; import { getAge } from '@/utils/util';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { toFolder } from '@/global/business'; import { toFolder } from '@/global/business';
@ -432,7 +432,6 @@ const router = useRouter();
const activeName = ref(i18n.global.t('app.installed')); const activeName = ref(i18n.global.t('app.installed'));
const mode = ref('installed'); const mode = ref('installed');
const moreTag = ref(''); const moreTag = ref('');
const language = getLanguage();
const defaultLink = ref(''); const defaultLink = ref('');
const options = { const options = {

View File

@ -185,13 +185,11 @@ import { changeLauncherStatus, loadAppLauncher, loadAppLauncherOption } from '@/
import i18n from '@/lang'; import i18n from '@/lang';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { getLanguage } from '@/utils/util';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { toFolder } from '@/global/business'; import { toFolder } from '@/global/business';
const router = useRouter(); const router = useRouter();
const language = getLanguage();
const globalStore = GlobalStore(); const globalStore = GlobalStore();
let loading = ref(false); let loading = ref(false);

View File

@ -1,50 +1,49 @@
<template> <template>
<div v-loading="loading"> <div class="w-full h-full flex items-center justify-center px-8 py-6">
<div v-if="mfaShow"> <div v-loading="loading" class="w-full flex-grow flex flex-col">
<div class="login-form"> <div v-if="mfaShow">
<el-form @submit.prevent> <el-form @submit.prevent>
<div class="login-title">{{ $t('commons.login.mfaTitle') }}</div> <div class="flex flex-col justify-center items-center mb-6">
<el-form-item class="no-border"> <div class="text-2xl font-medium text-gray-900 text-center">
<el-input {{ $t('commons.login.mfaTitle') }}
size="default" </div>
:placeholder="$t('commons.login.mfaCode')" </div>
v-model.trim="mfaLoginForm.code"
@input="mfaLogin(true)" <div class="space-y-6 flex-grow">
> <el-form-item>
<template #prefix> <el-input
<el-icon class="el-input__icon"> size="large"
<Finished /> :placeholder="$t('commons.login.mfaCode')"
</el-icon> v-model.trim="mfaLoginForm.code"
</template> @input="mfaLogin(true)"
</el-input> ></el-input>
<span v-if="errMfaInfo" class="input-error" style="line-height: 14px"> <div class="h-1">
{{ $t('commons.login.errorMfaInfo') }} <span v-if="errMfaInfo" class="input-error">
</span> {{ $t('commons.login.errorMfaInfo') }}
</el-form-item> </span>
<el-form-item> </div>
<el-button </el-form-item>
@focus="mfaButtonFocused = true" <el-form-item>
@blur="mfaButtonFocused = false" <el-button
class="login-button" @focus="mfaButtonFocused = true"
type="primary" @blur="mfaButtonFocused = false"
size="default" class="w-full"
round type="primary"
@click="mfaLogin(false)" @click="mfaLogin(false)"
> >
{{ $t('commons.button.verify') }} {{ $t('commons.button.verify') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</div>
</el-form> </el-form>
</div> </div>
</div> <div v-else>
<div v-else>
<div class="login-form">
<el-form ref="loginFormRef" :model="loginForm" size="default" :rules="loginRules"> <el-form ref="loginFormRef" :model="loginForm" size="default" :rules="loginRules">
<div class="login-form-header"> <div class="flex justify-between items-center mb-6">
<div class="title cursor-pointer">{{ $t('commons.button.login') }}</div> <div class="text-2xl font-medium text-gray-900">{{ $t('commons.button.login') }}</div>
<div class="cursor-pointer"> <div class="cursor-pointer">
<el-dropdown @command="handleCommand"> <el-dropdown @command="handleCommand">
<span> <span class="flex items-center space-x-1">
{{ dropdownText }} {{ dropdownText }}
<el-icon> <el-icon>
<arrow-down /> <arrow-down />
@ -70,75 +69,85 @@
</el-dropdown> </el-dropdown>
</div> </div>
</div> </div>
<el-form-item prop="name" class="no-border"> <div class="space-y-6 flex-grow">
<el-input <el-form-item prop="name" class="w-full">
v-model.trim="loginForm.name" <el-input
:placeholder="$t('commons.login.username')" v-model.trim="loginForm.name"
class="form-input" :placeholder="$t('commons.login.username')"
> class="w-full"
<template #prefix> size="large"
<el-icon class="el-input__icon"> ></el-input>
<user />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password" class="no-border">
<el-input
type="password"
clearable
v-model.trim="loginForm.password"
show-password
:placeholder="$t('commons.login.password')"
>
<template #prefix>
<el-icon class="el-input__icon">
<lock />
</el-icon>
</template>
</el-input>
<span v-if="errAuthInfo" class="input-error" style="line-height: 14px">
{{ $t('commons.login.errorAuthInfo') }}
</span>
</el-form-item>
<el-form-item v-if="!globalStore.ignoreCaptcha" prop="captcha" class="login-captcha">
<el-input v-model.trim="loginForm.captcha" :placeholder="$t('commons.login.captchaHelper')">
<template #prefix>
<svg-icon style="font-size: 7px" iconName="p-yanzhengma1"></svg-icon>
</template>
</el-input>
<img
v-if="captcha.imagePath"
:src="captcha.imagePath"
:alt="$t('commons.login.captchaHelper')"
@click="loginVerify()"
/>
<span v-if="errCaptcha" class="input-error" style="line-height: 14px">
{{ $t('commons.login.errorCaptcha') }}
</span>
</el-form-item>
<el-form-item>
<el-button
@click="login(loginFormRef)"
@focus="loginButtonFocused = true"
@blur="loginButtonFocused = false"
class="login-button"
type="primary"
size="default"
round
>
{{ $t('commons.button.login') }}
</el-button>
</el-form-item>
<template v-if="!isIntl">
<el-form-item prop="agreeLicense">
<el-checkbox v-model="loginForm.agreeLicense">
<template #default>
<span class="agree" v-html="$t('commons.login.licenseHelper')"></span>
</template>
</el-checkbox>
</el-form-item> </el-form-item>
</template> <el-form-item prop="password" class="w-full">
<el-input
type="password"
v-model.trim="loginForm.password"
class="w-full"
size="large"
:placeholder="$t('commons.login.password')"
></el-input>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12" v-if="!globalStore.ignoreCaptcha">
<el-form-item prop="captcha">
<el-input
v-model.trim="loginForm.captcha"
size="large"
:placeholder="$t('commons.login.captchaHelper')"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12" v-if="!globalStore.ignoreCaptcha">
<img
class="w-full h-10"
v-if="captcha.imagePath"
:src="captcha.imagePath"
:alt="$t('commons.login.captchaHelper')"
@click="loginVerify()"
/>
</el-col>
<el-col :span="24" class="h-1">
<span v-show="errCaptcha" class="input-error">
{{ $t('commons.login.errorCaptcha') }}
</span>
<span v-show="errAuthInfo" class="input-error">
{{ $t('commons.login.errorAuthInfo') }}
</span>
</el-col>
</el-row>
<el-form-item>
<el-button
@click="login(loginFormRef)"
@focus="loginButtonFocused = true"
@blur="loginButtonFocused = false"
class="w-full"
type="primary"
size="default"
>
{{ $t('commons.button.login') }}
</el-button>
</el-form-item>
<template v-if="!isIntl">
<el-form-item prop="agreeLicense">
<el-checkbox v-model="loginForm.agreeLicense">
<template #default>
<span>
{{ $t('commons.button.agree') }}
<a
class="agree"
href="https://www.fit2cloud.com/legal/licenses.html"
target="_blank"
>
{{ $t('commons.login.licenseHelper') }}
</a>
</span>
</template>
</el-checkbox>
</el-form-item>
</template>
</div>
</el-form> </el-form>
<div class="demo"> <div class="demo">
<span v-if="isDemo"> <span v-if="isDemo">
@ -146,28 +155,28 @@
</span> </span>
</div> </div>
</div> </div>
</div>
<DialogPro v-model="open" center size="w-90"> <DialogPro v-model="open" center size="w-90">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<span class="text-base mb-4"> <span class="text-base mb-4">
{{ $t('commons.login.agreeTitle') }} {{ $t('commons.login.agreeTitle') }}
</span> </span>
</el-row> </el-row>
<div> <div>
<span v-html="$t('commons.login.agreeContent')"></span> <span v-html="$t('commons.login.agreeContent')"></span>
</div> </div>
<template #footer> <template #footer>
<span class="dialog-footer login-footer-btn"> <span class="dialog-footer login-footer-btn">
<el-button @click="open = false"> <el-button @click="open = false">
{{ $t('commons.button.notAgree') }} {{ $t('commons.button.notAgree') }}
</el-button> </el-button>
<el-button type="primary" @click="agreeWithLogin()"> <el-button type="primary" @click="agreeWithLogin()">
{{ $t('commons.button.agree') }} {{ $t('commons.button.agree') }}
</el-button> </el-button>
</span> </span>
</template> </template>
</DialogPro> </DialogPro>
</div>
</div> </div>
</template> </template>
@ -319,22 +328,8 @@ const login = (formEl: FormInstance | undefined) => {
isLoggingIn = true; isLoggingIn = true;
loading.value = true; loading.value = true;
const res = await loginApi(requestLoginForm); const res = await loginApi(requestLoginForm);
if (res.code === 406) {
if (res.message === 'ErrCaptchaCode') {
loginForm.captcha = '';
errCaptcha.value = true;
errAuthInfo.value = false;
}
if (res.message === 'ErrAuth') {
globalStore.ignoreCaptcha = false;
errCaptcha.value = false;
errAuthInfo.value = true;
}
loginVerify();
return;
}
globalStore.ignoreCaptcha = true; globalStore.ignoreCaptcha = true;
if (res.data.mfaStatus === 'enable') { if (res.data.mfaStatus === 'Enable') {
mfaShow.value = true; mfaShow.value = true;
errMfaInfo.value = false; errMfaInfo.value = false;
return; return;
@ -346,7 +341,20 @@ const login = (formEl: FormInstance | undefined) => {
MsgSuccess(i18n.t('commons.msg.loginSuccess')); MsgSuccess(i18n.t('commons.msg.loginSuccess'));
loadDataFromDB(); loadDataFromDB();
router.push({ name: 'home' }); router.push({ name: 'home' });
} catch (error) { } catch (res) {
if (res.code === 401) {
if (res.message === 'ErrCaptchaCode') {
loginForm.captcha = '';
errCaptcha.value = true;
errAuthInfo.value = false;
}
if (res.message === 'ErrAuth') {
globalStore.ignoreCaptcha = false;
errCaptcha.value = false;
errAuthInfo.value = true;
console.log('11111');
}
}
loginVerify(); loginVerify();
} finally { } finally {
isLoggingIn = false; isLoggingIn = false;
@ -361,18 +369,23 @@ const mfaLogin = async (auto: boolean) => {
isLoggingIn = true; isLoggingIn = true;
mfaLoginForm.name = loginForm.name; mfaLoginForm.name = loginForm.name;
mfaLoginForm.password = loginForm.password; mfaLoginForm.password = loginForm.password;
const res = await mfaLoginApi(mfaLoginForm); try {
if (res.code === 406) { await mfaLoginApi(mfaLoginForm);
errMfaInfo.value = true; globalStore.setLogStatus(true);
menuStore.setMenuList([]);
tabsStore.removeAllTabs();
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
loadDataFromDB();
router.push({ name: 'home' });
} catch (res) {
if (res.code === 401) {
errMfaInfo.value = true;
isLoggingIn = false;
return;
}
} finally {
isLoggingIn = false; isLoggingIn = false;
return;
} }
globalStore.setLogStatus(true);
menuStore.setMenuList([]);
tabsStore.removeAllTabs();
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
loadDataFromDB();
router.push({ name: 'home' });
} }
}; };
const loginVerify = async () => { const loginVerify = async () => {
@ -430,9 +443,28 @@ onMounted(() => {
}; };
}); });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.login-form { .agree {
text-decoration: none;
}
.agree:hover {
text-decoration: underline;
}
:deep(.el-button) {
height: 2.5rem;
}
:deep(.el-input__inner) {
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
transition: background-color 50000s ease-in-out 0s;
}
:deep(.el-row) {
padding: 0 !important;
}
</style>
<!-- <style scoped lang='scss' > .login-form {
padding: 0 40px; padding: 0 40px;
.hide { .hide {
width: 0; width: 0;
@ -601,3 +633,4 @@ onMounted(() => {
} }
} }
</style> </style>
-->

View File

@ -1,5 +1,25 @@
<template> <template>
<div> <div class="flex items-center justify-center min-h-screen relative bg-gray-100">
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
:style="{ backgroundImage: `url(${backgroundImage})` }"
></div>
<div
:style="{ opacity: backgroundOpacity }"
class="w-[45%] min-h-[480px] bg-white rounded-lg shadow-lg relative z-10 border border-gray-200 flex overflow-hidden"
>
<div class="grid md:grid-cols-2 gap-4 items-stretch w-full">
<div class="flex flex-col justify-center items-center w-full p-4">
<img :src="logoImage" class="max-w-full max-h-full object-contain" />
</div>
<div class="hidden md:block w-px bg-gray-200 absolute left-1/2 top-4 bottom-4"></div>
<div class="hidden md:flex items-center justify-center p-4">
<LoginForm ref="loginRef"></LoginForm>
</div>
</div>
</div>
</div>
<!-- <div>
<div class="login-background" v-loading="loading"> <div class="login-background" v-loading="loading">
<div class="login-wrapper"> <div class="login-wrapper">
<div :class="screenWidth > 1110 ? 'left inline-block' : ''"> <div :class="screenWidth > 1110 ? 'left inline-block' : ''">
@ -15,7 +35,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
</template> </template>
<script setup lang="ts" name="login"> <script setup lang="ts" name="login">
@ -28,6 +48,9 @@ import { getXpackSettingForTheme } from '@/utils/xpack';
const gStore = GlobalStore(); const gStore = GlobalStore();
const loading = ref(); const loading = ref();
const backgroundOpacity = ref(1);
const backgroundImage = ref(new URL('', import.meta.url).href);
const logoImage = ref(new URL('@/assets/images/1panel-login.png', import.meta.url).href);
const mySafetyCode = defineProps({ const mySafetyCode = defineProps({
code: { code: {
@ -69,7 +92,7 @@ onMounted(() => {
}); });
</script> </script>
<style scoped lang="scss"> <!-- <style scoped lang="scss">
@mixin login-center { @mixin login-center {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -153,4 +176,4 @@ onMounted(() => {
} }
} }
} }
</style> </style> -->

View File

@ -88,6 +88,9 @@ interface DialogProps {
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = (params: DialogProps): void => { const acceptParams = (params: DialogProps): void => {
form.interval = params.interval; form.interval = params.interval;
if (params.interval == 0) {
form.interval = 30;
}
loadMfaCode(); loadMfaCode();
drawerVisible.value = true; drawerVisible.value = true;
}; };