diff --git a/main.go b/main.go index d534cbb..f4f8499 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,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" @@ -69,7 +70,7 @@ func Run() error { global.Config.Mysql.Database, ) global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ - //Logger: logger.Default.LogMode(logger.Info), + Logger: logger.Default.LogMode(logger.Info), }) } else { global.DB, err = gorm.Open(sqlite.Open(global.Config.Sqlite.File), &gorm.Config{}) @@ -118,7 +119,9 @@ func Run() error { if err := global.DB.AutoMigrate(&model.Property{}); err != nil { return err } - + if err := global.DB.AutoMigrate(&model.Resource{}); err != nil { + return err + } if err := global.DB.AutoMigrate(&model.Num{}); err != nil { return err } diff --git a/pkg/api/resource.go b/pkg/api/resource.go new file mode 100644 index 0000000..6dcf02b --- /dev/null +++ b/pkg/api/resource.go @@ -0,0 +1,26 @@ +package api + +import ( + "github.com/labstack/echo/v4" + "next-terminal/pkg/model" + "strings" +) + +func ResourceGetAssignEndPoint(c echo.Context) error { + resourceId := c.Param("id") + userIds, err := model.FindUserIdsByResourceId(resourceId) + if err != nil { + return err + } + return Success(c, userIds) +} + +func ResourceOverwriteAssignEndPoint(c echo.Context) error { + resourceId := c.Param("id") + userIds := c.QueryParam("userIds") + uIds := strings.Split(userIds, ",") + + model.OverwriteUserIdsByResourceId(resourceId, uIds) + + return Success(c, "") +} diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 759f925..f15049c 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -102,15 +102,11 @@ func SetupRoutes() *echo.Echo { sessions.GET("/:id", SessionGetEndpoint) } - //tags := e.Group("/tags") - //{ - // tags.POST("", TagAllEndpoint) - // tags.GET("/paging", TagPagingEndpoint) - // tags.POST("", TagCreateEndpoint) - // tags.PUT("/:id", TagUpdateEndpoint) - // tags.DELETE("/:id", TagDeleteEndpoint) - // tags.GET("/:id", TagGetEndpoint) - //} + resources := e.Group("/resources") + { + resources.GET("/:id/assign", ResourceGetAssignEndPoint) + resources.POST("/:id/assign", ResourceOverwriteAssignEndPoint) + } e.GET("/properties", PropertyGetEndpoint) e.PUT("/properties", PropertyUpdateEndpoint) diff --git a/pkg/model/asset.go b/pkg/model/asset.go index a37f661..56a47a3 100644 --- a/pkg/model/asset.go +++ b/pkg/model/asset.go @@ -58,24 +58,32 @@ func FindAssetByConditions(protocol string) (o []Asset, err error) { } func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string) (o []AssetVo, total int64, err error) { - db := global.DB - db = db.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as creator_name").Joins("left join users on assets.owner = users.id") + db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name").Joins("left join users on assets.owner = users.id") + dbCounter := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name").Joins("left join users on assets.owner = users.id") + if len(name) > 0 { db = db.Where("assets.name like ?", "%"+name+"%") + dbCounter = dbCounter.Where("assets.name like ?", "%"+name+"%") } if len(protocol) > 0 { db = db.Where("assets.protocol = ?", protocol) + dbCounter = dbCounter.Where("assets.protocol = ?", protocol) } if len(tags) > 0 { tagArr := strings.Split(tags, ",") for i := range tagArr { db = db.Where("find_in_set(?, assets.tags)", tagArr[i]) + dbCounter = dbCounter.Where("find_in_set(?, assets.tags)", tagArr[i]) } } - err = db.Order("assets.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Count(&total).Error + err = dbCounter.Count(&total).Error + if err != nil { + return nil, 0, err + } + err = db.Order("assets.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error if o == nil { o = make([]AssetVo, 0) diff --git a/pkg/model/credential.go b/pkg/model/credential.go index 42e7c40..991df15 100644 --- a/pkg/model/credential.go +++ b/pkg/model/credential.go @@ -28,13 +28,14 @@ func (r *Credential) TableName() string { } type CredentialVo struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Username string `json:"username"` - Created utils.JsonTime `json:"created"` - Owner string `json:"owner"` - OwnerName string `json:"ownerName"` + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Username string `json:"username"` + Created utils.JsonTime `json:"created"` + Owner string `json:"owner"` + OwnerName string `json:"ownerName"` + SharerCount int64 `json:"sharerCount"` } func FindAllCredential() (o []Credential, err error) { @@ -43,16 +44,24 @@ func FindAllCredential() (o []Credential, err error) { } func FindPageCredential(pageIndex, pageSize int, name, owner string) (o []CredentialVo, total int64, err error) { - db := global.DB - db = db.Table("credentials").Select("credentials.id,credentials.name,credentials.type,credentials.username,credentials.owner,credentials.created,users.nickname as owner_name").Joins("left join users on credentials.owner = users.id") - if len(name) > 0 { - db = db.Where("credentials.name like ?", "%"+name+"%") - } + db := global.DB.Table("credentials").Select("credentials.id,credentials.name,credentials.type,credentials.username,credentials.owner,credentials.created,users.nickname as owner_name,COUNT(resources.user_id) as sharer_count").Joins("left join users on credentials.owner = users.id").Joins("left join resources on credentials.id = resources.resource_id").Group("credentials.id") + dbCounter := global.DB.Table("credentials").Select("DISTINCT credentials.id,credentials.name,credentials.type,credentials.username,credentials.owner,credentials.created,users.nickname as owner_name").Joins("left join users on credentials.owner = users.id").Joins("left join resources on credentials.id = resources.resource_id") + if len(owner) > 0 { - db = db.Where("credentials.owner = ?", owner) + db = db.Where("credentials.owner = ? or resources.user_id = ?", owner, owner) + dbCounter = dbCounter.Where("credentials.owner = ? or resources.user_id = ?", owner, owner) } - err = db.Order("credentials.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Count(&total).Error + if len(name) > 0 { + db = db.Where("credentials.name like ?", "%"+name+"%") + dbCounter = dbCounter.Where("credentials.name like ?", "%"+name+"%") + } + + err = dbCounter.Count(&total).Error + if err != nil { + return nil, 0, err + } + err = db.Order("credentials.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error if o == nil { o = make([]CredentialVo, 0) } diff --git a/pkg/model/resource.go b/pkg/model/resource.go new file mode 100644 index 0000000..9182199 --- /dev/null +++ b/pkg/model/resource.go @@ -0,0 +1,45 @@ +package model + +import ( + "next-terminal/pkg/global" + "next-terminal/pkg/utils" +) + +type Resource struct { + ID string `gorm:"primary_key" json:"name"` + ResourceId string `json:"resourceId"` + UserId string `json:"userId"` +} + +func (r *Resource) TableName() string { + return "resources" +} + +func FindUserIdsByResourceId(resourceId string) (r []string, err error) { + db := global.DB + err = db.Table("resources").Select("user_id").Where("resource_id = ?", resourceId).Find(&r).Error + if r == nil { + r = make([]string, 0) + } + return +} + +func OverwriteUserIdsByResourceId(resourceId string, userIds []string) { + db := global.DB.Begin() + db.Where("resource_id = ?", resourceId).Delete(&Resource{}) + + for i := range userIds { + userId := userIds[i] + if len(userId) == 0 { + continue + } + id := utils.Sign([]string{resourceId, userId}) + resource := &Resource{ + ID: id, + ResourceId: resourceId, + UserId: userId, + } + _ = db.Create(resource).Error + } + db.Commit() +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4554efe..14a477c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "bytes" + "crypto/md5" "database/sql/driver" "encoding/base64" "fmt" @@ -9,7 +10,9 @@ import ( "image/png" "net" "os" + "sort" "strconv" + "strings" "time" "github.com/gofrs/uuid" @@ -121,10 +124,10 @@ func IsFile(path string) bool { } // 去除重复元素 -func Distinct(list []string) []string { - result := make([]string, 0, len(list)) +func Distinct(a []string) []string { + result := make([]string, 0, len(a)) temp := map[string]struct{}{} - for _, item := range list { + for _, item := range a { if _, ok := temp[item]; !ok { temp[item] = struct{}{} result = append(result, item) @@ -132,3 +135,11 @@ func Distinct(list []string) []string { } return result } + +// 排序+拼接+摘要 +func Sign(a []string) string { + sort.Strings(a) + data := []byte(strings.Join(a, "")) + has := md5.Sum(data) + return fmt.Sprintf("%x", has) +} diff --git a/web/src/App.js b/web/src/App.js index 2138eb2..d6a671b 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,9 +1,8 @@ import React, {Component} from 'react'; import 'antd/dist/antd.css'; import './App.css'; -import {Divider, Layout} from "antd"; -import {Switch, Route, Link} from "react-router-dom"; -import {Menu} from 'antd'; +import {Divider, Layout, Menu} from "antd"; +import {Link, Route, Switch} from "react-router-dom"; import Dashboard from "./components/dashboard/Dashboard"; import Asset from "./components/asset/Asset"; import Access from "./components/access/Access"; @@ -14,19 +13,20 @@ import Login from "./components/Login"; import DynamicCommand from "./components/command/DynamicCommand"; import Credential from "./components/credential/Credential"; import { - DashboardOutlined, - UserOutlined, - IdcardOutlined, + AuditOutlined, + BlockOutlined, CloudServerOutlined, CodeOutlined, - BlockOutlined, - AuditOutlined, + DashboardOutlined, DesktopOutlined, DisconnectOutlined, - MenuUnfoldOutlined, + IdcardOutlined, + LinkOutlined, MenuFoldOutlined, + MenuUnfoldOutlined, + SettingOutlined, SolutionOutlined, - SettingOutlined, LinkOutlined + UserOutlined } from '@ant-design/icons'; import Info from "./components/user/Info"; import request from "./common/request"; @@ -69,12 +69,13 @@ class App extends Component { } let result = await request.get('/info'); - if (result.code === 1) { + if (result['code'] === 1) { this.setState({ - user: result.data + user: result['data'] }) + sessionStorage.setItem('user', JSON.stringify(result['data'])); } else { - message.error(result.message); + message.error(result['message']); } } @@ -170,7 +171,7 @@ class App extends Component { }> - }> + }> 在线会话 diff --git a/web/src/components/credential/Credential.js b/web/src/components/credential/Credential.js index 2d0e49c..c85ea11 100644 --- a/web/src/components/credential/Credential.js +++ b/web/src/components/credential/Credential.js @@ -17,6 +17,7 @@ import { Table, Tag, Tooltip, + Transfer, Typography } from "antd"; import qs from "qs"; @@ -26,13 +27,14 @@ import {message} from "antd/es"; import { DeleteOutlined, DownOutlined, - ExclamationCircleOutlined, FormOutlined, + ExclamationCircleOutlined, PlusOutlined, SyncOutlined, UndoOutlined } from '@ant-design/icons'; import {itemRender} from "../../utils/utils"; import Logout from "../user/Logout"; +import {hasPermission} from "../../service/permission"; const confirm = Modal.confirm; const {Search} = Input; @@ -53,6 +55,7 @@ class Credential extends Component { inputRefOfName = React.createRef(); changeOwnerFormRef = React.createRef(); + changeSharerFormRef = React.createRef(); state = { items: [], @@ -69,9 +72,12 @@ class Credential extends Component { selectedRowKeys: [], delBtnLoading: false, changeOwnerModalVisible: false, + changeSharerModalVisible: false, changeOwnerConfirmLoading: false, + changeSharerConfirmLoading: false, users: [], - selected: undefined, + selected: {}, + selectedSharers: [], }; componentDidMount() { @@ -266,8 +272,39 @@ class Credential extends Component { return; } + const items = result['data']['items'].map(item => { + return {'key': item['id'], ...item} + }) + this.setState({ - users: result.data.items + users: items + }) + } + + handleSharersChange = async targetKeys => { + this.setState({ + selectedSharers: targetKeys + }) + } + + handleShowSharer = async (record, disabled) => { + let r1 = this.handleSearchByNickname(''); + let r2 = request.get(`/resources/${record['id']}/assign`); + + await r1; + let result = await r2; + + let selectedSharers = []; + if (result['code'] !== 1) { + message.error(result['message']); + } else { + selectedSharers = result['data']; + } + + this.setState({ + selectedSharers: selectedSharers, + selected: record, + changeSharerModalVisible: true }) } @@ -310,7 +347,6 @@ class Credential extends Component { 密码 ); } - } }, { title: '授权账户', @@ -334,6 +370,7 @@ class Credential extends Component { + disabled={!hasPermission(record['owner'])} + onClick={async () => { + await this.handleShowSharer(record, false); + }}>授权 @@ -367,6 +408,7 @@ class Credential extends Component { return (
+ } + }); + } + const selectedRowKeys = this.state.selectedRowKeys; const rowSelection = { selectedRowKeys: this.state.selectedRowKeys, @@ -388,9 +443,6 @@ class Credential extends Component { }; const hasSelected = selectedRowKeys.length > 0; - const userOptions = this.state.users.map(d => {d.nickname}); - return ( <> { this.setState({ @@ -560,12 +613,56 @@ class Credential extends Component { onSearch={this.handleSearchByNickname} filterOption={false} > - {userOptions} + {this.state.users.map(d => {d.nickname})} + { + this.setState({ + changeSharerConfirmLoading: true + }); + + let changeSharerModalVisible = false; + + let result = await request.post(`/resources/${this.state.selected['id']}/assign?userIds=${this.state.selectedSharers.join(',')}`); + if (result['code'] === 1) { + message.success('操作成功'); + this.loadTableData(); + } else { + message.success(result['message'], 10); + changeSharerModalVisible = true; + } + + this.setState({ + changeSharerConfirmLoading: false, + changeSharerModalVisible: changeSharerModalVisible + }) + }} + onCancel={() => { + this.setState({ + changeSharerModalVisible: false + }) + }} + okButtonProps={{disabled: !hasPermission(this.state.selected['owner'])}} + > + + `${item.nickname}`} + /> + ); diff --git a/web/src/components/user/User.js b/web/src/components/user/User.js index 7629d0d..09c95a2 100644 --- a/web/src/components/user/User.js +++ b/web/src/components/user/User.js @@ -6,10 +6,8 @@ import { Button, Col, Divider, - Dropdown, Input, Layout, - Menu, Modal, PageHeader, Row, @@ -271,7 +269,7 @@ class User extends Component { key: 'role', render: (role, record) => { - if (role === 'normal') { + if (role === 'user') { return ( 普通用户 ); diff --git a/web/src/service/permission.js b/web/src/service/permission.js new file mode 100644 index 0000000..b93392e --- /dev/null +++ b/web/src/service/permission.js @@ -0,0 +1,9 @@ +export function hasPermission(owner) { + let userJsonStr = sessionStorage.getItem('user'); + let user = JSON.parse(userJsonStr); + if (user['role'] === 'admin') { + return true; + } + + return user['id'] === owner; +} \ No newline at end of file