mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-13 17:24:44 +08:00
feat: 安全入口功能实现
This commit is contained in:
parent
e1bf00cb34
commit
389e268407
@ -1,11 +1,14 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/utils/captcha"
|
||||
"github.com/1Panel-dev/1Panel/utils/encrypt"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -46,6 +49,37 @@ func (b *BaseApi) Captcha(c *gin.Context) {
|
||||
captcha, err := captcha.CreateCaptcha()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, captcha)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetSafetyStatus(c *gin.Context) {
|
||||
if err := authService.SafetyStatus(c); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) SafeEntrance(c *gin.Context) {
|
||||
code, exist := c.Params.Get("code")
|
||||
if !exist {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
|
||||
return
|
||||
}
|
||||
ok, err := authService.VerifyCode(code)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code"))
|
||||
return
|
||||
}
|
||||
codeWithMD5 := encrypt.Md5(code)
|
||||
cookieValue, _ := encrypt.StringEncrypt(codeWithMD5)
|
||||
c.SetCookie(codeWithMD5, cookieValue, 86400, "", "", false, false)
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
type AuthService struct{}
|
||||
|
||||
type IAuthService interface {
|
||||
SafetyStatus(c *gin.Context) error
|
||||
VerifyCode(code string) (bool, error)
|
||||
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
|
||||
LogOut(c *gin.Context) error
|
||||
}
|
||||
@ -89,3 +91,30 @@ func (u *AuthService) LogOut(c *gin.Context) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AuthService) VerifyCode(code string) (bool, error) {
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return setting.Value == code, nil
|
||||
}
|
||||
|
||||
func (u *AuthService) SafetyStatus(c *gin.Context) error {
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
codeWithEcrypt, err := c.Cookie(encrypt.Md5(setting.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code, err := encrypt.StringDecrypt(codeWithEcrypt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != encrypt.Md5(setting.Value) {
|
||||
return errors.New("code not match")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ const (
|
||||
CodeSuccess = 200
|
||||
CodeErrBadRequest = 400
|
||||
CodeErrUnauthorized = 401
|
||||
CodeErrUnSafety = 402
|
||||
CodeErrForbidden = 403
|
||||
CodeErrNotFound = 404
|
||||
CodeErrInternalServer = 500
|
||||
@ -33,5 +34,6 @@ var (
|
||||
ErrTypeInvalidParams = "ErrInvalidParams"
|
||||
ErrTypeToken = "ErrToken"
|
||||
ErrTypeTokenTimeOut = "ErrTokenTimeOut"
|
||||
ErrTypeNotLogin = "ErrTypeNotLogin"
|
||||
ErrTypeNotLogin = "ErrNotLogin"
|
||||
ErrTypeNotSafety = "ErrNotSafety"
|
||||
)
|
||||
|
@ -8,4 +8,5 @@ ErrInternalServer: "Service internal error: {{ .detail }}"
|
||||
ErrRecordExist: "Record already exists: {{ .detail }}"
|
||||
ErrRecordNotFound: "Records not found: {{ .detail }}"
|
||||
ErrStructTransform: "Type conversion failure: {{ .detail }}"
|
||||
ErrTypeNotLogin: "User is not Login"
|
||||
ErrNotLogin: "User is not Login: {{ .detail }}"
|
||||
ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}"
|
@ -8,4 +8,5 @@ ErrInternalServer: "服务内部错误: {{ .detail }}"
|
||||
ErrRecordExist: "记录已存在: {{ .detail }}"
|
||||
ErrRecordNotFound: "记录未能找到: {{ .detail }}"
|
||||
ErrStructTransform: "类型转换失败: {{ .detail }}"
|
||||
ErrTypeNotLogin: "用户未登录"
|
||||
ErrNotLogin: "用户未登录: {{ .detail }}"
|
||||
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
|
@ -81,7 +81,7 @@ var AddTableSetting = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.Setting{Key: "ServerPort", Value: "4004"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "/89dc6ae8"}).Error; err != nil {
|
||||
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "89dc6ae8"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "PasswordTimeOut", Value: time.Now().AddDate(0, 0, 10).Format("2016.01.02 15:04:05")}).Error; err != nil {
|
||||
|
@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/docs"
|
||||
"github.com/1Panel-dev/1Panel/i18n"
|
||||
"github.com/1Panel-dev/1Panel/middleware"
|
||||
@ -19,8 +20,11 @@ func Routers() *gin.Engine {
|
||||
Router.Use(middleware.LoadCsrfToken())
|
||||
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
|
||||
Router.Use(i18n.GinI18nLocalize())
|
||||
Router.GET("/api/v1/info", v1.ApiGroupApp.BaseApi.GetSafetyStatus)
|
||||
Router.GET("/api/v1/:code", v1.ApiGroupApp.BaseApi.SafeEntrance)
|
||||
Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
|
||||
Router.SetFuncMap(template.FuncMap{
|
||||
"Localize": ginI18n.GetMessage,
|
||||
@ -35,7 +39,9 @@ func Routers() *gin.Engine {
|
||||
c.JSON(200, "ok")
|
||||
})
|
||||
}
|
||||
|
||||
PrivateGroup := Router.Group("/api/v1")
|
||||
PrivateGroup.Use(middleware.SafetyAuth())
|
||||
{
|
||||
systemRouter.InitBaseRouter(PrivateGroup)
|
||||
systemRouter.InitHostRouter(PrivateGroup)
|
||||
|
18
backend/middleware/safety.go
Normal file
18
backend/middleware/safety.go
Normal file
@ -0,0 +1,18 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/service"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SafetyAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if err := service.NewIAuthService().SafetyStatus(c); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, nil)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -4,8 +4,10 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@ -38,6 +40,12 @@ func StringDecrypt(text string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func Md5(str string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func padding(plaintext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(plaintext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
|
@ -49,6 +49,12 @@ class RequestHttp {
|
||||
});
|
||||
return Promise.reject(data);
|
||||
}
|
||||
if (data.code == ResultEnum.UNSAFETY) {
|
||||
router.replace({
|
||||
path: '/login',
|
||||
});
|
||||
return data;
|
||||
}
|
||||
if (data.code && data.code !== ResultEnum.SUCCESS) {
|
||||
ElMessage.error(data.msg);
|
||||
return Promise.reject(data);
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { CommonModel, ReqPage } from '.';
|
||||
|
||||
export namespace User {
|
||||
export interface User extends CommonModel {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
export interface UserCreate {
|
||||
username: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ReqGetUserParams extends ReqPage {
|
||||
info?: string;
|
||||
email?: string;
|
||||
}
|
||||
}
|
@ -12,3 +12,11 @@ export const getCaptcha = () => {
|
||||
export const logOutApi = () => {
|
||||
return http.post<any>(`/auth/logout`);
|
||||
};
|
||||
|
||||
export const entrance = (code: string) => {
|
||||
return http.get<any>(`/${code}`);
|
||||
};
|
||||
|
||||
export const loginStatus = () => {
|
||||
return http.get<any>('/info');
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import http from '@/api';
|
||||
import { ResPage } from '../interface';
|
||||
import { User } from '../interface/user';
|
||||
|
||||
export const getUserList = (params: User.ReqGetUserParams) => {
|
||||
return http.post<ResPage<User.User>>(`/users/search`, params);
|
||||
};
|
||||
|
||||
export const addUser = (params: User.User) => {
|
||||
return http.post(`/users`, params);
|
||||
};
|
||||
|
||||
export const getUserById = (id: number) => {
|
||||
return http.get<User.User>(`/users/${id}`);
|
||||
};
|
||||
|
||||
export const editUser = (params: User.User) => {
|
||||
return http.put(`/users/` + params.id, params);
|
||||
};
|
||||
|
||||
export const deleteUser = (params: { ids: number[] }) => {
|
||||
return http.post(`/users/del`, params);
|
||||
};
|
@ -41,7 +41,7 @@ import { loadingSvg } from '@/utils/svg';
|
||||
import Logo from './components/logo.vue';
|
||||
import SubItem from './components/sub-item.vue';
|
||||
import router, { menuList } from '@/routers/router';
|
||||
import { logOutApi } from '@/api/modules/login';
|
||||
import { logOutApi } from '@/api/modules/auth';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
@ -1,27 +0,0 @@
|
||||
<template>
|
||||
<el-switch
|
||||
v-model="themeConfig.isDark"
|
||||
@change="onAddDarkChange"
|
||||
inline-prompt
|
||||
active-color="#0a0a0a"
|
||||
inactive-color="#dcdfe6"
|
||||
:active-icon="Sunny"
|
||||
:inactive-icon="Moon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="switchDark">
|
||||
import { computed } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { Sunny, Moon } from '@element-plus/icons-vue';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const { switchDark } = useTheme();
|
||||
|
||||
const themeConfig = computed(() => globalStore.themeConfig);
|
||||
|
||||
const onAddDarkChange = () => {
|
||||
switchDark();
|
||||
};
|
||||
</script>
|
@ -2,6 +2,7 @@ export enum ResultEnum {
|
||||
SUCCESS = 200,
|
||||
ERROR = 500,
|
||||
OVERDUE = 401,
|
||||
UNSAFETY = 402,
|
||||
FORBIDDEN = 403,
|
||||
TIMEOUT = 100000,
|
||||
TYPE = 'success',
|
||||
|
@ -8,6 +8,8 @@ export default {
|
||||
sync: 'Sync',
|
||||
delete: 'Delete',
|
||||
edit: 'Edit',
|
||||
enable: 'Enable',
|
||||
disable: 'Disable',
|
||||
confirm: 'Confirm',
|
||||
cancel: 'Cancel',
|
||||
reset: 'Reset',
|
||||
@ -49,6 +51,15 @@ export default {
|
||||
},
|
||||
login: {
|
||||
captchaHelper: 'Please enter the verification code',
|
||||
safeEntrance: 'Please use the correct entry to log in to the panel',
|
||||
reason: 'Cause of error:',
|
||||
reasonHelper:
|
||||
'At present, the newly installed machine has enabled the security entrance login. The newly installed machine will have a random 8-character security entrance name, which can also be modified in the panel Settings. If you do not record or do not remember, you can use the following methods to solve the problem',
|
||||
solution: 'The solution:',
|
||||
solutionHelper:
|
||||
'Run the following command on the SSH terminal to solve the problem: 1. View the /etc/init.d/bt default command on the panel',
|
||||
warnning:
|
||||
'Note: [Closing the security entrance] will make your panel login address directly exposed to the Internet, very dangerous, please exercise caution',
|
||||
},
|
||||
rule: {
|
||||
username: 'Please enter a username',
|
||||
@ -87,7 +98,6 @@ export default {
|
||||
},
|
||||
menu: {
|
||||
home: 'Overview',
|
||||
demo: 'Example',
|
||||
terminal: 'Terminal',
|
||||
apps: 'App Store',
|
||||
website: 'Website',
|
||||
|
@ -61,6 +61,13 @@ export default {
|
||||
},
|
||||
login: {
|
||||
captchaHelper: '请输入验证码',
|
||||
safeEntrance: '请使用正确的入口登录面板',
|
||||
reason: '错误原因:',
|
||||
reasonHelper:
|
||||
'当前新安装的已经开启了安全入口登录,新装机器都会随机一个8位字符的安全入口名称,亦可以在面板设置处修改,如您没记录或不记得了,可以使用以下方式解决',
|
||||
solution: '解决方法:',
|
||||
solutionHelper: '在SSH终端输入以下一种命令来解决 1.查看面板入口:/etc/init.d/bt default',
|
||||
warnning: '注意:【关闭安全入口】将使您的面板登录地址被直接暴露在互联网上,非常危险,请谨慎操作',
|
||||
},
|
||||
rule: {
|
||||
username: '请输入用户名',
|
||||
@ -101,7 +108,6 @@ export default {
|
||||
},
|
||||
menu: {
|
||||
home: '概览',
|
||||
demo: '样例',
|
||||
monitor: '监控',
|
||||
terminal: '终端',
|
||||
operations: '操作日志',
|
||||
@ -287,7 +293,7 @@ export default {
|
||||
panelPort: '面板端口',
|
||||
portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口',
|
||||
safeEntrance: '安全入口',
|
||||
safeEntranceHelper: '面板管理入口,设置后只能通过指定安全入口登录面板,如: /89dc6ae8',
|
||||
safeEntranceHelper: '面板管理入口,设置后只能通过指定安全入口登录面板,如: 89dc6ae8',
|
||||
passwordTimeout: '密码过期时间',
|
||||
timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码',
|
||||
complexity: '密码复杂度验证',
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { Layout } from '@/routers/constant';
|
||||
|
||||
// demo
|
||||
const demoRouter = {
|
||||
sort: 1,
|
||||
path: '/demos',
|
||||
component: Layout,
|
||||
redirect: '/demos/table',
|
||||
meta: {
|
||||
icon: 'apple',
|
||||
title: 'menu.demo',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/demos/table',
|
||||
name: 'Table',
|
||||
component: () => import('@/views/demos/table/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/demos/table/:op/:id?',
|
||||
name: 'DemoOperate',
|
||||
props: true,
|
||||
hidden: true,
|
||||
component: () => import('@/views/demos/table/operate/index.vue'),
|
||||
meta: {
|
||||
activeMenu: '/demos/table',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default demoRouter;
|
@ -1,10 +1,10 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const modules = import.meta.globEager('./modules/*.ts');
|
||||
|
||||
const homeRouter: RouteRecordRaw = {
|
||||
path: '/',
|
||||
path: '/home',
|
||||
component: Layout,
|
||||
redirect: '/home/index',
|
||||
meta: {
|
||||
@ -55,8 +55,9 @@ menuList.unshift(homeRouter);
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
homeRouter,
|
||||
{
|
||||
path: '/login',
|
||||
path: '/login/:code?',
|
||||
name: 'login',
|
||||
props: true,
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
@ -70,7 +71,7 @@ export const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
];
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
history: createWebHistory(),
|
||||
routes: routes as RouteRecordRaw[],
|
||||
strict: false,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
|
@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<LayoutContent :header="'样例'">
|
||||
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="openOperate(null)">{{ $t('commons.button.create') }}</el-button>
|
||||
<el-button type="primary" plain>{{ '其他操作' }}</el-button>
|
||||
<el-button type="danger" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column label="ID" min-width="100" prop="id" fix />
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="100" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<fu-input-rw-switch v-model="row.name" size="mini" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Email" min-width="100" prop="email" />
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.createdAt')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
width="200"
|
||||
/>
|
||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||
</ComplexTable>
|
||||
</LayoutContent>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { User } from '@/api/interface/user';
|
||||
import { deleteUser, getUserList } from '@/api/modules/user';
|
||||
import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import i18n from '@/lang';
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
const userSearch = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
const openOperate = (row: User.User | null) => {
|
||||
let params: { [key: string]: any } = {
|
||||
op: 'create',
|
||||
};
|
||||
if (row !== null) {
|
||||
params.op = 'edit';
|
||||
params.id = row.id;
|
||||
}
|
||||
|
||||
router.push({ name: 'DemoOperate', params });
|
||||
};
|
||||
|
||||
const batchDelete = async (row: User.User | null) => {
|
||||
let ids: Array<number> = [];
|
||||
|
||||
if (row === null) {
|
||||
selects.value.forEach((item: User.User) => {
|
||||
ids.push(item.id);
|
||||
});
|
||||
} else {
|
||||
ids.push(row.id);
|
||||
}
|
||||
await useDeleteData(deleteUser, { ids: ids }, 'commons.msg.delete', true);
|
||||
search();
|
||||
};
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: openOperate,
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
type: 'danger',
|
||||
click: batchDelete,
|
||||
},
|
||||
];
|
||||
|
||||
const search = async () => {
|
||||
userSearch.page = paginationConfig.page;
|
||||
userSearch.pageSize = paginationConfig.pageSize;
|
||||
const res = await getUserList(userSearch);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<LayoutContent :header="$t('commons.button.' + op)" :back-name="'Table'">
|
||||
<template #form>
|
||||
<el-form ref="ruleFormRef" label-position="left" :model="demoForm" :rules="rules" label-width="140px">
|
||||
<el-form-item :label="$t('auth.username')" prop="name">
|
||||
<el-input v-model="demoForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('auth.email')" prop="email">
|
||||
<el-input v-model="demoForm.email" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('auth.password')" prop="password">
|
||||
<el-input type="password" v-model="demoForm.password" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="form-button">
|
||||
<el-button @click="router.back()">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submitForm(ruleFormRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { ElMessage, FormInstance, FormRules } from 'element-plus';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rues';
|
||||
import { addUser, editUser, getUserById } from '@/api/modules/user';
|
||||
import i18n from '@/lang';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { User } from '@/api/interface/user';
|
||||
const router = useRouter();
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
let demoForm = ref<User.User>({
|
||||
id: 0,
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
interface OperateProps {
|
||||
op: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<OperateProps>(), {
|
||||
op: 'create',
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
name: [Rules.requiredInput, Rules.name],
|
||||
email: [Rules.requiredInput, Rules.email],
|
||||
password: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (props.op === 'create') {
|
||||
addUser(demoForm.value).then(() => {
|
||||
ElMessage.success(i18n.global.t('commons.msg.createSuccess'));
|
||||
router.back();
|
||||
});
|
||||
} else {
|
||||
editUser(demoForm.value).then(() => {
|
||||
ElMessage.success(i18n.global.t('commons.msg.updateSuccess'));
|
||||
router.back();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getUser = async (id: number) => {
|
||||
const res = await getUserById(id);
|
||||
demoForm.value = res.data;
|
||||
};
|
||||
onMounted(() => {
|
||||
if (props.op == 'edit') {
|
||||
console.log(props);
|
||||
getUser(Number(props.id)).catch(() => {
|
||||
router.back();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
@ -47,7 +47,6 @@
|
||||
@change="quickInput"
|
||||
style="width: 25%"
|
||||
:placeholder="$t('terminal.quickCommand')"
|
||||
|
||||
>
|
||||
<el-option
|
||||
v-for="cmd in commandList"
|
||||
@ -61,7 +60,6 @@
|
||||
v-model="batchVal"
|
||||
@keyup.enter="batchInput"
|
||||
style="width: 75%"
|
||||
|
||||
>
|
||||
<template #append>
|
||||
<el-switch v-model="isBatch" class="ml-2" />
|
||||
|
@ -46,7 +46,7 @@ import { useRouter } from 'vue-router';
|
||||
import { Login } from '@/api/interface';
|
||||
import type { ElForm } from 'element-plus';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { loginApi, getCaptcha } from '@/api/modules/login';
|
||||
import { loginApi, getCaptcha } from '@/api/modules/auth';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { MenuStore } from '@/store/modules/menu';
|
||||
import i18n from '@/lang';
|
||||
@ -111,17 +111,15 @@ const resetForm = (formEl: FormInstance | undefined) => {
|
||||
formEl.resetFields();
|
||||
};
|
||||
|
||||
const loginVerify = () => {
|
||||
getCaptcha().then(async (ele) => {
|
||||
captcha.imagePath = ele.data?.imagePath ? ele.data.imagePath : '';
|
||||
captcha.captchaID = ele.data?.captchaID ? ele.data.captchaID : '';
|
||||
captcha.captchaLength = ele.data?.captchaLength ? ele.data.captchaLength : 0;
|
||||
});
|
||||
const loginVerify = async () => {
|
||||
const res = await getCaptcha();
|
||||
captcha.imagePath = res.data.imagePath ? res.data.imagePath : '';
|
||||
captcha.captchaID = res.data.captchaID ? res.data.captchaID : '';
|
||||
captcha.captchaLength = res.data.captchaLength ? res.data.captchaLength : 0;
|
||||
};
|
||||
loginVerify();
|
||||
|
||||
onMounted(() => {
|
||||
// 监听enter事件(调用登录)
|
||||
document.onkeydown = (e: any) => {
|
||||
e = window.event || e;
|
||||
if (e.code === 'Enter' || e.code === 'enter' || e.code === 'NumpadEnter') {
|
||||
|
@ -1,24 +1,82 @@
|
||||
<template>
|
||||
<div class="login-container flx-center">
|
||||
<SwitchDark class="dark"></SwitchDark>
|
||||
<div class="login-box">
|
||||
<div class="login-left">
|
||||
<img src="@/assets/images/login_left0.png" alt="login" />
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<div class="login-logo">
|
||||
<img class="login-icon" src="@/assets/images/logo.svg" alt="" />
|
||||
<h2 class="logo-text">1Panel</h2>
|
||||
<div>
|
||||
<div v-if="statusCode == 1" class="login-container flx-center">
|
||||
<div class="login-box">
|
||||
<div class="login-left">
|
||||
<img src="@/assets/images/login_left0.png" alt="login" />
|
||||
</div>
|
||||
<LoginForm ref="loginRef"></LoginForm>
|
||||
<div class="login-form">
|
||||
<div class="login-logo">
|
||||
<img class="login-icon" src="@/assets/images/logo.svg" alt="" />
|
||||
<h2 class="logo-text">1Panel</h2>
|
||||
</div>
|
||||
<LoginForm ref="loginRef"></LoginForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left: 50px" v-if="statusCode == -1">
|
||||
<h1>{{ $t('commons.login.safeEntrance') }}</h1>
|
||||
<div style="line-height: 30px">
|
||||
<span style="font-weight: 500">{{ $t('commons.login.reason') }}</span>
|
||||
<span>
|
||||
{{ $t('commons.login.reasonHelper') }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="line-height: 30px">
|
||||
<span style="font-weight: 500">{{ $t('commons.login.solution') }}</span>
|
||||
<span>{{ $t('commons.login.solutionHelper') }}</span>
|
||||
</div>
|
||||
<div style="line-height: 30px">
|
||||
<span style="color: red">
|
||||
{{ $t('commons.login.warnning') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="login">
|
||||
import SwitchDark from '@/components/switch-dark/index.vue';
|
||||
import LoginForm from './components/login-form.vue';
|
||||
import { ref, onMounted, onBeforeMount } from 'vue';
|
||||
import { loginStatus, entrance } from '@/api/modules/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
const router = useRouter();
|
||||
|
||||
interface Props {
|
||||
code: string;
|
||||
}
|
||||
const mySafetyCode = withDefaults(defineProps<Props>(), {
|
||||
code: '',
|
||||
});
|
||||
|
||||
const statusCode = ref<number>(0);
|
||||
|
||||
const getStatus = async () => {
|
||||
const res = await loginStatus();
|
||||
if (res.code === 402) {
|
||||
statusCode.value = -1;
|
||||
} else {
|
||||
statusCode.value = 1;
|
||||
return;
|
||||
}
|
||||
if (mySafetyCode.code) {
|
||||
const res = await entrance(mySafetyCode.code);
|
||||
if (res.code === 200) {
|
||||
statusCode.value = 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (globalStore.isLogin) {
|
||||
router.push({ name: 'home' });
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
getStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -221,7 +221,7 @@ const submitChangePassword = async (formEl: FormInstance | undefined) => {
|
||||
await updatePassword(passForm);
|
||||
passwordVisiable.value = false;
|
||||
ElMessage.success(i18n.t('commons.msg.operationSuccess'));
|
||||
router.push({ name: 'login' });
|
||||
router.push({ name: 'login', params: { code: '' } });
|
||||
globalStore.setLogStatus(false);
|
||||
});
|
||||
};
|
||||
|
@ -35,7 +35,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="$t('setting.safeEntrance')">
|
||||
<el-form-item :label="$t('setting.safeEntrance')">
|
||||
<el-input clearable v-model="form.settingInfo.securityEntrance">
|
||||
<template #append>
|
||||
<el-button
|
||||
|
Loading…
x
Reference in New Issue
Block a user