This commit is contained in:
chaos-zhu 2022-12-12 20:32:08 +08:00
parent 0d0b24de75
commit bcfbe0d6b3
28 changed files with 523 additions and 546 deletions

View File

@ -1,3 +1,14 @@
## [1.2.1](https://github.com/chaos-zhu/easynode/releases) (2022-12-12)
### Features
* 新增支持终端长命令输入模式 ✔
* 新增前端静态文件缓存 ✔
### Bug Fixes
* v1.2的若干bug...
## [1.2.0](https://github.com/chaos-zhu/easynode/releases) (2022-09-12) ## [1.2.0](https://github.com/chaos-zhu/easynode/releases) (2022-09-12)
### Features ### Features

10
Q&A.md
View File

@ -1,14 +1,12 @@
# 用于收集一些疑难杂症 # Q&A
- **欢迎pr~** ## CentOS7/8启动服务失败
## 甲骨文CentOS7/8启动服务失败
> 先关闭SELinux > 先关闭SELinux
```shell ```shell
vi /etc/selinux/config vi /etc/selinux/config
SELINUX=enforcing SELINUX=enforcing
// 修改为禁用 // 修改为禁用
SELINUX=disabled SELINUX=disabled
``` ```
@ -17,6 +15,6 @@ SELINUX=disabled
> 查看SELinux状态sestatus > 查看SELinux状态sestatus
## 甲骨文ubuntu20.04客户端服务启动成功,无法连接? ## 客户端服务启动成功,无法连接?
> 端口未开放:`iptables -I INPUT -s 0.0.0.0/0 -p tcp --dport 22022 -j ACCEPT` 或者 `rm -rf /etc/iptables && reboot` > 端口未开放:`iptables -I INPUT -s 0.0.0.0/0 -p tcp --dport 22022 -j ACCEPT` 或者 `rm -rf /etc/iptables && reboot`

View File

@ -42,12 +42,10 @@
- 依赖Node.js环境 - 依赖Node.js环境
- 占用端口8082(http端口)、8083(https端口)、22022(客户端端口) - 占用端口8082(http端口)、22022(客户端端口)
- 建议使用**境外服务器**(最好延迟低)安装服务端客户端信息监控与webssh功能都将以`该服务器作为跳板机` - 建议使用**境外服务器**(最好延迟低)安装服务端客户端信息监控与webssh功能都将以`该服务器作为跳板机`
- https服务需自行配置证书或者使用`nginx反代`解决(推荐)
#### Docker镜像 #### Docker镜像
> 注意网速统计功能可能受限docker网络将使用host模式(与宿主机共享端口,占用: 8082、22022) > 注意网速统计功能可能受限docker网络将使用host模式(与宿主机共享端口,占用: 8082、22022)
@ -88,12 +86,6 @@ wget -qO- --no-check-certificate https://ghproxy.com/https://raw.githubuserconte
- 默认登录密码admin(首次部署完成后请及时修改). - 默认登录密码admin(首次部署完成后请及时修改).
6. 部署https服务
- 部署https服务需要自己上传域名证书至`\server\app\config\pem`,并且证书和私钥分别命名:`key.pem``cert.pem`
- 配置域名vim server/app/config/index.js 在domain字段中填写你解析到服务器的域名
- pm2 restart easynode-server
- 不出意外你就可以访问https服务https://domain:8083
--- ---
### 客户端安装 ### 客户端安装

0
client/bin/www Normal file → Executable file
View File

View File

@ -73,9 +73,16 @@ mv ${FILE_PATH}/${SERVER_NAME}.service ${SERVICE_PATH}
# echo "***********************daemon-reload***********************" # echo "***********************daemon-reload***********************"
systemctl daemon-reload systemctl daemon-reload
echo "***********************启动服务***********************" echo "***********************准备启动服务***********************"
systemctl start ${SERVER_NAME} systemctl start ${SERVER_NAME}
if [ $? != 0 ]
then
echo "***********************${SERVER_NAME}.service启动失败***********************"
echo "***********************可能是服务器开启了SELinux, 参见Q&A***********************"
exit 1
fi
echo "***********************服务启动成功***********************"
# echo "***********************设置开机启动***********************" # echo "***********************设置开机启动***********************"
systemctl enable ${SERVER_NAME} systemctl enable ${SERVER_NAME}

