From 1a3f7acd1e9501916710d8af92d57ffddb15d7f9 Mon Sep 17 00:00:00 2001 From: dushixiang Date: Fri, 15 Jan 2021 22:13:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=B5=84=E4=BA=A7=E7=9A=84?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/api/asset.go | 37 +++- pkg/api/credential.go | 30 +++ pkg/api/resource.go | 3 +- pkg/api/routes.go | 17 ++ pkg/handle/runner.go | 8 +- pkg/model/asset.go | 32 +-- pkg/model/credential.go | 7 +- pkg/model/resource.go | 18 +- web/src/components/asset/Asset.js | 206 ++++++++++++++++++++ web/src/components/credential/Credential.js | 95 +++++---- web/src/service/permission.js | 7 + 11 files changed, 390 insertions(+), 70 deletions(-) diff --git a/pkg/api/asset.go b/pkg/api/asset.go index 2e82d18..1606813 100644 --- a/pkg/api/asset.go +++ b/pkg/api/asset.go @@ -1,6 +1,7 @@ package api import ( + "errors" "github.com/labstack/echo/v4" "next-terminal/pkg/model" "next-terminal/pkg/utils" @@ -31,7 +32,17 @@ func AssetPagingEndpoint(c echo.Context) error { protocol := c.QueryParam("protocol") tags := c.QueryParam("tags") - items, total, _ := model.FindPageAsset(pageIndex, pageSize, name, protocol, tags) + var ( + total int64 + items []model.AssetVo + ) + + account, _ := GetCurrentAccount(c) + if account.Role == model.RoleUser { + items, total, _ = model.FindPageAsset(pageIndex, pageSize, name, protocol, tags, account.ID) + } else { + items, total, _ = model.FindPageAsset(pageIndex, pageSize, name, protocol, tags, "") + } return Success(c, H{ "total": total, @@ -124,3 +135,27 @@ func AssetTagsEndpoint(c echo.Context) (err error) { } return Success(c, items) } + +func AssetChangeOwnerEndpoint(c echo.Context) (err error) { + id := c.Param("id") + + if err := PreCheckAssetPermission(c, id); err != nil { + return err + } + + owner := c.QueryParam("owner") + model.UpdateAssetById(&model.Asset{Owner: owner}, id) + return Success(c, "") +} + +func PreCheckAssetPermission(c echo.Context, id string) error { + item, err := model.FindAssetById(id) + if err != nil { + return err + } + + if !HasPermission(c, item.Owner) { + return errors.New("permission denied") + } + return nil +} diff --git a/pkg/api/credential.go b/pkg/api/credential.go index 921ea82..9c3e3ed 100644 --- a/pkg/api/credential.go +++ b/pkg/api/credential.go @@ -1,6 +1,7 @@ package api import ( + "errors" "github.com/labstack/echo/v4" "next-terminal/pkg/model" "next-terminal/pkg/utils" @@ -79,6 +80,10 @@ func CredentialPagingEndpoint(c echo.Context) error { func CredentialUpdateEndpoint(c echo.Context) error { id := c.Param("id") + if err := PreCheckCredentialPermission(c, id); err != nil { + return err + } + var item model.Credential if err := c.Bind(&item); err != nil { return err @@ -118,6 +123,9 @@ func CredentialDeleteEndpoint(c echo.Context) error { id := c.Param("id") split := strings.Split(id, ",") for i := range split { + if err := PreCheckCredentialPermission(c, split[i]); err != nil { + return err + } model.DeleteCredentialById(split[i]) } @@ -126,17 +134,39 @@ func CredentialDeleteEndpoint(c echo.Context) error { func CredentialGetEndpoint(c echo.Context) error { id := c.Param("id") + item, err := model.FindCredentialById(id) if err != nil { return err } + if !HasPermission(c, item.Owner) { + return errors.New("permission denied") + } + return Success(c, item) } func CredentialChangeOwnerEndpoint(c echo.Context) error { id := c.Param("id") + + if err := PreCheckCredentialPermission(c, id); err != nil { + return err + } + owner := c.QueryParam("owner") model.UpdateCredentialById(&model.Credential{Owner: owner}, id) return Success(c, "") } + +func PreCheckCredentialPermission(c echo.Context, id string) error { + item, err := model.FindCredentialById(id) + if err != nil { + return err + } + + if !HasPermission(c, item.Owner) { + return errors.New("permission denied") + } + return nil +} diff --git a/pkg/api/resource.go b/pkg/api/resource.go index 6dcf02b..e4d523a 100644 --- a/pkg/api/resource.go +++ b/pkg/api/resource.go @@ -18,9 +18,10 @@ func ResourceGetAssignEndPoint(c echo.Context) error { func ResourceOverwriteAssignEndPoint(c echo.Context) error { resourceId := c.Param("id") userIds := c.QueryParam("userIds") + resourceType := c.QueryParam("type") uIds := strings.Split(userIds, ",") - model.OverwriteUserIdsByResourceId(resourceId, uIds) + model.OverwriteUserIdsByResourceId(resourceId, resourceType, uIds) return Success(c, "") } diff --git a/pkg/api/routes.go b/pkg/api/routes.go index f15049c..b49e347 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -60,6 +60,7 @@ func SetupRoutes() *echo.Echo { assets.PUT("/:id", AssetUpdateEndpoint) assets.DELETE("/:id", AssetDeleteEndpoint) assets.GET("/:id", AssetGetEndpoint) + assets.POST("/:id/change-owner", AssetChangeOwnerEndpoint) } e.GET("/tags", AssetTagsEndpoint) @@ -157,3 +158,19 @@ func GetCurrentAccount(c echo.Context) (model.User, bool) { } return model.User{}, false } + +func HasPermission(c echo.Context, owner string) bool { + // 检测是否为创建者 + account, found := GetCurrentAccount(c) + if !found { + return false + } + if model.RoleAdmin == account.Role { + return true + } + + if owner == account.ID { + return true + } + return false +} diff --git a/pkg/handle/runner.go b/pkg/handle/runner.go index 515c96f..66fb5cd 100644 --- a/pkg/handle/runner.go +++ b/pkg/handle/runner.go @@ -18,7 +18,7 @@ func RunTicker() { c := cron.New(cron.WithSeconds()) //精确到秒 // 定时任务,每隔一小时删除一次未使用的会话信息 - _, _ = c.AddFunc("0 0/1 0/1 * * ?", func() { + _, _ = c.AddFunc("0 0 0/1 * * ?", func() { sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting}) if sessions != nil && len(sessions) > 0 { now := time.Now() @@ -26,7 +26,7 @@ func RunTicker() { if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 { model.DeleteSessionById(sessions[i].ID) s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port) - logrus.Debugf("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID) + logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID) } } } @@ -37,11 +37,11 @@ func RunTicker() { sessions, _ := model.FindSessionByStatus(model.Connected) if sessions != nil && len(sessions) > 0 { for i := range sessions { - _, found := global.Cache.Get(sessions[i].ID) + _, found := global.Store.Get(sessions[i].ID) if !found { api.CloseSessionById(sessions[i].ID, api.Normal, "") s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port) - logrus.Debugf("会话「%v」ID「%v」已离线,修改状态为「关闭」。", s, sessions[i].ID) + logrus.Infof("会话「%v」ID「%v」已离线,修改状态为「关闭」。", s, sessions[i].ID) } } } diff --git a/pkg/model/asset.go b/pkg/model/asset.go index 56a47a3..bdda511 100644 --- a/pkg/model/asset.go +++ b/pkg/model/asset.go @@ -26,16 +26,17 @@ type Asset struct { } type AssetVo struct { - ID string `json:"id"` - Name string `json:"name"` - IP string `json:"ip"` - Protocol string `json:"protocol"` - Port int `json:"port"` - Active bool `json:"active"` - Created utils.JsonTime `json:"created"` - Tags string `json:"tags"` - Owner string `json:"owner"` - OwnerName string `json:"ownerName"` + ID string `json:"id"` + Name string `json:"name"` + IP string `json:"ip"` + Protocol string `json:"protocol"` + Port int `json:"port"` + Active bool `json:"active"` + Created utils.JsonTime `json:"created"` + Tags string `json:"tags"` + Owner string `json:"owner"` + OwnerName string `json:"ownerName"` + SharerCount int64 `json:"sharerCount"` } func (r *Asset) TableName() string { @@ -57,9 +58,14 @@ func FindAssetByConditions(protocol string) (o []Asset, err error) { return } -func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string) (o []AssetVo, total int64, err error) { - 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") +func FindPageAsset(pageIndex, pageSize int, name, protocol, tags, owner string) (o []AssetVo, total int64, err error) { + 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,COUNT(resources.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resources on assets.id = resources.resource_id").Group("assets.id") + dbCounter := global.DB.Table("assets").Select("DISTINCT 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").Joins("left join resources on assets.id = resources.resource_id") + + if len(owner) > 0 { + db = db.Where("assets.owner = ? or resources.user_id = ?", owner, owner) + dbCounter = dbCounter.Where("assets.owner = ? or resources.user_id = ?", owner, owner) + } if len(name) > 0 { db = db.Where("assets.name like ?", "%"+name+"%") diff --git a/pkg/model/credential.go b/pkg/model/credential.go index 991df15..415d535 100644 --- a/pkg/model/credential.go +++ b/pkg/model/credential.go @@ -38,7 +38,12 @@ type CredentialVo struct { SharerCount int64 `json:"sharerCount"` } -func FindAllCredential() (o []Credential, err error) { +type CredentialSimpleVo struct { + ID string `json:"id"` + Name string `json:"name"` +} + +func FindAllCredential() (o []CredentialSimpleVo, err error) { err = global.DB.Find(&o).Error return } diff --git a/pkg/model/resource.go b/pkg/model/resource.go index 9182199..4dc596b 100644 --- a/pkg/model/resource.go +++ b/pkg/model/resource.go @@ -6,9 +6,10 @@ import ( ) type Resource struct { - ID string `gorm:"primary_key" json:"name"` - ResourceId string `json:"resourceId"` - UserId string `json:"userId"` + ID string `gorm:"primary_key" json:"name"` + ResourceId string `json:"resourceId"` + ResourceType string `json:"resourceType"` + UserId string `json:"userId"` } func (r *Resource) TableName() string { @@ -24,7 +25,7 @@ func FindUserIdsByResourceId(resourceId string) (r []string, err error) { return } -func OverwriteUserIdsByResourceId(resourceId string, userIds []string) { +func OverwriteUserIdsByResourceId(resourceId, resourceType string, userIds []string) { db := global.DB.Begin() db.Where("resource_id = ?", resourceId).Delete(&Resource{}) @@ -33,11 +34,12 @@ func OverwriteUserIdsByResourceId(resourceId string, userIds []string) { if len(userId) == 0 { continue } - id := utils.Sign([]string{resourceId, userId}) + id := utils.Sign([]string{resourceId, resourceType, userId}) resource := &Resource{ - ID: id, - ResourceId: resourceId, - UserId: userId, + ID: id, + ResourceId: resourceId, + ResourceType: resourceType, + UserId: userId, } _ = db.Create(resource).Error } diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js index 7445798..3af0480 100644 --- a/web/src/components/asset/Asset.js +++ b/web/src/components/asset/Asset.js @@ -1,11 +1,13 @@ import React, {Component} from 'react'; import { + Alert, Badge, Button, Col, Divider, Dropdown, + Form, Input, Layout, Menu, @@ -17,6 +19,7 @@ import { Table, Tag, Tooltip, + Transfer, Typography } from "antd"; import qs from "qs"; @@ -36,6 +39,7 @@ import { } from '@ant-design/icons'; import {PROTOCOL_COLORS} from "../../common/constants"; import Logout from "../user/Logout"; +import {hasPermission, isAdmin} from "../../service/permission"; const confirm = Modal.confirm; const {Search} = Input; @@ -55,6 +59,7 @@ const routes = [ class Asset extends Component { inputRefOfName = React.createRef(); + changeOwnerFormRef = React.createRef(); state = { items: [], @@ -73,6 +78,13 @@ class Asset extends Component { model: {}, selectedRowKeys: [], delBtnLoading: false, + changeOwnerModalVisible: false, + changeSharerModalVisible: false, + changeOwnerConfirmLoading: false, + changeSharerConfirmLoading: false, + users: [], + selected: {}, + selectedSharers: [], }; async componentDidMount() { @@ -331,6 +343,49 @@ class Asset extends Component { } } + handleSearchByNickname = async nickname => { + const result = await request.get(`/users/paging?pageIndex=1&pageSize=100&nickname=${nickname}`); + if (result.code !== 1) { + message.error(result.message, 10); + return; + } + + const items = result['data']['items'].map(item => { + return {'key': item['id'], ...item} + }) + + this.setState({ + users: items + }) + } + + handleSharersChange = async targetKeys => { + this.setState({ + selectedSharers: targetKeys + }) + } + + handleShowSharer = async (record) => { + 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 + }) + } + render() { const columns = [{ @@ -408,6 +463,37 @@ class Asset extends Component { onClick={() => this.copy(record.id)}>复制 + {isAdmin() ? + + + : undefined + } + + + + + + + } + }); + } + const selectedRowKeys = this.state.selectedRowKeys; const rowSelection = { selectedRowKeys: this.state.selectedRowKeys, @@ -584,6 +683,113 @@ class Asset extends Component { : null } + + 更换资源「{this.state.selected['name']}」的所有者 + } + visible={this.state.changeOwnerModalVisible} + confirmLoading={this.state.changeOwnerConfirmLoading} + onOk={() => { + this.setState({ + changeOwnerConfirmLoading: true + }); + + let changeOwnerModalVisible = false; + this.changeOwnerFormRef + .current + .validateFields() + .then(async values => { + let result = await request.post(`/assets/${this.state.selected['id']}/change-owner?owner=${values['owner']}`); + if (result['code'] === 1) { + message.success('操作成功'); + this.loadTableData(); + } else { + message.success(result['message'], 10); + changeOwnerModalVisible = true; + } + }) + .catch(info => { + + }) + .finally(() => { + this.setState({ + changeOwnerConfirmLoading: false, + changeOwnerModalVisible: changeOwnerModalVisible + }) + }); + }} + onCancel={() => { + this.setState({ + changeOwnerModalVisible: false + }) + }} + > + +
+ + + + + + + +
+ + 更新资源「{this.state.selected['name']}」的授权人 + } + visible={this.state.changeSharerModalVisible} + confirmLoading={this.state.changeSharerConfirmLoading} + onOk={async () => { + this.setState({ + changeSharerConfirmLoading: true + }); + + let changeSharerModalVisible = false; + + let result = await request.post(`/resources/${this.state.selected['id']}/assign?type=asset&userIds=${this.state.selectedSharers.join(',')}`); + if (result['code'] === 1) { + message.success('操作成功'); + this.loadTableData(); + } else { + message.error(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/credential/Credential.js b/web/src/components/credential/Credential.js index c85ea11..1422f6d 100644 --- a/web/src/components/credential/Credential.js +++ b/web/src/components/credential/Credential.js @@ -34,7 +34,7 @@ import { } from '@ant-design/icons'; import {itemRender} from "../../utils/utils"; import Logout from "../user/Logout"; -import {hasPermission} from "../../service/permission"; +import {hasPermission, isAdmin} from "../../service/permission"; const confirm = Modal.confirm; const {Search} = Input; @@ -55,7 +55,6 @@ class Credential extends Component { inputRefOfName = React.createRef(); changeOwnerFormRef = React.createRef(); - changeSharerFormRef = React.createRef(); state = { items: [], @@ -173,26 +172,32 @@ class Credential extends Component { showModal = async (title, id = null, index) => { let items = this.state.items; - items[index].updateBtnLoading = true; - this.setState({ - items: items - }); - let result = await request.get('/credentials/' + id); - if (result['code'] !== 1) { - message.error(result['message']); - items[index].updateBtnLoading = false; + let model = {} + if (id) { + items[index].updateBtnLoading = true; this.setState({ items: items }); - return; + + let result = await request.get('/credentials/' + id); + if (result['code'] !== 1) { + message.error(result['message']); + items[index].updateBtnLoading = false; + this.setState({ + items: items + }); + return; + } + + items[index].updateBtnLoading = false; + model = result['data'] } - items[index].updateBtnLoading = false; this.setState({ modalTitle: title, modalVisible: true, - model: result['data'], + model: model, items: items }); }; @@ -287,7 +292,7 @@ class Credential extends Component { }) } - handleShowSharer = async (record, disabled) => { + handleShowSharer = async (record) => { let r1 = this.handleSearchByNickname(''); let r2 = request.get(`/resources/${record['id']}/assign`); @@ -368,32 +373,35 @@ class Credential extends Component { const menu = ( - - + : undefined + } - }}>更换所有者 - + await this.handleShowSharer(record); + }}>更新授权人 @@ -421,14 +429,14 @@ class Credential extends Component { } ]; - if(hasPermission()){ - columns.splice(5,0,{ + if (isAdmin()) { + columns.splice(5, 0, { title: '授权人数', dataIndex: 'sharerCount', key: 'sharerCount', render: (text, record, index) => { return } }); @@ -490,7 +498,7 @@ class Credential extends Component { @@ -559,14 +567,13 @@ class Credential extends Component { handleCancel={this.handleCancelModal} confirmLoading={this.state.modalConfirmLoading} model={this.state.model} - footer={this.state.modalTitle.indexOf('查看') > -1 ? null : undefined} > : null } - 更换资源「{this.state.selected['name']}」的所有者} visible={this.state.changeOwnerModalVisible} confirmLoading={this.state.changeOwnerConfirmLoading} onOk={() => { @@ -620,7 +627,7 @@ class Credential extends Component { - 更新资源「{this.state.selected['name']}」的授权人} visible={this.state.changeSharerModalVisible} confirmLoading={this.state.changeSharerConfirmLoading} onOk={async () => { @@ -630,12 +637,12 @@ class Credential extends Component { let changeSharerModalVisible = false; - let result = await request.post(`/resources/${this.state.selected['id']}/assign?userIds=${this.state.selectedSharers.join(',')}`); + let result = await request.post(`/resources/${this.state.selected['id']}/assign?type=credential&userIds=${this.state.selectedSharers.join(',')}`); if (result['code'] === 1) { message.success('操作成功'); this.loadTableData(); } else { - message.success(result['message'], 10); + message.error(result['message'], 10); changeSharerModalVisible = true; } @@ -658,6 +665,10 @@ class Credential extends Component { showSearch titles={['未授权', '已授权']} operations={['授权', '移除']} + listStyle={{ + width: 250, + height: 300, + }} targetKeys={this.state.selectedSharers} onChange={this.handleSharersChange} render={item => `${item.nickname}`} diff --git a/web/src/service/permission.js b/web/src/service/permission.js index b93392e..d432bbe 100644 --- a/web/src/service/permission.js +++ b/web/src/service/permission.js @@ -6,4 +6,11 @@ export function hasPermission(owner) { } return user['id'] === owner; +} + +export function isAdmin(){ + let userJsonStr = sessionStorage.getItem('user'); + let user = JSON.parse(userJsonStr); + return user['role'] === 'admin'; + } \ No newline at end of file