From b6150c77f81ae0b1a16d24309b124e167773b45a Mon Sep 17 00:00:00 2001 From: dushixiang Date: Sat, 29 Oct 2022 17:36:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=201.3.0-beta4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++++ server/api/asset.go | 45 ++++++++++++------------ server/api/term.go | 2 +- server/branding/branding.go | 2 +- server/repository/asset.go | 9 +++-- server/service/asset.go | 12 +++---- server/service/backup.go | 4 +-- server/utils/license.go | 54 ----------------------------- web/package.json | 2 +- web/src/components/access/Guacd.css | 3 ++ web/src/components/access/Guacd.js | 44 ++++++++++++----------- web/src/components/access/Term.js | 30 ++++++++-------- web/src/components/asset/Asset.js | 10 ++++-- web/src/layout/ManagerLayout.js | 5 ++- web/src/layout/UserLayout.js | 9 +++-- 15 files changed, 104 insertions(+), 135 deletions(-) delete mode 100644 server/utils/license.go create mode 100644 web/src/components/access/Guacd.css diff --git a/README.md b/README.md index c71a969..0ddc070 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,14 @@ https://next.typesafe.cn/ 账号:test 密码:test 默认账号密码为 admin/admin 。 +## 手动编译 + +1. 找一台Linux 机器或者Mac +2. 安装 go 1.18 或以上版本 +3. 安装 nodejs 16,安装 npm 或 yarn +4. 进入 web 目录 执行 yarn install 或 npm install +5. 返回上级目录,也就是项目根目录,执行 sh build.sh + ## 问题反馈 - Issues diff --git a/server/api/asset.go b/server/api/asset.go index 3866970..02133e0 100644 --- a/server/api/asset.go +++ b/server/api/asset.go @@ -5,12 +5,11 @@ import ( "context" "encoding/csv" "errors" - "next-terminal/server/common" - "next-terminal/server/common/maps" - "next-terminal/server/common/nt" "strconv" "strings" + "next-terminal/server/common/maps" + "next-terminal/server/common/nt" "next-terminal/server/model" "next-terminal/server/repository" "next-terminal/server/service" @@ -22,7 +21,7 @@ import ( type AssetApi struct{} func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error { - m := echo.Map{} + m := maps.Map{} if err := c.Bind(&m); err != nil { return err } @@ -71,29 +70,31 @@ func (assetApi AssetApi) AssetImportEndpoint(c echo.Context) error { record := records[i] if len(record) >= 9 { port, _ := strconv.Atoi(record[3]) - asset := model.Asset{ - ID: utils.UUID(), - Name: record[0], - Protocol: record[1], - IP: record[2], - Port: port, - AccountType: nt.Custom, - Username: record[4], - Password: record[5], - PrivateKey: record[6], - Passphrase: record[7], - Description: record[8], - Created: common.NowJsonTime(), - Owner: account.ID, - Active: true, + asset := maps.Map{ + "id": utils.UUID(), + "name": record[0], + "protocol": record[1], + "ip": record[2], + "port": port, + "accountType": nt.Custom, + "username": record[4], + "password": record[5], + "privateKey": record[6], + "passphrase": record[7], + "Description": record[8], + "owner": account.ID, + } + + if record[6] != "" { + asset["accountType"] = nt.PrivateKey } if len(record) >= 10 { tags := strings.ReplaceAll(record[9], "|", ",") - asset.Tags = tags + asset["tags"] = tags } - err := repository.AssetRepository.Create(context.TODO(), &asset) + _, err := service.AssetService.Create(context.Background(), asset) if err != nil { errorCount++ m[strconv.Itoa(i)] = err.Error() @@ -151,7 +152,7 @@ func (assetApi AssetApi) AssetAllEndpoint(c echo.Context) error { func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error { id := c.Param("id") - m := echo.Map{} + m := maps.Map{} if err := c.Bind(&m); err != nil { return err } diff --git a/server/api/term.go b/server/api/term.go index ae580c4..1ef6c91 100644 --- a/server/api/term.go +++ b/server/api/term.go @@ -50,7 +50,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error { s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId) if err != nil { - return WriteMessage(ws, dto.NewMessage(Closed, "获取会话失败")) + return WriteMessage(ws, dto.NewMessage(Closed, "获取会话或解密数据失败")) } if err := api.permissionCheck(c, s.AssetId); err != nil { diff --git a/server/branding/branding.go b/server/branding/branding.go index 388f9eb..96d5abc 100644 --- a/server/branding/branding.go +++ b/server/branding/branding.go @@ -9,5 +9,5 @@ var Banner = ` ___ ___ \/|::/ / /:/\/__/ |:/ / \/__/ \/__/ ` -var Version = `v1.3.0-beta2` +var Version = `v1.3.0-beta4` var Hi = Banner + Version diff --git a/server/repository/asset.go b/server/repository/asset.go index 0993476..79f2929 100644 --- a/server/repository/asset.go +++ b/server/repository/asset.go @@ -3,15 +3,14 @@ package repository import ( "context" "fmt" - "next-terminal/server/common/nt" "strconv" "strings" + "next-terminal/server/common/maps" + "next-terminal/server/common/nt" "next-terminal/server/config" "next-terminal/server/model" "next-terminal/server/utils" - - "github.com/labstack/echo/v4" ) var AssetRepository = new(assetRepository) @@ -172,7 +171,7 @@ func (r assetRepository) FindTags(c context.Context) (o []string, err error) { return utils.Distinct(o), nil } -func (r assetRepository) UpdateAttributes(c context.Context, assetId, protocol string, m echo.Map) error { +func (r assetRepository) UpdateAttributes(c context.Context, assetId, protocol string, m maps.Map) error { var data []model.AssetAttribute var parameterNames []string switch protocol { @@ -202,7 +201,7 @@ func (r assetRepository) UpdateAttributes(c context.Context, assetId, protocol s return r.GetDB(c).CreateInBatches(&data, len(data)).Error } -func genAttribute(assetId, name string, m echo.Map) model.AssetAttribute { +func genAttribute(assetId, name string, m maps.Map) model.AssetAttribute { value := fmt.Sprintf("%v", m[name]) attribute := model.AssetAttribute{ Id: utils.Sign([]string{assetId, name}), diff --git a/server/service/asset.go b/server/service/asset.go index 499717b..df49195 100644 --- a/server/service/asset.go +++ b/server/service/asset.go @@ -7,6 +7,7 @@ import ( "fmt" "golang.org/x/net/proxy" "net" + "next-terminal/server/common/maps" "next-terminal/server/common/nt" "strconv" "time" @@ -18,7 +19,6 @@ import ( "next-terminal/server/repository" "next-terminal/server/utils" - "github.com/labstack/echo/v4" "gorm.io/gorm" ) @@ -173,22 +173,22 @@ func (s assetService) CheckStatus(asset *model.Asset, ip string, port int) (bool } } -func (s assetService) Create(ctx context.Context, m echo.Map) (model.Asset, error) { +func (s assetService) Create(ctx context.Context, m maps.Map) (*model.Asset, error) { data, err := json.Marshal(m) if err != nil { - return model.Asset{}, err + return nil, err } var item model.Asset if err := json.Unmarshal(data, &item); err != nil { - return model.Asset{}, err + return nil, err } item.ID = utils.UUID() item.Created = common.NowJsonTime() item.Active = true - return item, s.Transaction(ctx, func(ctx context.Context) error { + return &item, s.Transaction(ctx, func(ctx context.Context) error { if err := s.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil { return err } @@ -222,7 +222,7 @@ func (s assetService) DeleteById(id string) error { }) } -func (s assetService) UpdateById(id string, m echo.Map) error { +func (s assetService) UpdateById(id string, m maps.Map) error { data, err := json.Marshal(m) if err != nil { return err diff --git a/server/service/backup.go b/server/service/backup.go index 94a4ac1..43b1c9a 100644 --- a/server/service/backup.go +++ b/server/service/backup.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "next-terminal/server/common/maps" "strings" "next-terminal/server/common" @@ -15,7 +16,6 @@ import ( "next-terminal/server/repository" "next-terminal/server/utils" - "github.com/labstack/echo/v4" "gorm.io/gorm" ) @@ -265,7 +265,7 @@ func (service backupService) Import(backup *dto.Backup) error { if err != nil { return err } - m := echo.Map{} + m := maps.Map{} if err := json.Unmarshal(data, &m); err != nil { return err } diff --git a/server/utils/license.go b/server/utils/license.go deleted file mode 100644 index 7eac72f..0000000 --- a/server/utils/license.go +++ /dev/null @@ -1,54 +0,0 @@ -package utils - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha512" - "crypto/x509" - "encoding/pem" - - "github.com/denisbrodbeck/machineid" -) - -// SignatureRSA rsa私钥签名 -func SignatureRSA(plainText []byte, rsaPrivateKey string) (signed []byte, err error) { - // 使用pem对读取的内容解码得到block - block, _ := pem.Decode([]byte(rsaPrivateKey)) - //x509将数据解析得到私钥结构体 - privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - // 创建一个hash对象 - h := sha512.New() - _, _ = h.Write(plainText) - // 计算hash值 - hashText := h.Sum(nil) - // 使用rsa函数对散列值签名 - signed, err = rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA512, hashText) - if err != nil { - return - } - return signed, nil -} - -// VerifyRSA rsa签名认证 -func VerifyRSA(plainText, signText []byte, rsaPublicKey string) bool { - // pem解码得到block - block, _ := pem.Decode([]byte(rsaPublicKey)) - // x509解析得到接口 - publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) - if err != nil { - return false - } - // 对原始明文进行hash运算得到散列值 - hashText := sha512.Sum512(plainText) - // 签名认证 - 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 4b8796d..96448b9 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "next-terminal", - "version": "1.3.0-beta3", + "version": "1.3.0-beta4", "private": true, "dependencies": { "@ant-design/charts": "^1.4.2", diff --git a/web/src/components/access/Guacd.css b/web/src/components/access/Guacd.css new file mode 100644 index 0000000..a1758a9 --- /dev/null +++ b/web/src/components/access/Guacd.css @@ -0,0 +1,3 @@ +#display > div { + margin: 0 auto; +} \ No newline at end of file diff --git a/web/src/components/access/Guacd.js b/web/src/components/access/Guacd.js index 079bac2..42242e6 100644 --- a/web/src/components/access/Guacd.js +++ b/web/src/components/access/Guacd.js @@ -19,6 +19,7 @@ import Draggable from "react-draggable"; import FileSystem from "../devops/FileSystem"; import GuacdClipboard from "./GuacdClipboard"; import {debounce} from "../../utils/fun"; +import './Guacd.css'; let fixedSize = false; @@ -74,17 +75,15 @@ const Guacd = () => { tunnel.onerror = onError; // Get display div from document - const display = document.getElementById("display"); + const displayEle = document.getElementById("display"); // Add client to display div const element = client.getDisplay().getElement(); - display.appendChild(element); + displayEle.appendChild(element); - let scale = 1; let dpi = 96; if (protocol === 'telnet') { dpi = dpi * 2; - scale = 0.5; } let token = getToken(); @@ -99,21 +98,31 @@ const Guacd = () => { let paramStr = qs.stringify(params); client.connect(paramStr); + let display = client.getDisplay(); + display.onresize = function (width, height) { + display.scale(Math.min( + window.innerHeight / display.getHeight(), + window.innerWidth / display.getHeight() + )) + } const mouse = new Guacamole.Mouse(element); - mouse.onmousedown = mouse.onmouseup = function (mouseState) { + mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = function (mouseState) { + client.getDisplay().showCursor(false); + mouseState.x = mouseState.x / display.getScale(); + mouseState.y = mouseState.y / display.getScale(); client.sendMouseState(mouseState); }; - mouse.onmousemove = function (mouseState) { - mouseState.x = mouseState.x / scale; - mouseState.y = mouseState.y / scale; - client.sendMouseState(mouseState); + const touch = new Guacamole.Mouse.Touchpad(element); // or Guacamole.Touchscreen + + touch.onmousedown = touch.onmousemove = touch.onmouseup = function (state) { + client.sendMouseState(state); }; const sink = new Guacamole.InputSink(); - display.appendChild(sink.getElement()); + displayEle.appendChild(sink.getElement()); sink.focus(); const keyboard = new Guacamole.Keyboard(sink.getElement()); @@ -130,7 +139,6 @@ const Guacd = () => { setGuacd({ client, - scale, sink, }); } @@ -162,17 +170,12 @@ const Guacd = () => { }, [guacd]) const onWindowResize = () => { - console.log(guacd, fixedSize); if (guacd.client && !fixedSize) { const display = guacd.client.getDisplay(); - let scale = guacd.scale; - display.scale(scale); - let width = window.innerWidth; - let height = window.innerHeight; - - guacd.client.sendSize(width / scale, height / scale); - - setBox({width, height}) + display.scale(Math.min( + window.innerHeight / display.getHeight(), + window.innerWidth / display.getHeight() + )); } } @@ -438,7 +441,6 @@ const Guacd = () => {
{ } } - useEffect(() => { - if (term && websocket && fitAddon && websocket.readyState === WebSocket.OPEN) { - fit(); - focus(); - let terminalSize = { - cols: term.cols, - rows: term.rows - } - websocket.send(new Message(Message.Resize, window.btoa(JSON.stringify(terminalSize))).toString()); - } - - }, [box.width, box.height]); - const onWindowResize = () => { setBox({width: window.innerWidth, height: window.innerHeight}); }; @@ -225,9 +212,20 @@ const Term = () => { useEffect(() => { document.title = assetName; - window.addEventListener('beforeunload', handleUnload); - init(assetId); + }, [assetId]); + + useEffect(() => { + if (term && websocket && fitAddon && websocket.readyState === WebSocket.OPEN) { + fit(); + focus(); + let terminalSize = { + cols: term.cols, + rows: term.rows + } + websocket.send(new Message(Message.Resize, window.btoa(JSON.stringify(terminalSize))).toString()); + } + window.addEventListener('beforeunload', handleUnload); let resize = debounce(() => { onWindowResize(); @@ -242,7 +240,7 @@ const Term = () => { window.removeEventListener('resize', resize); window.removeEventListener('beforeunload', handleUnload); } - }, [assetId]); + }, [box.width, box.height]); const cmdMenuItems = commands.map(item => { return { diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js index 9bc9c09..98326dc 100644 --- a/web/src/components/asset/Asset.js +++ b/web/src/components/asset/Asset.js @@ -44,6 +44,7 @@ function downloadImportExampleCsv() { const importExampleContent = <> 下载示例 +
导入资产时,账号、密码和密钥、密码属于二选一,都填写时优先选择私钥和密码。
const Asset = () => { @@ -295,9 +296,9 @@ const Asset = () => { setSelectedRowKeys([]); } - const handleImportAsset = (file) => { + const handleImportAsset = async (file) => { - let [success, data] = api.importAsset(file); + let [success, data] = await api.importAsset(file); if (success === false) { notification['error']({ message: '导入资产失败', @@ -320,7 +321,7 @@ const Asset = () => { }); } actionRef.current.reload(); - return true; + return false; } const handleChangeOwner = (row) => { @@ -397,6 +398,7 @@ const Asset = () => { @@ -461,6 +463,8 @@ const Asset = () => { actionRef.current.reload(); } finally { setConfirmLoading(false); + setSelectedRowKey(undefined); + setCopied(false); } }} /> diff --git a/web/src/layout/ManagerLayout.js b/web/src/layout/ManagerLayout.js index 7bb1fec..b8b14fd 100644 --- a/web/src/layout/ManagerLayout.js +++ b/web/src/layout/ManagerLayout.js @@ -116,7 +116,10 @@ const ManagerLayout = () => { { + await accountApi.logout(); + navigate('/login'); + }} okText="确定" cancelText="取消" placement="left" diff --git a/web/src/layout/UserLayout.js b/web/src/layout/UserLayout.js index 351c47f..21d9e1b 100644 --- a/web/src/layout/UserLayout.js +++ b/web/src/layout/UserLayout.js @@ -1,5 +1,5 @@ import React, {Suspense, useEffect} from 'react'; -import {Link, Outlet, useLocation} from "react-router-dom"; +import {Link, Outlet, useLocation, useNavigate} from "react-router-dom"; import {Breadcrumb, Button, Dropdown, Layout, Menu, Popconfirm} from "antd"; import { CodeOutlined, @@ -27,6 +27,8 @@ const breadcrumbNameMap = { const UserLayout = () => { const location = useLocation(); + const navigate = useNavigate(); + let _current = location.pathname.split('/')[1]; useEffect(() => { @@ -63,7 +65,10 @@ const UserLayout = () => { { + await accountApi.logout(); + navigate('/login'); + }} okText="确定" cancelText="取消" placement="left"