完善docker部署和readme
This commit is contained in:
55
README.md
55
README.md
@ -1,4 +1,53 @@
|
|||||||
# next-terminal
|
# Next Terminal
|
||||||
just do go
|
你的下一个终端。
|
||||||
|
|
||||||
## 主要功能
|
## 快速了解
|
||||||
|
|
||||||
|
Next Terminal是使用Golang和React开发的一款HTML5的远程桌面网关,具有小巧、易安装、易使用、资源占用小的特点,支持RDP、SSH、VNC和Telnet协议的连接和管理。
|
||||||
|
|
||||||
|
Next Terminal基于Apache Guacamole开发,使用到了guacd服务。
|
||||||
|
|
||||||
|
## 快速安装
|
||||||
|
|
||||||
|
### docker安装
|
||||||
|
|
||||||
|
因为程序依赖了mysql,所以在启动时需要指定mysql的连接信息。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -p 8088:8088 --env MYSQL_HOSTNAME=d-mysql-57 --env MYSQL_USERNAME=root --env MYSQL_PASSWORD=root --name next-terminal --link d-mysql-57 dushixiang/next-terminal:0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
程序安装目录地址为 `/usr/local/nt`
|
||||||
|
|
||||||
|
录屏文件存放目录为 `/usr/local/nt/recording`
|
||||||
|
|
||||||
|
远程桌面挂载目录为 `/usr/local/nt/drive`
|
||||||
|
|
||||||
|
可以通过 `-v` 参数将宿主机器的目录映射到docker中
|
||||||
|
|
||||||
|
|
||||||
|
## 相关截图
|
||||||
|
|
||||||
|
资源占用截图
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
资产管理
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
rdp
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
vnc
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
ssh
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
批量执行命令
|
||||||
|
|
||||||
|

