🚩 移除服务端监控服务&新增用户名

This commit is contained in:
chaoszhu 2024-07-18 12:40:18 +08:00
parent 908558915d
commit 7b8014c36b
16 changed files with 291 additions and 372 deletions

96
client/.eslintrc.js Normal file
View File

@ -0,0 +1,96 @@
// 规则参见https://cn.eslint.org/docs/rules/
module.exports = {
root: true, // 当前配置文件不能往父级查找
'globals': {
'consola': true
},
env: {
node: true,
es6: true
},
extends: [
'eslint:recommended' // 应用Eslint全部默认规则
],
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module' // 目标类型 Node项目得添加这个
},
// 自定义规则,可以覆盖 extends 的配置【安装Eslint插件可以静态检查本地文件是否符合以下规则】
'ignorePatterns': ['*.html', 'node-os-utils'],
rules: {
// 0: 关闭规则(允许) 1/2: 警告warning/错误error(不允许)
'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'template-curly-spacing': ['error', 'always'], // 模板字符串空格
'default-case': 0,
'no-empty': 0,
'object-curly-spacing': ['error', 'always'],
'no-multi-spaces': ['error'],
indent: ['error', 2, { 'SwitchCase': 1 }], // 缩进2
quotes: ['error', 'single'], // 引号single单引 double双引
semi: ['error', 'never'], // 结尾分号never禁止 always必须
'comma-dangle': ['error', 'never'], // 对象拖尾逗号
'no-redeclare': ['error', { builtinGlobals: true }], // 禁止重复对象声明
'no-multi-assign': 0,
'no-restricted-globals': 0,
'space-before-function-paren': 0, // 函数定义时括号前面空格
'one-var': 0, // 允许连续声明
// 'no-undef': 0, // 允许未定义的变量【会使env配置无效】
'linebreak-style': 0, // 检测CRLF/LF检测【默认LF】
'no-extra-boolean-cast': 0, // 允许意外的Boolean值转换
'no-constant-condition': 0, // if语句中禁止常量表达式
'no-prototype-builtins': 0, // 允许使用Object.prototypes内置对象(如xxx.hasOwnProperty)
'no-regex-spaces': 0, // 允许正则匹配多个空格
'no-unexpected-multiline': 0, // 允许多行表达式
'no-fallthrough': 0, // 允许switch穿透
'no-delete-var': 0, // 允许 delete 删除对象属性
'no-mixed-spaces-and-tabs': 0, // 允许空格tab混用
'no-class-assign': 0, // 允许修改class类型
'no-param-reassign': 0, // 允许对函数params赋值
'max-len': 0, // 允许长行
'func-names': 0, // 允许命名函数
'import/no-unresolved': 0, // 不检测模块not fund
'import/prefer-default-export': 0, // 允许单个导出
'no-const-assign': 1, // 警告修改const命名的变量
'no-unused-vars': 1, // 警告:已声明未使用
'no-unsafe-negation': 1, // 警告:使用 in / instanceof 关系运算符时,左边表达式请勿使用 ! 否定操作符
'use-isnan': 1, // 警告:使用 isNaN() 检查 NaN
'no-var': 2, // 禁止使用var声明
'no-empty-pattern': 2, // 空解构赋值
'eqeqeq': 2, // 必须使用 全等=== 或 非全等 !==
'no-cond-assign': 2, // if语句中禁止赋值
'no-dupe-args': 2, // 禁止function重复参数
'no-dupe-keys': 2, // 禁止object重复key
'no-duplicate-case': 2,
'no-func-assign': 2, // 禁止重复声明函数
'no-inner-declarations': 2, // 禁止在嵌套的语句块中出现变量或 function 声明
'no-sparse-arrays': 2, // 禁止稀缺数组
'no-unreachable': 2, // 禁止非条件return、throw、continue 和 break 语句后出现代码
'no-unsafe-finally': 2, // 禁止finally出现控制流语句return、throw等因为这会导致try...catch捕获不到
'valid-typeof': 2, // 强制 typeof 表达式与有效的字符串进行比较
// auto format options
'prefer-const': 0, // 禁用声明自动化
'no-extra-parens': 0, // 允许函数周围出现不明括号
'no-extra-semi': 2, // 禁止不必要的分号
// curly: ['error', 'multi'], // if、else、for、while 语句单行代码时不使用大括号
'dot-notation': 0, // 允许使用点号或方括号来访问对象属性
'dot-location': ['error', 'property'], // 点操作符位置,要求跟随下一行
'no-else-return': 2, // 禁止if中有return后又else
'no-implicit-coercion': [2, { allow: ['!!', '~', '+'] }], // 禁止隐式转换allow字段内符号允许
'no-trailing-spaces': 1, //一行结束后面不要有空格
'no-multiple-empty-lines': [1, { 'max': 1 }], // 空行最多不能超过1行
'no-useless-return': 2,
'wrap-iife': 0, // 允许自调用函数
'yoda': 0, // 允许yoda语句
'strict': 0, // 允许strict
'no-undef-init': 0, // 允许将变量初始化为undefined
'prefer-promise-reject-errors': 0, // 允许使用非 Error 对象作为 Promise 拒绝的原因
'consistent-return': 0, // 允许函数不使用return
'no-new': 0, // 允许单独new
'no-restricted-syntax': 0, // 允许特定的语法
'no-plusplus': 0,
'import/extensions': 0, // 忽略扩展名
'global-require': 0,
'no-return-assign': 0
}
}

