登录日志本地化储存

This commit is contained in:
chaos-zhu 2024-10-20 22:31:27 +08:00
parent e9a567c3fe
commit fe5e75878a
11 changed files with 101 additions and 27 deletions

View File

@ -5,6 +5,7 @@
* 兼容移动端UI * 兼容移动端UI
* 新增移动端虚拟功能按键映射 * 新增移动端虚拟功能按键映射
* 调整终端功能菜单 * 调整终端功能菜单
* 登录日志本地化储存
* 修复终端选中文本无法复制的bug * 修复终端选中文本无法复制的bug
* 修复无法展示服务端ping客户端延迟ms的bug * 修复无法展示服务端ping客户端延迟ms的bug
* 修复暗黑模式下的一些样式问题 * 修复暗黑模式下的一些样式问题

View File

@ -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'),

View 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
}

View File

@ -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
} }

View File

@ -30,6 +30,4 @@ async function randomJWTSecret() {
module.exports = async () => { module.exports = async () => {
await randomJWTSecret() // 全局密钥 await randomJWTSecret() // 全局密钥
await initRsa() // 全局公钥密钥 await initRsa() // 全局公钥密钥
// 用于记录客户端登录IP的列表
global.loginRecord = []
} }

View File

@ -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)

View File

@ -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
}
}

View File

@ -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
} }

View File

@ -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 })

View File

@ -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 />

View File

@ -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>