From c2f9428ca0daf3c5fb3cdc3b632b8f082ea3d05a Mon Sep 17 00:00:00 2001 From: dushixiang Date: Tue, 25 Oct 2022 21:12:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=20v1.3.0-beta2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/api/account.go | 30 +- server/api/credential.go | 2 +- server/app/app.go | 4 + server/app/server.go | 2 +- server/branding/branding.go | 2 +- server/log/logger.go | 4 + server/repository/authorised.go | 2 +- server/repository/command.go | 2 +- server/sshd/ui.go | 2 +- server/utils/license.go | 6 + web/package.json | 2 +- web/src/App.js | 4 +- web/src/api/session.js | 5 + web/src/components/Login.js | 11 +- web/src/components/access/Access.css | 13 - web/src/components/access/Access.js | 852 ------------------ .../components/asset/AssetUserGroupBind.js | 6 +- 17 files changed, 65 insertions(+), 884 deletions(-) delete mode 100644 web/src/components/access/Access.css delete mode 100644 web/src/components/access/Access.js diff --git a/server/api/account.go b/server/api/account.go index a5ae10a..7b5d5ce 100644 --- a/server/api/account.go +++ b/server/api/account.go @@ -4,6 +4,7 @@ import ( "context" "errors" "next-terminal/server/common" + "next-terminal/server/common/maps" "next-terminal/server/common/nt" "path" "strings" @@ -90,7 +91,34 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error { return err } - return Success(c, token) + var menus []string + if service.UserService.IsSuperAdmin(user.ID) { + menus = service.MenuService.GetMenus() + } else { + roles, err := service.RoleService.GetRolesByUserId(user.ID) + if err != nil { + return err + } + for _, role := range roles { + items := service.RoleService.GetMenuListByRole(role) + menus = append(menus, items...) + } + } + + info := AccountInfo{ + Id: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Type: user.Type, + EnableTotp: user.TOTPSecret != "" && user.TOTPSecret != "-", + Roles: user.Roles, + Menus: menus, + } + + return Success(c, maps.Map{ + "info": info, + "token": token, + }) } func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.User, ip string) (string, error) { diff --git a/server/api/credential.go b/server/api/credential.go index 48d6441..a9fe1b0 100644 --- a/server/api/credential.go +++ b/server/api/credential.go @@ -21,7 +21,7 @@ import ( type CredentialApi struct{} func (api CredentialApi) CredentialAllEndpoint(c echo.Context) error { - items, err := repository.CredentialRepository.FindByAll(context.TODO()) + items, err := repository.CredentialRepository.FindByAll(context.Background()) if err != nil { return err } diff --git a/server/app/app.go b/server/app/app.go index 057ab3b..29e2c62 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -76,6 +76,10 @@ func (app App) InitDBData() (err error) { return err } + if err := service.MigrateService.Migrate(); err != nil { + return err + } + return nil } diff --git a/server/app/server.go b/server/app/server.go index 7d8e149..9747d22 100644 --- a/server/app/server.go +++ b/server/app/server.go @@ -178,7 +178,7 @@ func setupRoutes() *echo.Echo { credentials := e.Group("/credentials", mw.Admin) { - //credentials.GET("", CredentialApi.CredentialAllEndpoint) + credentials.GET("", CredentialApi.CredentialAllEndpoint) credentials.GET("/paging", CredentialApi.CredentialPagingEndpoint) credentials.POST("", CredentialApi.CredentialCreateEndpoint) credentials.PUT("/:id", CredentialApi.CredentialUpdateEndpoint) diff --git a/server/branding/branding.go b/server/branding/branding.go index 6d6bc74..388f9eb 100644 --- a/server/branding/branding.go +++ b/server/branding/branding.go @@ -9,5 +9,5 @@ var Banner = ` ___ ___ \/|::/ / /:/\/__/ |:/ / \/__/ \/__/ ` -var Version = `v1.3.0-beta1` +var Version = `v1.3.0-beta2` var Hi = Banner + Version diff --git a/server/log/logger.go b/server/log/logger.go index 35a381a..3a98173 100644 --- a/server/log/logger.go +++ b/server/log/logger.go @@ -149,3 +149,7 @@ func Fatal(msg string, fields ...Field) { func Sync() error { return _logger.Sync() } + +func GetLogger() *zap.Logger { + return _logger +} diff --git a/server/repository/authorised.go b/server/repository/authorised.go index 2b192fa..96a17b0 100644 --- a/server/repository/authorised.go +++ b/server/repository/authorised.go @@ -38,7 +38,7 @@ func (r authorisedRepository) FindByUserId(c context.Context, userId string) (it } func (r authorisedRepository) FindById(c context.Context, id string) (item model.Authorised, err error) { - err = r.GetDB(c).Where("id = ?", id).Find(&item).Error + err = r.GetDB(c).Where("id = ?", id).First(&item).Error return } diff --git a/server/repository/command.go b/server/repository/command.go index 013b3d6..fae84aa 100644 --- a/server/repository/command.go +++ b/server/repository/command.go @@ -115,6 +115,6 @@ func (r commandRepository) FindAll(c context.Context) (o []model.Command, err er } func (r commandRepository) FindByUserId(c context.Context, userId string) (o []model.Command, err error) { - err = r.GetDB(c).Where("owner = ?", userId).First(&o).Error + err = r.GetDB(c).Where("owner = ?", userId).Find(&o).Error return } diff --git a/server/sshd/ui.go b/server/sshd/ui.go index 87e0623..7308adb 100644 --- a/server/sshd/ui.go +++ b/server/sshd/ui.go @@ -55,7 +55,7 @@ MainLoop: } func (gui Gui) AssetUI(sess ssh.Session, user model.User) { - assets, err := service.WorkerService.FindMyAsset("", nt.SSH, "", "", "", "") + assets, err := service.WorkerService.FindMyAsset("", nt.SSH, "", user.ID, "", "") if err != nil { return } diff --git a/server/utils/license.go b/server/utils/license.go index 2c2d25c..7eac72f 100644 --- a/server/utils/license.go +++ b/server/utils/license.go @@ -7,6 +7,8 @@ import ( "crypto/sha512" "crypto/x509" "encoding/pem" + + "github.com/denisbrodbeck/machineid" ) // SignatureRSA rsa私钥签名 @@ -46,3 +48,7 @@ func VerifyRSA(plainText, signText []byte, rsaPublicKey string) bool { err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA512, hashText[:], signText) return err == nil } + +func GetMachineId() (string, error) { + return machineid.ID() +} diff --git a/web/package.json b/web/package.json index 022bd69..b360f0c 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "next-terminal", - "version": "1.3.0-beta1", + "version": "1.3.0-beta2", "private": true, "dependencies": { "@ant-design/charts": "^1.4.2", diff --git a/web/src/App.js b/web/src/App.js index cfc4d22..c36df04 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -32,7 +32,7 @@ const MyAsset = React.lazy(() => import("./components/worker/MyAsset")); const MyCommand = React.lazy(() => import("./components/worker/MyCommand")); const MyInfo = React.lazy(() => import("./components/worker/MyInfo")); -const Access = React.lazy(() => import("./components/access/Access")); +const Guacd = React.lazy(() => import("./components/access/Guacd")); const Term = React.lazy(() => import("./components/access/Term")); const User = React.lazy(() => import("./components/user/user/User")); @@ -81,7 +81,7 @@ const App = () => { }> - }/> + }/> }/> }/> }/> diff --git a/web/src/api/session.js b/web/src/api/session.js index 962a6ab..cc08c47 100644 --- a/web/src/api/session.js +++ b/web/src/api/session.js @@ -46,6 +46,11 @@ class SessionApi extends Api { } return result['data']; } + + resize = async (sessionId, width, height) => { + let result = await request.post(`/sessions/${sessionId}/resize?width=${width}&height=${height}`); + return result.code === 1; + } } const sessionApi = new SessionApi(); diff --git a/web/src/components/Login.js b/web/src/components/Login.js index 5e2f679..558250d 100644 --- a/web/src/components/Login.js +++ b/web/src/components/Login.js @@ -4,7 +4,6 @@ import './Login.css' import request from "../common/request"; import {LockOutlined, LockTwoTone, UserOutlined} from '@ant-design/icons'; import {setToken} from "../utils/utils"; -import accountApi from "../api/account"; import brandingApi from "../api/branding"; import strings from "../utils/strings"; @@ -24,18 +23,18 @@ const LoginForm = () => { x(); }, []); - const afterLoginSuccess = async (token) => { + const afterLoginSuccess = async (data) => { // 跳转登录 sessionStorage.removeItem('current'); sessionStorage.removeItem('openKeys'); - setToken(token); + setToken(data['token']); - let user = await accountApi.getUserInfo(); + let user = data['user']; if (user) { if (user['type'] === 'user') { - window.location.href = "/my-asset" + window.location.href = "#/my-asset"; } else { - window.location.href = "/" + window.location.href = "#/"; } } } diff --git a/web/src/components/access/Access.css b/web/src/components/access/Access.css deleted file mode 100644 index 72f9724..0000000 --- a/web/src/components/access/Access.css +++ /dev/null @@ -1,13 +0,0 @@ -.xterm-viewport.xterm-viewport { - scrollbar-width: thin; -} -.xterm-viewport::-webkit-scrollbar { - width: 10px; -} -.xterm-viewport::-webkit-scrollbar-track { - opacity: 0; -} -.xterm-viewport::-webkit-scrollbar-thumb { - min-height: 20px; - background-color: #ffffff20; -} \ No newline at end of file diff --git a/web/src/components/access/Access.js b/web/src/components/access/Access.js deleted file mode 100644 index a791716..0000000 --- a/web/src/components/access/Access.js +++ /dev/null @@ -1,852 +0,0 @@ -import React, {Component} from 'react'; -import Guacamole from 'guacamole-common-js'; -import {Affix, Button, Drawer, Dropdown, Form, Input, Menu, message, Modal, Tooltip} from 'antd' -import qs from "qs"; -import request from "../../common/request"; -import {wsServer} from "../../common/env"; -import { - CodeOutlined, - CopyOutlined, - ExclamationCircleOutlined, - ExpandOutlined, - FolderOutlined, - LineChartOutlined, - WindowsOutlined -} from '@ant-design/icons'; -import {exitFull, getToken, isEmpty, requestFullScreen, setToken} from "../../utils/utils"; -import './Access.css' -import Draggable from 'react-draggable'; -import FileSystem from "../devops/FileSystem"; -import Stats from "./Stats"; -import {Base64} from "js-base64"; - -const {TextArea} = Input; - -const STATE_IDLE = 0; -const STATE_CONNECTING = 1; -const STATE_WAITING = 2; -const STATE_CONNECTED = 3; -const STATE_DISCONNECTING = 4; -const STATE_DISCONNECTED = 5; - -class Access extends Component { - - clipboardFormRef = React.createRef(); - statsRef = undefined; - error = false; - - state = { - session: {}, - sessionId: '', - client: undefined, - scale: 1, - clientState: STATE_IDLE, - clipboardVisible: false, - clipboardText: '', - containerOverflow: 'hidden', - containerWidth: 1024, - containerHeight: 768, - uploadAction: '', - uploadHeaders: {}, - keyboard: {}, - protocol: '', - confirmLoading: false, - uploadVisible: false, - uploadLoading: false, - startTime: new Date(), - fullScreen: false, - fullScreenBtnText: '进入全屏', - sink: undefined, - commands: [], - showFileSystem: false, - external: false, - fixedSize: false, - }; - - async componentDidMount() { - let href = window.location.href; - let search = href.split('?')[1]; - let urlParams = new URLSearchParams(search); - let assetId = urlParams.get('assetId'); - document.title = urlParams.get('assetName'); - - if (!assetId) { - this.showMessage('获取资产ID失败'); - return; - } - - let protocol = urlParams.get('protocol'); - let width = urlParams.get('width'); - let height = urlParams.get('height'); - let fixedSize = false; - - if (width && height) { - fixedSize = true - } else { - width = window.innerWidth; - height = window.innerHeight; - } - - let shareSessionId = urlParams.get('shareSessionId'); - let external = false; - if (shareSessionId && shareSessionId !== '') { - setToken(shareSessionId); - external = true; - let shareSession = await this.getShareSession(shareSessionId); - if (!shareSession) { - return - } - assetId = shareSession['assetId']; - } - - let session = await this.createSession(assetId); - if (!session) { - return; - } - let sessionId = session['id']; - if (isEmpty(sessionId)) { - return; - } - - this.setState({ - session: session, - sessionId: sessionId, - protocol: protocol, - showFileSystem: session['fileSystem'] === '1', - external: external, - fixedSize: fixedSize, - containerWidth: width, - containerHeight: height, - }); - - this.renderDisplay(sessionId, protocol, width, height); - - window.addEventListener('resize', this.onWindowResize); - window.addEventListener('beforeunload', this.handleUnload); - window.onfocus = this.onWindowFocus; - } - - componentWillUnmount() { - if (this.state.client) { - this.state.client.disconnect(); - } - window.removeEventListener('beforeunload', this.handleUnload); - } - - sendClipboard(data) { - if (this.state.session['paste'] === '0') { - // message.warn('禁止粘贴'); - return - } - let writer; - - // Create stream with proper mimetype - const stream = this.state.client.createClipboardStream(data.type); - - // Send data as a string if it is stored as a string - if (typeof data.data === 'string') { - writer = new Guacamole.StringWriter(stream); - writer.sendText(data.data); - writer.sendEnd(); - } else { - - // Write File/Blob asynchronously - writer = new Guacamole.BlobWriter(stream); - writer.oncomplete = function clipboardSent() { - writer.sendEnd(); - }; - - // Begin sending data - writer.sendBlob(data.data); - } - - this.setState({ - clipboardText: data.data - }) - if (this.state.protocol === 'ssh') { - if (data.data && data.data.length > 0) { - // message.info('您输入的内容已复制到远程服务器上,使用右键将自动粘贴。'); - } - } else { - if (data.data && data.data.length > 0) { - // message.info('您输入的内容已复制到远程服务器上'); - } - } - - } - - onTunnelStateChange = (state) => { - console.log(state) - if (state === Guacamole.Tunnel.State.CLOSED) { - console.log('web socket 已关闭'); - } - }; - - updateSessionStatus = async (sessionId) => { - let result = await request.post(`/sessions/${sessionId}/connect`); - if (result.code !== 1) { - message.error(result.message); - } - } - - getCommands = async () => { - let result = await request.get('/commands'); - if (result.code !== 1) { - message.error(result.message); - return; - } - this.setState({ - commands: result['data'] - }) - } - - onClientStateChange = (state) => { - this.setState({ - clientState: state - }); - switch (state) { - case STATE_IDLE: - message.destroy(); - message.loading('正在初始化中...', 0); - break; - case STATE_CONNECTING: - message.destroy(); - message.loading('正在努力连接中...', 0); - break; - case STATE_WAITING: - message.destroy(); - message.loading('正在等待服务器响应...', 0); - break; - case STATE_CONNECTED: - this.onWindowResize(null); - Modal.destroyAll(); - message.destroy(); - message.success('连接成功'); - // 向后台发送请求,更新会话的状态 - this.updateSessionStatus(this.state.sessionId).then(_ => { - }) - if (this.state.protocol === 'ssh' && !this.state.external) { - // 加载指令 - this.getCommands(); - } - break; - case STATE_DISCONNECTING: - - break; - case STATE_DISCONNECTED: - if (!this.error) { - this.showMessage('连接已关闭'); - } - - break; - default: - break; - } - }; - - onError = (status) => { - this.error = true; - console.log('通道异常。', status); - - switch (status.code) { - case 256: - this.showMessage('未支持的访问'); - break; - case 512: - this.showMessage('远程服务异常,请检查目标设备能否正常访问。'); - break; - case 513: - this.showMessage('服务器忙碌'); - break; - case 514: - this.showMessage('服务器连接超时'); - break; - case 515: - this.showMessage('远程服务异常'); - break; - case 516: - this.showMessage('资源未找到'); - break; - case 517: - this.showMessage('资源冲突'); - break; - case 518: - this.showMessage('资源已关闭'); - break; - case 519: - this.showMessage('远程服务未找到'); - break; - case 520: - this.showMessage('远程服务不可用'); - break; - case 521: - this.showMessage('会话冲突'); - break; - case 522: - this.showMessage('会话连接超时'); - break; - case 523: - this.showMessage('会话已关闭'); - break; - case 768: - this.showMessage('网络不可达'); - break; - case 769: - this.showMessage('服务器密码验证失败'); - break; - case 771: - this.showMessage('客户端被禁止'); - break; - case 776: - this.showMessage('客户端连接超时'); - break; - case 781: - this.showMessage('客户端异常'); - break; - case 783: - this.showMessage('错误的请求类型'); - break; - case 800: - this.showMessage('会话不存在'); - break; - case 801: - this.showMessage('创建隧道失败,请检查Guacd服务是否正常。'); - break; - case 802: - this.showMessage('管理员强制关闭了此会话'); - break; - default: - if (status.message) { - // guacd 无法处理中文字符,所以进行了base64编码。 - this.showMessage(Base64.decode(status.message)); - } else { - this.showMessage('未知错误。'); - } - - } - }; - - showMessage(msg) { - message.destroy(); - Modal.confirm({ - title: '提示', - icon: , - content: msg, - centered: true, - okText: '重新连接', - cancelText: '关闭页面', - onOk() { - window.location.reload(); - }, - onCancel() { - window.close(); - }, - }); - } - - clientClipboardReceived = (stream, mimetype) => { - if (this.state.session['copy'] === '0') { - // message.warn('禁止复制'); - return - } - let reader; - // If the received data is text, read it as a simple string - if (/^text\//.exec(mimetype)) { - reader = new Guacamole.StringReader(stream); - let data = ''; - reader.ontext = function textReceived(text) { - data += text; - }; - - // Set clipboard contents once stream is finished - reader.onend = async () => { - // message.info('您选择的内容已复制到您的粘贴板中,在右侧的输入框中可同时查看到。'); - this.setState({ - clipboardText: data - }, () => { - // 选中粘贴板文本框中的内容 - - // 调用API粘贴进 - }); - - if (navigator.clipboard) { - await navigator.clipboard.writeText(data); - } - }; - - } - - // Otherwise read the clipboard data as a Blob - else { - reader = new Guacamole.BlobReader(stream, mimetype); - reader.onend = () => { - this.setState({ - clipboardText: reader.getBlob() - }) - } - } - }; - - onKeyDown = (keysym) => { - this.state.client.sendKeyEvent(1, keysym); - if (keysym === 65288) { - return false; - } - }; - - onKeyUp = (keysym) => { - this.state.client.sendKeyEvent(0, keysym); - }; - - fullScreen = () => { - let fs = this.state.fullScreen; - if (fs) { - exitFull(); - this.setState({ - fullScreen: false, - fullScreenBtnText: '进入全屏' - }) - } else { - requestFullScreen(document.documentElement); - this.setState({ - fullScreen: true, - fullScreenBtnText: '退出全屏' - }) - } - this.focus(); - } - - async createSession(assetsId) { - let result = await request.post(`/sessions?assetId=${assetsId}&mode=guacd`); - if (result['code'] !== 1) { - this.showMessage(result['message']); - return undefined; - } - return result['data']; - } - - async getShareSession(shareSessionId) { - let result = await request.get(`/share-sessions/${shareSessionId}`); - if (result['code'] !== 1) { - this.showMessage(result['message']); - return undefined; - } - return result['data']; - } - - async renderDisplay(sessionId, protocol, width, height) { - - let tunnel = new Guacamole.WebSocketTunnel(`${wsServer}/sessions/${sessionId}/tunnel`); - - tunnel.onstatechange = this.onTunnelStateChange; - // Get new client instance - let client = new Guacamole.Client(tunnel); - - // 设置虚拟机剪贴板内容 - client.sendClipboard = this.sendClipboard; - - // 处理从虚拟机收到的剪贴板内容 - client.onclipboard = this.clientClipboardReceived; - - // 处理客户端的状态变化事件 - client.onstatechange = this.onClientStateChange; - - client.onerror = this.onError; - tunnel.onerror = this.onError; - - // Get display div from document - const display = document.getElementById("display"); - - // Add client to display div - const element = client.getDisplay().getElement(); - display.appendChild(element); - - let scale = 1; - let dpi = 96; - if (protocol === 'ssh' || protocol === 'telnet') { - dpi = dpi * 2; - scale = 0.5; - } - - let token = getToken(); - - let params = { - 'width': width, - 'height': height, - 'dpi': dpi, - 'X-Auth-Token': token - }; - - let paramStr = qs.stringify(params); - - // Connect - client.connect(paramStr); - - // Disconnect on close - window.onunload = function () { - client.disconnect(); - }; - - // Mouse - const mouse = new Guacamole.Mouse(element); - - mouse.onmousedown = mouse.onmouseup = function (mouseState) { - client.sendMouseState(mouseState); - }; - - mouse.onmousemove = function (mouseState) { - mouseState.x = mouseState.x / scale; - mouseState.y = mouseState.y / scale; - client.sendMouseState(mouseState); - }; - - const sink = new Guacamole.InputSink(); - display.appendChild(sink.getElement()); - sink.focus(); - - // Keyboard - const keyboard = new Guacamole.Keyboard(sink.getElement()); - - keyboard.onkeydown = this.onKeyDown; - keyboard.onkeyup = this.onKeyUp; - - this.setState({ - client: client, - scale: scale, - keyboard: keyboard, - sink: sink - }); - } - - onWindowResize = (e) => { - - if (this.state.client && !this.state.fixedSize) { - const display = this.state.client.getDisplay(); - let scale = this.state.scale; - display.scale(scale); - let width = window.innerWidth; - let height = window.innerHeight; - - this.state.client.sendSize(width / scale, height / scale); - - this.setState({ - containerWidth: width, - containerHeight: height, - }) - - this.resize(this.state.sessionId, width, height).then(_ => { - }); - } - }; - - handleUnload(e) { - var message = "要离开网站吗?"; - (e || window.event).returnValue = message; //Gecko + IE - return message; - } - - resize = async (sessionId, width, height) => { - let result = await request.post(`/sessions/${sessionId}/resize?width=${width}&height=${height}`); - if (result.code !== 1) { - message.error(result.message); - } - } - - onWindowFocus = (e) => { - if (navigator.clipboard && this.state.clientState === STATE_CONNECTED) { - try { - navigator.clipboard.readText().then((text) => { - this.sendClipboard({ - 'data': text, - 'type': 'text/plain' - }); - }) - } catch (e) { - // console.error(e); - } - - } - }; - - focus = () => { - if (this.state.sink) { - this.state.sink.focus(); - } - } - - sendCombinationKey = (keys) => { - for (let i = 0; i < keys.length; i++) { - this.state.client.sendKeyEvent(1, keys[i]); - } - for (let j = 0; j < keys.length; j++) { - this.state.client.sendKeyEvent(0, keys[j]); - } - } - - writeCommand = (command) => { - let client = this.state.client; - let outputStream = client.createPipeStream('text/plain', 'STDIN'); - // Wrap output stream in writer - let writer = new Guacamole.StringWriter(outputStream); - writer.sendText(command); - writer.sendEnd(); - this.focus(); - } - - onRef = (statsRef) => { - this.statsRef = statsRef; - } - - render() { - - const hotKeyMenu = ( - - this.sendCombinationKey(['65507', '65513', '65535'])}>Ctrl+Alt+Delete - this.sendCombinationKey(['65507', '65513', '65288'])}>Ctrl+Alt+Backspace - this.sendCombinationKey(['65515', '100'])}>Windows+D - this.sendCombinationKey(['65515', '101'])}>Windows+E - this.sendCombinationKey(['65515', '114'])}>Windows+R - this.sendCombinationKey(['65515', '120'])}>Windows+X - this.sendCombinationKey(['65515'])}>Windows - - ); - - const cmdMenuItems = this.state.commands.map(item => { - return - { - this.writeCommand(item['content']) - }} key={'i-' + item['id']}>{item['name']} - ; - }); - - const cmdMenu = ( - - {cmdMenuItems} - - ); - - return ( -
- -
-
-
- - - -