✨ 登录日志本地化储存
This commit is contained in:
parent
e9a567c3fe
commit
fe5e75878a
@ -5,6 +5,7 @@
|
||||
* 兼容移动端UI
|
||||
* 新增移动端虚拟功能按键映射
|
||||
* 调整终端功能菜单
|
||||
* 登录日志本地化储存
|
||||
* 修复终端选中文本无法复制的bug
|
||||
* 修复无法展示服务端ping客户端延迟ms的bug
|
||||
* 修复暗黑模式下的一些样式问题
|
||||
|
@ -16,6 +16,7 @@ module.exports = {
|
||||
notifyDBPath: path.join(process.cwd(),'app/db/notify.db'),
|
||||
notifyConfigDBPath: path.join(process.cwd(),'app/db/notify-config.db'),
|
||||
onekeyDBPath: path.join(process.cwd(),'app/db/onekey.db'),
|
||||
logDBPath: path.join(process.cwd(),'app/db/log.db'),
|
||||
apiPrefix: '/api/v1',
|
||||
logConfig: {
|
||||
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 axios = require('axios')
|
||||
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 { getNetIPInfo } = require('../utils/tools')
|
||||
|
||||
@ -86,8 +86,7 @@ const beforeLoginHandler = async (clientIp, jwtExpires) => {
|
||||
// 邮件登录通知
|
||||
asyncSendNotice('login', '登录提醒', `地点:${ country + city }\nIP: ${ ip }`)
|
||||
|
||||
global.loginRecord.unshift(clientIPInfo)
|
||||
if (global.loginRecord.length > 10) global.loginRecord = global.loginRecord.slice(0, 10)
|
||||
await writeLog({ ip, country, city, date: Date.now(), type: 'login' })
|
||||
return token
|
||||
}
|
||||
|
||||
@ -109,10 +108,6 @@ const updatePwd = async ({ res, request }) => {
|
||||
res.success({ data: true, msg: 'success' })
|
||||
}
|
||||
|
||||
const getLoginRecord = async ({ res }) => {
|
||||
res.success({ data: global.loginRecord, msg: 'success' })
|
||||
}
|
||||
|
||||
const getEasynodeVersion = async ({ res }) => {
|
||||
try {
|
||||
// const { data } = await axios.get('https://api.github.com/repos/chaos-zhu/easynode/releases/latest')
|
||||
@ -129,6 +124,5 @@ module.exports = {
|
||||
login,
|
||||
getpublicKey,
|
||||
updatePwd,
|
||||
getLoginRecord,
|
||||
getEasynodeVersion
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ const { AESEncryptSync } = require('./utils/encrypt')
|
||||
// 初始化公私钥, 供登录、保存ssh密钥/密码等加解密
|
||||
async function initRsa() {
|
||||
let keyObj = await readKey()
|
||||
if(keyObj.privateKey && keyObj.publicKey) return consola.info('公私钥已存在[重新生成会导致已保存的ssh密钥信息失效]')
|
||||
if (keyObj.privateKey && keyObj.publicKey) return consola.info('公私钥已存在[重新生成会导致已保存的ssh密钥信息失效]')
|
||||
let key = new NodeRSA({ b: 1024 })
|
||||
key.setOptions({ encryptionScheme: 'pkcs1', environment: 'browser' })
|
||||
let privateKey = key.exportKey('pkcs1-private-pem')
|
||||
@ -20,7 +20,7 @@ async function initRsa() {
|
||||
// 随机的commonKey secret
|
||||
async function randomJWTSecret() {
|
||||
let keyObj = await readKey()
|
||||
if(keyObj?.commonKey) return consola.info('commonKey密钥已存在')
|
||||
if (keyObj?.commonKey) return consola.info('commonKey密钥已存在')
|
||||
|
||||
keyObj.commonKey = randomStr(16)
|
||||
await writeKey(keyObj)
|
||||
@ -30,6 +30,4 @@ async function randomJWTSecret() {
|
||||
module.exports = async () => {
|
||||
await randomJWTSecret() // 全局密钥
|
||||
await initRsa() // 全局公钥密钥
|
||||
// 用于记录客户端登录IP的列表
|
||||
global.loginRecord = []
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand } = require('../controller/ssh')
|
||||
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 { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group')
|
||||
const { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript } = require('../controller/scripts')
|
||||
const { getOnekeyRecord, removeOnekeyRecord } = require('../controller/onekey')
|
||||
const { getLog } = require('../controller/log')
|
||||
|
||||
const ssh = [
|
||||
{
|
||||
@ -76,11 +77,6 @@ const user = [
|
||||
path: '/pwd',
|
||||
controller: updatePwd
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/get-login-record',
|
||||
controller: getLoginRecord
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/version',
|
||||
@ -173,4 +169,12 @@ const onekey = [
|
||||
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,
|
||||
groupConfDBPath,
|
||||
scriptsDBPath,
|
||||
onekeyDBPath
|
||||
onekeyDBPath,
|
||||
logDBPath
|
||||
} = require('../config')
|
||||
|
||||
module.exports.KeyDB = class KeyDB {
|
||||
@ -97,3 +98,14 @@ module.exports.OnekeyDB = class OnekeyDB {
|
||||
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 () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -281,6 +281,7 @@ const writeOneKeyRecord = async (records =[]) => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const deleteOneKeyRecord = async (ids =[]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
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 = {
|
||||
readSSHRecord, writeSSHRecord,
|
||||
readHostList, writeHostList,
|
||||
@ -304,5 +335,6 @@ module.exports = {
|
||||
readNotifyConfig, writeNotifyConfig, getNotifySwByType,
|
||||
readGroupList, writeGroupList,
|
||||
readScriptList, writeScriptList,
|
||||
readOneKeyRecord, writeOneKeyRecord, deleteOneKeyRecord
|
||||
readOneKeyRecord, writeOneKeyRecord, deleteOneKeyRecord,
|
||||
readLog, writeLog
|
||||
}
|
@ -47,7 +47,7 @@ export default {
|
||||
return axios({ url: '/login', method: 'post', data })
|
||||
},
|
||||
getLoginRecord() {
|
||||
return axios({ url: '/get-login-record', method: 'get' })
|
||||
return axios({ url: '/log', method: 'get' })
|
||||
},
|
||||
updatePwd(data) {
|
||||
return axios({ url: '/pwd', method: 'put', data })
|
||||
|
@ -88,7 +88,7 @@
|
||||
<el-table-column label="操作" fixed="right" :width="isMobileScreen ? 'auto' : '260px'">
|
||||
<template #default="{ row }">
|
||||
<el-dropdown v-if="isMobileScreen" trigger="click">
|
||||
<span class="el-dropdown-link">
|
||||
<span class="link">
|
||||
操作
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
|
@ -1,7 +1,15 @@
|
||||
<template>
|
||||
<el-alert type="success" :closable="false">
|
||||
<el-alert v-if="allowedIPs" type="success" :closable="false">
|
||||
<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>
|
||||
</el-alert>
|
||||
<el-table v-loading="loading" :data="loginRecordList">
|
||||
@ -22,12 +30,17 @@ const { proxy: { $api, $tools } } = getCurrentInstance()
|
||||
|
||||
const loginRecordList = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref('')
|
||||
const allowedIPs = ref('')
|
||||
|
||||
const handleLookupLoginRecord = () => {
|
||||
loading.value = true
|
||||
$api.getLoginRecord()
|
||||
.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)
|
||||
return item
|
||||
})
|
||||
@ -43,4 +56,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.allowed_ip_tag {
|
||||
margin: 0 5px;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user