View File

@ -25,13 +25,13 @@ function ipSchedule() {
getIpInfo()
})
// 每日凌晨两点整,刷新ip信息(兼容动态ip服务器)
// 每日凌晨两点整,刷新ip信息
let rule2 = new schedule.RecurrenceRule()
rule2.hour = 2
rule2.minute = 0
rule2.second = 0
schedule.scheduleJob(rule2, () => {
console.log('Task: refresh ip info', new Date())
console.log('Task: refresh ip info: ', new Date())
getIpInfo()
})
}

View File

@ -22,6 +22,7 @@
},
"dependencies": {
"axios": "^1.7.2",
"eslint": "^8.56.0",
"koa": "^2.15.3",
"node-os-utils": "^1.3.7",
"node-schedule": "^2.1.1",

View File

@ -16,11 +16,9 @@ let loginCountDown = forbidTimer
let forbidLogin = false
const login = async ({ res, request }) => {
let { body: { ciphertext, jwtExpires }, ip: clientIp } = request
if(!ciphertext) return res.fail({ msg: '参数错误' })
let { body: { loginName, ciphertext, jwtExpires }, ip: clientIp } = request
if (!loginName && !ciphertext) return res.fail({ msg: '请求非法!' })
if (forbidLogin) return res.fail({ msg: `禁止登录! 倒计时[${ loginCountDown }s]后尝试登录或重启面板服务` })
loginErrCount++
loginErrTotal++
if (loginErrCount >= allowErrCount) {
@ -40,6 +38,7 @@ const login = async ({ res, request }) => {
timer = setInterval(() => {
if (loginCountDown <= 0) {
clearInterval(timer)
timer = null
loginCountDown = forbidTimer
return
}
@ -50,15 +49,15 @@ const login = async ({ res, request }) => {
// 登录流程
try {
// console.log('ciphertext', ciphertext)
let password = await RSADecryptSync(ciphertext)
console.log('Decrypt解密password:', password)
let { pwd } = await readKey()
if(password === 'admin' && pwd === 'admin') {
let loginPwd = await RSADecryptSync(ciphertext)
// console.log('Decrypt解密password:', loginPwd)
let { user, pwd } = await readKey()
if (loginName === user && loginPwd === 'admin' && pwd === 'admin') {
const token = await beforeLoginHandler(clientIp, jwtExpires)
return res.success({ data: { token, jwtExpires }, msg: '登录成功,请及时修改默认密码' })
return res.success({ data: { token, jwtExpires }, msg: '登录成功,请及时修改默认用户名和密码' })
}
password = SHA1Encrypt(password)
if(password !== pwd) return res.fail({ msg: '密码错误' })
loginPwd = SHA1Encrypt(loginPwd)
if (loginName !== user || loginPwd !== pwd) return res.fail({ msg: `用户名或密码错误 ${ loginErrTotal }/${ allowErrCount }` })
const token = await beforeLoginHandler(clientIp, jwtExpires)
return res.success({ data: { token, jwtExpires }, msg: '登录成功' })
} catch (error) {
@ -91,18 +90,20 @@ const beforeLoginHandler = async (clientIp, jwtExpires) => {
}
const updatePwd = async ({ res, request }) => {
let { body: { oldPwd, newPwd } } = request
let { body: { oldLoginName, oldPwd, newLoginName, newPwd } } = request
let rsaOldPwd = await RSADecryptSync(oldPwd)
oldPwd = rsaOldPwd === 'admin' ? 'admin' : SHA1Encrypt(rsaOldPwd)
let keyObj = await readKey()
if(oldPwd !== keyObj.pwd) return res.fail({ data: false, msg: '旧密码校验失败' })
let { user, pwd } = keyObj
if (oldLoginName !== user || oldPwd !== pwd) return res.fail({ data: false, msg: '原用户名或密码校验失败' })
// 旧密钥校验通过,加密保存新密码
newPwd = await RSADecryptSync(newPwd) === 'admin' ? 'admin' : SHA1Encrypt(await RSADecryptSync(newPwd))
keyObj.user = newLoginName
keyObj.pwd = newPwd
await writeKey(keyObj)
let sw = getNotifySwByType('updatePwd')
if(sw) sendEmailToConfList('密码修改提醒', '面板登录密码已更改')
if (sw) sendEmailToConfList(`登录信息修改提醒, 新用户名: ${ newLoginName }`)
res.success({ data: true, msg: 'success' })
}

View File

@ -12,6 +12,7 @@ function initKeyDB() {
if (count === 0) {
consola.log('初始化keyDB✔')
const defaultData = {
user: 'admin',
pwd: 'admin',
commonKey: '',
publicKey: '',

View File

@ -1,16 +1,15 @@
const consola = require('consola')
global.consola = consola
const { httpServer, clientHttpServer } = require('./server')
const { httpServer } = require('./server')
const initDB = require('./db')
const initEncryptConf = require('./init')
// const scheduleJob = require('./schedule')
const scheduleJob = require('./schedule')
async function main() {
await initDB()
await initEncryptConf()
httpServer()
clientHttpServer()
// scheduleJob()
scheduleJob()
}
main()

View File

@ -25,5 +25,6 @@ const expiredNotifyJob = async () => {
}
module.exports = () => {
// 每天中午12点执行一次。
schedule.scheduleJob('0 0 12 1/1 * ?', expiredNotifyJob)
}

View File

@ -1,7 +1,5 @@
const offlineInspect = require('./offline-inspect')
const expiredNotify = require('./expired-notify')
module.exports = () => {
offlineInspect()
expiredNotify()
}

View File

@ -1,40 +0,0 @@
const schedule = require('node-schedule')
const { clientPort } = require('../config')
const { readHostList, sendEmailToConfList, getNotifySwByType, formatTimestamp, isProd } = require('../utils')
const testConnectAsync = require('../utils/test-connect')
let sendNotifyRecord = new Map()
const offlineJob = async () => {
let sw = getNotifySwByType('host_offline')
if(!sw) return
consola.info('=====开始检测服务器状态=====', new Date())
const hostList = await readHostList()
for (const item of hostList) {
const { host, name } = item
// consola.info('start inpect:', host, name )
testConnectAsync({
port: clientPort ,
host: `http://${ host }`,
timeout: 3000,
retryTimes: 20 // 尝试重连次数
})
.then(() => {
// consola.success('测试连接成功:', host, name)
})
.catch((error) => {
consola.error('测试连接失败: ', host, name)
// 当前小时是否发送过通知
let curHourIsSend = sendNotifyRecord.has(host) && (sendNotifyRecord.get(host).sendTime === formatTimestamp(Date.now(), 'hour'))
if(curHourIsSend) return consola.info('当前小时已发送过通知: ', sendNotifyRecord.get(host).sendTime)
sendEmailToConfList('服务器离线提醒', `别名: ${ name }<br/>IP: ${ host }<br/>错误信息:${ error.message }`)
.then(() => {
sendNotifyRecord.set(host, { 'sendTime': formatTimestamp(Date.now(), 'hour') })
})
})
}
}
module.exports = () => {
if(!isProd()) return consola.info('本地开发不检测服务器离线状态')
schedule.scheduleJob('0 0/5 12 1/1 * ?', offlineJob)
}

View File

@ -1,10 +1,8 @@
const Koa = require('koa')
const compose = require('koa-compose') // 组合中间件,简化写法
const http = require('http')
const { clientPort } = require('./config')
const { httpPort } = require('./config')
const middlewares = require('./middlewares')
const wsMonitorOsInfo = require('./socket/monitor')
const wsTerminal = require('./socket/terminal')
const wsSftp = require('./socket/sftp')
const wsHostStatus = require('./socket/host-status')
@ -21,15 +19,6 @@ const httpServer = () => {
})
}
const clientHttpServer = () => {
const app = new Koa()
const server = http.createServer(app.callback())
wsMonitorOsInfo(server) // 监控本机信息
server.listen(clientPort, () => {
consola.success(`Client(http) is running on: http://localhost:${ clientPort }`)
})
}
// 服务
function serverHandler(app, server) {
app.proxy = true // 用于nginx反代时获取真实客户端ip
@ -50,6 +39,5 @@ function serverHandler(app, server) {
}
module.exports = {
httpServer,
clientHttpServer
httpServer
}

View File

@ -1,71 +0,0 @@
const { Server } = require('socket.io')
const schedule = require('node-schedule')
const axios = require('axios')
let getOsData = require('../utils/os-data')
const consola = require('consola')
let serverSockets = {}, ipInfo = {}, osData = {}
async function getIpInfo() {
try {
let { data } = await axios.get('http://ip-api.com/json?lang=zh-CN')
consola.success('getIpInfo Success: ', new Date())
ipInfo = data
} catch (error) {
consola.error('getIpInfo Error: ', new Date(), error.message)
}
}
function ipSchedule() {
let rule1 = new schedule.RecurrenceRule()
rule1.second = [0]
schedule.scheduleJob(rule1, () => {
let { query, country, city } = ipInfo || {}
if(query && country && city) return
consola.success('Task: start getIpInfo', new Date())
getIpInfo()
})
// 每日凌晨两点整,刷新ip信息(兼容动态ip服务器)
let rule2 = new schedule.RecurrenceRule()
rule2.hour = 2
rule2.minute = 0
rule2.second = 0
schedule.scheduleJob(rule2, () => {
consola.info('Task: refresh ip info', new Date())
getIpInfo()
})
}
ipSchedule()
module.exports = (httpServer) => {
const serverIo = new Server(httpServer, {
path: '/client/os-info',
cors: {
origin: '*'
}
})
serverIo.on('connection', (socket) => {
// 存储对应websocket连接的定时器
serverSockets[socket.id] = setInterval(async () => {
try {
osData = await getOsData()
socket && socket.emit('client_data', Object.assign(osData, { ipInfo }))
} catch (error) {
consola.error('客户端错误:', error)
socket && socket.emit('client_error', { error })
}
}, 1000)
socket.on('disconnect', () => {
// 断开时清除对应的websocket连接
if(serverSockets[socket.id]) clearInterval(serverSockets[socket.id])
delete serverSockets[socket.id]
socket.close && socket.close()
socket = null
// console.log('断开socketId: ', socket.id, '剩余链接数: ', Object.keys(serverSockets).length)
})
})
}

View File

View File

@ -29,12 +29,16 @@ html, body, div, ul, section, textarea {
// 全局背景
body {
// background-color: #f4f6f9;
background-position: center center;
background-attachment: fixed;
background-size: cover;
// background-position: center center;
// background-attachment: fixed;
// background-size: cover;
// background-repeat: no-repeat;
// // background-image: url(../bg.jpg), linear-gradient(to bottom, #010179, #F5C4C1, #151799);
background-color: #f4f6f9;
background-image: url(https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg);
background-repeat: no-repeat;
background-image: url(../bg.jpg), linear-gradient(to bottom, #010179, #F5C4C1, #151799);
background-position: center 110px;
background-size: 58%;
}
html, body {

View File

@ -7,29 +7,41 @@
:hide-required-asterisk="true"
label-suffix=""
label-width="90px"
:show-message="false"
>
<el-form-item label="旧密码" prop="oldPwd">
<el-form-item label="原用户名" prop="oldLoginName">
<el-input
v-model.trim="formData.oldLoginName"
clearable
placeholder=""
autocomplete="off"
/>
</el-form-item>
<el-form-item label="原密码" prop="oldPwd">
<el-input
v-model.trim="formData.oldPwd"
type="password"
clearable
placeholder="旧密码"
show-password
placeholder=""
autocomplete="off"
/>
</el-form-item>
<el-form-item label="新用户名" prop="oldPwd">
<el-input
v-model.trim="formData.newLoginName"
clearable
placeholder=""
autocomplete="off"
/>
</el-form-item>
<el-form-item label="新密码" prop="newPwd">
<el-input
v-model.trim="formData.newPwd"
type="password"
show-password
clearable
placeholder="新密码"
autocomplete="off"
@keyup.enter="handleUpdate"
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPwd">
<el-input
v-model.trim="formData.confirmPwd"
clearable
placeholder="确认密码"
placeholder=""
autocomplete="off"
@keyup.enter="handleUpdate"
/>
@ -49,28 +61,30 @@ const { proxy: { $api, $message } } = getCurrentInstance()
const loading = ref(false)
const formRef = ref(null)
const formData = reactive({
oldLoginName: '',
oldPwd: '',
newPwd: '',
confirmPwd: ''
newLoginName: '',
newPwd: ''
})
const rules = reactive({
oldPwd: { required: true, message: '输入旧密码', trigger: 'change' },
newPwd: { required: true, message: '输入新密码', trigger: 'change' },
confirmPwd: { required: true, message: '输入确认密码', trigger: 'change' }
oldLoginName: { required: true, message: '输入原用户名', trigger: 'change' },
oldPwd: { required: true, message: '输入原密码', trigger: 'change' },
newLoginName: { required: true, message: '输入新用户名', trigger: 'change' },
newPwd: { required: true, message: '输入新密码', trigger: 'change' }
})
const handleUpdate = () => {
formRef.value.validate()
.then(async () => {
let { oldPwd, newPwd, confirmPwd } = formData
if(newPwd !== confirmPwd) return $message.error({ center: true, message: '两次密码输入不一致' })
let { oldLoginName, oldPwd, newLoginName, newPwd } = formData
oldPwd = RSAEncrypt(oldPwd)
newPwd = RSAEncrypt(newPwd)
let { msg } = await $api.updatePwd({ oldPwd, newPwd })
let { msg } = await $api.updatePwd({ oldLoginName, oldPwd, newLoginName, newPwd })
$message({ type: 'success', center: true, message: msg })
formData.oldLoginName = ''
formData.oldPwd = ''
formData.newLoginName = ''
formData.newPwd = ''
formData.confirmPwd = ''
formRef.value.resetFields()
})
}

View File

@ -1,18 +1,9 @@
<template>
<el-dialog
v-model="visible"
width="500px"
:top="'30vh'"
destroy-on-close
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
center
>
<template #header>
<h2 v-if="notKey" style="color: #f56c6c;"> Error </h2>
<h2 v-else style="color: #409eff;"> LOGIN </h2>
</template>
<div class="login_container">
<div class="login_box">
<div>
<h2>EasyNode</h2>
</div>
<div v-if="notKey">
<el-alert title="Error: 用于加密的公钥获取失败,请尝试重新启动或部署服务" type="error" show-icon />
</div>
@ -24,12 +15,23 @@
:hide-required-asterisk="true"
label-suffix=""
label-width="90px"
:show-message="false"
>
<el-form-item prop="loginName" label="用户名">
<el-input
v-model.trim="loginForm.loginName"
type="text"
placeholder=""
autocomplete="off"
:trigger-on-focus="false"
clearable
/>
</el-form-item>
<el-form-item prop="pwd" label="密码">
<el-input
v-model.trim="loginForm.pwd"
type="password"
placeholder="Please input password"
placeholder=""
autocomplete="off"
:trigger-on-focus="false"
clearable
@ -59,12 +61,18 @@
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" :loading="loading" @click="handleLogin">登录</el-button>
</span>
</template>
</el-dialog>
<div class="footer_btns">
<el-button
type="primary"
class="login_btn"
:loading="loading"
@click="handleLogin"
>
登录
</el-button>
</div>
</div>
</div>
</template>
<script setup>
@ -82,23 +90,26 @@ const visible = ref(true)
const notKey = ref(false)
const loading = ref(false)
const loginForm = reactive({
loginName: '',
pwd: '',
jwtExpires: 8
})
const rules = reactive({
loginName: { required: true, message: '需输入用户名', trigger: 'change' },
pwd: { required: true, message: '需输入密码', trigger: 'change' }
})
const handleLogin = () => {
loginFormRefs.value.validate().then(() => {
let jwtExpires = isSession.value ? '12h' : `${ loginForm.jwtExpires }h`
let { jwtExpires, loginName, pwd } = loginForm
jwtExpires = isSession.value ? '12h' : `${ jwtExpires }h`
if (!isSession.value) {
localStorage.setItem('jwtExpires', loginForm.jwtExpires)
localStorage.setItem('jwtExpires', jwtExpires)
}
const ciphertext = RSAEncrypt(loginForm.pwd)
const ciphertext = RSAEncrypt(pwd)
if (ciphertext === -1) return $message.error({ message: '公钥加载失败', center: true })
loading.value = true
$api.login({ ciphertext, jwtExpires })
$api.login({ loginName, ciphertext, jwtExpires })
.then(({ data, msg }) => {
const { token } = data
$store.setJwtToken(token, isSession.value)
@ -120,6 +131,43 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
.login_container {
// min-height: 600px;
height: 100vh;
width: 100vw;
overflow-y: auto;
display: flex;
justify-content: center;
align-items: center;
background: rgba(171, 181, 196, 0.3); // #f0f2f5;
padding-top: 70px;
.login_box {
width: 500px;
min-height: 250px;
padding: 20px;
border-radius: 6px;
background-color: #ffffff;
border: 1px solid #ebedef;
h2 {
text-align: center;
margin: 25px;
color: #409eff;
font-size: 25px;
}
.footer_btns {
display: flex;
justify-content: center;
align-items: center;
.login_btn {
width: 88px;
}
}
}
}
.login-indate {
display: flex;
flex-wrap: nowrap;

129
yarn.lock
View File

@ -653,20 +653,11 @@
dependencies:
eslint-visitor-keys "^3.3.0"
"@eslint-community/regexpp@^4.11.0", "@eslint-community/regexpp@^4.6.1":
"@eslint-community/regexpp@^4.6.1":
version "4.11.0"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
"@eslint/config-array@^0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.17.0.tgz#ff305e1ee618a00e6e5d0485454c8d92d94a860d"
integrity sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==
dependencies:
"@eslint/object-schema" "^2.1.4"
debug "^4.3.1"
minimatch "^3.1.2"
"@eslint/eslintrc@^2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
@ -682,36 +673,11 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/eslintrc@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6"
integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^10.0.1"
globals "^14.0.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
"@eslint/js@9.7.0":
version "9.7.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.7.0.tgz#b712d802582f02b11cfdf83a85040a296afec3f0"
integrity sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==
"@eslint/object-schema@^2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843"
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
"@floating-ui/core@^1.6.0":
version "1.6.4"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.4.tgz#0140cf5091c8dee602bff9da5ab330840ff91df6"
@ -756,11 +722,6 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@humanwhocodes/retry@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570"
integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@ -1412,7 +1373,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.11.3, acorn@^8.12.0, acorn@^8.12.1, acorn@^8.9.0:
acorn@^8.11.3, acorn@^8.12.1, acorn@^8.9.0:
version "8.12.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
@ -2260,24 +2221,11 @@ eslint-scope@^7.1.1, eslint-scope@^7.2.2:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-scope@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.2.tgz#5cbb33d4384c9136083a71190d548158fe128f94"
integrity sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint@^8.56.0:
version "8.57.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
@ -2322,55 +2270,6 @@ eslint@^8.56.0:
strip-ansi "^6.0.1"
text-table "^0.2.0"
eslint@^9.6.0:
version "9.7.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.7.0.tgz#bedb48e1cdc2362a0caaa106a4c6ed943e8b09e4"
integrity sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.11.0"
"@eslint/config-array" "^0.17.0"
"@eslint/eslintrc" "^3.1.0"
"@eslint/js" "9.7.0"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.3.0"
"@nodelib/fs.walk" "^1.2.8"
ajv "^6.12.4"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
escape-string-regexp "^4.0.0"
eslint-scope "^8.0.2"
eslint-visitor-keys "^4.0.0"
espree "^10.1.0"
esquery "^1.5.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^8.0.0"
find-up "^5.0.0"
glob-parent "^6.0.2"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
is-path-inside "^3.0.3"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash.merge "^4.6.2"
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.3"
strip-ansi "^6.0.1"
text-table "^0.2.0"
espree@^10.0.1, espree@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56"
integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==
dependencies:
acorn "^8.12.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^4.0.0"
espree@^9.3.1, espree@^9.6.0, espree@^9.6.1:
version "9.6.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
@ -2380,7 +2279,7 @@ espree@^9.3.1, espree@^9.6.0, espree@^9.6.1:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
esquery@^1.4.0, esquery@^1.4.2, esquery@^1.5.0:
esquery@^1.4.0, esquery@^1.4.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
@ -2461,13 +2360,6 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
file-entry-cache@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
dependencies:
flat-cache "^4.0.0"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@ -2492,14 +2384,6 @@ flat-cache@^3.0.4:
keyv "^4.5.3"
rimraf "^3.0.2"
flat-cache@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
dependencies:
flatted "^3.2.9"
keyv "^4.5.4"
flatted@^3.2.7, flatted@^3.2.9:
version "3.3.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
@ -2689,11 +2573,6 @@ globals@^13.19.0, globals@^13.24.0:
dependencies:
type-fest "^0.20.2"
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
@ -3110,7 +2989,7 @@ keygrip@~1.1.0:
dependencies:
tsscmp "1.0.6"
keyv@^4.5.3, keyv@^4.5.4:
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==