View File

@ -1,25 +1,11 @@
const path = require('path') const path = require('path')
const fs = require('fs')
const getCertificate =() => {
try {
return {
cert: fs.readFileSync(path.join(__dirname, './pem/cert.pem')),
key: fs.readFileSync(path.join(__dirname, './pem/key.pem'))
}
} catch (error) {
return null
}
}
module.exports = { module.exports = {
domain: 'xxx.com', // https域名, 可不配置
httpPort: 8082, httpPort: 8082,
httpsPort: 8083,
clientPort: 22022, // 勿更改 clientPort: 22022, // 勿更改
certificate: getCertificate(),
uploadDir: path.join(process.cwd(),'app/static/upload'), uploadDir: path.join(process.cwd(),'app/static/upload'),
staticDir: path.join(process.cwd(),'app/static'), staticDir: path.join(process.cwd(),'app/static'),
sftpCacheDir: path.join(process.cwd(),'app/socket/.sftp-cache'), sftpCacheDir: path.join(process.cwd(),'app/socket/sftp-cache'),
sshRecordPath: path.join(process.cwd(),'app/storage/ssh-record.json'), sshRecordPath: path.join(process.cwd(),'app/storage/ssh-record.json'),
keyPath: path.join(process.cwd(),'app/storage/key.json'), keyPath: path.join(process.cwd(),'app/storage/key.json'),
hostListPath: path.join(process.cwd(),'app/storage/host-list.json'), hostListPath: path.join(process.cwd(),'app/storage/host-list.json'),

View File

@ -1,6 +1,6 @@
const consola = require('consola') const consola = require('consola')
global.consola = consola global.consola = consola
const { httpServer, httpsServer, clientHttpServer } = require('./server') const { httpServer, clientHttpServer } = require('./server')
const initLocal = require('./init') const initLocal = require('./init')
const scheduleJob = require('./schedule') const scheduleJob = require('./schedule')
@ -10,6 +10,4 @@ initLocal()
httpServer() httpServer()
httpsServer()
clientHttpServer() clientHttpServer()

View File

@ -1,12 +1,12 @@
const koaBody = require('koa-body') const koaBody = require('koa-body')
const { uploadDir } = require('../config') const { uploadDir } = require('../config')
module.exports = koaBody({ module.exports = koaBody({
multipart: true, // 支持 multipart-formdate 的表单 multipart: true, // 支持 multipart-formdate 的表单
formidable: { formidable: {
uploadDir, // 上传目录 uploadDir, // 上传目录
keepExtensions: true, // 保持文件的后缀 keepExtensions: true, // 保持文件的后缀
multipart: true, // 多文件上传 multipart: true, // 多文件上传
maxFieldsSize: 2 * 1024 * 1024 // 文件上传大小 单位B maxFieldsSize: 2 * 1024 * 1024 // 文件上传大小 单位B
} }
}) })

View File

@ -1,10 +1,8 @@
const cors = require('@koa/cors') const cors = require('@koa/cors')
// const { domain } = require('../config')
// 跨域处理 // 跨域处理
const useCors = cors({ const useCors = cors({
origin: ({ req }) => { origin: ({ req }) => {
// return domain || req.headers.origin
return req.headers.origin return req.headers.origin
}, },
credentials: true, credentials: true,

View File

@ -1,58 +1,58 @@
const log4js = require('log4js') const log4js = require('log4js')
const { outDir, recordLog } = require('../config').logConfig const { outDir, recordLog } = require('../config').logConfig
log4js.configure({ log4js.configure({
appenders: { appenders: {
// 控制台输出 // 控制台输出
out: { out: {
type: 'stdout', type: 'stdout',
layout: { layout: {
type: 'colored' type: 'colored'
} }
}, },
// 保存日志文件 // 保存日志文件
cheese: { cheese: {
type: 'file', type: 'file',
maxLogSize: 512*1024, // unit: bytes 1KB = 1024bytes maxLogSize: 512*1024, // unit: bytes 1KB = 1024bytes
filename: `${ outDir }/receive.log` filename: `${ outDir }/receive.log`
} }
}, },
categories: { categories: {
default: { default: {
appenders: [ 'out', 'cheese' ], // 配置 appenders: [ 'out', 'cheese' ], // 配置
level: 'info' // 只输出info以上级别的日志 level: 'info' // 只输出info以上级别的日志
} }
} }
// pm2: true // pm2: true
}) })
const logger = log4js.getLogger() const logger = log4js.getLogger()
const useLog = () => { const useLog = () => {
return async (ctx, next) => { return async (ctx, next) => {
const { method, path, origin, query, body, headers, ip } = ctx.request const { method, path, origin, query, body, headers, ip } = ctx.request
const data = { const data = {
method, method,
path, path,
origin, origin,
query, query,
body, body,
ip, ip,
headers headers
} }
await next() // 等待路由处理完成,再开始记录日志 await next() // 等待路由处理完成,再开始记录日志
// 是否记录日志 // 是否记录日志
if (recordLog) { if (recordLog) {
const { status, params } = ctx const { status, params } = ctx
data.status = status data.status = status
data.params = params data.params = params
data.result = ctx.body || 'no content' data.result = ctx.body || 'no content'
if (String(status).startsWith(4) || String(status).startsWith(5)) if (String(status).startsWith(4) || String(status).startsWith(5))
logger.error(JSON.stringify(data)) logger.error(JSON.stringify(data))
else else
logger.info(JSON.stringify(data)) logger.info(JSON.stringify(data))
} }
} }
} }
module.exports = useLog() module.exports = useLog()

View File

@ -1,13 +1,13 @@
const router = require('../router') const router = require('../router')
// 路由中间件 // 路由中间件
const useRoutes = router.routes() const useRoutes = router.routes()
// 优化错误提示中间件 // 优化错误提示中间件
// 原先如果请求方法错误响应404 // 原先如果请求方法错误响应404
// 使用该中间件后请求方法错误会提示405 Method Not Allowed【get list ✔200 post /list ❌405】 // 使用该中间件后请求方法错误会提示405 Method Not Allowed【get list ✔200 post /list ❌405】
const useAllowedMethods = router.allowedMethods() const useAllowedMethods = router.allowedMethods()
module.exports = { module.exports = {
useRoutes, useRoutes,
useAllowedMethods useAllowedMethods
} }

View File

@ -1,14 +1,14 @@
const koaStatic = require('koa-static') const koaStatic = require('koa-static')
const { staticDir } = require('../config') const { staticDir } = require('../config')
const useStatic = koaStatic(staticDir, { const useStatic = koaStatic(staticDir, {
maxage: 1000 * 60 * 60 * 24 * 30, maxage: 1000 * 60 * 60 * 24 * 30,
gzip: true, gzip: true,
setHeaders: (res, path) => { setHeaders: (res, path) => {
if(path && path.endsWith('.html')) { if(path && path.endsWith('.html')) {
res.setHeader('Cache-Control', 'max-age=0') res.setHeader('Cache-Control', 'max-age=0')
} }
} }
}) })
module.exports = useStatic module.exports = useStatic

View File

@ -1,68 +1,55 @@
const Koa = require('koa') const Koa = require('koa')
const compose = require('koa-compose') // 组合中间件,简化写法 const compose = require('koa-compose') // 组合中间件,简化写法
const http = require('http') const http = require('http')
const https = require('https') const { clientPort } = require('./config')
const { clientPort } = require('./config') const { httpPort } = require('./config')
const { domain, httpPort, httpsPort, certificate } = require('./config') const middlewares = require('./middlewares')
const middlewares = require('./middlewares') const wsMonitorOsInfo = require('./socket/monitor')
const wsMonitorOsInfo = require('./socket/monitor') const wsTerminal = require('./socket/terminal')
const wsTerminal = require('./socket/terminal') const wsSftp = require('./socket/sftp')
const wsSftp = require('./socket/sftp') const wsHostStatus = require('./socket/host-status')
const wsHostStatus = require('./socket/host-status') const wsClientInfo = require('./socket/clients')
const wsClientInfo = require('./socket/clients') const { throwError } = require('./utils')
const { throwError } = require('./utils')
const httpServer = () => {
const httpServer = () => { const app = new Koa()
const app = new Koa() const server = http.createServer(app.callback())
const server = http.createServer(app.callback()) serverHandler(app, server)
serverHandler(app, server) // ws一直报跨域的错误参照官方文档使用createServer API创建服务
// ws一直报跨域的错误参照官方文档使用createServer API创建服务 server.listen(httpPort, () => {
server.listen(process.env.PORT || httpPort, () => { consola.success(`Server(http) is running on: http://localhost:${ httpPort }`)
consola.success(`Server(http) is running on: http://localhost:${ httpPort }`) })
}) }
}
const clientHttpServer = () => {
const httpsServer = () => { const app = new Koa()
if(!certificate) return consola.error('未上传证书, 创建https服务失败') const server = http.createServer(app.callback())
const app = new Koa() wsMonitorOsInfo(server) // 监控本机信息
const server = https.createServer(certificate, app.callback()) server.listen(clientPort, () => {
serverHandler(app, server) consola.success(`Client(http) is running on: http://localhost:${ clientPort }`)
server.listen(httpsPort, (err) => { })
if (err) return consola.error('https server error: ', err) }
consola.success(`Server(https) is running: https://${ domain }:${ httpsPort }`)
}) // 服务
} function serverHandler(app, server) {
app.proxy = true // 用于nginx反代时获取真实客户端ip
const clientHttpServer = () => { wsTerminal(server) // 终端
const app = new Koa() wsSftp(server) // sftp
const server = http.createServer(app.callback()) wsHostStatus(server) // 终端侧边栏host信息
wsMonitorOsInfo(server) // 监控本机信息 wsClientInfo(server) // 客户端信息
server.listen(clientPort, () => { app.context.throwError = throwError // 常用方法挂载全局ctx上
consola.success(`Client(http) is running on: http://localhost:${ clientPort }`) app.use(compose(middlewares))
}) // 捕获error.js模块抛出的服务错误
} app.on('error', (err, ctx) => {
ctx.status = 500
// 服务 ctx.body = {
function serverHandler(app, server) { status: ctx.status,
app.proxy = true // 用于nginx反代时获取真实客户端ip message: `Program Error${ err.message }`
wsTerminal(server) // 终端 }
wsSftp(server) // sftp })
wsHostStatus(server) // 终端侧边栏host信息 }
wsClientInfo(server) // 客户端信息
app.context.throwError = throwError // 常用方法挂载全局ctx上 module.exports = {
app.use(compose(middlewares)) httpServer,
// 捕获error.js模块抛出的服务错误 clientHttpServer
app.on('error', (err, ctx) => { }
ctx.status = 500
ctx.body = {
status: ctx.status,
message: `Program Error${ err.message }`
}
})
}
module.exports = {
httpServer,
httpsServer,
clientHttpServer
}

View File

@ -1,74 +1,74 @@
const { Server: ServerIO } = require('socket.io') const { Server: ServerIO } = require('socket.io')
const { io: ClientIO } = require('socket.io-client') const { io: ClientIO } = require('socket.io-client')
const { clientPort } = require('../config') const { clientPort } = require('../config')
const { verifyAuth } = require('../utils') const { verifyAuth } = require('../utils')
let hostSockets = {} let hostSockets = {}
function getHostInfo(serverSocket, host) { function getHostInfo(serverSocket, host) {
let hostSocket = ClientIO(`http://${ host }:${ clientPort }`, { let hostSocket = ClientIO(`http://${ host }:${ clientPort }`, {
path: '/client/os-info', path: '/client/os-info',
forceNew: false, forceNew: false,
timeout: 5000, timeout: 5000,
reconnectionDelay: 3000, reconnectionDelay: 3000,
reconnectionAttempts: 100 reconnectionAttempts: 100
}) })
// 将与客户端连接的socket实例保存起来web端断开时关闭与客户端的连接 // 将与客户端连接的socket实例保存起来web端断开时关闭与客户端的连接
hostSockets[serverSocket.id] = hostSocket hostSockets[serverSocket.id] = hostSocket
hostSocket hostSocket
.on('connect', () => { .on('connect', () => {
consola.success('host-status-socket连接成功:', host) consola.success('host-status-socket连接成功:', host)
hostSocket.on('client_data', (data) => { hostSocket.on('client_data', (data) => {
serverSocket.emit('host_data', data) serverSocket.emit('host_data', data)
}) })
hostSocket.on('client_error', () => { hostSocket.on('client_error', () => {
serverSocket.emit('host_data', null) serverSocket.emit('host_data', null)
}) })
}) })
.on('connect_error', (error) => { .on('connect_error', (error) => {
consola.error('host-status-socket连接[失败]:', host, error.message) consola.error('host-status-socket连接[失败]:', host, error.message)
serverSocket.emit('host_data', null) serverSocket.emit('host_data', null)
}) })
.on('disconnect', () => { .on('disconnect', () => {
consola.info('host-status-socket连接[断开]:', host) consola.info('host-status-socket连接[断开]:', host)
serverSocket.emit('host_data', null) serverSocket.emit('host_data', null)
}) })
} }
module.exports = (httpServer) => { module.exports = (httpServer) => {
const serverIo = new ServerIO(httpServer, { const serverIo = new ServerIO(httpServer, {
path: '/host-status', path: '/host-status',
cors: { cors: {
origin: '*' // 需配置跨域 origin: '*' // 需配置跨域
} }
}) })
serverIo.on('connection', (serverSocket) => { serverIo.on('connection', (serverSocket) => {
// 前者兼容nginx反代, 后者兼容nodejs自身服务 // 前者兼容nginx反代, 后者兼容nodejs自身服务
let clientIp = serverSocket.handshake.headers['x-forwarded-for'] || serverSocket.handshake.address let clientIp = serverSocket.handshake.headers['x-forwarded-for'] || serverSocket.handshake.address
serverSocket.on('init_host_data', ({ token, host }) => { serverSocket.on('init_host_data', ({ token, host }) => {
// 校验登录态 // 校验登录态
const { code, msg } = verifyAuth(token, clientIp) const { code, msg } = verifyAuth(token, clientIp)
if(code !== 1) { if(code !== 1) {
serverSocket.emit('token_verify_fail', msg || '鉴权失败') serverSocket.emit('token_verify_fail', msg || '鉴权失败')
serverSocket.disconnect() serverSocket.disconnect()
return return
} }
// 获取客户端数据 // 获取客户端数据
getHostInfo(serverSocket, host) getHostInfo(serverSocket, host)
consola.info('host-status-socket连接socketId: ', serverSocket.id, 'host-status-socket已连接数: ', Object.keys(hostSockets).length) consola.info('host-status-socket连接socketId: ', serverSocket.id, 'host-status-socket已连接数: ', Object.keys(hostSockets).length)
// 关闭连接 // 关闭连接
serverSocket.on('disconnect', () => { serverSocket.on('disconnect', () => {
// 当web端与服务端断开连接时, 服务端与每个客户端的socket也应该断开连接 // 当web端与服务端断开连接时, 服务端与每个客户端的socket也应该断开连接
let socket = hostSockets[serverSocket.id] let socket = hostSockets[serverSocket.id]
socket.close && socket.close() socket.close && socket.close()
delete hostSockets[serverSocket.id] delete hostSockets[serverSocket.id]
consola.info('host-status-socket剩余连接数: ', Object.keys(hostSockets).length) consola.info('host-status-socket剩余连接数: ', Object.keys(hostSockets).length)
}) })
}) })
}) })
} }

View File

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

View File

@ -109,21 +109,21 @@ function listenInput(sftpClient, socket) {
socket.emit('sftp_error', error.message) socket.emit('sftp_error', error.message)
} }
}) })
// socket.on('up_file', async ({ targetPath, fullPath, name, file }) => { socket.on('up_file', async ({ targetPath, fullPath, name, file }) => {
// console.log({ targetPath, fullPath, name, file }) console.log({ targetPath, fullPath, name, file })
// const exists = await sftpClient.exists(targetPath) const exists = await sftpClient.exists(targetPath)
// if(!exists) return socket.emit('not_exists_dir', '文件夹不存在或当前不可访问') if(!exists) return socket.emit('not_exists_dir', '文件夹不存在或当前不可访问')
// try { try {
// const localPath = rawPath.join(sftpCacheDir, name) const localPath = rawPath.join(sftpCacheDir, name)
// fs.writeFileSync(localPath, file) fs.writeFileSync(localPath, file)
// let res = await sftpClient.fastPut(localPath, fullPath) let res = await sftpClient.fastPut(localPath, fullPath)
// consola.success('sftp上传成功: ', res) consola.success('sftp上传成功: ', res)
// socket.emit('up_file_success', res) socket.emit('up_file_success', res)
// } catch (error) { } catch (error) {
// consola.error('up_file Error', error.message) consola.error('up_file Error', error.message)
// socket.emit('sftp_error', error.message) socket.emit('sftp_error', error.message)
// } }
// }) })
/** 分片上传 */ /** 分片上传 */
// 1. 创建本地缓存文件夹 // 1. 创建本地缓存文件夹
@ -186,7 +186,7 @@ function listenInput(sftpClient, socket) {
clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件 clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件
} catch (error) { } catch (error) {
consola.error('sftp上传失败: ', error.message) consola.error('sftp上传失败: ', error.message)
socket.emit('sftp_error', error.message) socket.emit('up_file_fail', error.message)
clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件 clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件
} }
}) })

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -1,17 +1,17 @@
<html lang="zh"> <html lang="zh">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=0,user-scalable=yes"> <meta name="viewport" content="width=device-width, initial-scale=0,user-scalable=yes">
<title>EasyNode</title> <title>EasyNode</title>
<script src="//at.alicdn.com/t/c/font_3309550_flid0kihu36.js"></script> <script src="//at.alicdn.com/t/c/font_3309550_flid0kihu36.js"></script>
<script type="module" crossorigin src="/assets/index.eb5f280e.js"></script> <script type="module" crossorigin src="/assets/index.5de3ed69.js"></script>
<link rel="stylesheet" href="/assets/index.a9194a35.css"> <link rel="stylesheet" href="/assets/index.cf7d36d4.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
</body> </body>
</html> </html>

View File

@ -1,2 +1 @@
[ []
]

View File

@ -63,7 +63,7 @@ const getNetIPInfo = async (searchIp = '') => {
let { origip: ip, location: country, city = '', regionName = '' } = res || {} let { origip: ip, location: country, city = '', regionName = '' } = res || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date }) searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
} }
console.log(searchResult) // console.log(searchResult)
let validInfo = searchResult.find(item => Boolean(item.country)) let validInfo = searchResult.find(item => Boolean(item.country))
consola.info('查询IP信息', validInfo) consola.info('查询IP信息', validInfo)
return validInfo || { ip: '获取IP信息API出错,请排查或更新API', country: '未知', city: '未知', date } return validInfo || { ip: '获取IP信息API出错,请排查或更新API', country: '未知', city: '未知', date }

0
server/bin/www Normal file → Executable file
View File