✨ 更新版本通知
This commit is contained in:
parent
c04989b951
commit
079c62b838
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ plan.md
|
|||||||
.env-encrypt-key
|
.env-encrypt-key
|
||||||
*clear.js
|
*clear.js
|
||||||
local-script
|
local-script
|
||||||
|
版本发布.md
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
## [3.0.1](https://github.com/chaos-zhu/easynode/releases) (2024-11-18)
|
||||||
|
|
||||||
|
* 修复同IP实例SFTP连接到其他的实例的bug
|
||||||
|
* 修复一些UI问题
|
||||||
|
|
||||||
|
|
||||||
## [3.0.0](https://github.com/chaos-zhu/easynode/releases) (2024-11-09)
|
## [3.0.0](https://github.com/chaos-zhu/easynode/releases) (2024-11-09)
|
||||||
|
|
||||||
* 新增跳板机功能,支持选择多台机器跳转
|
* 新增跳板机功能,支持选择多台机器跳转
|
||||||
|
@ -66,11 +66,9 @@ _✨ 一个多功能Linux服务器WEB终端面板(webSSH&webSFTP) ✨_
|
|||||||
docker run -d -p 8082:8082 --restart=always -v /root/easynode/db:/easynode/app/db chaoszhu/easynode
|
docker run -d -p 8082:8082 --restart=always -v /root/easynode/db:/easynode/app/db chaoszhu/easynode
|
||||||
```
|
```
|
||||||
环境变量:
|
环境变量:
|
||||||
- `PLUS_KEY`: 激活PLUS功能的授权码
|
|
||||||
- `DEBUG`: 启动debug日志 0:关闭 1:开启, 默认关闭
|
- `DEBUG`: 启动debug日志 0:关闭 1:开启, 默认关闭
|
||||||
- `ALLOWED_IPS`: 可以访问服务的IP白名单, 多个使用逗号分隔, 支持填写部分ip前缀, 例如: `-e ALLOWED_IPS=127.0.0.1,196.168`
|
- `ALLOWED_IPS`: 可以访问服务的IP白名单, 多个使用逗号分隔, 支持填写部分ip前缀, 例如: `-e ALLOWED_IPS=127.0.0.1,196.168`
|
||||||
|
|
||||||
|
|
||||||
## 监控服务安装
|
## 监控服务安装
|
||||||
|
|
||||||
- 监控服务用于实时向服务端&web端推送**系统、公网IP、CPU、内存、硬盘、网卡**等基础信息
|
- 监控服务用于实时向服务端&web端推送**系统、公网IP、CPU、内存、硬盘、网卡**等基础信息
|
||||||
|
@ -2,6 +2,8 @@ const jwt = require('jsonwebtoken')
|
|||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const speakeasy = require('speakeasy')
|
const speakeasy = require('speakeasy')
|
||||||
const QRCode = require('qrcode')
|
const QRCode = require('qrcode')
|
||||||
|
const version = require('../../package.json').version
|
||||||
|
const { plusServer1, plusServer2 } = require('../utils/plus-server')
|
||||||
const { sendNoticeAsync } = require('../utils/notify')
|
const { sendNoticeAsync } = require('../utils/notify')
|
||||||
const { RSADecryptAsync, AESEncryptAsync, SHA1Encrypt } = require('../utils/encrypt')
|
const { RSADecryptAsync, AESEncryptAsync, SHA1Encrypt } = require('../utils/encrypt')
|
||||||
const { getNetIPInfo } = require('../utils/tools')
|
const { getNetIPInfo } = require('../utils/tools')
|
||||||
@ -86,7 +88,7 @@ const beforeLoginHandler = async (clientIp, jwtExpires) => {
|
|||||||
let token = jwt.sign({ date: Date.now() }, commonKey, { expiresIn: jwtExpires }) // 生成token
|
let token = jwt.sign({ date: Date.now() }, commonKey, { expiresIn: jwtExpires }) // 生成token
|
||||||
token = await AESEncryptAsync(token) // 对称加密token后再传输给前端
|
token = await AESEncryptAsync(token) // 对称加密token后再传输给前端
|
||||||
|
|
||||||
// 记录客户端登录IP(用于判断是否异地且只保留最近10条)
|
// 记录客户端登录IP(用于判断是否异地且只保留最近10<EFBFBD><EFBFBD>)
|
||||||
const clientIPInfo = await getNetIPInfo(clientIp)
|
const clientIPInfo = await getNetIPInfo(clientIp)
|
||||||
const { ip, country, city } = clientIPInfo || {}
|
const { ip, country, city } = clientIPInfo || {}
|
||||||
consola.info('登录成功:', new Date(), { ip, country, city })
|
consola.info('登录成功:', new Date(), { ip, country, city })
|
||||||
@ -172,6 +174,27 @@ const getPlusInfo = async ({ res }) => {
|
|||||||
res.success({ data, msg: 'success' })
|
res.success({ data, msg: 'success' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPlusDiscount = async ({ res } = {}) => {
|
||||||
|
const servers = [plusServer1, plusServer2]
|
||||||
|
for (const server of servers) {
|
||||||
|
try {
|
||||||
|
const url = `${ server }/api/announcement/public?version=${ version }`
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${ response.status }`)
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
return res.success({ data, msg: 'success' })
|
||||||
|
} catch (error) {
|
||||||
|
if (server === servers[servers.length - 1]) {
|
||||||
|
consola.error('All servers failed:', error.message)
|
||||||
|
return res.success({ discount: false })
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
login,
|
login,
|
||||||
getpublicKey,
|
getpublicKey,
|
||||||
@ -181,5 +204,6 @@ module.exports = {
|
|||||||
getMFA2Code,
|
getMFA2Code,
|
||||||
enableMFA2,
|
enableMFA2,
|
||||||
disableMFA2,
|
disableMFA2,
|
||||||
getPlusInfo
|
getPlusInfo,
|
||||||
|
getPlusDiscount
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand, decryptPrivateKey } = require('../controller/ssh')
|
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand, decryptPrivateKey } = require('../controller/ssh')
|
||||||
const { getHostList, addHost, updateHost, batchUpdateHost, removeHost, importHost } = require('../controller/host')
|
const { getHostList, addHost, updateHost, batchUpdateHost, removeHost, importHost } = require('../controller/host')
|
||||||
const { login, getpublicKey, updatePwd, getEasynodeVersion, getMFA2Status, getMFA2Code, enableMFA2, disableMFA2, getPlusInfo } = require('../controller/user')
|
const { login, getpublicKey, updatePwd, getEasynodeVersion, getMFA2Status, getMFA2Code, enableMFA2, disableMFA2, getPlusInfo, getPlusDiscount } = 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, batchRemoveScript, importScript } = require('../controller/scripts')
|
const { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript, batchRemoveScript, importScript } = require('../controller/scripts')
|
||||||
@ -116,6 +116,11 @@ const user = [
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/plus-info',
|
path: '/plus-info',
|
||||||
controller: getPlusInfo
|
controller: getPlusInfo
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
path: '/plus-discount',
|
||||||
|
controller: getPlusDiscount
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const notify = [
|
const notify = [
|
||||||
|
@ -224,18 +224,18 @@ module.exports = (httpServer) => {
|
|||||||
let sftpClient = new SFTPClient()
|
let sftpClient = new SFTPClient()
|
||||||
consola.success('terminal websocket 已连接')
|
consola.success('terminal websocket 已连接')
|
||||||
|
|
||||||
socket.on('create', async ({ host: ip, token }) => {
|
socket.on('create', async ({ hostId, token }) => {
|
||||||
const { code } = await verifyAuthSync(token, requestIP)
|
const { code } = await verifyAuthSync(token, requestIP)
|
||||||
|
consola.log('code:', code)
|
||||||
if (code !== 1) {
|
if (code !== 1) {
|
||||||
socket.emit('token_verify_fail')
|
socket.emit('token_verify_fail')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const targetHostInfo = await hostListDB.findOneAsync({ _id: hostId })
|
||||||
const hostList = await hostListDB.findAsync({})
|
if (!targetHostInfo) throw new Error(`Host with ID ${ hostId } not found`)
|
||||||
const targetHostInfo = hostList.find(item => item.host === ip) || {}
|
|
||||||
let { authType, host, port, username } = targetHostInfo
|
let { authType, host, port, username } = targetHostInfo
|
||||||
if (!host) return socket.emit('create_fail', `查找【${ ip }】凭证信息失败`)
|
if (!host) return socket.emit('create_fail', `查找id【${ hostId }】凭证信息失败`)
|
||||||
let authInfo = { host, port, username }
|
let authInfo = { host, port, username }
|
||||||
|
|
||||||
// 解密放到try里面,防止报错【commonKey必须配对, 否则需要重新添加服务器密钥】
|
// 解密放到try里面,防止报错【commonKey必须配对, 否则需要重新添加服务器密钥】
|
||||||
|
@ -2,6 +2,7 @@ const schedule = require('node-schedule')
|
|||||||
const { getLocalNetIP } = require('./tools')
|
const { getLocalNetIP } = require('./tools')
|
||||||
const { AESEncryptAsync } = require('./encrypt')
|
const { AESEncryptAsync } = require('./encrypt')
|
||||||
const version = require('../../package.json').version
|
const version = require('../../package.json').version
|
||||||
|
const { plusServer1, plusServer2 } = require('./plus-server')
|
||||||
|
|
||||||
async function getLicenseInfo() {
|
async function getLicenseInfo() {
|
||||||
let key = process.env.PLUS_KEY
|
let key = process.env.PLUS_KEY
|
||||||
@ -28,7 +29,7 @@ async function getLicenseInfo() {
|
|||||||
let headers = { 'Content-Type': 'application/json' }
|
let headers = { 'Content-Type': 'application/json' }
|
||||||
let timeout = 10000
|
let timeout = 10000
|
||||||
try {
|
try {
|
||||||
response = await fetch('https://en1.221022.xyz/api/licenses/activate', {
|
response = await fetch(plusServer1 + '/api/licenses/activate', {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
@ -41,7 +42,7 @@ async function getLicenseInfo() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
consola.log('retry to activate plus by backup server')
|
consola.log('retry to activate plus by backup server')
|
||||||
response = await fetch('https://en2.221022.xyz/api/licenses/activate', {
|
response = await fetch(plusServer2 + '/api/licenses/activate', {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
|
4
server/app/utils/plus-server.js
Normal file
4
server/app/utils/plus-server.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
plusServer1: 'https://en1.221022.xyz',
|
||||||
|
plusServer2: 'https://en2.221022.xyz'
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "easynode-server",
|
"description": "easynode-server",
|
||||||
"bin": "./bin/www",
|
"bin": "./bin/www",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "easynode-web",
|
"description": "easynode-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -22,6 +22,9 @@ export default {
|
|||||||
getPlusInfo() {
|
getPlusInfo() {
|
||||||
return axios({ url: '/plus-info', method: 'get' })
|
return axios({ url: '/plus-info', method: 'get' })
|
||||||
},
|
},
|
||||||
|
getPlusDiscount() {
|
||||||
|
return axios({ url: '/plus-discount', method: 'get' })
|
||||||
|
},
|
||||||
getCommand(hostId) {
|
getCommand(hostId) {
|
||||||
return axios({ url: '/command', method: 'get', params: { hostId } })
|
return axios({ url: '/command', method: 'get', params: { hostId } })
|
||||||
},
|
},
|
||||||
|
BIN
web/src/assets/discount.png
Normal file
BIN
web/src/assets/discount.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
@ -20,7 +20,7 @@
|
|||||||
link
|
link
|
||||||
@click="visible = true"
|
@click="visible = true"
|
||||||
>
|
>
|
||||||
关于 <span class="new_version">{{ isNew ? `(新版本可用)` : '' }}</span>
|
版本更新 <span class="new_version">{{ isNew ? `(新版本可用)` : '' }}</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-dropdown trigger="click">
|
<el-dropdown trigger="click">
|
||||||
<span class="username_wrap">
|
<span class="username_wrap">
|
||||||
@ -40,12 +40,20 @@
|
|||||||
|
|
||||||
<el-popover placement="left" :width="320" trigger="hover">
|
<el-popover placement="left" :width="320" trigger="hover">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<img
|
<div class="plus_icon_wrapper">
|
||||||
class="plus_icon"
|
<img
|
||||||
src="@/assets/plus.png"
|
class="plus_icon"
|
||||||
alt="PLUS"
|
src="@/assets/plus.png"
|
||||||
:style="{ filter: isPlusActive ? 'grayscale(0%)' : 'grayscale(100%)' }"
|
alt="PLUS"
|
||||||
>
|
:style="{ filter: isPlusActive ? 'grayscale(0%)' : 'grayscale(100%)' }"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="!isPlusActive && discount"
|
||||||
|
class="discount_badge"
|
||||||
|
src="@/assets/discount.png"
|
||||||
|
alt="Discount"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="plus_content_wrap">
|
<div class="plus_content_wrap">
|
||||||
@ -87,10 +95,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plus_benefits" :class="{ active: isPlusActive }" @click="handlePlus">
|
<div class="plus_benefits" :class="{ active: isPlusActive }" @click="handlePlus">
|
||||||
<span v-if="!isPlusActive" class="support_btn" @click="handlePlusSupport">去支持</span>
|
<span v-if="!isPlusActive" class="support_btn" @click="handlePlusSupport">激活PLUS</span>
|
||||||
|
<div v-if="!isPlusActive && discount" class="discount_content" @click="handlePlusSupport">
|
||||||
|
<el-tag type="warning" effect="dark">
|
||||||
|
<el-icon><Promotion /></el-icon>
|
||||||
|
<span>{{ discountContent || '限时优惠活动' }}</span>
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
<div class="benefits_header">
|
<div class="benefits_header">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<el-icon><StarFilled /></el-icon>
|
<StarFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>Plus功能介绍</span>
|
<span>Plus功能介绍</span>
|
||||||
</div>
|
</div>
|
||||||
@ -103,7 +117,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="coming_soon">
|
<!-- <div class="coming_soon">
|
||||||
<div class="soon_header">开发中的PLUS功能</div>
|
<div class="soon_header">开发中的PLUS功能</div>
|
||||||
<div class="current_benefits">
|
<div class="current_benefits">
|
||||||
<div v-for="soonFeature in soonFeatures" :key="soonFeature" class="benefit_item">
|
<div v-for="soonFeature in soonFeatures" :key="soonFeature" class="benefit_item">
|
||||||
@ -113,7 +127,7 @@
|
|||||||
<span>{{ soonFeature }}</span>
|
<span>{{ soonFeature }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -122,15 +136,16 @@
|
|||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="关于"
|
title="版本更新"
|
||||||
top="10vh"
|
top="10vh"
|
||||||
width="30%"
|
width="30%"
|
||||||
:append-to-body="false"
|
:append-to-body="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
>
|
>
|
||||||
<div class="about_content">
|
<div class="about_content">
|
||||||
<h1>EasyNode</h1>
|
<!-- <h1>EasyNode</h1> -->
|
||||||
<p>当前版本: {{ currentVersion }} <span v-show="!isNew">(最新)</span> </p>
|
<p>当前版本: {{ currentVersion }} <span v-show="!isNew">(最新)</span> </p>
|
||||||
<p v-if="checkVersionErr" class="conspicuous">Error:版本更新检测失败(版本检测API需要外网环境)</p>
|
<p v-if="checkVersionErr" class="conspicuous">Error:版本更新检测失败(版本检测API需要外网环境),请手动访问GitHub查看</p>
|
||||||
<p v-if="isNew" class="conspicuous">
|
<p v-if="isNew" class="conspicuous">
|
||||||
新版本可用: {{ latestVersion }} -> <a
|
新版本可用: {{ latestVersion }} -> <a
|
||||||
class="link"
|
class="link"
|
||||||
@ -139,14 +154,14 @@
|
|||||||
>https://github.com/chaos-zhu/easynode/releases</a>
|
>https://github.com/chaos-zhu/easynode/releases</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
更新日志:<a
|
功能更新日志:<a
|
||||||
class="link"
|
class="link"
|
||||||
href="https://github.com/chaos-zhu/easynode/blob/main/CHANGELOG.md"
|
href="https://github.com/chaos-zhu/easynode/blob/main/CHANGELOG.md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>https://github.com/chaos-zhu/easynode/blob/main/CHANGELOG.md</a>
|
>https://github.com/chaos-zhu/easynode/blob/main/CHANGELOG.md</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
tg更新通知:<a class="link" href="https://t.me/easynode_notify" target="_blank">https://t.me/easynode_notify</a>
|
TG更新通知频道:<a class="link" href="https://t.me/easynode_notify" target="_blank">https://t.me/easynode_notify</a>
|
||||||
</p>
|
</p>
|
||||||
<p style="line-height: 2;letter-spacing: 1px;">
|
<p style="line-height: 2;letter-spacing: 1px;">
|
||||||
<strong style="color: #F56C6C;font-weight: 600;">PLUS说明:</strong><br>
|
<strong style="color: #F56C6C;font-weight: 600;">PLUS说明:</strong><br>
|
||||||
@ -156,11 +171,12 @@
|
|||||||
为了项目的可持续发展,从<strong>3.0.0</strong>版本开始推出了<strong>PLUS</strong>版本,具体特性鼠标悬浮右上角PLUS图标查看,后续特性功能开发也会优先在<strong>PLUS</strong>版本中实现,但即使不升级到<strong>PLUS</strong>,也不会影响到<strong>EasyNode</strong>的基础功能使用【注意: 暂不支持纯内网用户激活PLUS功能】。
|
为了项目的可持续发展,从<strong>3.0.0</strong>版本开始推出了<strong>PLUS</strong>版本,具体特性鼠标悬浮右上角PLUS图标查看,后续特性功能开发也会优先在<strong>PLUS</strong>版本中实现,但即使不升级到<strong>PLUS</strong>,也不会影响到<strong>EasyNode</strong>的基础功能使用【注意: 暂不支持纯内网用户激活PLUS功能】。
|
||||||
<br>
|
<br>
|
||||||
<span style="text-decoration: underline;">
|
<span style="text-decoration: underline;">
|
||||||
为了感谢前期赞赏过的用户, 在<strong>PLUS</strong>功能正式发布前,所有进行过赞赏的用户,无论金额大小,均可联系作者TG: <a class="link" href="https://t.me/chaoszhu" target="_blank">@chaoszhu</a> 凭打赏记录获取永久<strong>PLUS</strong>授权码。
|
为了感谢前期赞赏过的用户, 在<strong>PLUS</strong>功能正式发布前,所有进行过赞赏的用户,无论金额大小,均可联系作者TG: <a class="link" href="https://t.me/chaoszhu" target="_blank">@chaoszhu</a> 凭打赏记录免费获取永久<strong>PLUS</strong>授权码。
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="!isPlusActive" class="about_footer">
|
<div class="about_footer">
|
||||||
<el-button type="primary" @click="handlePlusSupport">去支持</el-button>
|
<el-button type="info" @click="visible = false">关闭</el-button>
|
||||||
|
<el-button v-if="!isPlusActive" type="primary" @click="handlePlusSupport">激活PLUS</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -181,18 +197,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, getCurrentInstance, computed } from 'vue'
|
import { ref, getCurrentInstance, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { User, Sunny, Moon, Fold, CircleCheckFilled, Star, StarFilled } from '@element-plus/icons-vue'
|
import { User, Sunny, Moon, Fold, CircleCheckFilled, Star, StarFilled, Promotion } from '@element-plus/icons-vue'
|
||||||
import packageJson from '../../package.json'
|
import packageJson from '../../package.json'
|
||||||
import MenuList from './menuList.vue'
|
import MenuList from './menuList.vue'
|
||||||
|
|
||||||
const { proxy: { $router, $store, $message } } = getCurrentInstance()
|
const { proxy: { $router, $store, $api, $message } } = getCurrentInstance()
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const checkVersionErr = ref(false)
|
const checkVersionErr = ref(false)
|
||||||
const currentVersion = ref(`v${ packageJson.version }`)
|
const currentVersion = ref(`v${ packageJson.version }`)
|
||||||
const latestVersion = ref(null)
|
const latestVersion = ref(null)
|
||||||
const menuCollapse = ref(false)
|
const menuCollapse = ref(false)
|
||||||
|
const discount = ref(false)
|
||||||
|
const discountContent = ref('')
|
||||||
|
|
||||||
const plusFeatures = [
|
const plusFeatures = [
|
||||||
'跳板机功能,拯救被墙实例与龟速终端输入',
|
'跳板机功能,拯救被墙实例与龟速终端输入',
|
||||||
@ -202,11 +220,11 @@ const plusFeatures = [
|
|||||||
'凭据管理支持解密带密码保护的密钥',
|
'凭据管理支持解密带密码保护的密钥',
|
||||||
'提出的功能需求享有更高的开发优先级',
|
'提出的功能需求享有更高的开发优先级',
|
||||||
]
|
]
|
||||||
const soonFeatures = [
|
// const soonFeatures = [
|
||||||
'终端脚本变量及终端脚本输入优化',
|
// '终端脚本变量及终端脚本输入优化',
|
||||||
'终端分屏功能',
|
// '终端分屏功能',
|
||||||
'系统操作日志审计',
|
// '系统操作日志审计',
|
||||||
]
|
// ]
|
||||||
|
|
||||||
const isNew = computed(() => latestVersion.value && latestVersion.value !== currentVersion.value)
|
const isNew = computed(() => latestVersion.value && latestVersion.value !== currentVersion.value)
|
||||||
const user = computed(() => $store.user)
|
const user = computed(() => $store.user)
|
||||||
@ -268,6 +286,33 @@ async function checkLatestVersion() {
|
|||||||
|
|
||||||
checkLatestVersion()
|
checkLatestVersion()
|
||||||
|
|
||||||
|
let timer = null
|
||||||
|
const checkFirstVisit = () => {
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
const visitedVersion = localStorage.getItem('visitedVersion')
|
||||||
|
if (!visitedVersion || visitedVersion !== currentVersion.value) {
|
||||||
|
visible.value = true
|
||||||
|
localStorage.setItem('visitedVersion', currentVersion.value)
|
||||||
|
}
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlusDiscount = async () => {
|
||||||
|
const { data } = await $api.getPlusDiscount()
|
||||||
|
if (data?.discount) {
|
||||||
|
discount.value = data.discount
|
||||||
|
discountContent.value = data.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkFirstVisit()
|
||||||
|
getPlusDiscount()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -314,10 +359,26 @@ checkLatestVersion()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plus_icon {
|
.plus_icon_wrapper {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
width: 35px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.plus_icon {
|
||||||
|
width: 35px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount_badge {
|
||||||
|
width: 22px;
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
transform: rotate(25deg);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,6 +415,18 @@ checkLatestVersion()
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: rotate(25deg) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate(25deg) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(25deg) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -460,5 +533,19 @@ checkLatestVersion()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.discount_content {
|
||||||
|
margin: 8px 0;
|
||||||
|
|
||||||
|
.el-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 12px;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -168,7 +168,7 @@ import unknowIcon from '@/assets/image/system/unknow.png'
|
|||||||
const { io } = socketIo
|
const { io } = socketIo
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
host: {
|
hostId: {
|
||||||
required: true,
|
required: true,
|
||||||
type: String
|
type: String
|
||||||
}
|
}
|
||||||
@ -270,7 +270,7 @@ const connectSftp = () => {
|
|||||||
socket.value.on('connect', () => {
|
socket.value.on('connect', () => {
|
||||||
console.log('/sftp socket已连接:', socket.value.id)
|
console.log('/sftp socket已连接:', socket.value.id)
|
||||||
listenSftp()
|
listenSftp()
|
||||||
socket.value.emit('create', { host: props.host, token: token.value })
|
socket.value.emit('create', { hostId: props.hostId, token: token.value })
|
||||||
socket.value.on('root_ls', (tree) => {
|
socket.value.on('root_ls', (tree) => {
|
||||||
let temp = sortDirTree(tree).filter((item) => isDir(item.type))
|
let temp = sortDirTree(tree).filter((item) => isDir(item.type))
|
||||||
temp.unshift({ name: '/', type: 'd' })
|
temp.unshift({ name: '/', type: 'd' })
|
||||||
|
@ -167,7 +167,7 @@
|
|||||||
<Sftp
|
<Sftp
|
||||||
v-if="showSftp"
|
v-if="showSftp"
|
||||||
ref="sftpRefs"
|
ref="sftpRefs"
|
||||||
:host="item.host"
|
:host-id="item.id"
|
||||||
@resize="resizeTerminal"
|
@resize="resizeTerminal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user