|
@ -33,6 +33,22 @@ func SessionPagingEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(items); i++ {
|
||||||
|
if len(items[i].Recording) > 0 {
|
||||||
|
recording := items[i].Recording + "/recording"
|
||||||
|
|
||||||
|
if utils.FileExists(recording) {
|
||||||
|
log.Infof("检测到录屏文件[%v]存在", recording)
|
||||||
|
items[i].Recording = "1"
|
||||||
|
} else {
|
||||||
|
log.Warnf("检测到录屏文件[%v]不存在", recording)
|
||||||
|
items[i].Recording = "0"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items[i].Recording = "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Success(c, H{
|
return Success(c, H{
|
||||||
"total": total,
|
"total": total,
|
||||||
"items": items,
|
"items": items,
|
||||||
@ -436,11 +452,11 @@ func SessionRmEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
func SessionRecordingEndpoint(c echo.Context) error {
|
func SessionRecordingEndpoint(c echo.Context) error {
|
||||||
sessionId := c.Param("id")
|
sessionId := c.Param("id")
|
||||||
recordingPath, err := model.GetRecordingPath()
|
session, err := model.FindSessionById(sessionId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
recording := path.Join(recordingPath, sessionId, "recording")
|
recording := path.Join(session.Recording, "recording")
|
||||||
log.Printf("读取录屏文件:%s", recording)
|
log.Printf("读取录屏文件:%s", recording)
|
||||||
return c.File(recording)
|
return c.File(recording)
|
||||||
}
|
}
|
||||||
|
@ -49,18 +49,9 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for name := range propertyMap {
|
|
||||||
if name == guacd.FontSize {
|
|
||||||
fontSize, _ := strconv.Atoi(propertyMap[name])
|
|
||||||
fontSize = fontSize * 2
|
|
||||||
configuration.SetParameter(name, strconv.Itoa(fontSize))
|
|
||||||
} else {
|
|
||||||
configuration.SetParameter(name, propertyMap[name])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if propertyMap[guacd.EnableRecording] == "true" {
|
if propertyMap[guacd.EnableRecording] == "true" {
|
||||||
configuration.SetParameter(guacd.RecordingPath, path.Join(propertyMap[guacd.RecordingPath], sessionId))
|
configuration.SetParameter(guacd.RecordingPath, path.Join(propertyMap[guacd.RecordingPath], sessionId))
|
||||||
|
configuration.SetParameter(guacd.CreateRecordingPath, propertyMap[guacd.CreateRecordingPath])
|
||||||
} else {
|
} else {
|
||||||
configuration.SetParameter(guacd.RecordingPath, "")
|
configuration.SetParameter(guacd.RecordingPath, "")
|
||||||
}
|
}
|
||||||
@ -77,17 +68,34 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
configuration.SetParameter("dpi", "96")
|
configuration.SetParameter("dpi", "96")
|
||||||
configuration.SetParameter("resize-method", "reconnect")
|
configuration.SetParameter("resize-method", "reconnect")
|
||||||
configuration.SetParameter("enable-sftp", "")
|
configuration.SetParameter(guacd.EnableDrive, propertyMap[guacd.EnableDrive])
|
||||||
|
configuration.SetParameter(guacd.DriveName, propertyMap[guacd.DriveName])
|
||||||
|
configuration.SetParameter(guacd.DrivePath, propertyMap[guacd.DrivePath])
|
||||||
|
configuration.SetParameter(guacd.EnableWallpaper, propertyMap[guacd.EnableWallpaper])
|
||||||
|
configuration.SetParameter(guacd.EnableTheming, propertyMap[guacd.EnableTheming])
|
||||||
|
configuration.SetParameter(guacd.EnableFontSmoothing, propertyMap[guacd.EnableFontSmoothing])
|
||||||
|
configuration.SetParameter(guacd.EnableFullWindowDrag, propertyMap[guacd.EnableFullWindowDrag])
|
||||||
|
configuration.SetParameter(guacd.EnableDesktopComposition, propertyMap[guacd.EnableDesktopComposition])
|
||||||
|
configuration.SetParameter(guacd.EnableMenuAnimations, propertyMap[guacd.EnableMenuAnimations])
|
||||||
|
configuration.SetParameter(guacd.DisableBitmapCaching, propertyMap[guacd.DisableBitmapCaching])
|
||||||
|
configuration.SetParameter(guacd.DisableOffscreenCaching, propertyMap[guacd.DisableOffscreenCaching])
|
||||||
|
configuration.SetParameter(guacd.DisableGlyphCaching, propertyMap[guacd.DisableGlyphCaching])
|
||||||
break
|
break
|
||||||
case "ssh":
|
case "ssh":
|
||||||
if session.PrivateKey == "-" {
|
if len(session.PrivateKey) > 0 && session.PrivateKey != "-" {
|
||||||
configuration.SetParameter("username", session.Username)
|
|
||||||
configuration.SetParameter("password", session.Password)
|
|
||||||
} else {
|
|
||||||
configuration.SetParameter("private-key", session.PrivateKey)
|
configuration.SetParameter("private-key", session.PrivateKey)
|
||||||
configuration.SetParameter("passphrase", session.Passphrase)
|
configuration.SetParameter("passphrase", session.Passphrase)
|
||||||
|
} else {
|
||||||
|
configuration.SetParameter("username", session.Username)
|
||||||
|
configuration.SetParameter("password", session.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fontSize, _ := strconv.Atoi(propertyMap[guacd.FontSize])
|
||||||
|
fontSize = fontSize * 2
|
||||||
|
configuration.SetParameter(guacd.FontSize, strconv.Itoa(fontSize))
|
||||||
|
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
|
||||||
|
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
|
||||||
|
|
||||||
sftpClient, err = CreateSftpClient(session.AssetId)
|
sftpClient, err = CreateSftpClient(session.AssetId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -62,10 +62,18 @@ func UserUpdateEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserDeleteEndpoint(c echo.Context) error {
|
func UserDeleteEndpoint(c echo.Context) error {
|
||||||
id := c.Param("id")
|
ids := c.Param("id")
|
||||||
split := strings.Split(id, ",")
|
account, found := GetCurrentAccount(c)
|
||||||
|
if !found {
|
||||||
|
return Fail(c, -1, "获取当前登录账户失败")
|
||||||
|
}
|
||||||
|
split := strings.Split(ids, ",")
|
||||||
for i := range split {
|
for i := range split {
|
||||||
model.DeleteUserById(split[i])
|
userId := split[i]
|
||||||
|
if account.ID == userId {
|
||||||
|
return Fail(c, -1, "不允许删除自身账户")
|
||||||
|
}
|
||||||
|
model.DeleteUserById(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
|
@ -34,7 +34,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const Delimiter = ';'
|
const Delimiter = ';'
|
||||||
const Version = "VERSION_1_1_0"
|
const Version = "VERSION_1_2_0"
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
ConnectionID string
|
ConnectionID string
|
||||||
|
@ -87,7 +87,7 @@ func InitProperties() error {
|
|||||||
Name: guacd.RecordingPath,
|
Name: guacd.RecordingPath,
|
||||||
Value: path + "/recording/",
|
Value: path + "/recording/",
|
||||||
}
|
}
|
||||||
if !utils.Exists(property.Value) {
|
if !utils.FileExists(property.Value) {
|
||||||
if err := os.Mkdir(property.Value, os.ModePerm); err != nil {
|
if err := os.Mkdir(property.Value, os.ModePerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -125,7 +125,7 @@ func InitProperties() error {
|
|||||||
Name: guacd.DrivePath,
|
Name: guacd.DrivePath,
|
||||||
Value: path + "/drive/",
|
Value: path + "/drive/",
|
||||||
}
|
}
|
||||||
if !utils.Exists(property.Value) {
|
if !utils.FileExists(property.Value) {
|
||||||
if err := os.Mkdir(property.Value, os.ModePerm); err != nil {
|
if err := os.Mkdir(property.Value, os.ModePerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ func Tcping(ip string, port int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判断所给路径文件/文件夹是否存在
|
// 判断所给路径文件/文件夹是否存在
|
||||||
func Exists(path string) bool {
|
func FileExists(path string) bool {
|
||||||
_, err := os.Stat(path) //os.Stat获取文件信息
|
_, err := os.Stat(path) //os.Stat获取文件信息
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
|
BIN
screenshot/command.png
Normal file
BIN
screenshot/command.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
BIN
screenshot/docker_stats.png
Normal file
BIN
screenshot/docker_stats.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
BIN
screenshot/rdp.png
Normal file
BIN
screenshot/rdp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
BIN
screenshot/ssh.png
Normal file
BIN
screenshot/ssh.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
BIN
screenshot/vnc.png
Normal file
BIN
screenshot/vnc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 552 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-terminal",
|
"name": "next-terminal",
|
||||||
"version": "0.1.0",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.3.0",
|
"@ant-design/icons": "^4.3.0",
|
||||||
|
@ -288,7 +288,7 @@ class OfflineSession extends Component {
|
|||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
let disabled = true;
|
let disabled = true;
|
||||||
let color = '#d9d9d9'
|
let color = '#d9d9d9'
|
||||||
if (record['recording'] && record['recording'].length > 0) {
|
if (record['recording'] && record['recording'] === '1') {
|
||||||
disabled = false
|
disabled = false
|
||||||
color = ''
|
color = ''
|
||||||
}
|
}
|
||||||
@ -297,7 +297,8 @@ class OfflineSession extends Component {
|
|||||||
<div>
|
<div>
|
||||||
<Button type="link" size='small'
|
<Button type="link" size='small'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
icon={<PlaySquareTwoTone twoToneColor={color}/>} onClick={() => this.showPlayback(record.id)}>回放</Button>
|
icon={<PlaySquareTwoTone twoToneColor={color}/>}
|
||||||
|
onClick={() => this.showPlayback(record.id)}>回放</Button>
|
||||||
<Button type="link" size='small' icon={<DeleteTwoTone/>} onClick={() => {
|
<Button type="link" size='small' icon={<DeleteTwoTone/>} onClick={() => {
|
||||||
confirm({
|
confirm({
|
||||||
title: '您确定要删除此会话吗?',
|
title: '您确定要删除此会话吗?',
|
||||||
|
Reference in New Issue
Block a user