13
README.md
13
README.md
@@ -51,12 +51,13 @@
|
|||||||
|
|
||||||
## 演示环境
|
## 演示环境
|
||||||
|
|
||||||
演示地址: http://101.43.254.243:1081/
|
* 🔗 演示地址: http://101.43.254.243:1081/
|
||||||
演示账号: admin/admin
|
* 🔏 演示账号: admin/admin
|
||||||
|
* ⭐ 体验后可以点一下 `star` 这对我很重要! [github](https://github.com/lijiahangmax/orion-visor) [gitee](https://gitee.com/lijiahangmax/orion-visor)
|
||||||
⭐ 体验后可以点一下 `star` 这对我很重要!
|
* 🌈 如果本项目对你有帮助请帮忙推广一下 让更多的人知道此项目!
|
||||||
🌈 如果本项目对你有帮助请帮忙推广一下 让更多的人知道此项目!
|
* 🎭 演示环境部分功能不可用, 完整功能请本地部署!
|
||||||
[github](https://github.com/lijiahangmax/orion-visor) [gitee](https://gitee.com/lijiahangmax/orion-visor)
|
* 📛 演示环境请不要随便删除数据!
|
||||||
|
* 📧 如果演示环境不可用请联系我!
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
services:
|
services:
|
||||||
orion-visor-service:
|
orion-visor-service:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.5
|
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.6
|
||||||
ports:
|
ports:
|
||||||
- 1081:80
|
- 1081:80
|
||||||
environment:
|
environment:
|
||||||
@@ -20,7 +20,7 @@ services:
|
|||||||
- orion-visor-mysql
|
- orion-visor-mysql
|
||||||
- orion-visor-redis
|
- orion-visor-redis
|
||||||
orion-visor-mysql:
|
orion-visor-mysql:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.5
|
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.6
|
||||||
privileged: true
|
privileged: true
|
||||||
ports:
|
ports:
|
||||||
- 3307:3306
|
- 3307:3306
|
||||||
@@ -34,7 +34,7 @@ services:
|
|||||||
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/var-lib-mysql-files:/var/lib/mysql-files
|
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/var-lib-mysql-files:/var/lib/mysql-files
|
||||||
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/etc-mysql:/etc/mysql
|
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/etc-mysql:/etc/mysql
|
||||||
orion-visor-redis:
|
orion-visor-redis:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.5
|
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.6
|
||||||
privileged: true
|
privileged: true
|
||||||
ports:
|
ports:
|
||||||
- 6380:6379
|
- 6380:6379
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#/bin/bash
|
#/bin/bash
|
||||||
version=2.0.5
|
version=2.0.6
|
||||||
cp -r ../../sql ./sql
|
cp -r ../../sql ./sql
|
||||||
docker build -t orion-visor-mysql:${version} .
|
docker build -t orion-visor-mysql:${version} .
|
||||||
rm -rf ./sql
|
rm -rf ./sql
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#/bin/bash
|
#/bin/bash
|
||||||
version=2.0.5
|
version=2.0.6
|
||||||
docker build -t orion-visor-redis:${version} .
|
docker build -t orion-visor-redis:${version} .
|
||||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
|
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
|
||||||
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
|
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#/bin/bash
|
#/bin/bash
|
||||||
version=2.0.5
|
version=2.0.6
|
||||||
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
|
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
|
||||||
mv ../../orion-visor-ui/dist ./dist
|
mv ../../orion-visor-ui/dist ./dist
|
||||||
docker build -t orion-visor-service:${version} .
|
docker build -t orion-visor-service:${version} .
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<url>https://github.com/lijiahangmax/orion-visor</url>
|
<url>https://github.com/lijiahangmax/orion-visor</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2.0.5</revision>
|
<revision>2.0.6</revision>
|
||||||
<spring.boot.version>2.7.17</spring.boot.version>
|
<spring.boot.version>2.7.17</spring.boot.version>
|
||||||
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
|
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
|
||||||
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
|
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public interface AppConst extends OrionConst {
|
|||||||
/**
|
/**
|
||||||
* 同 ${orion.version} 迭代时候需要手动更改
|
* 同 ${orion.version} 迭代时候需要手动更改
|
||||||
*/
|
*/
|
||||||
String VERSION = "2.0.5";
|
String VERSION = "2.0.6";
|
||||||
|
|
||||||
String ORION_VISOR = "orion-visor";
|
String ORION_VISOR = "orion-visor";
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ public interface Const extends com.orion.lang.constant.Const, FieldConst, CnCons
|
|||||||
|
|
||||||
Integer DEFAULT_SORT = 10;
|
Integer DEFAULT_SORT = 10;
|
||||||
|
|
||||||
int LOGIN_HISTORY_COUNT = 30;
|
|
||||||
|
|
||||||
Long NONE_ID = -1L;
|
Long NONE_ID = -1L;
|
||||||
|
|
||||||
Integer DEFAULT_VERSION = 1;
|
Integer DEFAULT_VERSION = 1;
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ public class MineController {
|
|||||||
@IgnoreLog(IgnoreLogMode.RET)
|
@IgnoreLog(IgnoreLogMode.RET)
|
||||||
@GetMapping("/login-history")
|
@GetMapping("/login-history")
|
||||||
@Operation(summary = "查询当前用户登录日志")
|
@Operation(summary = "查询当前用户登录日志")
|
||||||
public List<LoginHistoryVO> getCurrentLoginHistory() {
|
public List<LoginHistoryVO> getCurrentLoginHistory(@RequestParam("count") Integer count) {
|
||||||
return mineService.getCurrentLoginHistory();
|
return mineService.getCurrentLoginHistory(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@IgnoreLog(IgnoreLogMode.RET)
|
@IgnoreLog(IgnoreLogMode.RET)
|
||||||
|
|||||||
@@ -182,8 +182,9 @@ public class SystemUserController {
|
|||||||
@GetMapping("/login-history")
|
@GetMapping("/login-history")
|
||||||
@Operation(summary = "查询用户登录日志")
|
@Operation(summary = "查询用户登录日志")
|
||||||
@PreAuthorize("@ss.hasPermission('infra:system-user:login-history')")
|
@PreAuthorize("@ss.hasPermission('infra:system-user:login-history')")
|
||||||
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username) {
|
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username,
|
||||||
return operatorLogService.getLoginHistory(username);
|
@RequestParam("count") Integer count) {
|
||||||
|
return operatorLogService.getLoginHistory(username, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ public interface MineService {
|
|||||||
/**
|
/**
|
||||||
* 获取当前用户登录日志
|
* 获取当前用户登录日志
|
||||||
*
|
*
|
||||||
|
* @param count count
|
||||||
* @return 登录日志
|
* @return 登录日志
|
||||||
*/
|
*/
|
||||||
List<LoginHistoryVO> getCurrentLoginHistory();
|
List<LoginHistoryVO> getCurrentLoginHistory(Integer count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前用户会话列表
|
* 获取当前用户会话列表
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ public interface OperatorLogService {
|
|||||||
* 查询用户登录日志
|
* 查询用户登录日志
|
||||||
*
|
*
|
||||||
* @param username username
|
* @param username username
|
||||||
|
* @param count count
|
||||||
* @return rows
|
* @return rows
|
||||||
*/
|
*/
|
||||||
List<LoginHistoryVO> getLoginHistory(String username);
|
List<LoginHistoryVO> getLoginHistory(String username, Integer count);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ public class MineServiceImpl implements MineService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<LoginHistoryVO> getCurrentLoginHistory() {
|
public List<LoginHistoryVO> getCurrentLoginHistory(Integer count) {
|
||||||
String username = SecurityUtils.getLoginUsername();
|
String username = SecurityUtils.getLoginUsername();
|
||||||
return operatorLogService.getLoginHistory(username);
|
return operatorLogService.getLoginHistory(username, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import com.alibaba.fastjson.JSON;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.orion.lang.define.wrapper.DataGrid;
|
import com.orion.lang.define.wrapper.DataGrid;
|
||||||
import com.orion.lang.utils.Arrays1;
|
import com.orion.lang.utils.Arrays1;
|
||||||
|
import com.orion.lang.utils.Valid;
|
||||||
import com.orion.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
import com.orion.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||||
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||||
import com.orion.visor.framework.common.constant.Const;
|
import com.orion.visor.framework.common.constant.ErrorMessage;
|
||||||
import com.orion.visor.module.infra.convert.OperatorLogConvert;
|
import com.orion.visor.module.infra.convert.OperatorLogConvert;
|
||||||
import com.orion.visor.module.infra.dao.OperatorLogDAO;
|
import com.orion.visor.module.infra.dao.OperatorLogDAO;
|
||||||
import com.orion.visor.module.infra.define.operator.AuthenticationOperatorType;
|
import com.orion.visor.module.infra.define.operator.AuthenticationOperatorType;
|
||||||
@@ -81,7 +82,8 @@ public class OperatorLogServiceImpl implements OperatorLogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<LoginHistoryVO> getLoginHistory(String username) {
|
public List<LoginHistoryVO> getLoginHistory(String username, Integer count) {
|
||||||
|
Valid.gt(count, 0, ErrorMessage.PARAM_ERROR);
|
||||||
// 条件
|
// 条件
|
||||||
OperatorLogQueryRequest request = new OperatorLogQueryRequest();
|
OperatorLogQueryRequest request = new OperatorLogQueryRequest();
|
||||||
request.setUsername(username);
|
request.setUsername(username);
|
||||||
@@ -89,7 +91,7 @@ public class OperatorLogServiceImpl implements OperatorLogService {
|
|||||||
LambdaQueryWrapper<OperatorLogDO> wrapper = this.buildQueryWrapper(request);
|
LambdaQueryWrapper<OperatorLogDO> wrapper = this.buildQueryWrapper(request);
|
||||||
// 查询
|
// 查询
|
||||||
return operatorLogDAO.of(wrapper)
|
return operatorLogDAO.of(wrapper)
|
||||||
.limit(Const.LOGIN_HISTORY_COUNT)
|
.limit(count)
|
||||||
.list(OperatorLogConvert.MAPPER::toLoginHistory);
|
.list(OperatorLogConvert.MAPPER::toLoginHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api'
|
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api'
|
||||||
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
|
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
|
||||||
VITE_APP_VERSION= '2.0.5'
|
VITE_APP_VERSION= '2.0.6'
|
||||||
|
VITE_APP_RELEASE= 'Community'
|
||||||
VITE_SFTP_PREVIEW_MB= 2
|
VITE_SFTP_PREVIEW_MB= 2
|
||||||
VITE_DEMO_MODE= false
|
VITE_DEMO_MODE= false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
VITE_API_BASE_URL= '/orion-visor/api'
|
VITE_API_BASE_URL= '/orion-visor/api'
|
||||||
VITE_WS_BASE_URL= '/orion-visor/keep-alive'
|
VITE_WS_BASE_URL= '/orion-visor/keep-alive'
|
||||||
VITE_APP_VERSION= '2.0.5'
|
VITE_APP_VERSION= '2.0.6'
|
||||||
|
VITE_APP_RELEASE= 'Community'
|
||||||
VITE_SFTP_PREVIEW_MB= 2
|
VITE_SFTP_PREVIEW_MB= 2
|
||||||
VITE_DEMO_MODE= false
|
VITE_DEMO_MODE= false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "orion-visor-ui",
|
"name": "orion-visor-ui",
|
||||||
"description": "Orion Visor UI",
|
"description": "Orion Visor UI",
|
||||||
"version": "2.0.5",
|
"version": "2.0.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": "Jiahang Li",
|
"author": "Jiahang Li",
|
||||||
"license": "Apache 2.0",
|
"license": "Apache 2.0",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function deleteHostSftpLog(idList: Array<number>) {
|
|||||||
/**
|
/**
|
||||||
* 下载文件
|
* 下载文件
|
||||||
*/
|
*/
|
||||||
export function downloadWithTransferToken(channelId: string, transferToken: string) {
|
export function getDownloadTransferUrl(channelId: string, transferToken: string) {
|
||||||
window.open(`${httpBaseUrl}/asset/host-sftp/download?channelId=${channelId}&transferToken=${transferToken}`, 'newWindow');
|
return `${httpBaseUrl}/asset/host-sftp/download?channelId=${channelId}&transferToken=${transferToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export function updateCurrentUser(request: UserUpdateRequest) {
|
|||||||
/**
|
/**
|
||||||
* 查询当前用户登录日志
|
* 查询当前用户登录日志
|
||||||
*/
|
*/
|
||||||
export function getCurrentLoginHistory() {
|
export function getCurrentLoginHistory(count: number) {
|
||||||
return axios.get<LoginHistoryQueryResponse[]>('/infra/mine/login-history');
|
return axios.get<LoginHistoryQueryResponse[]>('/infra/mine/login-history', { params: { count } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -192,6 +192,6 @@ export function offlineUserSession(request: UserSessionOfflineRequest) {
|
|||||||
/**
|
/**
|
||||||
* 查询登录日志
|
* 查询登录日志
|
||||||
*/
|
*/
|
||||||
export function getLoginHistory(username: string) {
|
export function getLoginHistory(username: string, count: number) {
|
||||||
return axios.get<LoginHistoryQueryResponse[]>('/infra/system-user/login-history', { params: { username } });
|
return axios.get<LoginHistoryQueryResponse[]>('/infra/system-user/login-history', { params: { username, count } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<a-link target="_blank" href="https://gitee.com/lijiahangmax/orion-visor">gitee</a-link>
|
<a-link target="_blank" href="https://gitee.com/lijiahangmax/orion-visor">gitee</a-link>
|
||||||
<a-link target="_blank" href="https://lijiahangmax.github.io/open-orion/orion-visor">文档</a-link>
|
<a-link target="_blank" href="https://lijiahangmax.github.io/open-orion/orion-visor">文档</a-link>
|
||||||
<a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor/blob/main/LICENSE">License</a-link>
|
<a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor/blob/main/LICENSE">License</a-link>
|
||||||
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">v{{ version }} Community</a-link>
|
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">v{{ version }} {{ release }}</a-link>
|
||||||
</a-space>
|
</a-space>
|
||||||
<span class="copyright">
|
<span class="copyright">
|
||||||
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Li Jiahang, All rights reserved.
|
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Li Jiahang, All rights reserved.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const version = import.meta.env.VITE_APP_VERSION;
|
const version = import.meta.env.VITE_APP_VERSION;
|
||||||
|
const release = import.meta.env.VITE_APP_RELEASE;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
1
orion-visor-ui/src/env.d.ts
vendored
1
orion-visor-ui/src/env.d.ts
vendored
@@ -19,6 +19,7 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_API_BASE_URL: string;
|
readonly VITE_API_BASE_URL: string;
|
||||||
readonly VITE_WS_BASE_URL: string;
|
readonly VITE_WS_BASE_URL: string;
|
||||||
readonly VITE_APP_VERSION: string;
|
readonly VITE_APP_VERSION: string;
|
||||||
|
readonly VITE_APP_RELEASE: string;
|
||||||
readonly VITE_SFTP_PREVIEW_MB: string;
|
readonly VITE_SFTP_PREVIEW_MB: string;
|
||||||
readonly VITE_DEMO_MODE: string;
|
readonly VITE_DEMO_MODE: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,27 @@ export function getParentPath(path: string) {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开下载文件
|
||||||
|
*/
|
||||||
|
export function openDownloadFile(url: string) {
|
||||||
|
try {
|
||||||
|
// 创建隐藏的可下载链接
|
||||||
|
let element = document.createElement('a');
|
||||||
|
element.setAttribute('href', url);
|
||||||
|
element.setAttribute('download', '');
|
||||||
|
element.style.display = 'none';
|
||||||
|
// 将其附加到文档中
|
||||||
|
document.body.appendChild(element);
|
||||||
|
// 点击该下载链接
|
||||||
|
element.click();
|
||||||
|
// 移除已下载的链接
|
||||||
|
document.body.removeChild(element);
|
||||||
|
} catch (e) {
|
||||||
|
window.open(url, 'newWindow');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载文件
|
* 下载文件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// 执行
|
// 历史数量
|
||||||
export const historyCount = 20;
|
export const historyCount = 15;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import type { ISftpTransferManager, ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
|
import type { ISftpTransferManager, ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
|
||||||
import { ISftpTransferDownloader, SftpFile, TransferOperatorResponse } from '../types/terminal.type';
|
import { ISftpTransferDownloader, SftpFile, TransferOperatorResponse } from '../types/terminal.type';
|
||||||
import { TransferReceiverType, TransferStatus, TransferType } from '../types/terminal.const';
|
import { sessionCloseMsg, TransferReceiverType, TransferStatus, TransferType } from '../types/terminal.const';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { getTerminalAccessToken, openHostTransferChannel } from '@/api/asset/host-terminal';
|
import { getTerminalAccessToken, openHostTransferChannel } from '@/api/asset/host-terminal';
|
||||||
import { nextId } from '@/utils';
|
import { nextId } from '@/utils';
|
||||||
import { downloadWithTransferToken } from '@/api/asset/host-sftp';
|
import { getDownloadTransferUrl } from '@/api/asset/host-sftp';
|
||||||
import SftpTransferUploader from './sftp-transfer-uploader';
|
import SftpTransferUploader from './sftp-transfer-uploader';
|
||||||
import SftpTransferDownloader from './sftp-transfer-downloader';
|
import SftpTransferDownloader from './sftp-transfer-downloader';
|
||||||
|
import { openDownloadFile } from '@/utils/file';
|
||||||
|
|
||||||
// sftp 传输管理器实现
|
// sftp 传输管理器实现
|
||||||
export default class SftpTransferManager implements ISftpTransferManager {
|
export default class SftpTransferManager implements ISftpTransferManager {
|
||||||
@@ -257,7 +258,10 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
|
|
||||||
// 接收开始下载响应
|
// 接收开始下载响应
|
||||||
private resolveDownloadStart(data: TransferOperatorResponse) {
|
private resolveDownloadStart(data: TransferOperatorResponse) {
|
||||||
downloadWithTransferToken(data.channelId as string, data.transferToken as string);
|
// 获取下载 url
|
||||||
|
const url = getDownloadTransferUrl(data.channelId as string, data.transferToken as string);
|
||||||
|
// 打开
|
||||||
|
openDownloadFile(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接收下载进度响应
|
// 接收下载进度响应
|
||||||
@@ -287,6 +291,14 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
this.run = false;
|
this.run = false;
|
||||||
// 关闭传输进度
|
// 关闭传输进度
|
||||||
clearInterval(this.progressIntervalId);
|
clearInterval(this.progressIntervalId);
|
||||||
|
// 进行中和等待中的文件改为失败
|
||||||
|
this.transferList.forEach(s => {
|
||||||
|
if (s.status === TransferStatus.WAITING ||
|
||||||
|
s.status === TransferStatus.TRANSFERRING) {
|
||||||
|
s.status = TransferStatus.ERROR;
|
||||||
|
s.errorMessage = sessionCloseMsg;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { InputPayload, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload, Protocol, } from '../types/terminal.type';
|
import type { InputPayload, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, Protocol, } from '../types/terminal.type';
|
||||||
import { OutputProtocol } from '../types/terminal.protocol';
|
import { format, OutputProtocol, parse } from '../types/terminal.protocol';
|
||||||
|
import { sessionCloseMsg } from '../types/terminal.const';
|
||||||
import { getTerminalAccessToken, openHostTerminalChannel } from '@/api/asset/host-terminal';
|
import { getTerminalAccessToken, openHostTerminalChannel } from '@/api/asset/host-terminal';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import TerminalOutputProcessor from './terminal-output-processor';
|
import TerminalOutputProcessor from './terminal-output-processor';
|
||||||
@@ -9,9 +10,12 @@ export default class TerminalChannel implements ITerminalChannel {
|
|||||||
|
|
||||||
private client?: WebSocket;
|
private client?: WebSocket;
|
||||||
|
|
||||||
|
private readonly sessionManager: ITerminalSessionManager;
|
||||||
|
|
||||||
private readonly processor: ITerminalOutputProcessor;
|
private readonly processor: ITerminalOutputProcessor;
|
||||||
|
|
||||||
constructor(sessionManager: ITerminalSessionManager) {
|
constructor(sessionManager: ITerminalSessionManager) {
|
||||||
|
this.sessionManager = sessionManager;
|
||||||
this.processor = new TerminalOutputProcessor(sessionManager, this);
|
this.processor = new TerminalOutputProcessor(sessionManager, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ export default class TerminalChannel implements ITerminalChannel {
|
|||||||
}
|
}
|
||||||
this.client.onclose = event => {
|
this.client.onclose = event => {
|
||||||
console.warn('terminal close', event);
|
console.warn('terminal close', event);
|
||||||
|
// 关闭回调
|
||||||
|
this.closeCallback();
|
||||||
};
|
};
|
||||||
this.client.onmessage = this.handlerMessage.bind(this);
|
this.client.onmessage = this.handlerMessage.bind(this);
|
||||||
}
|
}
|
||||||
@@ -66,6 +72,25 @@ export default class TerminalChannel implements ITerminalChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭回调
|
||||||
|
private closeCallback(): void {
|
||||||
|
// 关闭时将手动触发 close 消息, 有可能是其他原因关闭的, 没有接收到 close 消息, 导致已断开是终端还是显示已连接
|
||||||
|
Object.values(this.sessionManager.sessions).forEach(s => {
|
||||||
|
if (!s?.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// close 消息
|
||||||
|
const data = format(OutputProtocol.CLOSE, {
|
||||||
|
type: OutputProtocol.CLOSE.type,
|
||||||
|
sessionId: s.sessionId,
|
||||||
|
forceClose: 0,
|
||||||
|
msg: sessionCloseMsg,
|
||||||
|
});
|
||||||
|
// 触发 close 消息
|
||||||
|
this.handlerMessage({ data } as MessageEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭
|
// 关闭
|
||||||
close(): void {
|
close(): void {
|
||||||
// 关闭 client
|
// 关闭 client
|
||||||
@@ -78,55 +103,3 @@ export default class TerminalChannel implements ITerminalChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分隔符
|
|
||||||
export const SEPARATOR = '|';
|
|
||||||
|
|
||||||
// 解析参数
|
|
||||||
export const parse = (payload: string) => {
|
|
||||||
const protocols = Object.values(OutputProtocol);
|
|
||||||
const useProtocol = protocols.find(p => payload.startsWith(p.type + SEPARATOR) || p.type === payload);
|
|
||||||
if (!useProtocol) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const template = useProtocol.template;
|
|
||||||
const res = {} as OutputPayload;
|
|
||||||
let curr = 0;
|
|
||||||
let len = payload.length;
|
|
||||||
for (let i = 0, pl = template.length; i < pl; i++) {
|
|
||||||
if (i == pl - 1) {
|
|
||||||
// 最后一次
|
|
||||||
res[template[i]] = payload.substring(curr, len);
|
|
||||||
} else {
|
|
||||||
// 非最后一次
|
|
||||||
let tmp = '';
|
|
||||||
for (; curr < len; curr++) {
|
|
||||||
const c = payload.charAt(curr);
|
|
||||||
if (c == SEPARATOR) {
|
|
||||||
res[template[i]] = tmp;
|
|
||||||
curr++;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
tmp += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化参数
|
|
||||||
export const format = (protocol: Protocol, payload: InputPayload) => {
|
|
||||||
payload.type = protocol.type;
|
|
||||||
return protocol.template
|
|
||||||
.map(i => getPayloadValueString(payload[i]))
|
|
||||||
.join(SEPARATOR);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取默认值
|
|
||||||
export const getPayloadValueString = (value: unknown): any => {
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理关闭消息
|
// 处理关闭消息
|
||||||
processClose({ sessionId, msg, forceClose }: OutputPayload): void {
|
processClose({ sessionId, forceClose, msg }: OutputPayload): void {
|
||||||
const session = this.sessionManager.getSession(sessionId);
|
const session = this.sessionManager.getSession(sessionId);
|
||||||
// 无需处理 (直接关闭 tab)
|
// 无需处理 (直接关闭 tab)
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|||||||
@@ -19,17 +19,17 @@ import SftpSession from './sftp-session';
|
|||||||
// 终端会话管理器实现
|
// 终端会话管理器实现
|
||||||
export default class TerminalSessionManager implements ITerminalSessionManager {
|
export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||||
|
|
||||||
private readonly channel: ITerminalChannel;
|
public sessions: Record<string, ITerminalSession>;
|
||||||
|
|
||||||
private sessions: Record<string, ITerminalSession>;
|
private readonly channel: ITerminalChannel;
|
||||||
|
|
||||||
private keepAliveTaskId?: any;
|
private keepAliveTaskId?: any;
|
||||||
|
|
||||||
private readonly dispatchResizeFn: () => {};
|
private readonly dispatchResizeFn: () => {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.channel = new TerminalChannel(this);
|
|
||||||
this.sessions = {};
|
this.sessions = {};
|
||||||
|
this.channel = new TerminalChannel(this);
|
||||||
this.dispatchResizeFn = useDebounceFn(this.dispatchResize).bind(this);
|
this.dispatchResizeFn = useDebounceFn(this.dispatchResize).bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,9 @@ export const TransferReceiverType = {
|
|||||||
DOWNLOAD_ERROR: 'downloadError',
|
DOWNLOAD_ERROR: 'downloadError',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 会话关闭信息
|
||||||
|
export const sessionCloseMsg = 'session closed...';
|
||||||
|
|
||||||
// 打开 settingModal key
|
// 打开 settingModal key
|
||||||
export const openSettingModalKey = Symbol();
|
export const openSettingModalKey = Symbol();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import type { InputPayload, OutputPayload, Protocol } from './terminal.type';
|
||||||
|
|
||||||
|
// 分隔符
|
||||||
|
export const SEPARATOR = '|';
|
||||||
|
|
||||||
// 输入协议
|
// 输入协议
|
||||||
export const InputProtocol = {
|
export const InputProtocol = {
|
||||||
// 主机连接检查
|
// 主机连接检查
|
||||||
@@ -164,3 +169,52 @@ export const OutputProtocol = {
|
|||||||
processMethod: 'processSftpSetContent'
|
processMethod: 'processSftpSetContent'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
export const parse = (payload: string) => {
|
||||||
|
const protocols = Object.values(OutputProtocol);
|
||||||
|
const useProtocol = protocols.find(p => payload.startsWith(p.type + SEPARATOR) || p.type === payload);
|
||||||
|
if (!useProtocol) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const template = useProtocol.template;
|
||||||
|
const res = {} as OutputPayload;
|
||||||
|
let curr = 0;
|
||||||
|
let len = payload.length;
|
||||||
|
for (let i = 0, pl = template.length; i < pl; i++) {
|
||||||
|
if (i == pl - 1) {
|
||||||
|
// 最后一次
|
||||||
|
res[template[i]] = payload.substring(curr, len);
|
||||||
|
} else {
|
||||||
|
// 非最后一次
|
||||||
|
let tmp = '';
|
||||||
|
for (; curr < len; curr++) {
|
||||||
|
const c = payload.charAt(curr);
|
||||||
|
if (c == SEPARATOR) {
|
||||||
|
res[template[i]] = tmp;
|
||||||
|
curr++;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tmp += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化参数
|
||||||
|
export const format = (protocol: Protocol, payload: InputPayload | OutputPayload) => {
|
||||||
|
payload.type = protocol.type;
|
||||||
|
return protocol.template
|
||||||
|
.map(i => getPayloadValueString(payload[i]))
|
||||||
|
.join(SEPARATOR);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取默认值
|
||||||
|
export const getPayloadValueString = (value: unknown): any => {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ export interface ITerminalPanelManager {
|
|||||||
|
|
||||||
// 终端会话管理器定义
|
// 终端会话管理器定义
|
||||||
export interface ITerminalSessionManager {
|
export interface ITerminalSessionManager {
|
||||||
|
// 全部会话
|
||||||
|
sessions: Record<string, ITerminalSession>;
|
||||||
|
|
||||||
// 打开 ssh 会话
|
// 打开 ssh 会话
|
||||||
openSsh: (tab: TerminalPanelTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
|
openSsh: (tab: TerminalPanelTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
|
||||||
// 打开 sftp 会话
|
// 打开 sftp 会话
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<span class="extra-message">
|
<span class="extra-message">
|
||||||
<template v-if="user">
|
<template v-if="user">
|
||||||
只展示用户 <span class="user-info">{{ user.nickname }}({{ user.username }})</span> 最近登录的 30 条历史记录
|
只展示用户 <span class="user-info">{{ user.nickname }}({{ user.username }})</span> 最近登录的 {{ historyCount }} 条历史记录
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
只展示最近登录的 30 条历史记录
|
只展示最近登录的 {{ historyCount }} 条历史记录
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<!-- 加载中 -->
|
<!-- 加载中 -->
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
import type { UserQueryResponse, LoginHistoryQueryResponse } from '@/api/user/user';
|
import type { UserQueryResponse, LoginHistoryQueryResponse } from '@/api/user/user';
|
||||||
import { ref, onBeforeMount } from 'vue';
|
import { ref, onBeforeMount } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import { ResultStatus } from '../types/const';
|
import { historyCount, ResultStatus } from '../types/const';
|
||||||
import { getCurrentLoginHistory } from '@/api/user/mine';
|
import { getCurrentLoginHistory } from '@/api/user/mine';
|
||||||
import { getLoginHistory } from '@/api/user/user';
|
import { getLoginHistory } from '@/api/user/user';
|
||||||
import { dateFormat } from '@/utils';
|
import { dateFormat } from '@/utils';
|
||||||
@@ -82,11 +82,11 @@
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (props.user) {
|
if (props.user) {
|
||||||
// 查询其他用户
|
// 查询其他用户
|
||||||
const { data } = await getLoginHistory(props.user.username);
|
const { data } = await getLoginHistory(props.user.username, historyCount);
|
||||||
list.value = data;
|
list.value = data;
|
||||||
} else {
|
} else {
|
||||||
// 查询当前用户
|
// 查询当前用户
|
||||||
const { data } = await getCurrentLoginHistory();
|
const { data } = await getCurrentLoginHistory(historyCount);
|
||||||
list.value = data;
|
list.value = data;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ export const ResultStatus = {
|
|||||||
// 成功
|
// 成功
|
||||||
SUCCESS: 1,
|
SUCCESS: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 历史数量
|
||||||
|
export const historyCount = 30;
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -22,7 +22,7 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2.0.5</revision>
|
<revision>2.0.6</revision>
|
||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
|
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
|
||||||
|
|||||||
Reference in New Issue
Block a user