diff --git a/config.yml b/config.yml
index 62aa885..d4292cc 100644
--- a/config.yml
+++ b/config.yml
@@ -1,3 +1,4 @@
+debug: true
db: mysql
mysql:
hostname: 172.16.101.32
diff --git a/main.go b/main.go
index 92b513f..fcfa9b9 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
+ "gorm.io/gorm/logger"
"io"
"next-terminal/pkg/api"
"next-terminal/pkg/config"
@@ -24,7 +25,7 @@ import (
"time"
)
-const Version = "v0.3.2"
+const Version = "v0.3.3"
func main() {
err := Run()
@@ -65,6 +66,13 @@ func Run() error {
return err
}
+ var logMode logger.Interface
+ if global.Config.Debug {
+ logMode = logger.Default.LogMode(logger.Info)
+ } else {
+ logMode = logger.Default.LogMode(logger.Silent)
+ }
+
fmt.Printf("当前数据库模式为:%v\n", global.Config.DB)
if global.Config.DB == "mysql" {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
@@ -75,11 +83,11 @@ func Run() error {
global.Config.Mysql.Database,
)
global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
- //Logger: logger.Default.LogMode(logger.Info),
+ Logger: logMode,
})
} else {
global.DB, err = gorm.Open(sqlite.Open(global.Config.Sqlite.File), &gorm.Config{
- //Logger: logger.Default.LogMode(logger.Info),
+ Logger: logMode,
})
}
@@ -196,9 +204,12 @@ func Run() error {
global.Cache = cache.New(5*time.Minute, 10*time.Minute)
global.Cache.OnEvicted(func(key string, value interface{}) {
if strings.HasPrefix(key, api.Token) {
- token := strings.Split(key, ":")[1]
+ token := api.GetTokenFormCacheKey(key)
logrus.Debugf("用户Token「%v」过期", token)
- model.Logout(token)
+ err := model.Logout(token)
+ if err != nil {
+ logrus.Errorf("退出登录失败 %v", err)
+ }
}
})
global.Store = global.NewStore()
@@ -256,7 +267,7 @@ func Run() error {
User: user,
}
- cacheKey := strings.Join([]string{api.Token, token}, ":")
+ cacheKey := api.BuildCacheKeyByToken(token)
if authorization.Remember {
// 记住登录有效期两周
@@ -267,6 +278,23 @@ func Run() error {
logrus.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token)
}
+ // 修正用户登录状态
+ onlineUsers, err := model.FindOnlineUsers()
+ if err != nil {
+ return err
+ }
+ for i := range onlineUsers {
+ logs, err := model.FindAliveLoginLogsByUserId(onlineUsers[i].ID)
+ if err != nil {
+ return err
+ }
+ if len(logs) == 0 {
+ if err := model.UpdateUserOnline(false, onlineUsers[i].ID); err != nil {
+ return err
+ }
+ }
+ }
+
e := api.SetupRoutes()
if err := handle.InitProperties(); err != nil {
return err
diff --git a/pkg/api/account.go b/pkg/api/account.go
index c673de1..29e7a10 100644
--- a/pkg/api/account.go
+++ b/pkg/api/account.go
@@ -91,7 +91,7 @@ func LoginSuccess(c echo.Context, loginAccount LoginAccount, user model.User) (t
User: user,
}
- cacheKey := strings.Join([]string{Token, token}, ":")
+ cacheKey := BuildCacheKeyByToken(token)
if authorization.Remember {
// 记住登录有效期两周
@@ -119,6 +119,16 @@ func LoginSuccess(c echo.Context, loginAccount LoginAccount, user model.User) (t
return token, nil
}
+func BuildCacheKeyByToken(token string) string {
+ cacheKey := strings.Join([]string{Token, token}, ":")
+ return cacheKey
+}
+
+func GetTokenFormCacheKey(cacheKey string) string {
+ token := strings.Split(cacheKey, ":")[1]
+ return token
+}
+
func loginWithTotpEndpoint(c echo.Context) error {
var loginAccount LoginAccount
if err := c.Bind(&loginAccount); err != nil {
@@ -165,7 +175,7 @@ func loginWithTotpEndpoint(c echo.Context) error {
func LogoutEndpoint(c echo.Context) error {
token := GetToken(c)
- cacheKey := strings.Join([]string{Token, token}, ":")
+ cacheKey := BuildCacheKeyByToken(token)
global.Cache.Delete(cacheKey)
model.Logout(token)
return Success(c, nil)
diff --git a/pkg/api/asset.go b/pkg/api/asset.go
index ca4e734..4e2f30d 100644
--- a/pkg/api/asset.go
+++ b/pkg/api/asset.go
@@ -1,6 +1,8 @@
package api
import (
+ "bufio"
+ "encoding/csv"
"encoding/json"
"errors"
"github.com/labstack/echo/v4"
@@ -44,6 +46,77 @@ func AssetCreateEndpoint(c echo.Context) error {
return Success(c, item)
}
+func AssetImportEndpoint(c echo.Context) error {
+ account, _ := GetCurrentAccount(c)
+
+ file, err := c.FormFile("file")
+ if err != nil {
+ return err
+ }
+
+ src, err := file.Open()
+ if err != nil {
+ return err
+ }
+
+ defer src.Close()
+ reader := csv.NewReader(bufio.NewReader(src))
+ records, err := reader.ReadAll()
+ if err != nil {
+ return err
+ }
+
+ total := len(records)
+ if total == 0 {
+ return errors.New("csv数据为空")
+ }
+
+ var successCount = 0
+ var errorCount = 0
+ m := echo.Map{}
+
+ for i := 0; i < total; i++ {
+ 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: model.Custom,
+ Username: record[4],
+ Password: record[5],
+ PrivateKey: record[6],
+ Passphrase: record[7],
+ Description: record[8],
+ Created: utils.NowJsonTime(),
+ Owner: account.ID,
+ }
+
+ err := model.CreateNewAsset(&asset)
+ if err != nil {
+ errorCount++
+ m[strconv.Itoa(i)] = err.Error()
+ } else {
+ successCount++
+ // 创建后自动检测资产是否存活
+ go func() {
+ active := utils.Tcping(asset.IP, asset.Port)
+ model.UpdateAssetActiveById(active, asset.ID)
+ }()
+ }
+ }
+ }
+
+ return Success(c, echo.Map{
+ "successCount": successCount,
+ "errorCount": errorCount,
+ "data": m,
+ })
+}
+
func AssetPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
@@ -134,6 +207,10 @@ func AssetUpdateEndpoint(c echo.Context) error {
func AssetGetAttributeEndpoint(c echo.Context) error {
assetId := c.Param("id")
+ if err := PreCheckAssetPermission(c, assetId); err != nil {
+ return err
+ }
+
attributeMap, err := model.FindAssetAttrMapByAssetId(assetId)
if err != nil {
return err
@@ -177,6 +254,9 @@ func AssetDeleteEndpoint(c echo.Context) error {
func AssetGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
+ if err := PreCheckAssetPermission(c, id); err != nil {
+ return err
+ }
var item model.Asset
if item, err = model.FindAssetById(id); err != nil {
diff --git a/pkg/api/command.go b/pkg/api/command.go
index 2d71058..78ba421 100644
--- a/pkg/api/command.go
+++ b/pkg/api/command.go
@@ -84,6 +84,11 @@ func CommandDeleteEndpoint(c echo.Context) error {
func CommandGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
+
+ if err := PreCheckCommandPermission(c, id); err != nil {
+ return err
+ }
+
var item model.Command
if item, err = model.FindCommandById(id); err != nil {
return err
diff --git a/pkg/api/credential.go b/pkg/api/credential.go
index a091557..aa2848e 100644
--- a/pkg/api/credential.go
+++ b/pkg/api/credential.go
@@ -140,6 +140,9 @@ func CredentialDeleteEndpoint(c echo.Context) error {
func CredentialGetEndpoint(c echo.Context) error {
id := c.Param("id")
+ if err := PreCheckCredentialPermission(c, id); err != nil {
+ return err
+ }
item, err := model.FindCredentialById(id)
if err != nil {
diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go
index 775b21c..acb8173 100644
--- a/pkg/api/middleware.go
+++ b/pkg/api/middleware.go
@@ -55,7 +55,7 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
}
token := GetToken(c)
- cacheKey := strings.Join([]string{Token, token}, ":")
+ cacheKey := BuildCacheKeyByToken(token)
authorization, found := global.Cache.Get(cacheKey)
if !found {
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
@@ -63,9 +63,9 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
if authorization.(Authorization).Remember {
// 记住登录有效期两周
- global.Cache.Set(token, authorization, time.Hour*time.Duration(24*14))
+ global.Cache.Set(cacheKey, authorization, time.Hour*time.Duration(24*14))
} else {
- global.Cache.Set(token, authorization, time.Hour*time.Duration(2))
+ global.Cache.Set(cacheKey, authorization, time.Hour*time.Duration(2))
}
return next(c)
diff --git a/pkg/api/routes.go b/pkg/api/routes.go
index bb33423..2805deb 100644
--- a/pkg/api/routes.go
+++ b/pkg/api/routes.go
@@ -71,10 +71,11 @@ func SetupRoutes() *echo.Echo {
//userGroups.DELETE("/:id/members/:memberId", UserGroupDelMembersEndpoint)
}
- assets := e.Group("/assets", Auth)
+ assets := e.Group("/assets")
{
assets.GET("", AssetAllEndpoint)
assets.POST("", AssetCreateEndpoint)
+ assets.POST("/import", Admin(AssetImportEndpoint))
assets.GET("/paging", AssetPagingEndpoint)
assets.POST("/:id/tcping", AssetTcpingEndpoint)
assets.PUT("/:id", AssetUpdateEndpoint)
@@ -110,7 +111,7 @@ func SetupRoutes() *echo.Echo {
sessions := e.Group("/sessions")
{
sessions.POST("", SessionCreateEndpoint)
- sessions.GET("/paging", SessionPagingEndpoint)
+ sessions.GET("/paging", Admin(SessionPagingEndpoint))
sessions.POST("/:id/connect", SessionConnectEndpoint)
sessions.POST("/:id/disconnect", Admin(SessionDisconnectEndpoint))
sessions.POST("/:id/resize", SessionResizeEndpoint)
@@ -138,7 +139,7 @@ func SetupRoutes() *echo.Echo {
loginLogs.DELETE("/:id", LoginLogDeleteEndpoint)
}
- e.GET("/properties", PropertyGetEndpoint)
+ e.GET("/properties", Admin(PropertyGetEndpoint))
e.PUT("/properties", Admin(PropertyUpdateEndpoint))
e.GET("/overview/counter", OverviewCounterEndPoint)
@@ -202,7 +203,8 @@ func GetToken(c echo.Context) string {
func GetCurrentAccount(c echo.Context) (model.User, bool) {
token := GetToken(c)
- get, b := global.Cache.Get(token)
+ cacheKey := BuildCacheKeyByToken(token)
+ get, b := global.Cache.Get(cacheKey)
if b {
return get.(Authorization).User, true
}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index fe9cceb..4b1bc47 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -8,6 +8,7 @@ import (
)
type Config struct {
+ Debug bool
DB string
Server *Server
Mysql *Mysql
@@ -78,6 +79,7 @@ func SetupConfig() (*Config, error) {
Key: viper.GetString("server.key"),
},
ResetPassword: viper.GetString("reset-password"),
+ Debug: viper.GetBool("debug"),
}
return config, nil
diff --git a/pkg/model/asset.go b/pkg/model/asset.go
index c58f2f3..9e6fe52 100644
--- a/pkg/model/asset.go
+++ b/pkg/model/asset.go
@@ -9,8 +9,8 @@ import (
type Asset struct {
ID string `gorm:"primary_key " json:"id"`
Name string `json:"name"`
- IP string `json:"ip"`
Protocol string `json:"protocol"`
+ IP string `json:"ip"`
Port int `json:"port"`
AccountType string `json:"accountType"`
Username string `json:"username"`
diff --git a/pkg/model/login-log.go b/pkg/model/login-log.go
index 295f4fc..44f845a 100644
--- a/pkg/model/login-log.go
+++ b/pkg/model/login-log.go
@@ -81,7 +81,7 @@ func FindLoginLogById(id string) (o LoginLog, err error) {
return
}
-func Logout(token string) {
+func Logout(token string) (err error) {
loginLog, err := FindLoginLogById(token)
if err != nil {
@@ -89,7 +89,10 @@ func Logout(token string) {
return
}
- global.DB.Table("login_logs").Where("id = ?", token).Update("logout_time", utils.NowJsonTime())
+ err = global.DB.Updates(&LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}).Error
+ if err != nil {
+ return err
+ }
loginLogs, err := FindAliveLoginLogsByUserId(loginLog.UserId)
if err != nil {
@@ -97,6 +100,7 @@ func Logout(token string) {
}
if len(loginLogs) == 0 {
- UpdateUserById(&User{Online: false}, loginLog.UserId)
+ err = UpdateUserOnline(false, loginLog.UserId)
}
+ return
}
diff --git a/pkg/model/user.go b/pkg/model/user.go
index 14825f0..226f43f 100644
--- a/pkg/model/user.go
+++ b/pkg/model/user.go
@@ -129,6 +129,17 @@ func UpdateUserById(o *User, id string) {
global.DB.Updates(o)
}
+func UpdateUserOnline(online bool, id string) (err error) {
+ sql := "update users set online = ? where id = ?"
+ err = global.DB.Exec(sql, online, id).Error
+ return
+}
+
+func FindOnlineUsers() (o []User, err error) {
+ err = global.DB.Where("online = ?", true).Find(&o).Error
+ return
+}
+
func DeleteUserById(id string) {
global.DB.Where("id = ?", id).Delete(&User{})
// 删除用户组中的用户关系
diff --git a/sample.csv b/sample.csv
new file mode 100644
index 0000000..ccdea43
--- /dev/null
+++ b/sample.csv
@@ -0,0 +1 @@
+测试阿里云,ssh,10.1.1.2,22,username,password,privateKey,passphrase,description
\ No newline at end of file
diff --git a/web/package.json b/web/package.json
index 8b0614f..ec06b94 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,6 +1,6 @@
{
"name": "next-terminal",
- "version": "0.3.2",
+ "version": "0.3.3-beta",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.3.0",
diff --git a/web/src/common/constants.js b/web/src/common/constants.js
index 2c22a59..7bead42 100644
--- a/web/src/common/constants.js
+++ b/web/src/common/constants.js
@@ -2,5 +2,6 @@ export const PROTOCOL_COLORS = {
'rdp': 'cyan',
'ssh': 'blue',
'telnet': 'geekblue',
- 'vnc': 'purple'
+ 'vnc': 'purple',
+ 'kubernetes': 'volcano'
}
\ No newline at end of file
diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js
index 8c316e9..5d443ba 100644
--- a/web/src/components/asset/Asset.js
+++ b/web/src/components/asset/Asset.js
@@ -12,6 +12,7 @@ import {
Layout,
Menu,
Modal,
+ notification,
PageHeader,
Row,
Select,
@@ -26,19 +27,24 @@ import qs from "qs";
import AssetModal from "./AssetModal";
import request from "../../common/request";
import {message} from "antd/es";
-import {isEmpty, itemRender} from "../../utils/utils";
+import {getHeaders, isEmpty, itemRender} from "../../utils/utils";
import dayjs from 'dayjs';
import {
DeleteOutlined,
DownOutlined,
ExclamationCircleOutlined,
+ ImportOutlined,
PlusOutlined,
SyncOutlined,
- UndoOutlined
+ UndoOutlined,
+ UploadOutlined
} from '@ant-design/icons';
import {PROTOCOL_COLORS} from "../../common/constants";
import Logout from "../user/Logout";
import {hasPermission, isAdmin} from "../../service/permission";
+import Upload from "antd/es/upload";
+import axios from "axios";
+import {server} from "../../common/env";
const confirm = Modal.confirm;
@@ -88,6 +94,9 @@ class Asset extends Component {
users: [],
selected: {},
selectedSharers: [],
+ importModalVisible: false,
+ fileList: [],
+ uploading: false,
};
async componentDidMount() {
@@ -726,6 +735,20 @@ class Asset extends Component {
+ {isAdmin() ?
+
+
+ : undefined
+ }
+
+