增加备份和导出功能
This commit is contained in:
parent
52309870d8
commit
1d232f7269
@ -1,27 +1,6 @@
|
|||||||
# Created by .ignore support plugin (hsz.mobi)
|
.*
|
||||||
### Go template
|
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
||||||
.gitignore
|
|
||||||
bin
|
|
||||||
data
|
data
|
||||||
docs
|
guacd
|
||||||
# guacd
|
|
||||||
logs
|
logs
|
||||||
playground
|
playground
|
||||||
screenshot
|
|
||||||
web/node_modules/
|
web/node_modules/
|
||||||
.dockerignore
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
debug: true
|
debug: true
|
||||||
demo: false
|
demo: false
|
||||||
db: mysql
|
db: sqlite
|
||||||
mysql:
|
mysql:
|
||||||
hostname: localhost
|
hostname: localhost
|
||||||
port: 3306
|
port: 3306
|
||||||
|
37
go.mod
37
go.mod
@ -21,10 +21,45 @@ require (
|
|||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
|
|
||||||
golang.org/x/text v0.3.6
|
golang.org/x/text v0.3.6
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gorm.io/driver/mysql v1.0.3
|
gorm.io/driver/mysql v1.0.3
|
||||||
gorm.io/driver/sqlite v1.1.4
|
gorm.io/driver/sqlite v1.1.4
|
||||||
gorm.io/gorm v1.20.7
|
gorm.io/gorm v1.20.7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.1 // indirect
|
||||||
|
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
|
||||||
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
|
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect
|
||||||
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.5 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/spf13/afero v1.1.2 // indirect
|
||||||
|
github.com/spf13/cast v1.3.0 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.51.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
||||||
|
@ -4,25 +4,32 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"next-terminal/server/model"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"next-terminal/server/config"
|
||||||
|
"next-terminal/server/constant"
|
||||||
|
"next-terminal/server/global/security"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"next-terminal/server/utils"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backup struct {
|
type Backup struct {
|
||||||
Users []model.User `json:"users"`
|
Users []model.User `json:"users"`
|
||||||
UserGroups []model.UserGroup `json:"user_groups"`
|
UserGroups []model.UserGroup `json:"user_groups"`
|
||||||
UserGroupMembers []model.UserGroupMember `json:"user_group_members"`
|
|
||||||
|
|
||||||
Strategies []model.Strategy `json:"strategies"`
|
Storages []model.Storage `json:"storages"`
|
||||||
Jobs []model.Job `json:"jobs"`
|
Strategies []model.Strategy `json:"strategies"`
|
||||||
AccessSecurities []model.AccessSecurity `json:"access_securities"`
|
AccessSecurities []model.AccessSecurity `json:"access_securities"`
|
||||||
AccessGateways []model.AccessGateway `json:"access_gateways"`
|
AccessGateways []model.AccessGateway `json:"access_gateways"`
|
||||||
Commands []model.Command `json:"commands"`
|
Commands []model.Command `json:"commands"`
|
||||||
Credentials []model.Credential `json:"credentials"`
|
Credentials []model.Credential `json:"credentials"`
|
||||||
Assets []model.Asset `json:"assets"`
|
Assets []map[string]interface{} `json:"assets"`
|
||||||
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
|
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
|
||||||
|
Jobs []model.Job `json:"jobs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func BackupExportEndpoint(c echo.Context) error {
|
func BackupExportEndpoint(c echo.Context) error {
|
||||||
@ -37,7 +44,17 @@ func BackupExportEndpoint(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
userGroupMembers, err := userGroupRepository.FindAllUserGroupMembers()
|
if len(userGroups) > 0 {
|
||||||
|
for i := range userGroups {
|
||||||
|
members, err := userGroupRepository.FindMembersById(userGroups[i].ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userGroups[i].Members = members
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storages, err := storageRepository.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -66,10 +83,36 @@ func BackupExportEndpoint(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(credentials) > 0 {
|
||||||
|
for i := range credentials {
|
||||||
|
if err := credentialRepository.Decrypt(&credentials[i], config.GlobalCfg.EncryptionPassword); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
assets, err := assetRepository.FindAll()
|
assets, err := assetRepository.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var assetMaps = make([]map[string]interface{}, 0)
|
||||||
|
if len(assets) > 0 {
|
||||||
|
for i := range assets {
|
||||||
|
asset := assets[i]
|
||||||
|
if err := assetRepository.Decrypt(&asset, config.GlobalCfg.EncryptionPassword); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attributeMap, err := assetRepository.FindAssetAttrMapByAssetId(asset.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
itemMap := utils.StructToMap(asset)
|
||||||
|
for key := range attributeMap {
|
||||||
|
itemMap[key] = attributeMap[key]
|
||||||
|
}
|
||||||
|
assetMaps = append(assetMaps, itemMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resourceSharers, err := resourceSharerRepository.FindAll()
|
resourceSharers, err := resourceSharerRepository.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -78,14 +121,14 @@ func BackupExportEndpoint(c echo.Context) error {
|
|||||||
backup := Backup{
|
backup := Backup{
|
||||||
Users: users,
|
Users: users,
|
||||||
UserGroups: userGroups,
|
UserGroups: userGroups,
|
||||||
UserGroupMembers: userGroupMembers,
|
Storages: storages,
|
||||||
Strategies: strategies,
|
Strategies: strategies,
|
||||||
Jobs: jobs,
|
Jobs: jobs,
|
||||||
AccessSecurities: accessSecurities,
|
AccessSecurities: accessSecurities,
|
||||||
AccessGateways: accessGateways,
|
AccessGateways: accessGateways,
|
||||||
Commands: commands,
|
Commands: commands,
|
||||||
Credentials: credentials,
|
Credentials: credentials,
|
||||||
Assets: assets,
|
Assets: assetMaps,
|
||||||
ResourceSharers: resourceSharers,
|
ResourceSharers: resourceSharers,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,5 +141,199 @@ func BackupExportEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BackupImportEndpoint(c echo.Context) error {
|
func BackupImportEndpoint(c echo.Context) error {
|
||||||
return nil
|
var backup Backup
|
||||||
|
if err := c.Bind(&backup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var userIdMapping = make(map[string]string, 0)
|
||||||
|
if len(backup.Users) > 0 {
|
||||||
|
for _, item := range backup.Users {
|
||||||
|
if userRepository.ExistByUsername(item.Username) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
oldId := item.ID
|
||||||
|
newId := utils.UUID()
|
||||||
|
item.ID = newId
|
||||||
|
item.Password = utils.GenPassword()
|
||||||
|
if err := userRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userIdMapping[oldId] = newId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userGroupIdMapping = make(map[string]string, 0)
|
||||||
|
if len(backup.UserGroups) > 0 {
|
||||||
|
for _, item := range backup.UserGroups {
|
||||||
|
oldId := item.ID
|
||||||
|
newId := utils.UUID()
|
||||||
|
item.ID = newId
|
||||||
|
|
||||||
|
var members = make([]string, 0)
|
||||||
|
if len(item.Members) > 0 {
|
||||||
|
for _, member := range item.Members {
|
||||||
|
members = append(members, userIdMapping[member])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userGroupRepository.Create(&item, members); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userGroupIdMapping[oldId] = newId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(backup.Storages) > 0 {
|
||||||
|
for _, item := range backup.Storages {
|
||||||
|
item.ID = utils.UUID()
|
||||||
|
item.Owner = userIdMapping[item.Owner]
|
||||||
|
if err := storageRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var strategyIdMapping = make(map[string]string, 0)
|
||||||
|
if len(backup.Strategies) > 0 {
|
||||||
|
for _, item := range backup.Strategies {
|
||||||
|
oldId := item.ID
|
||||||
|
newId := utils.UUID()
|
||||||
|
item.ID = newId
|
||||||
|
if err := strategyRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
strategyIdMapping[oldId] = newId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(backup.AccessSecurities) > 0 {
|
||||||
|
for _, item := range backup.AccessSecurities {
|
||||||
|
item.ID = utils.UUID()
|
||||||
|
if err := accessSecurityRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 更新内存中的安全规则
|
||||||
|
rule := &security.Security{
|
||||||
|
ID: item.ID,
|
||||||
|
IP: item.IP,
|
||||||
|
Rule: item.Rule,
|
||||||
|
Priority: item.Priority,
|
||||||
|
}
|
||||||
|
security.GlobalSecurityManager.Add <- rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessGatewayIdMapping = make(map[string]string, 0)
|
||||||
|
if len(backup.AccessGateways) > 0 {
|
||||||
|
for _, item := range backup.AccessGateways {
|
||||||
|
oldId := item.ID
|
||||||
|
newId := utils.UUID()
|
||||||
|
item.ID = newId
|
||||||
|
if err := accessGatewayRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accessGatewayIdMapping[oldId] = newId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(backup.Commands) > 0 {
|
||||||
|
for _, item := range backup.Commands {
|
||||||
|
item.ID = utils.UUID()
|
||||||
|
if err := commandRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var credentialIdMapping = make(map[string]string, 0)
|
||||||
|
if len(backup.Credentials) > 0 {
|
||||||
|
for _, item := range backup.Credentials {
|
||||||
|
oldId := item.ID
|
||||||
|
newId := utils.UUID()
|
||||||
|
item.ID = newId
|
||||||
|
if err := credentialRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
credentialIdMapping[oldId] = newId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetIdMapping = make(map[string]string, 0)
|
||||||
|
if len(backup.Assets) > 0 {
|
||||||
|
for _, m := range backup.Assets {
|
||||||
|
data, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var item model.Asset
|
||||||
|
if err := json.Unmarshal(data, &item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.CredentialId != "" && item.CredentialId != "-" {
|
||||||
|
item.CredentialId = credentialIdMapping[item.CredentialId]
|
||||||
|
}
|
||||||
|
if item.AccessGatewayId != "" && item.AccessGatewayId != "-" {
|
||||||
|
item.AccessGatewayId = accessGatewayIdMapping[item.AccessGatewayId]
|
||||||
|
}
|
||||||
|
|
||||||
|
oldId := item.ID
|
||||||
|
newId := utils.UUID()
|
||||||
|
item.ID = newId
|
||||||
|
if err := assetRepository.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := assetRepository.UpdateAttributes(item.ID, item.Protocol, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
active, _ := assetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||||
|
|
||||||
|
if item.Active != active {
|
||||||
|
_ = assetRepository.UpdateActiveById(active, item.ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
assetIdMapping[oldId] = newId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(backup.ResourceSharers) > 0 {
|
||||||
|
for _, item := range backup.ResourceSharers {
|
||||||
|
|
||||||
|
userGroupId := userGroupIdMapping[item.UserGroupId]
|
||||||
|
userId := userIdMapping[item.UserId]
|
||||||
|
strategyId := strategyIdMapping[item.StrategyId]
|
||||||
|
resourceId := assetIdMapping[item.ResourceId]
|
||||||
|
|
||||||
|
if err := resourceSharerRepository.AddSharerResources(userGroupId, userId, strategyId, item.ResourceType, []string{resourceId}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(backup.Jobs) > 0 {
|
||||||
|
for _, item := range backup.Jobs {
|
||||||
|
if item.Func == constant.FuncCheckAssetStatusJob {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceIds := strings.Split(item.ResourceIds, ",")
|
||||||
|
if len(resourceIds) > 0 {
|
||||||
|
var newResourceIds = make([]string, 0)
|
||||||
|
for _, resourceId := range resourceIds {
|
||||||
|
newResourceIds = append(newResourceIds, assetIdMapping[resourceId])
|
||||||
|
}
|
||||||
|
item.ResourceIds = strings.Join(newResourceIds, ",")
|
||||||
|
}
|
||||||
|
if err := jobService.Create(&item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success(c, "")
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,10 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"next-terminal/server/constant"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"next-terminal/server/constant"
|
||||||
"next-terminal/server/global/cache"
|
"next-terminal/server/global/cache"
|
||||||
"next-terminal/server/log"
|
"next-terminal/server/log"
|
||||||
"next-terminal/server/model"
|
"next-terminal/server/model"
|
||||||
@ -20,6 +20,10 @@ func UserCreateEndpoint(c echo.Context) (err error) {
|
|||||||
if err := c.Bind(&item); err != nil {
|
if err := c.Bind(&item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if userRepository.ExistByUsername(item.Username) {
|
||||||
|
return Fail(c, -1, "username is already in use")
|
||||||
|
}
|
||||||
|
|
||||||
password := item.Password
|
password := item.Password
|
||||||
|
|
||||||
var pass []byte
|
var pass []byte
|
||||||
@ -71,6 +75,11 @@ func UserPagingEndpoint(c echo.Context) error {
|
|||||||
func UserUpdateEndpoint(c echo.Context) error {
|
func UserUpdateEndpoint(c echo.Context) error {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
|
|
||||||
|
account, _ := GetCurrentAccount(c)
|
||||||
|
if account.ID == id {
|
||||||
|
return Fail(c, -1, "cannot modify itself")
|
||||||
|
}
|
||||||
|
|
||||||
var item model.User
|
var item model.User
|
||||||
if err := c.Bind(&item); err != nil {
|
if err := c.Bind(&item); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -8,6 +8,7 @@ type UserGroup struct {
|
|||||||
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||||
Name string `gorm:"type:varchar(500)" json:"name"`
|
Name string `gorm:"type:varchar(500)" json:"name"`
|
||||||
Created utils.JsonTime `json:"created"`
|
Created utils.JsonTime `json:"created"`
|
||||||
|
Members []string `gorm:"-" json:"members"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserGroupForPage struct {
|
type UserGroupForPage struct {
|
||||||
|
@ -79,9 +79,7 @@ func (r StorageRepository) FindById(id string) (m model.Storage, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r StorageRepository) FindAll() (o []model.Storage) {
|
func (r StorageRepository) FindAll() (o []model.Storage, err error) {
|
||||||
if r.DB.Find(&o).Error != nil {
|
err = r.DB.Find(&o).Error
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,16 @@ func (r UserRepository) FindByUsername(username string) (o model.User, err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r UserRepository) ExistByUsername(username string) (exist bool) {
|
||||||
|
count := int64(0)
|
||||||
|
err := r.DB.Table("users").Where("username = ?", username).Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (r UserRepository) FindOnlineUsers() (o []model.User, err error) {
|
func (r UserRepository) FindOnlineUsers() (o []model.User, err error) {
|
||||||
err = r.DB.Where("online = ?", true).Find(&o).Error
|
err = r.DB.Where("online = ?", true).Find(&o).Error
|
||||||
return
|
return
|
||||||
|
@ -42,7 +42,10 @@ func (r StorageService) InitStorages() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drivePath := r.GetBaseDrivePath()
|
drivePath := r.GetBaseDrivePath()
|
||||||
storages := r.storageRepository.FindAll()
|
storages, err := r.storageRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for i := 0; i < len(storages); i++ {
|
for i := 0; i < len(storages); i++ {
|
||||||
storage := storages[i]
|
storage := storages[i]
|
||||||
// 判断是否为遗留的数据:磁盘空间在,但用户已删除
|
// 判断是否为遗留的数据:磁盘空间在,但用户已删除
|
||||||
@ -137,6 +140,9 @@ func (r StorageService) DeleteStorageById(id string, force bool) error {
|
|||||||
drivePath := r.GetBaseDrivePath()
|
drivePath := r.GetBaseDrivePath()
|
||||||
storage, err := r.storageRepository.FindById(id)
|
storage, err := r.storageRepository.FindById(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !force && storage.IsDefault {
|
if !force && storage.IsDefault {
|
||||||
|
56
server/utils/jsontime.go
Normal file
56
server/utils/jsontime.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JsonTime struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJsonTime(t time.Time) JsonTime {
|
||||||
|
return JsonTime{
|
||||||
|
Time: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NowJsonTime() JsonTime {
|
||||||
|
return JsonTime{
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonTime) MarshalJSON() ([]byte, error) {
|
||||||
|
var stamp = fmt.Sprintf("\"%s\"", j.Format("2006-01-02 15:04:05"))
|
||||||
|
return []byte(stamp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonTime) UnmarshalJSON(b []byte) error {
|
||||||
|
s := strings.ReplaceAll(string(b), "\"", "")
|
||||||
|
t, err := time.Parse("2006-01-02 15:04:05", s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*j = NewJsonTime(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j JsonTime) Value() (driver.Value, error) {
|
||||||
|
var zeroTime time.Time
|
||||||
|
if j.Time.UnixNano() == zeroTime.UnixNano() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return j.Time, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonTime) Scan(v interface{}) error {
|
||||||
|
value, ok := v.(time.Time)
|
||||||
|
if ok {
|
||||||
|
*j = JsonTime{Time: value}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("can not convert %v to timestamp", v)
|
||||||
|
}
|
44
server/utils/password.go
Normal file
44
server/utils/password.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bcrypt struct {
|
||||||
|
cost int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bcrypt) Encode(password []byte) ([]byte, error) {
|
||||||
|
return bcrypt.GenerateFromPassword(password, b.cost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bcrypt) Match(hashedPassword, password []byte) error {
|
||||||
|
return bcrypt.CompareHashAndPassword(hashedPassword, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Encoder = Bcrypt{
|
||||||
|
cost: bcrypt.DefaultCost,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenPassword() string {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
digits := "0123456789"
|
||||||
|
specials := "~=+%^*/()[]{}/!@#$?|"
|
||||||
|
all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||||
|
"abcdefghijklmnopqrstuvwxyz" +
|
||||||
|
digits + specials
|
||||||
|
length := 8
|
||||||
|
buf := make([]byte, length)
|
||||||
|
buf[0] = digits[rand.Intn(len(digits))]
|
||||||
|
buf[1] = specials[rand.Intn(len(specials))]
|
||||||
|
for i := 2; i < length; i++ {
|
||||||
|
buf[i] = all[rand.Intn(len(all))]
|
||||||
|
}
|
||||||
|
rand.Shuffle(len(buf), func(i, j int) {
|
||||||
|
buf[i], buf[j] = buf[j], buf[i]
|
||||||
|
})
|
||||||
|
return string(buf)
|
||||||
|
}
|
@ -11,7 +11,6 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
@ -36,64 +35,9 @@ import (
|
|||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
errors2 "github.com/pkg/errors"
|
errors2 "github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JsonTime struct {
|
|
||||||
time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJsonTime(t time.Time) JsonTime {
|
|
||||||
return JsonTime{
|
|
||||||
Time: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NowJsonTime() JsonTime {
|
|
||||||
return JsonTime{
|
|
||||||
Time: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t JsonTime) MarshalJSON() ([]byte, error) {
|
|
||||||
var stamp = fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05"))
|
|
||||||
return []byte(stamp), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t JsonTime) Value() (driver.Value, error) {
|
|
||||||
var zeroTime time.Time
|
|
||||||
if t.Time.UnixNano() == zeroTime.UnixNano() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return t.Time, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JsonTime) Scan(v interface{}) error {
|
|
||||||
value, ok := v.(time.Time)
|
|
||||||
if ok {
|
|
||||||
*t = JsonTime{Time: value}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("can not convert %v to timestamp", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bcrypt struct {
|
|
||||||
cost int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bcrypt) Encode(password []byte) ([]byte, error) {
|
|
||||||
return bcrypt.GenerateFromPassword(password, b.cost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bcrypt) Match(hashedPassword, password []byte) error {
|
|
||||||
return bcrypt.CompareHashAndPassword(hashedPassword, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Encoder = Bcrypt{
|
|
||||||
cost: bcrypt.DefaultCost,
|
|
||||||
}
|
|
||||||
|
|
||||||
func UUID() string {
|
func UUID() string {
|
||||||
v4, _ := uuid.NewV4()
|
v4, _ := uuid.NewV4()
|
||||||
return v4.String()
|
return v4.String()
|
||||||
|
@ -32,7 +32,7 @@ class Setting extends Component {
|
|||||||
vncSettingFormRef = React.createRef();
|
vncSettingFormRef = React.createRef();
|
||||||
guacdSettingFormRef = React.createRef();
|
guacdSettingFormRef = React.createRef();
|
||||||
mailSettingFormRef = React.createRef();
|
mailSettingFormRef = React.createRef();
|
||||||
otherSettingFormRef = React.createRef();
|
logSettingFormRef = React.createRef();
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getProperties();
|
this.getProperties();
|
||||||
@ -94,8 +94,8 @@ class Setting extends Component {
|
|||||||
this.mailSettingFormRef.current.setFieldsValue(properties)
|
this.mailSettingFormRef.current.setFieldsValue(properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.otherSettingFormRef.current) {
|
if (this.logSettingFormRef.current) {
|
||||||
this.otherSettingFormRef.current.setFieldsValue(properties)
|
this.logSettingFormRef.current.setFieldsValue(properties)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(result['message']);
|
message.error(result['message']);
|
||||||
@ -106,6 +106,35 @@ class Setting extends Component {
|
|||||||
this.getProperties()
|
this.getProperties()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleImport = () => {
|
||||||
|
let files = window.document.getElementById('file-upload').files;
|
||||||
|
if (files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async () => {
|
||||||
|
let backup = JSON.parse(reader.result.toString());
|
||||||
|
this.setState({
|
||||||
|
importBtnLoading: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let result = await request.post('/backup/import', backup);
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
message.success('恢复成功', 3);
|
||||||
|
} else {
|
||||||
|
message.error(result['message'], 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({
|
||||||
|
importBtnLoading: false
|
||||||
|
})
|
||||||
|
window.document.getElementById('file-upload').value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -419,7 +448,7 @@ class Setting extends Component {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Switch checkedChildren="开启" unCheckedChildren="关闭" onChange={(checked, event) => {
|
<Switch checkedChildren="开启" unCheckedChildren="关闭" onChange={(checked) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
properties: {
|
properties: {
|
||||||
...this.state.properties,
|
...this.state.properties,
|
||||||
@ -528,9 +557,9 @@ class Setting extends Component {
|
|||||||
</Form>
|
</Form>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab="其他配置" key="other">
|
<TabPane tab="日志配置" key="log">
|
||||||
<Title level={3}>其他配置</Title>
|
<Title level={3}>其他配置</Title>
|
||||||
<Form ref={this.otherSettingFormRef} name="other" onFinish={this.changeProperties}
|
<Form ref={this.logSettingFormRef} name="log" onFinish={this.changeProperties}
|
||||||
layout="vertical">
|
layout="vertical">
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -587,9 +616,13 @@ class Setting extends Component {
|
|||||||
导出备份
|
导出备份
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button type="dashed">
|
<Button type="dashed" loading={this.state['importBtnLoading']} onClick={() => {
|
||||||
|
window.document.getElementById('file-upload').click();
|
||||||
|
}}>
|
||||||
恢复备份
|
恢复备份
|
||||||
</Button>
|
</Button>
|
||||||
|
<input type="file" id="file-upload" style={{display: 'none'}}
|
||||||
|
onChange={this.handleImport}/>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
|
@ -95,14 +95,9 @@ class User extends Component {
|
|||||||
} else {
|
} else {
|
||||||
message.error(result.message, 10);
|
message.error(result.message, 10);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
const items = data.items.map(item => {
|
|
||||||
return {'key': item['id'], ...item}
|
|
||||||
})
|
|
||||||
this.setState({
|
this.setState({
|
||||||
items: items,
|
items: data.items,
|
||||||
total: data.total,
|
total: data.total,
|
||||||
queryParams: queryParams,
|
queryParams: queryParams,
|
||||||
loading: false
|
loading: false
|
||||||
@ -145,7 +140,7 @@ class User extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCancelModal = e => {
|
handleCancelModal = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
modalVisible: false,
|
modalVisible: false,
|
||||||
modalTitle: ''
|
modalTitle: ''
|
||||||
@ -245,13 +240,6 @@ class User extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAssetCancel = () => {
|
|
||||||
this.loadTableData()
|
|
||||||
this.setState({
|
|
||||||
assetVisible: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangePassword = async (values) => {
|
handleChangePassword = async (values) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
changePasswordConfirmLoading: true
|
changePasswordConfirmLoading: true
|
||||||
@ -362,7 +350,7 @@ class User extends Component {
|
|||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
key: 'username',
|
key: 'username',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (username, record, index) => {
|
render: (username, record) => {
|
||||||
return (
|
return (
|
||||||
<Button type="link" size='small'
|
<Button type="link" size='small'
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@ -384,7 +372,7 @@ class User extends Component {
|
|||||||
title: '用户类型',
|
title: '用户类型',
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
render: (text, record) => {
|
render: (text) => {
|
||||||
|
|
||||||
if (text === 'user') {
|
if (text === 'user') {
|
||||||
return (
|
return (
|
||||||
@ -419,7 +407,7 @@ class User extends Component {
|
|||||||
title: '双因素认证',
|
title: '双因素认证',
|
||||||
dataIndex: 'totpSecret',
|
dataIndex: 'totpSecret',
|
||||||
key: 'totpSecret',
|
key: 'totpSecret',
|
||||||
render: (text, record) => {
|
render: (text) => {
|
||||||
|
|
||||||
if (text === '1') {
|
if (text === '1') {
|
||||||
return <Tag icon={<InsuranceOutlined/>} color="success">已开启</Tag>;
|
return <Tag icon={<InsuranceOutlined/>} color="success">已开启</Tag>;
|
||||||
@ -442,7 +430,7 @@ class User extends Component {
|
|||||||
title: '授权资产',
|
title: '授权资产',
|
||||||
dataIndex: 'sharerAssetCount',
|
dataIndex: 'sharerAssetCount',
|
||||||
key: 'sharerAssetCount',
|
key: 'sharerAssetCount',
|
||||||
render: (text, record, index) => {
|
render: (text, record) => {
|
||||||
return <Button type='link' onClick={async () => {
|
return <Button type='link' onClick={async () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
assetVisible: true,
|
assetVisible: true,
|
||||||
@ -454,7 +442,7 @@ class User extends Component {
|
|||||||
title: '创建日期',
|
title: '创建日期',
|
||||||
dataIndex: 'created',
|
dataIndex: 'created',
|
||||||
key: 'created',
|
key: 'created',
|
||||||
render: (text, record) => {
|
render: (text) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip title={text}>
|
<Tooltip title={text}>
|
||||||
{dayjs(text).fromNow()}
|
{dayjs(text).fromNow()}
|
||||||
@ -519,6 +507,7 @@ class User extends Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button type="link" size='small'
|
<Button type="link" size='small'
|
||||||
|
disabled={getCurrentUser()['id'] === record['id']}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
let result = await request.get(`/users/${record['id']}`);
|
let result = await request.get(`/users/${record['id']}`);
|
||||||
if (result['code'] !== 1) {
|
if (result['code'] !== 1) {
|
||||||
@ -541,7 +530,7 @@ class User extends Component {
|
|||||||
const selectedRowKeys = this.state.selectedRowKeys;
|
const selectedRowKeys = this.state.selectedRowKeys;
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
selectedRowKeys: this.state.selectedRowKeys,
|
selectedRowKeys: this.state.selectedRowKeys,
|
||||||
onChange: (selectedRowKeys, selectedRows) => {
|
onChange: (selectedRowKeys) => {
|
||||||
this.setState({selectedRowKeys});
|
this.setState({selectedRowKeys});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -699,9 +688,6 @@ class User extends Component {
|
|||||||
.then(values => {
|
.then(values => {
|
||||||
this.changePasswordFormRef.current.resetFields();
|
this.changePasswordFormRef.current.resetFields();
|
||||||
this.handleChangePassword(values);
|
this.handleChangePassword(values);
|
||||||
})
|
|
||||||
.catch(info => {
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user