- 修复无法查看原生会话录屏的bug

- 优化列表显示的时间
- 优化获取开发环境的方式
This commit is contained in:
dushixiang 2021-02-20 17:03:32 +08:00
parent b977b85cdf
commit 11d3dc167b
26 changed files with 195 additions and 78 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ web/build
*.log
*.db
.DS_Store
.eslintcache
.eslintcache
.env

View File

@ -53,9 +53,10 @@ func AssetPagingEndpoint(c echo.Context) error {
owner := c.QueryParam("owner")
sharer := c.QueryParam("sharer")
userGroupId := c.QueryParam("userGroupId")
ip := c.QueryParam("ip")
account, _ := GetCurrentAccount(c)
items, total, err := model.FindPageAsset(pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId)
items, total, err := model.FindPageAsset(pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip)
if err != nil {
return err
}

View File

@ -27,7 +27,7 @@ func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
urls := []string{"download", "recording", "login", "static", "favicon", "logo"}
urls := []string{"download", "recording", "login", "static", "favicon", "logo", "asciinema"}
return func(c echo.Context) error {
// 路由拦截 - 登录身份、资源权限判断等

View File

@ -22,12 +22,12 @@ func OverviewCounterEndPoint(c echo.Context) error {
asset int64
)
if model.TypeUser == account.Type {
countUser, _ = model.CountUser()
countUser, _ = model.CountOnlineUser()
countOnlineSession, _ = model.CountOnlineSession()
credential, _ = model.CountCredentialByUserId(account.ID)
asset, _ = model.CountAssetByUserId(account.ID)
} else {
countUser, _ = model.CountUser()
countUser, _ = model.CountOnlineUser()
countOnlineSession, _ = model.CountOnlineSession()
credential, _ = model.CountCredential()
asset, _ = model.CountAsset()

View File

@ -63,8 +63,8 @@ func FindAssetByConditions(protocol string, account User) (o []Asset, err error)
return
}
func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string, account User, owner, sharer, userGroupId string) (o []AssetVo, total int64, err error) {
db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string, account User, owner, sharer, userGroupId, ip string) (o []AssetVo, total int64, err error) {
db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created,assets.tags, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
dbCounter := global.DB.Table("assets").Select("DISTINCT assets.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
if TypeUser == account.Type {
@ -103,6 +103,11 @@ func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string, account
dbCounter = dbCounter.Where("assets.name like ?", "%"+name+"%")
}
if len(ip) > 0 {
db = db.Where("assets.ip like ?", "%"+ip+"%")
dbCounter = dbCounter.Where("assets.ip like ?", "%"+ip+"%")
}
if len(protocol) > 0 {
db = db.Where("assets.protocol = ?", protocol)
dbCounter = dbCounter.Where("assets.protocol = ?", protocol)

View File

@ -107,7 +107,7 @@ func DeleteUserById(id string) {
global.DB.Where("user_id = ?", id).Delete(&ResourceSharer{})
}
func CountUser() (total int64, err error) {
err = global.DB.Find(&User{}).Count(&total).Error
func CountOnlineUser() (total int64, err error) {
err = global.DB.Where("online = ?", true).Find(&User{}).Count(&total).Error
return
}

1
web/.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_ENV=development

8
web/package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "next-terminal",
"version": "0.1.1",
"version": "0.2.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -5363,9 +5363,9 @@
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
},
"dayjs": {
"version": "1.9.6",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.6.tgz",
"integrity": "sha512-HngNLtPEBWRo8EFVmHFmSXAjtCX8rGNqeXQI0Gh7wCTSqwaKgPIDqu9m07wABVopNwzvOeCb+2711vQhDlcIXw=="
"version": "1.10.4",
"resolved": "https://registry.npm.taobao.org/dayjs/download/dayjs-1.10.4.tgz?cache=0&sync_timestamp=1611309982734&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdayjs%2Fdownload%2Fdayjs-1.10.4.tgz",
"integrity": "sha1-jlRKm4aD9heD9XCYCoqA6vVKseI="
},
"debug": {
"version": "3.1.0",

View File

@ -6,6 +6,7 @@
"@ant-design/icons": "^4.3.0",
"antd": "^4.8.4",
"axios": "^0.21.1",
"dayjs": "^1.10.4",
"guacamole-common-js": "^1.2.0",
"qs": "^6.9.4",
"react": "^16.14.0",

View File

@ -1,22 +1,5 @@
// prod
let wsPrefix;
if (window.location.protocol === 'https:') {
wsPrefix = 'wss:'
} else {
wsPrefix = 'ws:'
}
export const server = '';
export const wsServer = wsPrefix + window.location.host;
export const prefix = window.location.protocol + '//' + window.location.host;
// dev
// export const server = '//127.0.0.1:8088';
// export const wsServer = 'ws://127.0.0.1:8088';
// export const prefix = '';
export const PROTOCOL_COLORS = {
'rdp': 'red',
'rdp': 'cyan',
'ssh': 'blue',
'telnet': 'geekblue',
'vnc': 'purple'

28
web/src/common/env.js Normal file
View File

@ -0,0 +1,28 @@
function env() {
if (process.env.REACT_APP_ENV === 'development') {
// 本地开发环境
return {
server: '//127.0.0.1:8088',
wsServer: 'ws://127.0.0.1:8088',
prefix: '',
}
} else {
// 生产环境
let wsPrefix;
if (window.location.protocol === 'https:') {
wsPrefix = 'wss:'
} else {
wsPrefix = 'ws:'
}
return {
server: '',
wsServer: wsPrefix + window.location.host,
prefix: window.location.protocol + '//' + window.location.host,
}
}
}
export default env();
export const server = env().server;
export const wsServer = env().wsServer;
export const prefix = env().prefix;

View File

@ -1,5 +1,5 @@
import axios from 'axios'
import {server} from "./constants";
import {server} from "./env";
import {message} from 'antd';
import {getHeaders} from "../utils/utils";

View File

@ -3,7 +3,7 @@ import Guacamole from 'guacamole-common-js';
import {Affix, Button, Col, Drawer, Dropdown, Form, Input, Menu, message, Modal, Row} from 'antd'
import qs from "qs";
import request from "../../common/request";
import {wsServer} from "../../common/constants";
import {wsServer} from "../../common/env";
import {
AppstoreTwoTone,
CopyTwoTone,

View File

@ -2,7 +2,7 @@ import React, {Component} from 'react';
import "xterm/css/xterm.css"
import {Terminal} from "xterm";
import qs from "qs";
import {wsServer} from "../../common/constants";
import {wsServer} from "../../common/env";
import "./Console.css"
import {getToken, isEmpty} from "../../utils/utils";
import {FitAddon} from 'xterm-addon-fit'

View File

@ -22,7 +22,7 @@ import {
} from "@ant-design/icons";
import qs from "qs";
import request from "../../common/request";
import {server} from "../../common/constants";
import {server} from "../../common/env";
import Upload from "antd/es/upload";
import {download, getFileName, getToken, isEmpty, renderSize} from "../../utils/utils";
import './FileSystem.css'
@ -107,7 +107,7 @@ class FileSystem extends Component {
if (isEmpty(key)) {
key = '/';
}
let result = await request.get(`${server}/sessions/${this.state.sessionId}/ls?dir=${key}`);
let result = await request.get(`/sessions/${this.state.sessionId}/ls?dir=${key}`);
if (result['code'] !== 1) {
message.error(result['message']);
return;

View File

@ -2,7 +2,7 @@ import React, {Component} from 'react';
import Guacamole from 'guacamole-common-js';
import {Modal, Result, Spin} from 'antd'
import qs from "qs";
import {wsServer} from "../../common/constants";
import {wsServer} from "../../common/env";
import {getToken} from "../../utils/utils";
import './Access.css'

View File

@ -2,7 +2,7 @@ import React, {Component} from 'react';
import "xterm/css/xterm.css"
import {Terminal} from "xterm";
import qs from "qs";
import {wsServer} from "../../common/constants";
import {wsServer} from "../../common/env";
import {getToken, isEmpty} from "../../utils/utils";
import {FitAddon} from 'xterm-addon-fit';
import "./Access.css"

View File

@ -26,9 +26,8 @@ import qs from "qs";
import AssetModal from "./AssetModal";
import request from "../../common/request";
import {message} from "antd/es";
import {itemRender} from "../../utils/utils";
import {isEmpty, itemRender} from "../../utils/utils";
import dayjs from 'dayjs';
import {
DeleteOutlined,
DownOutlined,
@ -41,6 +40,7 @@ import {PROTOCOL_COLORS} from "../../common/constants";
import Logout from "../user/Logout";
import {hasPermission, isAdmin} from "../../service/permission";
const confirm = Modal.confirm;
const {Search} = Input;
const {Content} = Layout;
@ -59,6 +59,7 @@ const routes = [
class Asset extends Component {
inputRefOfName = React.createRef();
inputRefOfIp = React.createRef();
changeOwnerFormRef = React.createRef();
state = {
@ -172,6 +173,17 @@ class Asset extends Component {
this.loadTableData(query);
};
handleSearchByIp = ip => {
let query = {
...this.state.queryParams,
'pageIndex': 1,
'pageSize': this.state.queryParams.pageSize,
'ip': ip,
}
this.loadTableData(query);
};
handleTagsChange = tags => {
this.setState({
selectedTags: tags
@ -459,18 +471,48 @@ class Asset extends Component {
dataIndex: 'protocol',
key: 'protocol',
render: (text, record) => {
return (<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>);
const title = `${record['ip'] + ':' + record['port']}`
return (
<Tooltip title={title}>
<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>
</Tooltip>
)
}
}, {
title: '标签',
dataIndex: 'tags',
key: 'tags',
render: tags => {
if (!isEmpty(tags)) {
let tagDocuments = []
let tagArr = tags.split(',');
for (let i = 0; i < tagArr.length; i++) {
if (tags[i] === '-') {
continue;
}
tagDocuments.push(<Tag>{tagArr[i]}</Tag>)
}
return tagDocuments;
}
}
}, {
title: '状态',
dataIndex: 'active',
key: 'active',
render: text => {
if (text) {
return (<Badge status="processing" text="运行中"/>);
return (
<Tooltip title='运行中'>
<Badge status="processing"/>
</Tooltip>
)
} else {
return (<Badge status="error" text="不可用"/>);
return (
<Tooltip title='不可用'>
<Badge status="error"/>
</Tooltip>
)
}
}
}, {
@ -480,7 +522,14 @@ class Asset extends Component {
}, {
title: '创建日期',
dataIndex: 'created',
key: 'created'
key: 'created',
render: (text, record) => {
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
},
{
title: '操作',
@ -598,10 +647,10 @@ class Asset extends Component {
<Content key='page-content' className="site-layout-background page-content">
<div style={{marginBottom: 20}}>
<Row justify="space-around" align="middle" gutter={24}>
<Col span={8} key={1}>
<Col span={4} key={1}>
<Title level={3}>资产列表</Title>
</Col>
<Col span={16} key={2} style={{textAlign: 'right'}}>
<Col span={20} key={2} style={{textAlign: 'right'}}>
<Space>
<Search
@ -609,6 +658,15 @@ class Asset extends Component {
placeholder="资产名称"
allowClear
onSearch={this.handleSearchByName}
style={{width: 200}}
/>
<Search
ref={this.inputRefOfIp}
placeholder="资产IP"
allowClear
onSearch={this.handleSearchByIp}
style={{width: 200}}
/>
<Select mode="multiple"
@ -638,6 +696,7 @@ class Asset extends Component {
<Button icon={<UndoOutlined/>} onClick={() => {
this.inputRefOfName.current.setValue('');
this.inputRefOfIp.current.setValue('');
this.setState({
selectedTags: []
})

View File

@ -36,6 +36,7 @@ import {
import {compare, itemRender} from "../../utils/utils";
import Logout from "../user/Logout";
import {hasPermission, isAdmin} from "../../service/permission";
import dayjs from "dayjs";
const confirm = Modal.confirm;
const {Content} = Layout;
@ -403,7 +404,14 @@ class DynamicCommand extends Component {
}, {
title: '创建日期',
dataIndex: 'created',
key: 'created'
key: 'created',
render: (text, record) => {
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
}, {
title: '操作',
key: 'action',

View File

@ -35,6 +35,7 @@ import {
import {itemRender} from "../../utils/utils";
import Logout from "../user/Logout";
import {hasPermission, isAdmin} from "../../service/permission";
import dayjs from "dayjs";
const confirm = Modal.confirm;
const {Search} = Input;
@ -375,6 +376,13 @@ class Credential extends Component {
title: '创建时间',
dataIndex: 'created',
key: 'created',
render: (text, record) => {
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
},
{
title: '操作',

View File

@ -18,12 +18,13 @@ import {
} from "antd";
import qs from "qs";
import request from "../../common/request";
import {differTime, formatDate, itemRender} from "../../utils/utils";
import {differTime, itemRender} from "../../utils/utils";
import Playback from "./Playback";
import {message} from "antd/es";
import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
import {PROTOCOL_COLORS} from "../../common/constants";
import Logout from "../user/Logout";
import dayjs from "dayjs";
const confirm = Modal.confirm;
const {Content} = Layout;
@ -244,29 +245,28 @@ class OfflineSession extends Component {
title: '资产名称',
dataIndex: 'assetName',
key: 'assetName'
}, {
title: '远程连接',
dataIndex: 'access',
key: 'access',
render: (text, record) => {
return `${record.username}@${record.ip}:${record.port}`;
}
}, {
title: '连接协议',
dataIndex: 'protocol',
key: 'protocol',
render: (text, record) => {
return (<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>);
const title = `${record.username}@${record.ip}:${record.port}`;
return (
<Tooltip title={title}>
<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>
</Tooltip>
)
}
}, {
title: '接入时间',
dataIndex: 'connectedTime',
key: 'connectedTime',
render: (text, record) => {
return formatDate(text, 'yyyy-MM-dd hh:mm:ss');
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
}, {
title: '接入时长',

View File

@ -19,12 +19,13 @@ import {
} from "antd";
import qs from "qs";
import request from "../../common/request";
import {differTime, formatDate, itemRender} from "../../utils/utils";
import {differTime, itemRender} from "../../utils/utils";
import {message} from "antd/es";
import {PROTOCOL_COLORS} from "../../common/constants";
import {DisconnectOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
import Monitor from "../access/Monitor";
import Logout from "../user/Logout";
import dayjs from "dayjs";
const confirm = Modal.confirm;
const {Content} = Layout;
@ -241,29 +242,28 @@ class OnlineSession extends Component {
title: '资产名称',
dataIndex: 'assetName',
key: 'assetName'
}, {
title: '远程连接',
dataIndex: 'access',
key: 'access',
render: (text, record) => {
return `${record.username}@${record.ip}:${record.port}`;
}
}, {
title: '连接协议',
dataIndex: 'protocol',
key: 'protocol',
render: (text, record) => {
return (<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>);
const title = `${record.username}@${record.ip}:${record.port}`;
return (
<Tooltip title={title}>
<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>
</Tooltip>
)
}
}, {
title: '接入时间',
dataIndex: 'connectedTime',
key: 'connectedTime',
render: (text, record) => {
return formatDate(text, 'yyyy-MM-dd hh:mm:ss');
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
}, {
title: '接入时长',

View File

@ -1,6 +1,6 @@
import React, {Component} from 'react';
import Guacamole from "guacamole-common-js";
import {server} from "../../common/constants";
import server from "../../common/env";
import {Button, Col, Row, Slider, Typography} from "antd";
import {PauseCircleOutlined, PlayCircleOutlined} from '@ant-design/icons';
import {Tooltip} from "antd/lib/index";

View File

@ -36,6 +36,7 @@ import {
import Logout from "./Logout";
import UserShareAsset from "./UserShareAsset";
import {hasPermission} from "../../service/permission";
import dayjs from "dayjs";
const confirm = Modal.confirm;
const {Search} = Input;
@ -342,7 +343,14 @@ class User extends Component {
}, {
title: '创建日期',
dataIndex: 'created',
key: 'created'
key: 'created',
render: (text, record) => {
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
},
{
title: '操作',

View File

@ -9,6 +9,7 @@ import {DeleteOutlined, ExclamationCircleOutlined, PlusOutlined, SyncOutlined, U
import Logout from "./Logout";
import UserGroupModal from "./UserGroupModal";
import UserShareAsset from "./UserShareAsset";
import dayjs from "dayjs";
const confirm = Modal.confirm;
const {Search} = Input;
@ -285,7 +286,14 @@ class UserGroup extends Component {
}, {
title: '创建日期',
dataIndex: 'created',
key: 'created'
key: 'created',
render: (text, record) => {
return (
<Tooltip title={text}>
{dayjs(text).fromNow()}
</Tooltip>
)
}
},
{
title: '操作',

View File

@ -6,6 +6,12 @@ import * as serviceWorker from './serviceWorker';
import zhCN from 'antd/es/locale-provider/zh_CN';
import {ConfigProvider} from 'antd';
import {HashRouter as Router} from "react-router-dom";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import 'dayjs/locale/zh-cn'
dayjs.extend(relativeTime);
dayjs.locale('zh-cn');
ReactDOM.render(
<ConfigProvider locale={zhCN}>