diff --git a/pkg/api/asset.go b/pkg/api/asset.go
index 8c90f1b..5b2de68 100644
--- a/pkg/api/asset.go
+++ b/pkg/api/asset.go
@@ -1,6 +1,7 @@
package api
import (
+ "encoding/json"
"errors"
"github.com/labstack/echo/v4"
"next-terminal/pkg/model"
@@ -68,10 +69,17 @@ func AssetUpdateEndpoint(c echo.Context) error {
return err
}
- var item model.Asset
- if err := c.Bind(&item); err != nil {
+ m := echo.Map{}
+ if err := c.Bind(&m); err != nil {
return err
}
+
+ data, _ := json.Marshal(m)
+ var item model.Asset
+ if err := json.Unmarshal(data, &item); err != nil {
+ return err
+ }
+
switch item.AccountType {
case "credential":
item.Username = "-"
@@ -102,6 +110,9 @@ func AssetUpdateEndpoint(c echo.Context) error {
}
model.UpdateAssetById(&item, id)
+ if err := model.UpdateAssetAttributes(id, item.Protocol, m); err != nil {
+ return err
+ }
return Success(c, nil)
}
@@ -151,8 +162,16 @@ func AssetGetEndpoint(c echo.Context) (err error) {
if item, err = model.FindAssetById(id); err != nil {
return err
}
+ attributeMap, err := model.FindAssetAttrMapByAssetId(id)
+ if err != nil {
+ return err
+ }
+ itemMap := utils.StructToMap(item)
+ for key := range attributeMap {
+ itemMap[key] = attributeMap[key]
+ }
- return Success(c, item)
+ return Success(c, itemMap)
}
func AssetTcpingEndpoint(c echo.Context) (err error) {
diff --git a/pkg/api/property.go b/pkg/api/property.go
index 4a0916f..a7bfaba 100644
--- a/pkg/api/property.go
+++ b/pkg/api/property.go
@@ -1,8 +1,10 @@
package api
import (
+ "errors"
"fmt"
"github.com/labstack/echo/v4"
+ "gorm.io/gorm"
"next-terminal/pkg/model"
)
@@ -19,11 +21,23 @@ func PropertyUpdateEndpoint(c echo.Context) error {
for key := range item {
value := fmt.Sprintf("%v", item[key])
+ if value == "" {
+ value = "-"
+ }
+
property := model.Property{
Name: key,
Value: value,
}
- model.UpdatePropertyByName(&property, key)
+
+ _, err := model.FindPropertyByName(key)
+ if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
+ if err := model.CreateNewProperty(&property); err != nil {
+ return err
+ }
+ } else {
+ model.UpdatePropertyByName(&property, key)
+ }
}
return Success(c, nil)
}
diff --git a/pkg/api/routes.go b/pkg/api/routes.go
index 7a6f54d..1936864 100644
--- a/pkg/api/routes.go
+++ b/pkg/api/routes.go
@@ -74,8 +74,6 @@ func SetupRoutes() *echo.Echo {
assets.GET("/paging", AssetPagingEndpoint)
assets.POST("/:id/tcping", AssetTcpingEndpoint)
assets.PUT("/:id", AssetUpdateEndpoint)
- assets.GET("/:id/attributes", AssetGetAttributeEndpoint)
- assets.PUT("/:id/attributes", AssetUpdateAttributeEndpoint)
assets.DELETE("/:id", AssetDeleteEndpoint)
assets.GET("/:id", AssetGetEndpoint)
assets.POST("/:id/change-owner", Admin(AssetChangeOwnerEndpoint))
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 930b842..22fb258 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -11,6 +11,7 @@ import (
"net"
"os"
"path/filepath"
+ "reflect"
"sort"
"strconv"
"strings"
@@ -157,3 +158,27 @@ func Contains(s []string, str string) bool {
}
return false
}
+
+func StructToMap(obj interface{}) map[string]interface{} {
+ t := reflect.TypeOf(obj)
+ v := reflect.ValueOf(obj)
+ if t.Kind() == reflect.Ptr {
+ // 如果是指针,则获取其所指向的元素
+ t = t.Elem()
+ v = v.Elem()
+ }
+
+ var data = make(map[string]interface{})
+ if t.Kind() == reflect.Struct {
+ // 只有结构体可以获取其字段信息
+ for i := 0; i < t.NumField(); i++ {
+ jsonName := t.Field(i).Tag.Get("json")
+ if jsonName != "" {
+ data[jsonName] = v.Field(i).Interface()
+ } else {
+ data[t.Field(i).Name] = v.Field(i).Interface()
+ }
+ }
+ }
+ return data
+}
diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js
index 707db36..0735a20 100644
--- a/web/src/components/asset/Asset.js
+++ b/web/src/components/asset/Asset.js
@@ -40,10 +40,6 @@ import {
import {PROTOCOL_COLORS} from "../../common/constants";
import Logout from "../user/Logout";
import {hasPermission, isAdmin} from "../../service/permission";
-import AssetSSHAttributeModal from "./AssetSSHAttributeModal";
-import AssetRDPAttributeModal from "./AssetRDPAttributeModal";
-import AssetVNCAttributeModal from "./AssetVNCAttributeModal";
-import AssetTelnetAttributeModal from "./AssetTelnetAttributeModal";
const confirm = Modal.confirm;
const {Search} = Input;
@@ -89,8 +85,6 @@ class Asset extends Component {
users: [],
selected: {},
selectedSharers: [],
- attrVisible: false,
- attributes: {}
};
async componentDidMount() {
@@ -230,34 +224,6 @@ class Asset extends Component {
await this.showModal('复制资产', result.data);
}
- async attr(record) {
- let result = await request.get(`/assets/${record['id']}/attributes`);
- if (result.code !== 1) {
- message.error(result.message, 10);
- return;
- }
-
- // eslint-disable-next-line no-extend-native
- String.prototype.bool = function () {
- return (/^true$/i).test(this);
- };
-
- let attributes = result['data'];
- for (const key in attributes) {
- if (!attributes.hasOwnProperty(key)) {
- continue;
- }
- if (key === 'swap-red-blue') {
- attributes[key] = attributes[key].bool();
- }
- }
- this.setState({
- selected: record,
- attrVisible: true,
- attributes: attributes
- });
- }
-
async showModal(title, asset = {}) {
// 并行请求
let getCredentials = request.get('/credentials');
@@ -447,30 +413,6 @@ class Asset extends Component {
})
}
- handleUpdateAttr = async (formData) => {
- // 弹窗 form 传来的数据
- this.setState({
- modalConfirmLoading: true
- });
-
- try {
- let selected = this.state.selected;
- let result = await request.put(`/assets/${selected['id']}/attributes?protocol=${selected['protocol']}`, formData);
- if (result['code'] !== 1) {
- message.error(result['message'], 10);
- } else {
- message.success('操作成功');
- this.setState({
- attrVisible: false
- })
- }
- } finally {
- this.setState({
- modalConfirmLoading: false
- });
- }
- }
-
handleCancelUpdateAttr = () => {
this.setState({
attrVisible: false,
@@ -550,12 +492,6 @@ class Asset extends Component {
onClick={() => this.copy(record.id)}>复制
-
-
-
-
{isAdmin() ?
: undefined
}
-
- {
- this.state.attrVisible && this.state.selected['protocol'] === 'ssh' ?
-
- : null
- }
-
- {
- this.state.attrVisible && this.state.selected['protocol'] === 'rdp' ?
-
- : null
- }
-
- {
- this.state.attrVisible && this.state.selected['protocol'] === 'vnc' ?
-
- : null
- }
-
- {
- this.state.attrVisible && this.state.selected['protocol'] === 'telnet' ?
-
- : null
- }
>
);
diff --git a/web/src/components/asset/AssetModal.js b/web/src/components/asset/AssetModal.js
index 5eff2fe..826eb84 100644
--- a/web/src/components/asset/AssetModal.js
+++ b/web/src/components/asset/AssetModal.js
@@ -1,8 +1,24 @@
import React, {useState} from 'react';
-import {Form, Input, InputNumber, Modal, Radio, Select, Tooltip} from "antd/lib/index";
+import {
+ Col,
+ Collapse,
+ Form,
+ Input,
+ InputNumber,
+ Modal,
+ Radio,
+ Row,
+ Select,
+ Switch,
+ Tooltip,
+ Typography
+} from "antd/lib/index";
+import {ExclamationCircleOutlined} from "@ant-design/icons";
const {TextArea} = Input;
const {Option} = Select;
+const {Text, Title} = Typography;
+const {Panel} = Collapse;
// 子级页面
// Ant form create 表单内置方法
@@ -18,11 +34,29 @@ const protocolMapping = {
'telnet': [{text: '密码', value: 'custom'}, {text: '授权凭证', value: 'credential'}]
}
+const formLayout = {
+ labelCol: {span: 6},
+ wrapperCol: {span: 18},
+};
+const RDPFormItemLayout = {
+ labelCol: {span: 12},
+ wrapperCol: {span: 12},
+};
+const TELENETFormItemLayout = {
+ labelCol: {span: 8},
+ wrapperCol: {span: 16},
+};
+
const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoading, credentials, tags, model}) {
const [form] = Form.useForm();
+ if (model.accountType === undefined) {
+ model.accountType = 'rdp';
+ }
+
let [accountType, setAccountType] = useState(model.accountType);
+ let [protocol, setProtocol] = useState(model.protocol);
let initAccountTypes = []
if (model.protocol) {
@@ -38,12 +72,8 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
}
}
- const formItemLayout = {
- labelCol: {span: 6},
- wrapperCol: {span: 14},
- };
-
const handleProtocolChange = e => {
+ setProtocol(e.target.value)
let port;
switch (e.target.value) {
case 'ssh':
@@ -109,109 +139,495 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
.catch(info => {
});
}}
+ width={1040}
onCancel={handleCancel}
confirmLoading={confirmLoading}
okText='确定'
cancelText='取消'
>
-
-
-
+
)
diff --git a/web/src/components/asset/AssetRDPAttributeModal.js b/web/src/components/asset/AssetRDPAttributeModal.js
deleted file mode 100644
index 5d59b28..0000000
--- a/web/src/components/asset/AssetRDPAttributeModal.js
+++ /dev/null
@@ -1,205 +0,0 @@
-import React from 'react';
-import {Collapse, Form, Input, Modal, Switch, Tooltip, Typography} from "antd/lib/index";
-import {ExclamationCircleOutlined} from "@ant-design/icons";
-
-const {Text} = Typography;
-const {Panel} = Collapse;
-const formLayout = {
- labelCol: {span: 6},
- wrapperCol: {span: 14},
-};
-const formItemLayout = {
- labelCol: {span: 12},
- wrapperCol: {span: 12},
-};
-
-const AssetRDPAttributeModal = function ({handleOk, handleCancel, confirmLoading, attributes}) {
-
- const [form] = Form.useForm();
-
- return (
-
- {
- form
- .validateFields()
- .then(values => {
- form.resetFields();
- handleOk(values);
- })
- .catch(info => {
- });
- }}
- onCancel={handleCancel}
- confirmLoading={confirmLoading}
- okText='确定'
- cancelText='取消'
- >
-
-
-
- )
-}
-
-export default AssetRDPAttributeModal;
diff --git a/web/src/components/asset/AssetSSHAttributeModal.js b/web/src/components/asset/AssetSSHAttributeModal.js
deleted file mode 100644
index 1b3df49..0000000
--- a/web/src/components/asset/AssetSSHAttributeModal.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import React from 'react';
-import {Collapse, Form, Input, Modal, Select, Typography} from "antd/lib/index";
-
-const {Option} = Select;
-const {Panel} = Collapse;
-const {Text} = Typography;
-
-const AssetSSHAttributeModal = function ({handleOk, handleCancel, confirmLoading, attributes}) {
-
- const [form] = Form.useForm();
-
- const formItemLayout = {
- labelCol: {span: 6},
- wrapperCol: {span: 14},
- };
-
- return (
-
- {
- form
- .validateFields()
- .then(values => {
- form.resetFields();
- handleOk(values);
- })
- .catch(info => {
- });
- }}
- onCancel={handleCancel}
- confirmLoading={confirmLoading}
- okText='确定'
- cancelText='取消'
- >
-
-
-
- )
-}
-
-export default AssetSSHAttributeModal;
diff --git a/web/src/components/asset/AssetTelnetAttributeModal.js b/web/src/components/asset/AssetTelnetAttributeModal.js
deleted file mode 100644
index cfea57e..0000000
--- a/web/src/components/asset/AssetTelnetAttributeModal.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from 'react';
-import {Collapse, Form, Input, Modal, Select, Typography} from "antd/lib/index";
-
-const {Option} = Select;
-const {Panel} = Collapse;
-const {Text} = Typography;
-
-const AssetTelnetAttributeModal = function ({handleOk, handleCancel, confirmLoading, attributes}) {
-
- const [form] = Form.useForm();
-
- const formItemLayout = {
- labelCol: {span: 8},
- wrapperCol: {span: 14},
- };
-
- return (
-
- {
- form
- .validateFields()
- .then(values => {
- form.resetFields();
- handleOk(values);
- })
- .catch(info => {
- });
- }}
- onCancel={handleCancel}
- confirmLoading={confirmLoading}
- okText='确定'
- cancelText='取消'
- >
-
-
-
- )
-}
-
-export default AssetTelnetAttributeModal;
diff --git a/web/src/components/asset/AssetVNCAttributeModal.js b/web/src/components/asset/AssetVNCAttributeModal.js
deleted file mode 100644
index 19d10f3..0000000
--- a/web/src/components/asset/AssetVNCAttributeModal.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react';
-import {Collapse, Form, Input, Modal, Select, Switch, Tooltip, Typography} from "antd/lib/index";
-import {ExclamationCircleOutlined} from "@ant-design/icons";
-
-const {Text} = Typography;
-
-const {Option} = Select;
-const {Panel} = Collapse;
-const formLayout = {
- labelCol: {span: 6},
- wrapperCol: {span: 14},
-};
-
-const AssetRDPAttributeModal = function ({handleOk, handleCancel, confirmLoading, attributes}) {
-
- const [form] = Form.useForm();
-
- return (
-
- {
- form
- .validateFields()
- .then(values => {
- form.resetFields();
- handleOk(values);
- })
- .catch(info => {
- });
- }}
- onCancel={handleCancel}
- confirmLoading={confirmLoading}
- okText='确定'
- cancelText='取消'
- >
-
-
-
- )
-}
-
-export default AssetRDPAttributeModal;
diff --git a/web/src/components/setting/Setting.js b/web/src/components/setting/Setting.js
index 648691b..7bbc82c 100644
--- a/web/src/components/setting/Setting.js
+++ b/web/src/components/setting/Setting.js
@@ -1,9 +1,10 @@
import React, {Component} from 'react';
-import {Button, Form, Input, Layout, PageHeader, Select, Switch, Tabs, Typography} from "antd";
+import {Button, Form, Input, Layout, PageHeader, Select, Switch, Tabs, Tooltip, Typography} from "antd";
import {itemRender} from '../../utils/utils'
import request from "../../common/request";
import {message} from "antd/es";
import Logout from "../user/Logout";
+import {ExclamationCircleOutlined} from "@ant-design/icons";
const {Content} = Layout;
const {Option} = Select;
@@ -37,9 +38,11 @@ class Setting extends Component {
properties: {}
}
- settingFormRef1 = React.createRef();
- settingFormRef2 = React.createRef();
- settingFormRef3 = React.createRef();
+ rdpSettingFormRef = React.createRef();
+ sshSettingFormRef = React.createRef();
+ vncSettingFormRef = React.createRef();
+ telnetSettingFormRef = React.createRef();
+ otherSettingFormRef = React.createRef();
componentDidMount() {
this.getProperties();
@@ -66,28 +69,36 @@ class Setting extends Component {
let properties = result['data'];
for (let key in properties) {
- if(!properties.hasOwnProperty(key)){
+ if (!properties.hasOwnProperty(key)) {
continue;
}
- if(key.startsWith('enable') || key.startsWith("disable")){
+ if (key.startsWith('enable') || key.startsWith("disable" || key === 'swap-red-blue')) {
properties[key] = properties[key].bool();
}
}
- console.log(properties)
+
this.setState({
properties: properties
})
- if (this.settingFormRef1.current) {
- this.settingFormRef1.current.setFieldsValue(properties)
+ if (this.rdpSettingFormRef.current) {
+ this.rdpSettingFormRef.current.setFieldsValue(properties)
}
- if (this.settingFormRef2.current) {
- this.settingFormRef2.current.setFieldsValue(properties)
+ if (this.sshSettingFormRef.current) {
+ this.sshSettingFormRef.current.setFieldsValue(properties)
}
- if (this.settingFormRef3.current) {
- this.settingFormRef3.current.setFieldsValue(properties)
+ if (this.vncSettingFormRef.current) {
+ this.vncSettingFormRef.current.setFieldsValue(properties)
+ }
+
+ if (this.telnetSettingFormRef.current) {
+ this.telnetSettingFormRef.current.setFieldsValue(properties)
+ }
+
+ if (this.otherSettingFormRef.current) {
+ this.otherSettingFormRef.current.setFieldsValue(properties)
}
} else {
message.error(result['message']);
@@ -119,8 +130,8 @@ class Setting extends Component {
-
-
-
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 目标主机
+ }
+ {...formItemLayout}
+ name='dest-host'>
+
+
+ 目标端口
+ }
+ {...formItemLayout}
+ name='dest-port'>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Guacd 服务配置
-
-
-
>
);