提交 1.3.0-beta4

This commit is contained in:
dushixiang 2022-10-29 17:36:24 +08:00
parent f87d44d38b
commit b6150c77f8
15 changed files with 104 additions and 135 deletions

View File

@ -46,6 +46,14 @@ https://next.typesafe.cn/ 账号test 密码test
默认账号密码为 admin/admin 。 默认账号密码为 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 - Issues

View File

@ -5,12 +5,11 @@ import (
"context" "context"
"encoding/csv" "encoding/csv"
"errors" "errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"strconv" "strconv"
"strings" "strings"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/model" "next-terminal/server/model"
"next-terminal/server/repository" "next-terminal/server/repository"
"next-terminal/server/service" "next-terminal/server/service"
@ -22,7 +21,7 @@ import (
type AssetApi struct{} type AssetApi struct{}
func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error { func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
m := echo.Map{} m := maps.Map{}
if err := c.Bind(&m); err != nil { if err := c.Bind(&m); err != nil {
return err return err
} }
@ -71,29 +70,31 @@ func (assetApi AssetApi) AssetImportEndpoint(c echo.Context) error {
record := records[i] record := records[i]
if len(record) >= 9 { if len(record) >= 9 {
port, _ := strconv.Atoi(record[3]) port, _ := strconv.Atoi(record[3])
asset := model.Asset{ asset := maps.Map{
ID: utils.UUID(), "id": utils.UUID(),
Name: record[0], "name": record[0],
Protocol: record[1], "protocol": record[1],
IP: record[2], "ip": record[2],
Port: port, "port": port,
AccountType: nt.Custom, "accountType": nt.Custom,
Username: record[4], "username": record[4],
Password: record[5], "password": record[5],
PrivateKey: record[6], "privateKey": record[6],
Passphrase: record[7], "passphrase": record[7],
Description: record[8], "Description": record[8],
Created: common.NowJsonTime(), "owner": account.ID,
Owner: account.ID, }
Active: true,
if record[6] != "" {
asset["accountType"] = nt.PrivateKey
} }
if len(record) >= 10 { if len(record) >= 10 {
tags := strings.ReplaceAll(record[9], "|", ",") 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 { if err != nil {
errorCount++ errorCount++
m[strconv.Itoa(i)] = err.Error() 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 { func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
m := echo.Map{} m := maps.Map{}
if err := c.Bind(&m); err != nil { if err := c.Bind(&m); err != nil {
return err return err
} }

View File

@ -50,7 +50,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId) s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId)
if err != nil { if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "获取会话失败")) return WriteMessage(ws, dto.NewMessage(Closed, "获取会话或解密数据失败"))
} }
if err := api.permissionCheck(c, s.AssetId); err != nil { if err := api.permissionCheck(c, s.AssetId); err != nil {

View File

@ -9,5 +9,5 @@ var Banner = ` ___ ___
\/|::/ / /:/\/__/ \/|::/ / /:/\/__/
|:/ / \/__/ |:/ / \/__/
\/__/ ` \/__/ `
var Version = `v1.3.0-beta2` var Version = `v1.3.0-beta4`
var Hi = Banner + Version var Hi = Banner + Version

View File

@ -3,15 +3,14 @@ package repository
import ( import (
"context" "context"
"fmt" "fmt"
"next-terminal/server/common/nt"
"strconv" "strconv"
"strings" "strings"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/config" "next-terminal/server/config"
"next-terminal/server/model" "next-terminal/server/model"
"next-terminal/server/utils" "next-terminal/server/utils"
"github.com/labstack/echo/v4"
) )
var AssetRepository = new(assetRepository) var AssetRepository = new(assetRepository)
@ -172,7 +171,7 @@ func (r assetRepository) FindTags(c context.Context) (o []string, err error) {
return utils.Distinct(o), nil 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 data []model.AssetAttribute
var parameterNames []string var parameterNames []string
switch protocol { 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 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]) value := fmt.Sprintf("%v", m[name])
attribute := model.AssetAttribute{ attribute := model.AssetAttribute{
Id: utils.Sign([]string{assetId, name}), Id: utils.Sign([]string{assetId, name}),

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"net" "net"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt" "next-terminal/server/common/nt"
"strconv" "strconv"
"time" "time"
@ -18,7 +19,6 @@ import (
"next-terminal/server/repository" "next-terminal/server/repository"
"next-terminal/server/utils" "next-terminal/server/utils"
"github.com/labstack/echo/v4"
"gorm.io/gorm" "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) data, err := json.Marshal(m)
if err != nil { if err != nil {
return model.Asset{}, err return nil, err
} }
var item model.Asset var item model.Asset
if err := json.Unmarshal(data, &item); err != nil { if err := json.Unmarshal(data, &item); err != nil {
return model.Asset{}, err return nil, err
} }
item.ID = utils.UUID() item.ID = utils.UUID()
item.Created = common.NowJsonTime() item.Created = common.NowJsonTime()
item.Active = true 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 { if err := s.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err 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) data, err := json.Marshal(m)
if err != nil { if err != nil {
return err return err

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"next-terminal/server/common/maps"
"strings" "strings"
"next-terminal/server/common" "next-terminal/server/common"
@ -15,7 +16,6 @@ import (
"next-terminal/server/repository" "next-terminal/server/repository"
"next-terminal/server/utils" "next-terminal/server/utils"
"github.com/labstack/echo/v4"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -265,7 +265,7 @@ func (service backupService) Import(backup *dto.Backup) error {
if err != nil { if err != nil {
return err return err
} }
m := echo.Map{} m := maps.Map{}
if err := json.Unmarshal(data, &m); err != nil { if err := json.Unmarshal(data, &m); err != nil {
return err return err
} }

View File

@ -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()
}

View File

@ -1,6 +1,6 @@
{ {
"name": "next-terminal", "name": "next-terminal",
"version": "1.3.0-beta3", "version": "1.3.0-beta4",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/charts": "^1.4.2", "@ant-design/charts": "^1.4.2",

View File

@ -0,0 +1,3 @@
#display > div {
margin: 0 auto;
}

View File

@ -19,6 +19,7 @@ import Draggable from "react-draggable";
import FileSystem from "../devops/FileSystem"; import FileSystem from "../devops/FileSystem";
import GuacdClipboard from "./GuacdClipboard"; import GuacdClipboard from "./GuacdClipboard";
import {debounce} from "../../utils/fun"; import {debounce} from "../../utils/fun";
import './Guacd.css';
let fixedSize = false; let fixedSize = false;
@ -74,17 +75,15 @@ const Guacd = () => {
tunnel.onerror = onError; tunnel.onerror = onError;
// Get display div from document // Get display div from document
const display = document.getElementById("display"); const displayEle = document.getElementById("display");
// Add client to display div // Add client to display div
const element = client.getDisplay().getElement(); const element = client.getDisplay().getElement();
display.appendChild(element); displayEle.appendChild(element);
let scale = 1;
let dpi = 96; let dpi = 96;
if (protocol === 'telnet') { if (protocol === 'telnet') {
dpi = dpi * 2; dpi = dpi * 2;
scale = 0.5;
} }
let token = getToken(); let token = getToken();
@ -99,21 +98,31 @@ const Guacd = () => {
let paramStr = qs.stringify(params); let paramStr = qs.stringify(params);
client.connect(paramStr); 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); 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); client.sendMouseState(mouseState);
}; };
mouse.onmousemove = function (mouseState) { const touch = new Guacamole.Mouse.Touchpad(element); // or Guacamole.Touchscreen
mouseState.x = mouseState.x / scale;
mouseState.y = mouseState.y / scale; touch.onmousedown = touch.onmousemove = touch.onmouseup = function (state) {
client.sendMouseState(mouseState); client.sendMouseState(state);
}; };
const sink = new Guacamole.InputSink(); const sink = new Guacamole.InputSink();
display.appendChild(sink.getElement()); displayEle.appendChild(sink.getElement());
sink.focus(); sink.focus();
const keyboard = new Guacamole.Keyboard(sink.getElement()); const keyboard = new Guacamole.Keyboard(sink.getElement());
@ -130,7 +139,6 @@ const Guacd = () => {
setGuacd({ setGuacd({
client, client,
scale,
sink, sink,
}); });
} }
@ -162,17 +170,12 @@ const Guacd = () => {
}, [guacd]) }, [guacd])
const onWindowResize = () => { const onWindowResize = () => {
console.log(guacd, fixedSize);
if (guacd.client && !fixedSize) { if (guacd.client && !fixedSize) {
const display = guacd.client.getDisplay(); const display = guacd.client.getDisplay();
let scale = guacd.scale; display.scale(Math.min(
display.scale(scale); window.innerHeight / display.getHeight(),
let width = window.innerWidth; window.innerWidth / display.getHeight()
let height = window.innerHeight; ));
guacd.client.sendSize(width / scale, height / scale);
setBox({width, height})
} }
} }
@ -438,7 +441,6 @@ const Guacd = () => {
<div> <div>
<div className="container" style={{ <div className="container" style={{
overflow: 'hidden',
width: box.width, width: box.width,
height: box.height, height: box.height,
margin: '0 auto' margin: '0 auto'

View File

@ -88,19 +88,6 @@ const Term = () => {
} }
} }
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 = () => { const onWindowResize = () => {
setBox({width: window.innerWidth, height: window.innerHeight}); setBox({width: window.innerWidth, height: window.innerHeight});
}; };
@ -225,9 +212,20 @@ const Term = () => {
useEffect(() => { useEffect(() => {
document.title = assetName; document.title = assetName;
window.addEventListener('beforeunload', handleUnload);
init(assetId); 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(() => { let resize = debounce(() => {
onWindowResize(); onWindowResize();
@ -242,7 +240,7 @@ const Term = () => {
window.removeEventListener('resize', resize); window.removeEventListener('resize', resize);
window.removeEventListener('beforeunload', handleUnload); window.removeEventListener('beforeunload', handleUnload);
} }
}, [assetId]); }, [box.width, box.height]);
const cmdMenuItems = commands.map(item => { const cmdMenuItems = commands.map(item => {
return { return {

View File

@ -44,6 +44,7 @@ function downloadImportExampleCsv() {
const importExampleContent = <> const importExampleContent = <>
<a onClick={downloadImportExampleCsv}>下载示例</a> <a onClick={downloadImportExampleCsv}>下载示例</a>
<div>导入资产时账号密码和密钥密码属于二选一都填写时优先选择私钥和密码</div>
</> </>
const Asset = () => { const Asset = () => {
@ -295,9 +296,9 @@ const Asset = () => {
setSelectedRowKeys([]); setSelectedRowKeys([]);
} }
const handleImportAsset = (file) => { const handleImportAsset = async (file) => {
let [success, data] = api.importAsset(file); let [success, data] = await api.importAsset(file);
if (success === false) { if (success === false) {
notification['error']({ notification['error']({
message: '导入资产失败', message: '导入资产失败',
@ -320,7 +321,7 @@ const Asset = () => {
}); });
} }
actionRef.current.reload(); actionRef.current.reload();
return true; return false;
} }
const handleChangeOwner = (row) => { const handleChangeOwner = (row) => {
@ -397,6 +398,7 @@ const Asset = () => {
<Upload <Upload
maxCount={1} maxCount={1}
beforeUpload={handleImportAsset} beforeUpload={handleImportAsset}
showUploadList={false}
> >
<Button key='import'>导入</Button> <Button key='import'>导入</Button>
</Upload> </Upload>
@ -461,6 +463,8 @@ const Asset = () => {
actionRef.current.reload(); actionRef.current.reload();
} finally { } finally {
setConfirmLoading(false); setConfirmLoading(false);
setSelectedRowKey(undefined);
setCopied(false);
} }
}} }}
/> />

View File

@ -116,7 +116,10 @@ const ManagerLayout = () => {
<Popconfirm <Popconfirm
key='login-btn-pop' key='login-btn-pop'
title="您确定要退出登录吗?" title="您确定要退出登录吗?"
onConfirm={accountApi.logout} onConfirm={async ()=>{
await accountApi.logout();
navigate('/login');
}}
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
placement="left" placement="left"

View File

@ -1,5 +1,5 @@
import React, {Suspense, useEffect} from 'react'; 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 {Breadcrumb, Button, Dropdown, Layout, Menu, Popconfirm} from "antd";
import { import {
CodeOutlined, CodeOutlined,
@ -27,6 +27,8 @@ const breadcrumbNameMap = {
const UserLayout = () => { const UserLayout = () => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate();
let _current = location.pathname.split('/')[1]; let _current = location.pathname.split('/')[1];
useEffect(() => { useEffect(() => {
@ -63,7 +65,10 @@ const UserLayout = () => {
<Popconfirm <Popconfirm
key='login-btn-pop' key='login-btn-pop'
title="您确定要退出登录吗?" title="您确定要退出登录吗?"
onConfirm={accountApi.logout} onConfirm={async ()=>{
await accountApi.logout();
navigate('/login');
}}
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
placement="left" placement="left"