✨ 登录日志本地化储存
This commit is contained in:
parent
e9a567c3fe
commit
fe5e75878a
@ -5,6 +5,7 @@
|
|||||||
* 兼容移动端UI
|
* 兼容移动端UI
|
||||||
* 新增移动端虚拟功能按键映射
|
* 新增移动端虚拟功能按键映射
|
||||||
* 调整终端功能菜单
|
* 调整终端功能菜单
|
||||||
|
* 登录日志本地化储存
|
||||||
* 修复终端选中文本无法复制的bug
|
* 修复终端选中文本无法复制的bug
|
||||||
* 修复无法展示服务端ping客户端延迟ms的bug
|
* 修复无法展示服务端ping客户端延迟ms的bug
|
||||||
* 修复暗黑模式下的一些样式问题
|
* 修复暗黑模式下的一些样式问题
|
||||||
|
@ -16,6 +16,7 @@ module.exports = {
|
|||||||
notifyDBPath: path.join(process.cwd(),'app/db/notify.db'),
|
notifyDBPath: path.join(process.cwd(),'app/db/notify.db'),
|
||||||
notifyConfigDBPath: path.join(process.cwd(),'app/db/notify-config.db'),
|
notifyConfigDBPath: path.join(process.cwd(),'app/db/notify-config.db'),
|
||||||
onekeyDBPath: path.join(process.cwd(),'app/db/onekey.db'),
|
onekeyDBPath: path.join(process.cwd(),'app/db/onekey.db'),
|
||||||
|
logDBPath: path.join(process.cwd(),'app/db/log.db'),
|
||||||
apiPrefix: '/api/v1',
|
apiPrefix: '/api/v1',
|
||||||
logConfig: {
|
logConfig: {
|
||||||
outDir: path.join(process.cwd(),'./app/logs'),
|
outDir: path.join(process.cwd(),'./app/logs'),
|
||||||
|
16
server/app/controller/log.js
Normal file
16
server/app/controller/log.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const { readLog } = require('../utils/storage')
|
||||||
|
|
||||||
|
let whiteList = process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(',') : []
|
||||||
|
|
||||||
|
async function getLog({ res }) {
|
||||||
|
let list = await readLog()
|
||||||
|
list = list.map(item => {
|
||||||
|
return { ...item, id: item._id }
|
||||||
|
})
|
||||||
|
list?.sort((a, b) => Number(b.date) - Number(a.date))
|
||||||
|
res.success({ data: { list, whiteList } })
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getLog
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
const jwt = require('jsonwebtoken')
|
const jwt = require('jsonwebtoken')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const { asyncSendNotice } = require('../utils/notify')
|
const { asyncSendNotice } = require('../utils/notify')
|
||||||
const { readKey, writeKey } = require('../utils/storage')
|
const { readKey, writeKey, writeLog } = require('../utils/storage')
|
||||||
const { RSADecryptSync, AESEncryptSync, SHA1Encrypt } = require('../utils/encrypt')
|
const { RSADecryptSync, AESEncryptSync, SHA1Encrypt } = require('../utils/encrypt')
|
||||||
const { getNetIPInfo } = require('../utils/tools')
|
const { getNetIPInfo } = require('../utils/tools')
|
||||||
|
|
||||||
@ -86,8 +86,7 @@ const beforeLoginHandler = async (clientIp, jwtExpires) => {
|
|||||||
// 邮件登录通知
|
// 邮件登录通知
|
||||||
asyncSendNotice('login', '登录提醒', `地点:${ country + city }\nIP: ${ ip }`)
|
asyncSendNotice('login', '登录提醒', `地点:${ country + city }\nIP: ${ ip }`)
|
||||||
|
|
||||||
global.loginRecord.unshift(clientIPInfo)
|
await writeLog({ ip, country, city, date: Date.now(), type: 'login' })
|
||||||
if (global.loginRecord.length > 10) global.loginRecord = global.loginRecord.slice(0, 10)
|
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,10 +108,6 @@ const updatePwd = async ({ res, request }) => {
|
|||||||
res.success({ data: true, msg: 'success' })
|
res.success({ data: true, msg: 'success' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLoginRecord = async ({ res }) => {
|
|
||||||
res.success({ data: global.loginRecord, msg: 'success' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEasynodeVersion = async ({ res }) => {
|
const getEasynodeVersion = async ({ res }) => {
|
||||||
try {
|
try {
|
||||||
// const { data } = await axios.get('https://api.github.com/repos/chaos-zhu/easynode/releases/latest')
|
// const { data } = await axios.get('https://api.github.com/repos/chaos-zhu/easynode/releases/latest')
|
||||||
@ -129,6 +124,5 @@ module.exports = {
|
|||||||
login,
|
login,
|
||||||
getpublicKey,
|
getpublicKey,
|
||||||
updatePwd,
|
updatePwd,
|
||||||
getLoginRecord,
|
|
||||||
getEasynodeVersion
|
getEasynodeVersion
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,4 @@ async function randomJWTSecret() {
|
|||||||
module.exports = async () => {
|
module.exports = async () => {
|
||||||
await randomJWTSecret() // 全局密钥
|
await randomJWTSecret() // 全局密钥
|
||||||
await initRsa() // 全局公钥密钥
|
await initRsa() // 全局公钥密钥
|
||||||
// 用于记录客户端登录IP的列表
|
|
||||||
global.loginRecord = []
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand } = require('../controller/ssh')
|
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand } = require('../controller/ssh')
|
||||||
const { getHostList, addHost, updateHost, removeHost, importHost } = require('../controller/host')
|
const { getHostList, addHost, updateHost, removeHost, importHost } = require('../controller/host')
|
||||||
const { login, getpublicKey, updatePwd, getLoginRecord, getEasynodeVersion } = require('../controller/user')
|
const { login, getpublicKey, updatePwd, getEasynodeVersion } = require('../controller/user')
|
||||||
const { getNotifyConfig, updateNotifyConfig, getNotifyList, updateNotifyList } = require('../controller/notify')
|
const { getNotifyConfig, updateNotifyConfig, getNotifyList, updateNotifyList } = require('../controller/notify')
|
||||||
const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group')
|
const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group')
|
||||||
const { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript } = require('../controller/scripts')
|
const { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript } = require('../controller/scripts')
|
||||||
const { getOnekeyRecord, removeOnekeyRecord } = require('../controller/onekey')
|
const { getOnekeyRecord, removeOnekeyRecord } = require('../controller/onekey')
|
||||||
|
const { getLog } = require('../controller/log')
|
||||||
|
|
||||||
const ssh = [
|
const ssh = [
|
||||||
{
|
{
|
||||||
@ -76,11 +77,6 @@ const user = [
|
|||||||
path: '/pwd',
|
path: '/pwd',
|
||||||
controller: updatePwd
|
controller: updatePwd
|
||||||
},
|
},
|
||||||
{
|
|
||||||
method: 'get',
|
|
||||||
path: '/get-login-record',
|
|
||||||
controller: getLoginRecord
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/version',
|
path: '/version',
|
||||||
@ -173,4 +169,12 @@ const onekey = [
|
|||||||
controller: removeOnekeyRecord
|
controller: removeOnekeyRecord
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
module.exports = [].concat(ssh, host, user, notify, group, scripts, onekey)
|
|
||||||
|
const log = [
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
path: '/log',
|
||||||
|
controller: getLog
|
||||||
|
}
|
||||||
|
]
|
||||||
|
module.exports = [].concat(ssh, host, user, notify, group, scripts, onekey, log)
|
||||||
|
@ -7,7 +7,8 @@ const {
|
|||||||
notifyConfigDBPath,
|
notifyConfigDBPath,
|
||||||
groupConfDBPath,
|
groupConfDBPath,
|
||||||
scriptsDBPath,
|
scriptsDBPath,
|
||||||
onekeyDBPath
|
onekeyDBPath,
|
||||||
|
logDBPath
|
||||||
} = require('../config')
|
} = require('../config')
|
||||||
|
|
||||||
module.exports.KeyDB = class KeyDB {
|
module.exports.KeyDB = class KeyDB {
|
||||||
@ -97,3 +98,14 @@ module.exports.OnekeyDB = class OnekeyDB {
|
|||||||
return OnekeyDB.instance
|
return OnekeyDB.instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.LogDB = class LogDB {
|
||||||
|
constructor() {
|
||||||
|
if (!LogDB.instance) {
|
||||||
|
LogDB.instance = new Datastore({ filename: logDBPath, autoload: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getInstance() {
|
||||||
|
return LogDB.instance
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
const { KeyDB, HostListDB, SshRecordDB, NotifyDB, NotifyConfigDB, ScriptsDB, GroupDB, OnekeyDB } = require('./db-class')
|
const { KeyDB, HostListDB, SshRecordDB, NotifyDB, NotifyConfigDB, ScriptsDB, GroupDB, OnekeyDB, LogDB } = require('./db-class')
|
||||||
|
|
||||||
const readKey = async () => {
|
const readKey = async () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -281,6 +281,7 @@ const writeOneKeyRecord = async (records =[]) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteOneKeyRecord = async (ids =[]) => {
|
const deleteOneKeyRecord = async (ids =[]) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const onekeyDB = new OnekeyDB().getInstance()
|
const onekeyDB = new OnekeyDB().getInstance()
|
||||||
@ -296,6 +297,36 @@ const deleteOneKeyRecord = async (ids =[]) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readLog = async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const logDB = new LogDB().getInstance()
|
||||||
|
logDB.find({}, (err, docs) => {
|
||||||
|
if (err) {
|
||||||
|
consola.error('读取log DB错误: ', err)
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
logDB.compactDatafile()
|
||||||
|
resolve(docs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeLog = async (records = {}) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const logDB = new LogDB().getInstance()
|
||||||
|
logDB.insert(records, (err, newDocs) => {
|
||||||
|
if (err) {
|
||||||
|
consola.error('写入新的onekey记录出错:', err)
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
logDB.compactDatafile()
|
||||||
|
resolve(newDocs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
readSSHRecord, writeSSHRecord,
|
readSSHRecord, writeSSHRecord,
|
||||||
readHostList, writeHostList,
|
readHostList, writeHostList,
|
||||||
@ -304,5 +335,6 @@ module.exports = {
|
|||||||
readNotifyConfig, writeNotifyConfig, getNotifySwByType,
|
readNotifyConfig, writeNotifyConfig, getNotifySwByType,
|
||||||
readGroupList, writeGroupList,
|
readGroupList, writeGroupList,
|
||||||
readScriptList, writeScriptList,
|
readScriptList, writeScriptList,
|
||||||
readOneKeyRecord, writeOneKeyRecord, deleteOneKeyRecord
|
readOneKeyRecord, writeOneKeyRecord, deleteOneKeyRecord,
|
||||||
|
readLog, writeLog
|
||||||
}
|
}
|
@ -47,7 +47,7 @@ export default {
|
|||||||
return axios({ url: '/login', method: 'post', data })
|
return axios({ url: '/login', method: 'post', data })
|
||||||
},
|
},
|
||||||
getLoginRecord() {
|
getLoginRecord() {
|
||||||
return axios({ url: '/get-login-record', method: 'get' })
|
return axios({ url: '/log', method: 'get' })
|
||||||
},
|
},
|
||||||
updatePwd(data) {
|
updatePwd(data) {
|
||||||
return axios({ url: '/pwd', method: 'put', data })
|
return axios({ url: '/pwd', method: 'put', data })
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
<el-table-column label="操作" fixed="right" :width="isMobileScreen ? 'auto' : '260px'">
|
<el-table-column label="操作" fixed="right" :width="isMobileScreen ? 'auto' : '260px'">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-dropdown v-if="isMobileScreen" trigger="click">
|
<el-dropdown v-if="isMobileScreen" trigger="click">
|
||||||
<span class="el-dropdown-link">
|
<span class="link">
|
||||||
操作
|
操作
|
||||||
<el-icon class="el-icon--right">
|
<el-icon class="el-icon--right">
|
||||||
<arrow-down />
|
<arrow-down />
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-alert type="success" :closable="false">
|
<el-alert v-if="allowedIPs" type="success" :closable="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span style="letter-spacing: 2px;"> 系统只保存最近10条登录记录, 目前版本只保存在内存中, 重启面板服务后会丢失 </span>
|
<span style="letter-spacing: 2px;"> 登录白名单IP: </span>
|
||||||
|
<el-tag
|
||||||
|
v-for="(item, index) in allowedIPs"
|
||||||
|
:key="index"
|
||||||
|
class="allowed_ip_tag"
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-alert>
|
</el-alert>
|
||||||
<el-table v-loading="loading" :data="loginRecordList">
|
<el-table v-loading="loading" :data="loginRecordList">
|
||||||
@ -22,12 +30,17 @@ const { proxy: { $api, $tools } } = getCurrentInstance()
|
|||||||
|
|
||||||
const loginRecordList = ref([])
|
const loginRecordList = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const total = ref('')
|
||||||
|
const allowedIPs = ref('')
|
||||||
|
|
||||||
const handleLookupLoginRecord = () => {
|
const handleLookupLoginRecord = () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
$api.getLoginRecord()
|
$api.getLoginRecord()
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
loginRecordList.value = data.map((item) => {
|
const { list, whiteList } = data
|
||||||
|
total.value = list.length
|
||||||
|
allowedIPs.value = whiteList || []
|
||||||
|
loginRecordList.value = list.map((item) => {
|
||||||
item.date = $tools.formatTimestamp(item.date)
|
item.date = $tools.formatTimestamp(item.date)
|
||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
@ -43,4 +56,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.allowed_ip_tag {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user