560 lines
13 KiB
Go
560 lines
13 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"next-terminal/server/common/maps"
|
|
"next-terminal/server/common/nt"
|
|
"next-terminal/server/global/session"
|
|
"next-terminal/server/log"
|
|
"next-terminal/server/model"
|
|
"next-terminal/server/repository"
|
|
"next-terminal/server/service"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
type PortalApi struct{}
|
|
|
|
func (api PortalApi) AssetsEndpoint(c echo.Context) error {
|
|
assetType := c.QueryParam("type")
|
|
ctx := context.TODO()
|
|
var items []model.Asset
|
|
var err error
|
|
if assetType != "" {
|
|
items, err = repository.AssetRepository.FindByProtocol(ctx, assetType)
|
|
} else {
|
|
items, err = repository.AssetRepository.FindAll(ctx)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result := make([]maps.Map, len(items))
|
|
for i, a := range items {
|
|
result[i] = maps.Map{
|
|
"id": a.ID,
|
|
"logo": "",
|
|
"name": a.Name,
|
|
"address": a.IP + ":" + strconv.Itoa(a.Port),
|
|
"protocol": a.Protocol,
|
|
"tags": []string{},
|
|
"status": "unknown",
|
|
"type": "asset",
|
|
"groupId": "",
|
|
"users": []string{},
|
|
}
|
|
}
|
|
return Success(c, result)
|
|
}
|
|
|
|
func (api PortalApi) DatabaseAssetsEndpoint(c echo.Context) error {
|
|
return Success(c, []interface{}{})
|
|
}
|
|
|
|
func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
|
protocol := c.QueryParam("protocol")
|
|
keyword := c.QueryParam("keyword")
|
|
ctx := context.TODO()
|
|
|
|
items, _ := repository.AssetRepository.FindByProtocol(ctx, protocol)
|
|
groups, _ := repository.AssetGroupRepository.FindAll(ctx)
|
|
|
|
tree := make([]maps.Map, 0)
|
|
for _, g := range groups {
|
|
node := maps.Map{
|
|
"key": "group_" + g.ID,
|
|
"title": g.Name,
|
|
"isLeaf": false,
|
|
"children": []interface{}{},
|
|
}
|
|
tree = append(tree, node)
|
|
}
|
|
for _, a := range items {
|
|
if keyword != "" && !containsKeyword(a.Name, keyword) {
|
|
continue
|
|
}
|
|
node := maps.Map{
|
|
"key": "asset_" + a.ID,
|
|
"title": a.Name,
|
|
"isLeaf": true,
|
|
"extra": maps.Map{
|
|
"protocol": a.Protocol,
|
|
"logo": "",
|
|
"status": "unknown",
|
|
"network": a.IP,
|
|
"wolEnabled": false,
|
|
},
|
|
}
|
|
tree = append(tree, node)
|
|
}
|
|
return Success(c, tree)
|
|
}
|
|
|
|
func containsKeyword(name, keyword string) bool {
|
|
if keyword == "" {
|
|
return true
|
|
}
|
|
for _, ch := range name {
|
|
for _, kh := range keyword {
|
|
if ch == kh {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (api PortalApi) WebsitesTreeEndpoint(c echo.Context) error {
|
|
keyword := c.QueryParam("keyword")
|
|
items, _ := repository.WebsiteRepository.FindAll(context.TODO())
|
|
tree := make([]maps.Map, 0)
|
|
for _, w := range items {
|
|
if keyword != "" && !containsKeyword(w.Name, keyword) {
|
|
continue
|
|
}
|
|
node := maps.Map{
|
|
"key": "website_" + w.ID,
|
|
"title": w.Name,
|
|
"isLeaf": true,
|
|
"extra": maps.Map{
|
|
"protocol": "website",
|
|
"logo": "",
|
|
"status": w.Status,
|
|
"network": w.Domain,
|
|
},
|
|
}
|
|
tree = append(tree, node)
|
|
}
|
|
return Success(c, tree)
|
|
}
|
|
|
|
func (api PortalApi) AssetsGroupTreeEndpoint(c echo.Context) error {
|
|
groups, _ := repository.AssetGroupRepository.FindAll(context.TODO())
|
|
tree := make([]maps.Map, 0)
|
|
for _, g := range groups {
|
|
node := maps.Map{
|
|
"key": "group_" + g.ID,
|
|
"title": g.Name,
|
|
"isLeaf": false,
|
|
"children": []interface{}{},
|
|
}
|
|
tree = append(tree, node)
|
|
}
|
|
return Success(c, tree)
|
|
}
|
|
|
|
func (api PortalApi) WebsitesGroupTreeEndpoint(c echo.Context) error {
|
|
return Success(c, []interface{}{})
|
|
}
|
|
|
|
func (api PortalApi) AccessRequireMfaEndpoint(c echo.Context) error {
|
|
return Success(c, maps.Map{"required": false})
|
|
}
|
|
|
|
func (api PortalApi) CreateSessionEndpoint(c echo.Context) error {
|
|
var req map[string]interface{}
|
|
if err := c.Bind(&req); err != nil {
|
|
return err
|
|
}
|
|
assetId, _ := req["assetId"].(string)
|
|
|
|
account, _ := GetCurrentAccount(c)
|
|
|
|
clientIP := service.PropertyService.GetClientIP(c)
|
|
s, err := service.SessionService.Create(clientIP, assetId, nt.Native, account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
asset, _ := repository.AssetRepository.FindById(context.TODO(), assetId)
|
|
assetName := asset.Name
|
|
if assetName == "" {
|
|
assetName = assetId
|
|
}
|
|
|
|
return Success(c, maps.Map{
|
|
"id": s.ID,
|
|
"protocol": s.Protocol,
|
|
"assetName": assetName,
|
|
"strategy": maps.Map{
|
|
"upload": s.Upload == "1",
|
|
"download": s.Download == "1",
|
|
"delete": s.Delete == "1",
|
|
"rename": s.Rename == "1",
|
|
"edit": s.Edit == "1",
|
|
"copy": s.Copy == "1",
|
|
"paste": s.Paste == "1",
|
|
"fileSystem": s.FileSystem == "1",
|
|
},
|
|
"url": "",
|
|
"watermark": maps.Map{},
|
|
"readonly": false,
|
|
"idle": 3600,
|
|
"fileSystem": s.FileSystem == "1",
|
|
"width": 800,
|
|
"height": 600,
|
|
})
|
|
}
|
|
|
|
func (api PortalApi) GetSessionEndpoint(c echo.Context) error {
|
|
id := c.Param("id")
|
|
return Success(c, maps.Map{
|
|
"id": id,
|
|
"protocol": "ssh",
|
|
"assetName": id,
|
|
"strategy": maps.Map{},
|
|
"url": "",
|
|
"watermark": maps.Map{},
|
|
"readonly": false,
|
|
"idle": 3600,
|
|
"fileSystem": false,
|
|
"width": 800,
|
|
"height": 600,
|
|
})
|
|
}
|
|
|
|
func (api PortalApi) GetShareEndpoint(c echo.Context) error {
|
|
return Success(c, maps.Map{
|
|
"enabled": false,
|
|
"passcode": "",
|
|
"url": "",
|
|
})
|
|
}
|
|
|
|
func (api PortalApi) CreateShareEndpoint(c echo.Context) error {
|
|
return Success(c, maps.Map{
|
|
"enabled": true,
|
|
"passcode": "",
|
|
"url": "",
|
|
})
|
|
}
|
|
|
|
func (api PortalApi) CancelShareEndpoint(c echo.Context) error {
|
|
return Success(c, nil)
|
|
}
|
|
|
|
func (api PortalApi) AccessWebsiteEndpoint(c echo.Context) error {
|
|
websiteId := c.QueryParam("websiteId")
|
|
item, _ := repository.WebsiteRepository.FindById(context.TODO(), websiteId)
|
|
url := ""
|
|
if item.TargetUrl != "" {
|
|
url = item.TargetUrl
|
|
} else {
|
|
url = "http://" + item.Domain
|
|
}
|
|
return Success(c, maps.Map{"url": url})
|
|
}
|
|
|
|
func (api PortalApi) AllowWebsiteIpEndpoint(c echo.Context) error {
|
|
return Success(c, nil)
|
|
}
|
|
|
|
func (api PortalApi) WakeOnLanEndpoint(c echo.Context) error {
|
|
assetId := c.Param("id")
|
|
_ = assetId
|
|
return Success(c, maps.Map{
|
|
"delay": 5,
|
|
})
|
|
}
|
|
|
|
func (api PortalApi) PingAssetEndpoint(c echo.Context) error {
|
|
assetId := c.Param("id")
|
|
item, _ := repository.AssetRepository.FindById(context.TODO(), assetId)
|
|
return Success(c, maps.Map{
|
|
"name": item.Name,
|
|
"active": true,
|
|
"usedTime": 10,
|
|
"usedTimeStr": "10ms",
|
|
})
|
|
}
|
|
|
|
func (api PortalApi) TerminalStatsEndpoint(c echo.Context) error {
|
|
sessionId := c.Param("id")
|
|
|
|
sess := session.GlobalSessionManager.GetById(sessionId)
|
|
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
|
|
return Success(c, maps.Map{
|
|
"info": maps.Map{
|
|
"id": "",
|
|
"name": "",
|
|
"version": "",
|
|
"arch": "",
|
|
"uptime": 0,
|
|
"hostname": "",
|
|
"upDays": 0,
|
|
},
|
|
"load": maps.Map{
|
|
"load1": "0.00",
|
|
"load5": "0.00",
|
|
"load15": "0.00",
|
|
"runningProcess": "0",
|
|
"totalProcess": "0",
|
|
},
|
|
"memory": maps.Map{
|
|
"memTotal": 0,
|
|
"memAvailable": 0,
|
|
"memFree": 0,
|
|
"memBuffers": 0,
|
|
"memCached": 0,
|
|
"memUsed": 0,
|
|
"swapTotal": 0,
|
|
"swapFree": 0,
|
|
},
|
|
"fileSystems": []interface{}{},
|
|
"network": []interface{}{},
|
|
"cpu": []interface{}{},
|
|
})
|
|
}
|
|
|
|
stats, err := getSystemStats(sess.NextTerminal.SshClient)
|
|
if err != nil {
|
|
log.Warn("Failed to get system stats", log.NamedError("error", err))
|
|
return Success(c, maps.Map{
|
|
"info": maps.Map{
|
|
"id": "",
|
|
"name": "",
|
|
"version": "",
|
|
"arch": "",
|
|
"uptime": 0,
|
|
"hostname": "",
|
|
"upDays": 0,
|
|
},
|
|
"load": maps.Map{
|
|
"load1": "0.00",
|
|
"load5": "0.00",
|
|
"load15": "0.00",
|
|
"runningProcess": "0",
|
|
"totalProcess": "0",
|
|
},
|
|
"memory": maps.Map{
|
|
"memTotal": 0,
|
|
"memAvailable": 0,
|
|
"memFree": 0,
|
|
"memBuffers": 0,
|
|
"memCached": 0,
|
|
"memUsed": 0,
|
|
"swapTotal": 0,
|
|
"swapFree": 0,
|
|
},
|
|
"fileSystems": []interface{}{},
|
|
"network": []interface{}{},
|
|
"cpu": []interface{}{},
|
|
})
|
|
}
|
|
|
|
return Success(c, stats)
|
|
}
|
|
|
|
func getSystemStats(client *ssh.Client) (maps.Map, error) {
|
|
result := maps.Map{}
|
|
|
|
// Get hostname
|
|
hostname, _ := sshExec(client, "hostname")
|
|
|
|
// Get OS info
|
|
osId, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'")
|
|
osName, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^NAME=' | cut -d'=' -f2 | tr -d '\"'")
|
|
osVersion, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^VERSION_ID=' | cut -d'=' -f2 | tr -d '\"'")
|
|
|
|
// Get arch
|
|
arch, _ := sshExec(client, "uname -m")
|
|
|
|
// Get uptime
|
|
uptimeStr, _ := sshExec(client, "cat /proc/uptime | awk '{print $1}'")
|
|
uptimeFloat, _ := strconv.ParseFloat(strings.TrimSpace(uptimeStr), 64)
|
|
uptime := int64(uptimeFloat)
|
|
upDays := uptime / 86400
|
|
|
|
// Get load average
|
|
loadStr, _ := sshExec(client, "cat /proc/loadavg")
|
|
loadParts := strings.Fields(loadStr)
|
|
load1, load5, load15 := "0.00", "0.00", "0.00"
|
|
if len(loadParts) >= 3 {
|
|
load1 = loadParts[0]
|
|
load5 = loadParts[1]
|
|
load15 = loadParts[2]
|
|
}
|
|
|
|
// Get process count
|
|
processStr, _ := sshExec(client, "ps aux | wc -l")
|
|
totalProcess := strings.TrimSpace(processStr)
|
|
|
|
// Get memory info
|
|
memInfo, _ := sshExec(client, "cat /proc/meminfo")
|
|
memTotal, memFree, memAvailable, memBuffers, memCached, swapTotal, swapFree := parseMemInfo(memInfo)
|
|
memUsed := memTotal - memAvailable
|
|
|
|
// Get CPU info
|
|
cpuInfo, _ := sshExec(client, "cat /proc/stat | head -1")
|
|
cpuStats := parseCpuStat(cpuInfo)
|
|
|
|
// Get filesystem info
|
|
dfOutput, _ := sshExec(client, "df -B1 | tail -n +2")
|
|
fileSystems := parseDfOutput(dfOutput)
|
|
|
|
// Get network info
|
|
netDev, _ := sshExec(client, "cat /proc/net/dev | tail -n +3")
|
|
network := parseNetDev(netDev)
|
|
|
|
result["info"] = maps.Map{
|
|
"id": strings.TrimSpace(osId),
|
|
"name": strings.TrimSpace(osName),
|
|
"version": strings.TrimSpace(osVersion),
|
|
"arch": strings.TrimSpace(arch),
|
|
"uptime": uptime,
|
|
"hostname": strings.TrimSpace(hostname),
|
|
"upDays": upDays,
|
|
}
|
|
result["load"] = maps.Map{
|
|
"load1": load1,
|
|
"load5": load5,
|
|
"load15": load15,
|
|
"runningProcess": totalProcess,
|
|
"totalProcess": totalProcess,
|
|
}
|
|
result["memory"] = maps.Map{
|
|
"memTotal": memTotal * 1024,
|
|
"memAvailable": memAvailable * 1024,
|
|
"memFree": memFree * 1024,
|
|
"memBuffers": memBuffers * 1024,
|
|
"memCached": memCached * 1024,
|
|
"memUsed": memUsed * 1024,
|
|
"swapTotal": swapTotal * 1024,
|
|
"swapFree": swapFree * 1024,
|
|
}
|
|
result["cpu"] = cpuStats
|
|
result["fileSystems"] = fileSystems
|
|
result["network"] = network
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func sshExec(client *ssh.Client, cmd string) (string, error) {
|
|
session, err := client.NewSession()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer session.Close()
|
|
|
|
output, err := session.CombinedOutput(cmd)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(output), nil
|
|
}
|
|
|
|
func parseMemInfo(info string) (total, free, available, buffers, cached, swapTotal, swapFree int64) {
|
|
lines := strings.Split(info, "\n")
|
|
for _, line := range lines {
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
key := strings.TrimSuffix(parts[0], ":")
|
|
value, _ := strconv.ParseInt(parts[1], 10, 64)
|
|
|
|
switch key {
|
|
case "MemTotal":
|
|
total = value
|
|
case "MemFree":
|
|
free = value
|
|
case "MemAvailable":
|
|
available = value
|
|
case "Buffers":
|
|
buffers = value
|
|
case "Cached":
|
|
cached = value
|
|
case "SwapTotal":
|
|
swapTotal = value
|
|
case "SwapFree":
|
|
swapFree = value
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func parseCpuStat(stat string) []interface{} {
|
|
parts := strings.Fields(stat)
|
|
if len(parts) < 5 {
|
|
return []interface{}{maps.Map{"user": 0.0, "nice": 0.0, "system": 0.0}}
|
|
}
|
|
|
|
user, _ := strconv.ParseFloat(parts[1], 64)
|
|
nice, _ := strconv.ParseFloat(parts[2], 64)
|
|
system, _ := strconv.ParseFloat(parts[3], 64)
|
|
|
|
return []interface{}{
|
|
maps.Map{
|
|
"user": user,
|
|
"nice": nice,
|
|
"system": system,
|
|
},
|
|
}
|
|
}
|
|
|
|
func parseDfOutput(output string) []interface{} {
|
|
var result []interface{}
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 6 {
|
|
continue
|
|
}
|
|
|
|
total, _ := strconv.ParseInt(parts[1], 10, 64)
|
|
used, _ := strconv.ParseInt(parts[2], 10, 64)
|
|
free, _ := strconv.ParseInt(parts[3], 10, 64)
|
|
|
|
var percent float64 = 0
|
|
if total > 0 {
|
|
percent = float64(used) / float64(total)
|
|
}
|
|
|
|
result = append(result, maps.Map{
|
|
"mountPoint": parts[5],
|
|
"total": total,
|
|
"used": used,
|
|
"free": free,
|
|
"percent": percent,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func parseNetDev(output string) []interface{} {
|
|
var result []interface{}
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 10 {
|
|
continue
|
|
}
|
|
|
|
iface := strings.TrimSuffix(parts[0], ":")
|
|
if strings.HasPrefix(iface, "lo") {
|
|
continue
|
|
}
|
|
|
|
rx, _ := strconv.ParseInt(parts[1], 10, 64)
|
|
tx, _ := strconv.ParseInt(parts[9], 10, 64)
|
|
|
|
result = append(result, maps.Map{
|
|
"iface": iface,
|
|
"rx": rx,
|
|
"tx": tx,
|
|
"rxSec": 0,
|
|
"txSec": 0,
|
|
})
|
|
}
|
|
return result
|
|
}
|