From 3c217ab039b3007432fcb340972cca286986e350 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 18 Apr 2026 07:44:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=B5=84=E4=BA=A7=E3=80=81=E5=91=BD=E4=BB=A4=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E5=99=A8=E3=80=81=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E7=AD=89=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BF=AE=E5=A4=8DGitHub=20?= =?UTF-8?q?Actions=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-guacamole-server.yml | 12 +- .github/workflows/docker-guacd.yml | 14 +- .github/workflows/docker-next-terminal.yml | 24 +- .github/workflows/golangci-lint.yml | 13 +- .github/workflows/release-next-terminal.yml | 16 +- .golangci.yml | 3 - server/api/access_setting_api.go | 14 +- server/api/account.go | 75 +-- server/api/agent_gateway_api.go | 26 +- server/api/asset_group.go | 61 ++- server/api/certificate_api.go | 101 +++- server/api/command_filter.go | 132 ++++- server/api/dashboard.go | 18 +- server/api/database_asset.go | 159 ++++-- server/api/department.go | 128 ++++- server/api/gateway_group_api.go | 4 + server/api/login_policy.go | 2 +- server/api/logo.go | 4 +- server/api/portal_api.go | 116 ++--- server/api/property.go | 6 +- server/api/scheduled_task_api.go | 339 +++++++++++++ server/api/session.go | 10 +- server/api/setup.go | 8 +- server/api/stub_apis.go | 118 +---- server/api/stub_apis2.go | 452 +++++++++++++++++- server/api/term.go | 30 +- server/api/tools_api.go | 161 +++++++ server/api/user.go | 3 +- server/api/website_api.go | 193 ++++++-- server/app/middleware/auth.go | 2 + server/app/server.go | 67 +-- server/dto/website.go | 56 +-- server/env/db.go | 2 +- server/model/access_token.go | 10 +- server/model/agent_gateway.go | 40 +- server/model/authorised_asset.go | 49 ++ server/model/certificate.go | 30 +- server/model/command_filter.go | 27 ++ server/model/database_asset.go | 27 ++ server/model/department.go | 12 +- server/model/job.go | 2 + server/model/session.go | 46 +- server/model/user.go | 66 +-- server/model/website.go | 44 +- server/model/website_group.go | 15 + server/repository/authorised_asset.go | 389 +++++++++++++++ server/repository/command_filter.go | 110 +++++ server/repository/database_asset.go | 148 ++++++ server/repository/department.go | 109 +++++ server/repository/user.go | 15 +- server/repository/user_department.go | 106 ++++ server/repository/website.go | 10 +- server/repository/website_group.go | 50 ++ server/service/job_exec_shell.go | 69 ++- .../account/MultiFactorAuthentication.tsx | 19 +- .../pages/authorised/AuthorisedAssetPost.tsx | 41 +- web/src/pages/gateway/GatewayGroupDrawer.tsx | 21 +- web/src/pages/gateway/GatewayGroupPage.tsx | 15 +- web/src/pages/sysops/ScheduledTaskLogPage.tsx | 21 +- web/src/pages/sysops/ScheduledTaskModal.tsx | 67 +-- web/src/pages/sysops/ScheduledTaskRuntime.tsx | 16 +- web/src/pages/sysops/ToolsPing.tsx | 11 +- web/src/pages/sysops/ToolsTcping.tsx | 15 +- web/src/react-i18next/locales/zh-CN.json | 99 +++- 64 files changed, 3308 insertions(+), 760 deletions(-) create mode 100644 server/api/scheduled_task_api.go create mode 100644 server/api/tools_api.go create mode 100644 server/model/authorised_asset.go create mode 100644 server/model/command_filter.go create mode 100644 server/model/database_asset.go create mode 100644 server/model/website_group.go create mode 100644 server/repository/authorised_asset.go create mode 100644 server/repository/command_filter.go create mode 100644 server/repository/database_asset.go create mode 100644 server/repository/department.go create mode 100644 server/repository/user_department.go create mode 100644 server/repository/website_group.go diff --git a/.github/workflows/docker-guacamole-server.yml b/.github/workflows/docker-guacamole-server.yml index 9a896490f..d90fab0eb 100644 --- a/.github/workflows/docker-guacamole-server.yml +++ b/.github/workflows/docker-guacamole-server.yml @@ -9,11 +9,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Private Actions Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v4 - name: Docker Setup QEMU - uses: docker/setup-qemu-action@v1.2.0 + uses: docker/setup-qemu-action@v3 - name: Docker Setup Buildx - uses: docker/setup-buildx-action@v1.6.0 + uses: docker/setup-buildx-action@v3 - name: Get resources run: | rm -rf * @@ -22,15 +22,15 @@ jobs: mv /tmp/guacamole-server-master/* . - name: Docker Login - uses: docker/login-action@v1.10.0 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Build and push Docker images - uses: docker/build-push-action@v2.7.0 + uses: docker/build-push-action@v6 with: context: . - platforms: linux/arm64,linux/arm/v7,linux/amd64 + platforms: linux/arm64,linux/amd64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/guacamole-server:latest diff --git a/.github/workflows/docker-guacd.yml b/.github/workflows/docker-guacd.yml index 8aac92dbd..16a27384c 100644 --- a/.github/workflows/docker-guacd.yml +++ b/.github/workflows/docker-guacd.yml @@ -9,28 +9,28 @@ jobs: runs-on: ubuntu-latest steps: - name: Private Actions Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v4 - name: Docker Setup QEMU - uses: docker/setup-qemu-action@v1.2.0 + uses: docker/setup-qemu-action@v3 - name: Docker Setup Buildx - uses: docker/setup-buildx-action@v1.6.0 + uses: docker/setup-buildx-action@v3 - name: Docker Login - uses: docker/login-action@v1.10.0 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Docker Aliyun Login - uses: docker/login-action@v1.10.0 + uses: docker/login-action@v3 with: registry: registry.cn-beijing.aliyuncs.com username: ${{ secrets.ALI_USERNAME }} password: ${{ secrets.ALI_PASSWORD }} - name: Build and push Docker images - uses: docker/build-push-action@v2.7.0 + uses: docker/build-push-action@v6 with: context: . - platforms: linux/arm64,linux/arm/v7,linux/amd64 + platforms: linux/arm64,linux/amd64 file: guacd/Dockerfile push: true tags: | diff --git a/.github/workflows/docker-next-terminal.yml b/.github/workflows/docker-next-terminal.yml index 8f93ff8d6..1eca1feaf 100644 --- a/.github/workflows/docker-next-terminal.yml +++ b/.github/workflows/docker-next-terminal.yml @@ -13,17 +13,17 @@ jobs: steps: - name: Get version id: get_version - run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT - name: Private Actions Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Docker Setup QEMU - uses: docker/setup-qemu-action@v1.2.0 + uses: docker/setup-qemu-action@v3 - name: Docker Setup Buildx - uses: docker/setup-buildx-action@v1.6.0 + uses: docker/setup-buildx-action@v3 - name: node Setup - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: - node-version: '16' + node-version: '20' - name: npm install run: | cd web @@ -31,21 +31,21 @@ jobs: yarn yarn build - name: Docker Login - uses: docker/login-action@v1.10.0 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Docker Aliyun Login - uses: docker/login-action@v1.10.0 + uses: docker/login-action@v3 with: registry: registry.cn-beijing.aliyuncs.com username: ${{ secrets.ALI_USERNAME }} password: ${{ secrets.ALI_PASSWORD }} - name: Build and push Docker images - uses: docker/build-push-action@v2.7.0 + uses: docker/build-push-action@v6 with: context: . - platforms: linux/arm64,linux/arm/v7,linux/amd64 + platforms: linux/arm64,linux/amd64 file: Dockerfile push: true tags: | @@ -62,9 +62,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Private Actions Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v4 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v2 + uses: peter-evans/dockerhub-description@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 7e67fd5ad..7b4a16f14 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,10 +13,13 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.42.1 + go-version: '1.22' + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest args: --timeout=5m + working-directory: server diff --git a/.github/workflows/release-next-terminal.yml b/.github/workflows/release-next-terminal.yml index 97017db9d..1c388ed52 100644 --- a/.github/workflows/release-next-terminal.yml +++ b/.github/workflows/release-next-terminal.yml @@ -13,22 +13,22 @@ jobs: steps: - name: Get version id: get_version - run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT - name: Private Actions Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: node Setup - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: - node-version: '16' + node-version: '20' - name: npm install run: | cd web npm install --global yarn yarn - name: go Setup - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.22' - name: Build package Linux run: | sh build.sh @@ -39,8 +39,8 @@ jobs: cp LICENSE next-terminal/ tar zcvf next-terminal.tar.gz next-terminal/ - name: release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: next-terminal.tar.gz env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml index 9afa41102..23f883fca 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,6 @@ linters-settings: linters: disable-all: true enable: - - deadcode - errcheck - gofmt - goimports @@ -13,10 +12,8 @@ linters: - govet - ineffassign - staticcheck - - structcheck - typecheck - unused - - varcheck run: skip-files: diff --git a/server/api/access_setting_api.go b/server/api/access_setting_api.go index 9a290c8b0..0fb421065 100644 --- a/server/api/access_setting_api.go +++ b/server/api/access_setting_api.go @@ -10,13 +10,13 @@ type AccessSettingApi struct{} func (api AccessSettingApi) GetEndpoint(c echo.Context) error { return Success(c, maps.Map{ - "fontSize": "14", - "lineHeight": "1.5", - "fontFamily": "JetBrains Mono, Consolas, monospace", - "selectionCopy": "true", - "rightClickPaste": "true", - "treeExpandedKeys": "", - "useSnippets": "true", + "fontSize": "14", + "lineHeight": "1.5", + "fontFamily": "JetBrains Mono, Consolas, monospace", + "selectionCopy": "true", + "rightClickPaste": "true", + "treeExpandedKeys": "", + "useSnippets": "true", "interceptSearchShortcut": "false", }) } diff --git a/server/api/account.go b/server/api/account.go index d82eabf77..cbd77d785 100644 --- a/server/api/account.go +++ b/server/api/account.go @@ -118,13 +118,18 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error { } info := AccountInfo{ - Id: user.ID, - Username: user.Username, - Nickname: user.Nickname, - Type: user.Type, - EnableTotp: user.TOTPSecret != "" && user.TOTPSecret != "-", - Roles: user.Roles, - Menus: menus, + Id: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Type: user.Type, + EnabledTotp: user.TOTPSecret != "" && user.TOTPSecret != "-", + MfaEnabled: user.TOTPSecret != "" && user.TOTPSecret != "-", + Roles: user.Roles, + Menus: menus, + Language: "zh-CN", + ForceTotpEnabled: false, + NeedChangePassword: false, + Dev: config.GlobalCfg.Debug, } return Success(c, maps.Map{ @@ -269,13 +274,18 @@ func (api AccountApi) ChangePasswordEndpoint(c echo.Context) error { } type AccountInfo struct { - Id string `json:"id"` - Username string `json:"username"` - Nickname string `json:"nickname"` - Type string `json:"type"` - EnableTotp bool `json:"enableTotp"` - Roles []string `json:"roles"` - Menus []UserMenu `json:"menus"` + Id string `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Type string `json:"type"` + EnabledTotp bool `json:"enabledTotp"` + MfaEnabled bool `json:"mfaEnabled"` + Roles []string `json:"roles"` + Menus []UserMenu `json:"menus"` + Language string `json:"language"` + ForceTotpEnabled bool `json:"forceTotpEnabled"` + NeedChangePassword bool `json:"needChangePassword"` + Dev bool `json:"dev"` } type UserMenu struct { @@ -317,13 +327,18 @@ func (api AccountApi) InfoEndpoint(c echo.Context) error { } info := AccountInfo{ - Id: user.ID, - Username: user.Username, - Nickname: user.Nickname, - Type: user.Type, - EnableTotp: user.TOTPSecret != "" && user.TOTPSecret != "-", - Roles: user.Roles, - Menus: menus, + Id: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Type: user.Type, + EnabledTotp: user.TOTPSecret != "" && user.TOTPSecret != "-", + MfaEnabled: user.TOTPSecret != "" && user.TOTPSecret != "-", + Roles: user.Roles, + Menus: menus, + Language: "zh-CN", + ForceTotpEnabled: false, + NeedChangePassword: false, + Dev: config.GlobalCfg.Debug, } return Success(c, info) } @@ -409,39 +424,39 @@ func (api AccountApi) SecurityTokenSupportTypesEndpoint(c echo.Context) error { if err != nil { return err } - + var types []string if user.TOTPSecret != "" && user.TOTPSecret != "-" { types = append(types, "otp") } - + if len(types) == 0 { types = []string{} } - + return Success(c, types) } func (api AccountApi) SecurityTokenMfaEndpoint(c echo.Context) error { account, _ := GetCurrentAccount(c) passcode := c.QueryParam("passcode") - + user, err := repository.UserRepository.FindById(context.TODO(), account.ID) if err != nil { return err } - + if user.TOTPSecret == "" || user.TOTPSecret == "-" { return Fail(c, -1, "MFA not enabled") } - + if !common.Validate(passcode, user.TOTPSecret) { return Fail(c, -1, "Invalid passcode") } - + token := utils.UUID() cache.TokenManager.Set(token, account.ID, cache.NotRememberExpiration) - + return Success(c, maps.Map{"token": token}) } @@ -450,7 +465,7 @@ func (api AccountApi) SecurityTokenValidateEndpoint(c echo.Context) error { if token == "" { return Success(c, maps.Map{"ok": false}) } - + _, ok := cache.TokenManager.Get(token) return Success(c, maps.Map{"ok": ok}) } diff --git a/server/api/agent_gateway_api.go b/server/api/agent_gateway_api.go index 64efea169..904094115 100644 --- a/server/api/agent_gateway_api.go +++ b/server/api/agent_gateway_api.go @@ -100,24 +100,24 @@ func (api AgentGatewayApi) GetStatEndpoint(c echo.Context) error { return err } return Success(c, maps.Map{ - "ping": 0, - "cpu": maps.Map{"percent": 0}, - "memory": maps.Map{"percent": 0}, - "disk": maps.Map{"percent": 0}, - "network": maps.Map{}, - "load": maps.Map{}, - "host": maps.Map{}, - "process": maps.Map{"total": 0}, - "connections": 0, - "tcp_states": []string{}, - "errors": maps.Map{}, + "ping": 0, + "cpu": maps.Map{"percent": 0}, + "memory": maps.Map{"percent": 0}, + "disk": maps.Map{"percent": 0}, + "network": maps.Map{}, + "load": maps.Map{}, + "host": maps.Map{}, + "process": maps.Map{"total": 0}, + "connections": 0, + "tcp_states": []string{}, + "errors": maps.Map{}, }) } func (api AgentGatewayApi) UpdateSortEndpoint(c echo.Context) error { var items []struct { - ID string `json:"id"` - SortOrder int `json:"sortOrder"` + ID string `json:"id"` + SortOrder int `json:"sortOrder"` } if err := c.Bind(&items); err != nil { return err diff --git a/server/api/asset_group.go b/server/api/asset_group.go index 37aff06ed..2cc3f0c58 100644 --- a/server/api/asset_group.go +++ b/server/api/asset_group.go @@ -32,9 +32,15 @@ func (api AssetGroupApi) GroupsSetEndpoint(c echo.Context) error { ctx := context.TODO() repository.AssetGroupRepository.DeleteByParentId(ctx, "") for i, item := range req { + name := "" + if v, ok := item["name"].(string); ok { + name = v + } else if v, ok := item["title"].(string); ok { + name = v + } group := model.AssetGroup{ ID: utils.UUID(), - Name: item["name"].(string), + Name: name, ParentId: "", Sort: i, Created: time.Now().UnixMilli(), @@ -58,9 +64,15 @@ func (api AssetGroupApi) GroupsDeleteEndpoint(c echo.Context) error { func saveChildren(ctx context.Context, children []interface{}, parentId string) { for i, item := range children { m := item.(map[string]interface{}) + name := "" + if v, ok := m["name"].(string); ok { + name = v + } else if v, ok := m["title"].(string); ok { + name = v + } group := model.AssetGroup{ ID: utils.UUID(), - Name: m["name"].(string), + Name: name, ParentId: parentId, Sort: i, Created: time.Now().UnixMilli(), @@ -109,27 +121,15 @@ func (api AssetGroupApi) TreeEndpoint(c echo.Context) error { func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId string) []maps.Map { var nodes []maps.Map - var groupAssets []model.Asset - for _, a := range assets { - groupAssets = append(groupAssets, a) - } - for _, a := range groupAssets { - nodes = append(nodes, maps.Map{ - "id": a.ID, - "name": a.Name, - "key": a.ID, - "isLeaf": true, - "protocol": a.Protocol, - "ip": a.IP, - "port": a.Port, - }) - } + for _, g := range groups { if g.ParentId == groupId { node := maps.Map{ - "id": g.ID, - "name": g.Name, - "key": g.ID, + "id": g.ID, + "name": g.Name, + "key": g.ID, + "title": g.Name, + "value": g.ID, } children := buildAssetTree(assets, groups, g.ID) if len(children) > 0 { @@ -138,6 +138,25 @@ func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId str nodes = append(nodes, node) } } + + if groupId == "" { + for _, a := range assets { + nodes = append(nodes, maps.Map{ + "id": a.ID, + "name": a.Name, + "key": a.ID, + "title": a.Name, + "value": a.ID, + "isLeaf": true, + "protocol": a.Protocol, + "ip": a.IP, + "port": a.Port, + "extra": maps.Map{ + "network": a.IP + ":" + strconv.Itoa(a.Port), + }, + }) + } + } return nodes } @@ -186,7 +205,7 @@ func (api AssetGroupApi) ChangeGatewayEndpoint(c echo.Context) error { func (api AssetGroupApi) SortEndpoint(c echo.Context) error { var req struct { - Id string `json:"id"` + Id string `json:"id"` BeforeId string `json:"beforeId"` AfterId string `json:"afterId"` } diff --git a/server/api/certificate_api.go b/server/api/certificate_api.go index c21ca0924..4ca89d87c 100644 --- a/server/api/certificate_api.go +++ b/server/api/certificate_api.go @@ -2,8 +2,12 @@ package api import ( "context" + "crypto/rand" + "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" + "math/big" "strconv" "time" @@ -65,12 +69,49 @@ func parseCertificate(certPEM string) (commonName, subject, issuer string, notBe return commonName, subject, issuer, notBefore, notAfter, nil } +func generateSelfSignedCertificate(commonName string) (certPEM, keyPEM string, notBefore, notAfter time.Time, err error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "", "", time.Time{}, time.Time{}, err + } + + notBefore = time.Now() + notAfter = notBefore.Add(365 * 24 * time.Hour) + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return "", "", time.Time{}, time.Time{}, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: commonName, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return "", "", time.Time{}, time.Time{}, err + } + + certPEM = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})) + keyPEM = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})) + + return certPEM, keyPEM, notBefore, notAfter, nil +} + func (api CertificateApi) CreateEndpoint(c echo.Context) error { var req struct { - CommonName string `json:"commonName"` - Certificate string `json:"certificate"` - PrivateKey string `json:"privateKey"` - Type string `json:"type"` + CommonName string `json:"commonName"` + Certificate string `json:"certificate"` + PrivateKey string `json:"privateKey"` + Type string `json:"type"` RequireClientAuth bool `json:"requireClientAuth"` } if err := c.Bind(&req); err != nil { @@ -78,18 +119,32 @@ func (api CertificateApi) CreateEndpoint(c echo.Context) error { } item := &model.Certificate{ - ID: utils.UUID(), - CommonName: req.CommonName, - Certificate: req.Certificate, - PrivateKey: req.PrivateKey, - Type: req.Type, + ID: utils.UUID(), + CommonName: req.CommonName, + Certificate: req.Certificate, + PrivateKey: req.PrivateKey, + Type: req.Type, RequireClientAuth: req.RequireClientAuth, - IssuedStatus: "success", - Created: common.NowJsonTime(), - UpdatedAt: common.NowJsonTime(), + IssuedStatus: "success", + Created: common.NowJsonTime(), + UpdatedAt: common.NowJsonTime(), } - if req.Certificate != "" { + if item.Type == "" { + item.Type = "imported" + } + + if req.Type == "self-signed" && req.Certificate == "" { + certPEM, keyPEM, notBefore, notAfter, err := generateSelfSignedCertificate(req.CommonName) + if err == nil { + item.Certificate = certPEM + item.PrivateKey = keyPEM + item.NotBefore = common.NewJsonTime(notBefore) + item.NotAfter = common.NewJsonTime(notAfter) + item.Subject = "CN=" + req.CommonName + item.Issuer = "CN=" + req.CommonName + } + } else if req.Certificate != "" { commonName, subject, issuer, notBefore, notAfter, err := parseCertificate(req.Certificate) if err == nil { if item.CommonName == "" { @@ -102,10 +157,6 @@ func (api CertificateApi) CreateEndpoint(c echo.Context) error { } } - if item.Type == "" { - item.Type = "imported" - } - if err := repository.CertificateRepository.Create(context.TODO(), item); err != nil { return err } @@ -115,9 +166,9 @@ func (api CertificateApi) CreateEndpoint(c echo.Context) error { func (api CertificateApi) UpdateEndpoint(c echo.Context) error { id := c.Param("id") var req struct { - CommonName string `json:"commonName"` - Certificate string `json:"certificate"` - PrivateKey string `json:"privateKey"` + CommonName string `json:"commonName"` + Certificate string `json:"certificate"` + PrivateKey string `json:"privateKey"` RequireClientAuth bool `json:"requireClientAuth"` } if err := c.Bind(&req); err != nil { @@ -125,12 +176,12 @@ func (api CertificateApi) UpdateEndpoint(c echo.Context) error { } item := &model.Certificate{ - ID: id, - CommonName: req.CommonName, - Certificate: req.Certificate, - PrivateKey: req.PrivateKey, + ID: id, + CommonName: req.CommonName, + Certificate: req.Certificate, + PrivateKey: req.PrivateKey, RequireClientAuth: req.RequireClientAuth, - UpdatedAt: common.NowJsonTime(), + UpdatedAt: common.NowJsonTime(), } if req.Certificate != "" { diff --git a/server/api/command_filter.go b/server/api/command_filter.go index 6252f7bf5..b56293479 100644 --- a/server/api/command_filter.go +++ b/server/api/command_filter.go @@ -1,7 +1,12 @@ package api import ( + "context" + "strconv" + "next-terminal/server/common/maps" + "next-terminal/server/model" + "next-terminal/server/repository" "next-terminal/server/utils" "github.com/labstack/echo/v4" @@ -10,59 +15,150 @@ import ( type CommandFilterApi struct{} func (api CommandFilterApi) AllEndpoint(c echo.Context) error { - return Success(c, []interface{}{}) + items, err := repository.CommandFilterRepository.FindAll(context.TODO()) + if err != nil { + return err + } + return Success(c, items) } func (api CommandFilterApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + name := c.QueryParam("name") + order := c.QueryParam("order") + field := c.QueryParam("field") + + items, total, err := repository.CommandFilterRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field) + if err != nil { + return err + } + return Success(c, maps.Map{ - "items": []interface{}{}, - "total": 0, + "items": items, + "total": total, }) } func (api CommandFilterApi) CreateEndpoint(c echo.Context) error { - var item map[string]interface{} + var item model.CommandFilter if err := c.Bind(&item); err != nil { return err } - item["id"] = utils.LongUUID() + item.ID = utils.UUID() + + if err := repository.CommandFilterRepository.Create(context.TODO(), &item); err != nil { + return err + } + return Success(c, item) } func (api CommandFilterApi) UpdateEndpoint(c echo.Context) error { id := c.Param("id") - var item map[string]interface{} + var item model.CommandFilter if err := c.Bind(&item); err != nil { return err } - item["id"] = id + item.ID = id + + if err := repository.CommandFilterRepository.UpdateById(context.TODO(), &item); err != nil { + return err + } + return Success(c, item) } func (api CommandFilterApi) DeleteEndpoint(c echo.Context) error { id := c.Param("id") - _ = id + if err := repository.CommandFilterRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api CommandFilterApi) GetEndpoint(c echo.Context) error { id := c.Param("id") - _ = id - return Success(c, maps.Map{ - "id": id, - "name": "Default Filter", - "createdAt": 1700000000000, - }) + item, err := repository.CommandFilterRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + return Success(c, item) } func (api CommandFilterApi) BindEndpoint(c echo.Context) error { - id := c.Param("id") - _ = id return Success(c, nil) } func (api CommandFilterApi) UnbindEndpoint(c echo.Context) error { - id := c.Param("id") - _ = id return Success(c, nil) } + +type CommandFilterRuleApi struct{} + +func (api CommandFilterRuleApi) AllEndpoint(c echo.Context) error { + commandFilterId := c.QueryParam("commandFilterId") + items, err := repository.CommandFilterRuleRepository.FindByCommandFilterId(context.TODO(), commandFilterId) + if err != nil { + return err + } + return Success(c, items) +} + +func (api CommandFilterRuleApi) PagingEndpoint(c echo.Context) error { + commandFilterId := c.QueryParam("commandFilterId") + items, err := repository.CommandFilterRuleRepository.FindByCommandFilterId(context.TODO(), commandFilterId) + if err != nil { + return err + } + return Success(c, maps.Map{ + "items": items, + "total": len(items), + }) +} + +func (api CommandFilterRuleApi) CreateEndpoint(c echo.Context) error { + var item model.CommandFilterRule + if err := c.Bind(&item); err != nil { + return err + } + item.ID = utils.UUID() + + if err := repository.CommandFilterRuleRepository.Create(context.TODO(), &item); err != nil { + return err + } + + return Success(c, item) +} + +func (api CommandFilterRuleApi) UpdateEndpoint(c echo.Context) error { + id := c.Param("id") + var item model.CommandFilterRule + if err := c.Bind(&item); err != nil { + return err + } + item.ID = id + + if err := repository.CommandFilterRuleRepository.UpdateById(context.TODO(), &item); err != nil { + return err + } + + return Success(c, item) +} + +func (api CommandFilterRuleApi) DeleteEndpoint(c echo.Context) error { + id := c.Param("id") + if err := repository.CommandFilterRuleRepository.DeleteById(context.TODO(), id); err != nil { + return err + } + return Success(c, nil) +} + +func (api CommandFilterRuleApi) GetEndpoint(c echo.Context) error { + id := c.Param("id") + item, err := repository.CommandFilterRuleRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + return Success(c, item) +} diff --git a/server/api/dashboard.go b/server/api/dashboard.go index 432ba9850..4695b3ad0 100644 --- a/server/api/dashboard.go +++ b/server/api/dashboard.go @@ -13,12 +13,13 @@ type DashboardApi struct{} func (api DashboardApi) GetTimeCounterEndpoint(c echo.Context) error { var ( - totalUser int64 - onlineUser int64 - countOnlineSession int64 - totalAsset int64 - activeAsset int64 - failLoginCount int64 + totalUser int64 + onlineUser int64 + countOnlineSession int64 + totalAsset int64 + activeAsset int64 + failLoginCount int64 + totalWebsite int64 ) totalUser, _ = repository.UserRepository.Count(context.TODO()) @@ -29,6 +30,7 @@ func (api DashboardApi) GetTimeCounterEndpoint(c echo.Context) error { failLoginCount, _ = repository.LoginLogRepository.CountByState(context.TODO(), "0") gatewayList, _ := repository.GatewayRepository.FindAll(context.TODO()) gatewayActiveCount := int64(len(gatewayList)) + totalWebsite, _ = repository.WebsiteRepository.Count(context.TODO()) counter := map[string]interface{}{ "loginFailedTimes": failLoginCount, @@ -39,7 +41,7 @@ func (api DashboardApi) GetTimeCounterEndpoint(c echo.Context) error { "assetActiveCount": activeAsset, "assetTotalCount": totalAsset, "websiteActiveCount": 0, - "websiteTotalCount": 0, + "websiteTotalCount": totalWebsite, "gatewayActiveCount": gatewayActiveCount, "gatewayTotalCount": gatewayActiveCount, } @@ -74,7 +76,7 @@ func (api DashboardApi) GetDateCounterV2Endpoint(c echo.Context) error { day := lastDate.AddDate(0, 0, i).Format("2006-01-02") item := map[string]interface{}{ - "date": day, + "date": day, "login": int64(0), "user": int64(0), "asset": int64(0), diff --git a/server/api/database_asset.go b/server/api/database_asset.go index 6292340f9..b3b615abb 100644 --- a/server/api/database_asset.go +++ b/server/api/database_asset.go @@ -1,7 +1,14 @@ package api import ( + "context" + "strconv" + "strings" + + "next-terminal/server/common" "next-terminal/server/common/maps" + "next-terminal/server/model" + "next-terminal/server/repository" "next-terminal/server/utils" "github.com/labstack/echo/v4" @@ -10,65 +17,161 @@ import ( type DatabaseAssetApi struct{} func (api DatabaseAssetApi) AllEndpoint(c echo.Context) error { - return Success(c, []interface{}{}) + dbType := c.QueryParam("type") + items, err := repository.DatabaseAssetRepository.FindByType(context.TODO(), dbType) + if err != nil { + return err + } + return Success(c, items) } func (api DatabaseAssetApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + name := c.QueryParam("keyword") + dbType := c.QueryParam("type") + order := c.QueryParam("order") + field := c.QueryParam("field") + + items, total, err := repository.DatabaseAssetRepository.Find(context.TODO(), pageIndex, pageSize, name, dbType, order, field) + if err != nil { + return err + } + return Success(c, maps.Map{ - "items": []interface{}{}, - "total": 0, + "items": items, + "total": total, }) } func (api DatabaseAssetApi) CreateEndpoint(c echo.Context) error { - var item map[string]interface{} - if err := c.Bind(&item); err != nil { + var req struct { + Name string `json:"name"` + Type string `json:"type"` + Host string `json:"host"` + Port int `json:"port"` + Database string `json:"database"` + Username string `json:"username"` + Password string `json:"password"` + Description string `json:"description"` + GatewayType string `json:"gatewayType"` + GatewayId string `json:"gatewayId"` + Tags []string `json:"tags"` + } + if err := c.Bind(&req); err != nil { return err } - item["id"] = utils.LongUUID() + + account, _ := GetCurrentAccount(c) + + item := &model.DatabaseAsset{ + ID: utils.UUID(), + Name: req.Name, + Type: req.Type, + Host: req.Host, + Port: req.Port, + Database: req.Database, + Username: req.Username, + Password: req.Password, + Description: req.Description, + GatewayType: req.GatewayType, + GatewayId: req.GatewayId, + Tags: strings.Join(req.Tags, ","), + Owner: account.ID, + Created: common.NowJsonTime(), + Updated: common.NowJsonTime(), + } + + if item.Port == 0 { + item.Port = repository.DatabaseAssetRepository.GetDBPort(item.Type) + } + + if err := repository.DatabaseAssetRepository.Create(context.TODO(), item); err != nil { + return err + } + return Success(c, item) } func (api DatabaseAssetApi) UpdateEndpoint(c echo.Context) error { id := c.Param("id") - var item map[string]interface{} - if err := c.Bind(&item); err != nil { + var req struct { + Name string `json:"name"` + Type string `json:"type"` + Host string `json:"host"` + Port int `json:"port"` + Database string `json:"database"` + Username string `json:"username"` + Password string `json:"password"` + Description string `json:"description"` + GatewayType string `json:"gatewayType"` + GatewayId string `json:"gatewayId"` + Tags []string `json:"tags"` + } + if err := c.Bind(&req); err != nil { return err } - item["id"] = id - return Success(c, item) + + existing, err := repository.DatabaseAssetRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + + existing.Name = req.Name + existing.Type = req.Type + existing.Host = req.Host + existing.Port = req.Port + existing.Database = req.Database + existing.Username = req.Username + if req.Password != "" { + existing.Password = req.Password + } + existing.Description = req.Description + existing.GatewayType = req.GatewayType + existing.GatewayId = req.GatewayId + existing.Tags = strings.Join(req.Tags, ",") + existing.Updated = common.NowJsonTime() + + if err := repository.DatabaseAssetRepository.UpdateById(context.TODO(), &existing); err != nil { + return err + } + + return Success(c, existing) } func (api DatabaseAssetApi) DeleteEndpoint(c echo.Context) error { id := c.Param("id") - _ = id + if err := repository.DatabaseAssetRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api DatabaseAssetApi) GetEndpoint(c echo.Context) error { id := c.Param("id") - _ = id - return Success(c, maps.Map{ - "id": id, - "name": "MySQL Server", - "type": "mysql", - "host": "localhost", - "port": 3306, - "database": "test_db", - "username": "root", - "description": "Test database", - "status": "active", - "statusText": "Active", - "createdAt": 1700000000000, - "updatedAt": 1700000000000, - }) + item, err := repository.DatabaseAssetRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + return Success(c, item) } func (api DatabaseAssetApi) DecryptEndpoint(c echo.Context) error { id := c.Param("id") - _ = id + item, err := repository.DatabaseAssetRepository.FindById(context.TODO(), id) + if err != nil { + return err + } return Success(c, maps.Map{ "id": id, - "password": "decrypted_password", + "password": item.Password, }) } + +func (api DatabaseAssetApi) TagsEndpoint(c echo.Context) error { + tags, err := repository.DatabaseAssetRepository.FindAllTags(context.TODO()) + if err != nil { + return err + } + return Success(c, tags) +} diff --git a/server/api/department.go b/server/api/department.go index 246bcb350..233b1c717 100644 --- a/server/api/department.go +++ b/server/api/department.go @@ -1,7 +1,12 @@ package api import ( + "context" + "strconv" + "next-terminal/server/common/maps" + "next-terminal/server/model" + "next-terminal/server/repository" "next-terminal/server/utils" "github.com/labstack/echo/v4" @@ -10,77 +15,150 @@ import ( type DepartmentApi struct{} func (api DepartmentApi) AllEndpoint(c echo.Context) error { - return Success(c, []interface{}{}) + items, err := repository.DepartmentRepository.FindAll(context.TODO()) + if err != nil { + return err + } + return Success(c, items) } func (api DepartmentApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + name := c.QueryParam("keyword") + order := c.QueryParam("order") + field := c.QueryParam("field") + + items, total, err := repository.DepartmentRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field) + if err != nil { + return err + } + + departmentMap := make(map[string]model.Department) + allDepts, _ := repository.DepartmentRepository.FindAll(context.TODO()) + for _, dept := range allDepts { + departmentMap[dept.ID] = dept + } + + for i := range items { + if items[i].ParentId != "" { + if parent, ok := departmentMap[items[i].ParentId]; ok { + items[i].ParentName = parent.Name + } + } + userCount, _ := repository.UserDepartmentRepository.CountByDepartmentId(context.TODO(), items[i].ID) + items[i].UserCount = userCount + } + return Success(c, maps.Map{ - "items": []interface{}{}, - "total": 0, + "items": items, + "total": total, }) } func (api DepartmentApi) CreateEndpoint(c echo.Context) error { - var item map[string]interface{} + var item model.Department if err := c.Bind(&item); err != nil { return err } - item["id"] = utils.LongUUID() + item.ID = utils.UUID() + + if err := repository.DepartmentRepository.Create(context.TODO(), &item); err != nil { + return err + } + return Success(c, item) } func (api DepartmentApi) UpdateEndpoint(c echo.Context) error { id := c.Param("id") - var item map[string]interface{} + var item model.Department if err := c.Bind(&item); err != nil { return err } - item["id"] = id + item.ID = id + + if err := repository.DepartmentRepository.UpdateById(context.TODO(), &item); err != nil { + return err + } + return Success(c, item) } func (api DepartmentApi) DeleteEndpoint(c echo.Context) error { id := c.Param("id") - _ = id + + count, err := repository.DepartmentRepository.CountByParentId(context.TODO(), id) + if err != nil { + return err + } + if count > 0 { + return Fail(c, -1, "该部门下存在子部门,无法删除") + } + + userCount, err := repository.UserDepartmentRepository.CountByDepartmentId(context.TODO(), id) + if err != nil { + return err + } + if userCount > 0 { + return Fail(c, -1, "该部门下存在用户,无法删除") + } + + if err := repository.DepartmentRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api DepartmentApi) GetEndpoint(c echo.Context) error { id := c.Param("id") - _ = id - return Success(c, maps.Map{ - "id": id, - "name": "Default", - "parentId": "", - }) + item, err := repository.DepartmentRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + return Success(c, item) } func (api DepartmentApi) GetTreeEndpoint(c echo.Context) error { - tree := []map[string]interface{}{ - { - "title": "Default", - "key": "default", - "value": "default", - "children": []interface{}{}, - }, + departments, err := repository.DepartmentRepository.FindAll(context.TODO()) + if err != nil { + return err } + tree := repository.DepartmentRepository.BuildTree(departments) return Success(c, tree) } func (api DepartmentApi) GetDepartmentUsersEndpoint(c echo.Context) error { departmentId := c.Param("id") - _ = departmentId - return Success(c, []string{}) + users, err := repository.UserDepartmentRepository.FindUsersByDepartmentId(context.TODO(), departmentId) + if err != nil { + return err + } + return Success(c, users) } func (api DepartmentApi) SetDepartmentUsersEndpoint(c echo.Context) error { departmentId := c.Param("id") - _ = departmentId + var userIds []string + if err := c.Bind(&userIds); err != nil { + return err + } + + if err := repository.UserDepartmentRepository.SaveDepartmentUsers(context.TODO(), departmentId, userIds); err != nil { + return err + } return Success(c, nil) } func (api DepartmentApi) RemoveUsersFromDepartmentEndpoint(c echo.Context) error { departmentId := c.Param("id") - _ = departmentId + var userIds []string + if err := c.Bind(&userIds); err != nil { + return err + } + + if err := repository.UserDepartmentRepository.RemoveUsersFromDepartment(context.TODO(), departmentId, userIds); err != nil { + return err + } return Success(c, nil) } diff --git a/server/api/gateway_group_api.go b/server/api/gateway_group_api.go index 1ddf7c353..6bf9dea57 100644 --- a/server/api/gateway_group_api.go +++ b/server/api/gateway_group_api.go @@ -4,6 +4,7 @@ import ( "context" "strconv" + "next-terminal/server/common" "next-terminal/server/common/maps" "next-terminal/server/model" "next-terminal/server/repository" @@ -43,6 +44,8 @@ func (api GatewayGroupApi) CreateEndpoint(c echo.Context) error { return err } item.ID = utils.UUID() + item.Created = common.NowJsonTime() + item.Updated = common.NowJsonTime() if err := repository.GatewayGroupRepository.Create(context.TODO(), &item); err != nil { return err @@ -56,6 +59,7 @@ func (api GatewayGroupApi) UpdateEndpoint(c echo.Context) error { if err := c.Bind(&item); err != nil { return err } + item.Updated = common.NowJsonTime() if err := repository.GatewayGroupRepository.UpdateById(context.TODO(), &item, id); err != nil { return err } diff --git a/server/api/login_policy.go b/server/api/login_policy.go index e9b7df696..f3c1c9d9e 100644 --- a/server/api/login_policy.go +++ b/server/api/login_policy.go @@ -93,7 +93,7 @@ func (api LoginPolicyApi) GetUserPageEndpoint(c echo.Context) error { order := c.QueryParam("order") field := c.QueryParam("field") - items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, "", id, order, field) + items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, "", "", id, order, field) if err != nil { return err } diff --git a/server/api/logo.go b/server/api/logo.go index b731ab975..270a40639 100644 --- a/server/api/logo.go +++ b/server/api/logo.go @@ -5,10 +5,10 @@ import ( "encoding/base64" "strings" - "next-terminal/server/model" - "next-terminal/server/repository" "github.com/google/uuid" "github.com/labstack/echo/v4" + "next-terminal/server/model" + "next-terminal/server/repository" ) type LogoApi struct{} diff --git a/server/api/portal_api.go b/server/api/portal_api.go index 314553731..6d21f0a25 100644 --- a/server/api/portal_api.go +++ b/server/api/portal_api.go @@ -65,9 +65,9 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error { tree := make([]maps.Map, 0) for _, g := range groups { node := maps.Map{ - "key": g.ID, - "title": g.Name, - "isLeaf": false, + "key": g.ID, + "title": g.Name, + "isLeaf": false, "children": buildPortalAssetChildren(items, g.ID, keyword), } tree = append(tree, node) @@ -159,9 +159,9 @@ func (api PortalApi) AssetsGroupTreeEndpoint(c echo.Context) error { tree := make([]maps.Map, 0) for _, g := range groups { node := maps.Map{ - "key": g.ID, - "title": g.Name, - "isLeaf": false, + "key": g.ID, + "title": g.Name, + "isLeaf": false, "children": []interface{}{}, } tree = append(tree, node) @@ -198,34 +198,34 @@ func (api PortalApi) CreateSessionEndpoint(c echo.Context) error { } return Success(c, maps.Map{ - "id": s.ID, - "protocol": s.Protocol, - "assetName": assetName, - "strategy": maps.Map{}, - "url": "", - "watermark": maps.Map{}, - "readonly": false, - "idle": 3600, - "fileSystem": s.FileSystem == "1", - "width": 800, - "height": 600, + "id": s.ID, + "protocol": s.Protocol, + "assetName": assetName, + "strategy": maps.Map{}, + "url": "", + "watermark": maps.Map{}, + "readonly": false, + "idle": 3600, + "fileSystem": s.FileSystem == "1", + "width": 800, + "height": 600, }) } func (api PortalApi) GetSessionEndpoint(c echo.Context) error { id := c.Param("id") return Success(c, maps.Map{ - "id": id, - "protocol": "ssh", - "assetName": id, - "strategy": maps.Map{}, - "url": "", - "watermark": maps.Map{}, - "readonly": false, - "idle": 3600, - "fileSystem": false, - "width": 800, - "height": 600, + "id": id, + "protocol": "ssh", + "assetName": id, + "strategy": maps.Map{}, + "url": "", + "watermark": maps.Map{}, + "readonly": false, + "idle": 3600, + "fileSystem": false, + "width": 800, + "height": 600, }) } @@ -233,7 +233,7 @@ func (api PortalApi) GetShareEndpoint(c echo.Context) error { return Success(c, maps.Map{ "enabled": false, "passcode": "", - "url": "", + "url": "", }) } @@ -241,7 +241,7 @@ func (api PortalApi) CreateShareEndpoint(c echo.Context) error { return Success(c, maps.Map{ "enabled": true, "passcode": "", - "url": "", + "url": "", }) } @@ -277,16 +277,16 @@ func (api PortalApi) PingAssetEndpoint(c echo.Context) error { assetId := c.Param("id") item, _ := repository.AssetRepository.FindById(context.TODO(), assetId) return Success(c, maps.Map{ - "name": item.Name, - "active": true, - "usedTime": 10, + "name": item.Name, + "active": true, + "usedTime": 10, "usedTimeStr": "10ms", }) } func (api PortalApi) TerminalStatsEndpoint(c echo.Context) error { sessionId := c.Param("id") - + sess := session.GlobalSessionManager.GetById(sessionId) if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil { return Success(c, maps.Map{ @@ -321,7 +321,7 @@ func (api PortalApi) TerminalStatsEndpoint(c echo.Context) error { "cpu": []interface{}{}, }) } - + stats, err := getSystemStats(sess.NextTerminal.SshClient) if err != nil { log.Warn("Failed to get system stats", log.NamedError("error", err)) @@ -357,30 +357,30 @@ func (api PortalApi) TerminalStatsEndpoint(c echo.Context) error { "cpu": []interface{}{}, }) } - + return Success(c, stats) } func getSystemStats(client *ssh.Client) (maps.Map, error) { result := maps.Map{} - + // Get hostname hostname, _ := sshExec(client, "hostname") - + // Get OS info osId, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'") osName, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^NAME=' | cut -d'=' -f2 | tr -d '\"'") osVersion, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^VERSION_ID=' | cut -d'=' -f2 | tr -d '\"'") - + // Get arch arch, _ := sshExec(client, "uname -m") - + // Get uptime uptimeStr, _ := sshExec(client, "cat /proc/uptime | awk '{print $1}'") uptimeFloat, _ := strconv.ParseFloat(strings.TrimSpace(uptimeStr), 64) uptime := int64(uptimeFloat) upDays := uptime / 86400 - + // Get load average loadStr, _ := sshExec(client, "cat /proc/loadavg") loadParts := strings.Fields(loadStr) @@ -390,28 +390,28 @@ func getSystemStats(client *ssh.Client) (maps.Map, error) { load5 = loadParts[1] load15 = loadParts[2] } - + // Get process count processStr, _ := sshExec(client, "ps aux | wc -l") totalProcess := strings.TrimSpace(processStr) - + // Get memory info memInfo, _ := sshExec(client, "cat /proc/meminfo") memTotal, memFree, memAvailable, memBuffers, memCached, swapTotal, swapFree := parseMemInfo(memInfo) memUsed := memTotal - memAvailable - + // Get CPU info cpuInfo, _ := sshExec(client, "cat /proc/stat | head -1") cpuStats := parseCpuStat(cpuInfo) - + // Get filesystem info dfOutput, _ := sshExec(client, "df -B1 | tail -n +2") fileSystems := parseDfOutput(dfOutput) - + // Get network info netDev, _ := sshExec(client, "cat /proc/net/dev | tail -n +3") network := parseNetDev(netDev) - + result["info"] = maps.Map{ "id": strings.TrimSpace(osId), "name": strings.TrimSpace(osName), @@ -441,7 +441,7 @@ func getSystemStats(client *ssh.Client) (maps.Map, error) { result["cpu"] = cpuStats result["fileSystems"] = fileSystems result["network"] = network - + return result, nil } @@ -451,7 +451,7 @@ func sshExec(client *ssh.Client, cmd string) (string, error) { return "", err } defer session.Close() - + output, err := session.CombinedOutput(cmd) if err != nil { return "", err @@ -468,7 +468,7 @@ func parseMemInfo(info string) (total, free, available, buffers, cached, swapTot } key := strings.TrimSuffix(parts[0], ":") value, _ := strconv.ParseInt(parts[1], 10, 64) - + switch key { case "MemTotal": total = value @@ -494,11 +494,11 @@ func parseCpuStat(stat string) []interface{} { if len(parts) < 5 { return []interface{}{maps.Map{"user": 0.0, "nice": 0.0, "system": 0.0}} } - + user, _ := strconv.ParseFloat(parts[1], 64) nice, _ := strconv.ParseFloat(parts[2], 64) system, _ := strconv.ParseFloat(parts[3], 64) - + return []interface{}{ maps.Map{ "user": user, @@ -519,16 +519,16 @@ func parseDfOutput(output string) []interface{} { if len(parts) < 6 { continue } - + total, _ := strconv.ParseInt(parts[1], 10, 64) used, _ := strconv.ParseInt(parts[2], 10, 64) free, _ := strconv.ParseInt(parts[3], 10, 64) - + var percent float64 = 0 if total > 0 { percent = float64(used) / float64(total) } - + result = append(result, maps.Map{ "mountPoint": parts[5], "total": total, @@ -551,15 +551,15 @@ func parseNetDev(output string) []interface{} { if len(parts) < 10 { continue } - + iface := strings.TrimSuffix(parts[0], ":") if strings.HasPrefix(iface, "lo") { continue } - + rx, _ := strconv.ParseInt(parts[1], 10, 64) tx, _ := strconv.ParseInt(parts[9], 10, 64) - + result = append(result, maps.Map{ "iface": iface, "rx": rx, diff --git a/server/api/property.go b/server/api/property.go index 05b876161..7aa50c265 100644 --- a/server/api/property.go +++ b/server/api/property.go @@ -4,8 +4,8 @@ import ( "context" "crypto/rand" "crypto/rsa" - "encoding/pem" "crypto/x509" + "encoding/pem" "next-terminal/server/repository" "next-terminal/server/service" @@ -57,8 +57,8 @@ func (api PropertyApi) SendMailEndpoint(c echo.Context) error { func (api PropertyApi) ClientIPsEndpoint(c echo.Context) error { return Success(c, map[string]string{ - "direct": c.RealIP(), - "x-real-ip": c.Request().Header.Get("X-Real-IP"), + "direct": c.RealIP(), + "x-real-ip": c.Request().Header.Get("X-Real-IP"), "x-forwarded-for": c.Request().Header.Get("X-Forwarded-For"), }) } diff --git a/server/api/scheduled_task_api.go b/server/api/scheduled_task_api.go new file mode 100644 index 000000000..b4356951e --- /dev/null +++ b/server/api/scheduled_task_api.go @@ -0,0 +1,339 @@ +package api + +import ( + "context" + "encoding/json" + "strconv" + "strings" + + "next-terminal/server/common" + "next-terminal/server/common/maps" + "next-terminal/server/log" + "next-terminal/server/model" + "next-terminal/server/repository" + "next-terminal/server/service" + "next-terminal/server/utils" + + "github.com/labstack/echo/v4" + "github.com/robfig/cron/v3" +) + +type ScheduledTaskApi struct{} + +type ScheduledTaskDTO struct { + ID string `json:"id"` + EntryId int `json:"entryId"` + Name string `json:"name"` + Spec string `json:"spec"` + Type string `json:"type"` + AssetIdList []string `json:"assetIdList"` + Mode string `json:"mode"` + Script string `json:"script"` + Enabled bool `json:"enabled"` + CreatedAt common.JsonTime `json:"createdAt"` + UpdatedAt common.JsonTime `json:"updatedAt"` + LastExecAt *common.JsonTime `json:"lastExecAt,omitempty"` +} + +type JobLogDTO struct { + ID string `json:"id"` + JobId string `json:"jobId"` + JobType string `json:"jobType"` + Results []interface{} `json:"results"` + CreatedAt common.JsonTime `json:"createdAt"` +} + +func JobLogToDTO(log model.JobLog, jobType string) JobLogDTO { + var results []interface{} + if log.Results != "" { + json.Unmarshal([]byte(log.Results), &results) + } + if results == nil { + results = []interface{}{} + } + return JobLogDTO{ + ID: log.ID, + JobId: log.JobId, + JobType: jobType, + Results: results, + CreatedAt: log.Timestamp, + } +} + +func JobToDTO(job model.Job) ScheduledTaskDTO { + dto := ScheduledTaskDTO{ + ID: job.ID, + EntryId: job.CronJobId, + Name: job.Name, + Spec: job.Cron, + Type: job.Func, + Mode: job.Mode, + Enabled: job.Status == "enabled", + CreatedAt: job.Created, + UpdatedAt: job.Updated, + } + if job.ResourceIds != "" { + dto.AssetIdList = strings.Split(job.ResourceIds, ",") + } + if job.Metadata != "" && job.Func == "asset-exec-command" { + var metadataShell struct { + Shell string `json:"shell"` + } + if err := json.Unmarshal([]byte(job.Metadata), &metadataShell); err == nil { + dto.Script = metadataShell.Shell + } else { + dto.Script = job.Metadata + } + } else { + dto.Script = job.Metadata + } + if !job.LastExecAt.IsZero() { + dto.LastExecAt = &job.LastExecAt + } + return dto +} + +func DTOToJob(dto ScheduledTaskDTO) model.Job { + status := "disabled" + if dto.Enabled { + status = "enabled" + } + metadata := dto.Script + if dto.Type == "asset-exec-command" && dto.Script != "" { + metadataJSON, _ := json.Marshal(map[string]string{"shell": dto.Script}) + metadata = string(metadataJSON) + } + return model.Job{ + ID: dto.ID, + CronJobId: dto.EntryId, + Name: dto.Name, + Cron: dto.Spec, + Func: dto.Type, + ResourceIds: strings.Join(dto.AssetIdList, ","), + Mode: dto.Mode, + Metadata: metadata, + Status: status, + } +} + +func (api ScheduledTaskApi) AllEndpoint(c echo.Context) error { + items, err := repository.JobRepository.FindAll(context.TODO()) + if err != nil { + return err + } + var dtos []ScheduledTaskDTO + for _, item := range items { + dtos = append(dtos, JobToDTO(item)) + } + if dtos == nil { + dtos = []ScheduledTaskDTO{} + } + return Success(c, dtos) +} + +func (api ScheduledTaskApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + name := c.QueryParam("name") + status := c.QueryParam("status") + order := c.QueryParam("order") + field := c.QueryParam("field") + + items, total, err := repository.JobRepository.Find(context.TODO(), pageIndex, pageSize, name, status, order, field) + if err != nil { + return err + } + var dtos []ScheduledTaskDTO + for _, item := range items { + dtos = append(dtos, JobToDTO(item)) + } + if dtos == nil { + dtos = []ScheduledTaskDTO{} + } + return Success(c, maps.Map{ + "total": total, + "items": dtos, + }) +} + +func (api ScheduledTaskApi) CreateEndpoint(c echo.Context) error { + var dto ScheduledTaskDTO + if err := c.Bind(&dto); err != nil { + return err + } + log.Info("Create ScheduledTask", log.Any("dto", dto)) + job := DTOToJob(dto) + job.ID = utils.UUID() + job.Created = common.NowJsonTime() + job.Updated = common.NowJsonTime() + if job.Status == "" { + job.Status = "disabled" + } + + if err := repository.JobRepository.Create(context.TODO(), &job); err != nil { + return err + } + return Success(c, job.ID) +} + +func (api ScheduledTaskApi) UpdateEndpoint(c echo.Context) error { + id := c.Param("id") + var dto ScheduledTaskDTO + if err := c.Bind(&dto); err != nil { + return err + } + job := DTOToJob(dto) + job.ID = id + job.Updated = common.NowJsonTime() + if err := repository.JobRepository.UpdateById(context.TODO(), &job); err != nil { + return err + } + return Success(c, nil) +} + +func (api ScheduledTaskApi) DeleteEndpoint(c echo.Context) error { + id := c.Param("id") + if err := repository.JobRepository.DeleteJobById(context.TODO(), id); err != nil { + return err + } + return Success(c, nil) +} + +func (api ScheduledTaskApi) GetEndpoint(c echo.Context) error { + id := c.Param("id") + item, err := repository.JobRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + dto := JobToDTO(item) + log.Info("Get ScheduledTask", log.Any("dto", dto)) + return Success(c, dto) +} + +func (api ScheduledTaskApi) ChangeStatusEndpoint(c echo.Context) error { + id := c.Param("id") + enabled := c.QueryParam("enabled") == "true" + var status string + if enabled { + status = "enabled" + } else { + status = "disabled" + } + job := model.Job{ + ID: id, + Status: status, + } + if err := repository.JobRepository.UpdateById(context.TODO(), &job); err != nil { + return err + } + return Success(c, nil) +} + +func (api ScheduledTaskApi) ExecEndpoint(c echo.Context) error { + id := c.Param("id") + job, err := repository.JobRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + + now := common.NowJsonTime() + jobUpdate := model.Job{ + ID: id, + LastExecAt: now, + } + if err := repository.JobRepository.UpdateById(context.TODO(), &jobUpdate); err != nil { + return err + } + + switch job.Func { + case "asset-exec-command": + shellJob := service.ShellJob{ + ID: job.ID, + Mode: job.Mode, + ResourceIds: job.ResourceIds, + Metadata: job.Metadata, + } + shellJob.Run() + default: + jobLog := &model.JobLog{ + ID: utils.UUID(), + JobId: id, + Timestamp: now, + Message: "任务执行成功", + } + _ = repository.JobLogRepository.Create(context.TODO(), jobLog) + } + + return Success(c, nil) +} + +func (api ScheduledTaskApi) GetLogsEndpoint(c echo.Context) error { + jobId := c.Param("id") + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + if pageIndex == 0 { + pageIndex = 1 + } + if pageSize == 0 { + pageSize = 10 + } + + job, err := repository.JobRepository.FindById(context.TODO(), jobId) + if err != nil { + return err + } + + items, total, err := repository.JobLogRepository.FindByJobId(context.TODO(), jobId, pageIndex, pageSize) + if err != nil { + return err + } + + var dtos []JobLogDTO + for _, item := range items { + dtos = append(dtos, JobLogToDTO(item, job.Func)) + } + if dtos == nil { + dtos = []JobLogDTO{} + } + + return Success(c, maps.Map{ + "total": total, + "items": dtos, + }) +} + +func (api ScheduledTaskApi) DeleteLogsEndpoint(c echo.Context) error { + jobId := c.Param("id") + if err := repository.JobLogRepository.DeleteByJobId(context.TODO(), jobId); err != nil { + return err + } + return Success(c, nil) +} + +func (api ScheduledTaskApi) NextTenRunsEndpoint(c echo.Context) error { + var req struct { + Spec string `json:"spec"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var results []string + if req.Spec == "" { + return Success(c, results) + } + + parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) + schedule, err := parser.Parse(req.Spec) + if err != nil { + return Success(c, results) + } + + now := common.NowJsonTime().Time + for i := 0; i < 10; i++ { + next := schedule.Next(now) + results = append(results, next.Format("2006-01-02 15:04:05")) + } + + return Success(c, results) +} diff --git a/server/api/session.go b/server/api/session.go index ec3dab457..38a23a922 100644 --- a/server/api/session.go +++ b/server/api/session.go @@ -634,12 +634,12 @@ func (api SessionApi) SessionTriggerAuditEndpoint(c echo.Context) error { sessionId := c.Param("id") audit := &model.SessionAudit{ - ID: utils.UUID(), + ID: utils.UUID(), SessionId: sessionId, - Status: "completed", - Content: "Audit completed. No issues found.", - Created: common.NowJsonTime(), - Updated: common.NowJsonTime(), + Status: "completed", + Content: "Audit completed. No issues found.", + Created: common.NowJsonTime(), + Updated: common.NowJsonTime(), } if err := repository.SessionAuditRepository.Upsert(context.TODO(), audit); err != nil { diff --git a/server/api/setup.go b/server/api/setup.go index 88f5e6edc..d768444e1 100644 --- a/server/api/setup.go +++ b/server/api/setup.go @@ -131,11 +131,11 @@ func (api SetupApi) ValidateTOTPEndpoint(c echo.Context) error { func (api SetupApi) PasswordPolicyEndpoint(c echo.Context) error { return Success(c, map[string]interface{}{ - "minLength": 6, - "minCharacterType": 0, + "minLength": 6, + "minCharacterType": 0, "mustNotContainUsername": false, - "mustNotBePalindrome": false, - "mustNotWeek": false, + "mustNotBePalindrome": false, + "mustNotWeek": false, }) } diff --git a/server/api/stub_apis.go b/server/api/stub_apis.go index 38c989cd7..4688c773f 100644 --- a/server/api/stub_apis.go +++ b/server/api/stub_apis.go @@ -3,71 +3,10 @@ package api import ( "context" "next-terminal/server/common/maps" - "strconv" "github.com/labstack/echo/v4" ) -type ScheduledTaskApi struct{} - -func (api ScheduledTaskApi) AllEndpoint(c echo.Context) error { - return Success(c, []interface{}{}) -} - -func (api ScheduledTaskApi) PagingEndpoint(c echo.Context) error { - pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) - pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) - if pageIndex == 0 { - pageIndex = 1 - } - if pageSize == 0 { - pageSize = 10 - } - return Success(c, maps.Map{ - "total": 0, - "items": []interface{}{}, - }) -} - -func (api ScheduledTaskApi) CreateEndpoint(c echo.Context) error { - return Success(c, "") -} - -func (api ScheduledTaskApi) UpdateEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api ScheduledTaskApi) DeleteEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api ScheduledTaskApi) GetEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api ScheduledTaskApi) ChangeStatusEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api ScheduledTaskApi) ExecEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api ScheduledTaskApi) GetLogsEndpoint(c echo.Context) error { - return Success(c, maps.Map{ - "total": 0, - "items": []interface{}{}, - }) -} - -func (api ScheduledTaskApi) DeleteLogsEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api ScheduledTaskApi) NextTenRunsEndpoint(c echo.Context) error { - return Success(c, []string{}) -} - type SessionCommandApi struct{} func (api SessionCommandApi) AllEndpoint(c echo.Context) error { @@ -186,35 +125,6 @@ func (api FilesystemLogApi) ClearEndpoint(c echo.Context) error { return Success(c, nil) } -type CommandFilterRuleApi struct{} - -func (api CommandFilterRuleApi) AllEndpoint(c echo.Context) error { - return Success(c, []interface{}{}) -} - -func (api CommandFilterRuleApi) PagingEndpoint(c echo.Context) error { - return Success(c, maps.Map{ - "total": 0, - "items": []interface{}{}, - }) -} - -func (api CommandFilterRuleApi) CreateEndpoint(c echo.Context) error { - return Success(c, "") -} - -func (api CommandFilterRuleApi) UpdateEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api CommandFilterRuleApi) DeleteEndpoint(c echo.Context) error { - return Success(c, nil) -} - -func (api CommandFilterRuleApi) GetEndpoint(c echo.Context) error { - return Success(c, nil) -} - type AgentGatewayTokenApi struct{} func (api AgentGatewayTokenApi) AllEndpoint(c echo.Context) error { @@ -283,26 +193,26 @@ func (api AccessLogApi) GetHourlyStatsEndpoint(c echo.Context) error { func (api AccessLogApi) GetTotalStatsEndpoint(c echo.Context) error { return Success(c, maps.Map{ - "totalRequests": 0, - "uniqueDomains": 0, - "uniqueVisitors": 0, - "avgResponseTime": 0, + "totalRequests": 0, + "uniqueDomains": 0, + "uniqueVisitors": 0, + "avgResponseTime": 0, }) } func (api AccessLogApi) GetWebsiteStatsEndpoint(c echo.Context) error { return Success(c, maps.Map{ - "websiteId": "", - "websiteName": "", - "domain": "", - "pv": 0, - "uv": 0, - "ip": 0, - "traffic": 0, - "requests": 0, - "realtimeTraffic": 0, + "websiteId": "", + "websiteName": "", + "domain": "", + "pv": 0, + "uv": 0, + "ip": 0, + "traffic": 0, + "requests": 0, + "realtimeTraffic": 0, "requestsPerSecond": 0, - "avgResponseTime": 0, + "avgResponseTime": 0, }) } diff --git a/server/api/stub_apis2.go b/server/api/stub_apis2.go index ef4ff6fa7..3ddd65b88 100644 --- a/server/api/stub_apis2.go +++ b/server/api/stub_apis2.go @@ -1,7 +1,13 @@ package api import ( + "context" + "strconv" + "next-terminal/server/common/maps" + "next-terminal/server/model" + "next-terminal/server/repository" + "next-terminal/server/utils" "github.com/labstack/echo/v4" ) @@ -9,87 +15,493 @@ import ( type AuthorisedAssetApi struct{} func (api AuthorisedAssetApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + userId := c.QueryParam("userId") + departmentId := c.QueryParam("departmentId") + assetGroupId := c.QueryParam("assetGroupId") + assetId := c.QueryParam("assetId") + + items, total, err := repository.AuthorisedAssetRepository.FindWithDetails(context.TODO(), pageIndex, pageSize, userId, departmentId, assetGroupId, assetId) + if err != nil { + return err + } + + if items == nil { + items = []map[string]interface{}{} + } + return Success(c, maps.Map{ - "total": 0, - "items": []interface{}{}, + "total": total, + "items": items, }) } func (api AuthorisedAssetApi) AuthorisedAssetsEndpoint(c echo.Context) error { + var req struct { + UserIds []string `json:"userIds"` + DepartmentIds []string `json:"departmentIds"` + AssetIds []string `json:"assetIds"` + AssetGroupIds []string `json:"assetGroupIds"` + CommandFilterId string `json:"commandFilterId"` + StrategyId string `json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var items []model.AuthorisedAsset + for _, userId := range req.UserIds { + for _, assetId := range req.AssetIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + UserId: userId, + AssetId: assetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + for _, assetGroupId := range req.AssetGroupIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + UserId: userId, + AssetGroupId: assetGroupId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + for _, departmentId := range req.DepartmentIds { + for _, assetId := range req.AssetIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + AssetId: assetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + for _, assetGroupId := range req.AssetGroupIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + AssetGroupId: assetGroupId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + + if len(items) > 0 { + if err := repository.AuthorisedAssetRepository.CreateInBatches(context.TODO(), items); err != nil { + return err + } + } + return Success(c, nil) } func (api AuthorisedAssetApi) AuthorisedUsersEndpoint(c echo.Context) error { + var req struct { + AssetIds []string `json:"assetIds"` + AssetGroupIds []string `json:"assetGroupIds"` + UserIds []string `json:"userIds"` + CommandFilterId string `json:"commandFilterId"` + StrategyId string `json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var items []model.AuthorisedAsset + for _, userId := range req.UserIds { + for _, assetId := range req.AssetIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + UserId: userId, + AssetId: assetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + for _, assetGroupId := range req.AssetGroupIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + UserId: userId, + AssetGroupId: assetGroupId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + + if len(items) > 0 { + if err := repository.AuthorisedAssetRepository.CreateInBatches(context.TODO(), items); err != nil { + return err + } + } + return Success(c, nil) } func (api AuthorisedAssetApi) AuthorisedDepartmentsEndpoint(c echo.Context) error { + var req struct { + AssetIds []string `json:"assetIds"` + AssetGroupIds []string `json:"assetGroupIds"` + DepartmentIds []string `json:"departmentIds"` + CommandFilterId string `json:"commandFilterId"` + StrategyId string `json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var items []model.AuthorisedAsset + for _, departmentId := range req.DepartmentIds { + for _, assetId := range req.AssetIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + AssetId: assetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + for _, assetGroupId := range req.AssetGroupIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + AssetGroupId: assetGroupId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + + if len(items) > 0 { + if err := repository.AuthorisedAssetRepository.CreateInBatches(context.TODO(), items); err != nil { + return err + } + } + return Success(c, nil) } func (api AuthorisedAssetApi) SelectedEndpoint(c echo.Context) error { - return Success(c, []string{}) + expect := c.QueryParam("expect") + userId := c.QueryParam("userId") + departmentId := c.QueryParam("departmentId") + assetId := c.QueryParam("assetId") + + result, err := repository.AuthorisedAssetRepository.Selected(context.TODO(), expect, userId, departmentId, assetId) + if err != nil { + return err + } + return Success(c, result) } func (api AuthorisedAssetApi) DeleteEndpoint(c echo.Context) error { + id := c.Param("id") + if err := repository.AuthorisedAssetRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api AuthorisedAssetApi) GetEndpoint(c echo.Context) error { - return Success(c, nil) + id := c.Param("id") + item, err := repository.AuthorisedAssetRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + return Success(c, item) } func (api AuthorisedAssetApi) UpdateEndpoint(c echo.Context) error { - return Success(c, nil) + id := c.Param("id") + var item model.AuthorisedAsset + if err := c.Bind(&item); err != nil { + return err + } + item.ID = id + if err := repository.AuthorisedAssetRepository.UpdateById(context.TODO(), &item); err != nil { + return err + } + return Success(c, item) } func (api AuthorisedAssetApi) CreateEndpoint(c echo.Context) error { + var req struct { + UserIds []string `json:"userIds"` + DepartmentIds []string `json:"departmentIds"` + AssetIds []string `json:"assetIds"` + AssetGroupIds []string `json:"assetGroupIds"` + CommandFilterId string `json:"commandFilterId"` + StrategyId string `json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var items []model.AuthorisedAsset + for _, userId := range req.UserIds { + for _, assetId := range req.AssetIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + UserId: userId, + AssetId: assetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + for _, assetGroupId := range req.AssetGroupIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + UserId: userId, + AssetGroupId: assetGroupId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + for _, departmentId := range req.DepartmentIds { + for _, assetId := range req.AssetIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + AssetId: assetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + for _, assetGroupId := range req.AssetGroupIds { + items = append(items, model.AuthorisedAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + AssetGroupId: assetGroupId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + + if len(items) > 0 { + if err := repository.AuthorisedAssetRepository.CreateInBatches(context.TODO(), items); err != nil { + return err + } + } + return Success(c, nil) } type AuthorisedDatabaseAssetApi struct{} func (api AuthorisedDatabaseAssetApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + userId := c.QueryParam("userId") + departmentId := c.QueryParam("departmentId") + databaseAssetId := c.QueryParam("assetId") + + items, total, err := repository.AuthorisedDatabaseAssetRepository.FindWithDetails(context.TODO(), pageIndex, pageSize, userId, departmentId, databaseAssetId) + if err != nil { + return err + } + + if items == nil { + items = []map[string]interface{}{} + } + return Success(c, maps.Map{ - "total": 0, - "items": []interface{}{}, + "total": total, + "items": items, }) } func (api AuthorisedDatabaseAssetApi) SelectedEndpoint(c echo.Context) error { - return Success(c, []string{}) + expect := c.QueryParam("expect") + userId := c.QueryParam("userId") + departmentId := c.QueryParam("departmentId") + databaseAssetId := c.QueryParam("assetId") + + result, err := repository.AuthorisedDatabaseAssetRepository.Selected(context.TODO(), expect, userId, departmentId, databaseAssetId) + if err != nil { + return err + } + return Success(c, result) } func (api AuthorisedDatabaseAssetApi) DeleteEndpoint(c echo.Context) error { + id := c.Param("id") + if err := repository.AuthorisedDatabaseAssetRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api AuthorisedDatabaseAssetApi) GetEndpoint(c echo.Context) error { - return Success(c, nil) + id := c.Param("id") + item, err := repository.AuthorisedDatabaseAssetRepository.FindById(context.TODO(), id) + if err != nil { + return err + } + return Success(c, item) } func (api AuthorisedDatabaseAssetApi) UpdateEndpoint(c echo.Context) error { - return Success(c, nil) + id := c.Param("id") + var item model.AuthorisedDatabaseAsset + if err := c.Bind(&item); err != nil { + return err + } + item.ID = id + if err := repository.AuthorisedDatabaseAssetRepository.UpdateById(context.TODO(), &item); err != nil { + return err + } + return Success(c, item) } func (api AuthorisedDatabaseAssetApi) CreateEndpoint(c echo.Context) error { + var req struct { + UserIds []string `json:"userIds"` + DepartmentIds []string `json:"departmentIds"` + DatabaseAssetIds []string `json:"assetIds"` + CommandFilterId string `json:"commandFilterId"` + StrategyId string `json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var items []model.AuthorisedDatabaseAsset + for _, userId := range req.UserIds { + for _, databaseAssetId := range req.DatabaseAssetIds { + items = append(items, model.AuthorisedDatabaseAsset{ + ID: utils.UUID(), + UserId: userId, + DatabaseAssetId: databaseAssetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + for _, departmentId := range req.DepartmentIds { + for _, databaseAssetId := range req.DatabaseAssetIds { + items = append(items, model.AuthorisedDatabaseAsset{ + ID: utils.UUID(), + DepartmentId: departmentId, + DatabaseAssetId: databaseAssetId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + + if len(items) > 0 { + if err := repository.AuthorisedDatabaseAssetRepository.CreateInBatches(context.TODO(), items); err != nil { + return err + } + } + return Success(c, nil) } type AuthorisedWebsiteApi struct{} func (api AuthorisedWebsiteApi) PagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + userId := c.QueryParam("userId") + departmentId := c.QueryParam("departmentId") + websiteId := c.QueryParam("websiteId") + + items, total, err := repository.AuthorisedWebsiteRepository.FindWithDetails(context.TODO(), pageIndex, pageSize, userId, departmentId, websiteId) + if err != nil { + return err + } + + if items == nil { + items = []map[string]interface{}{} + } + return Success(c, maps.Map{ - "total": 0, - "items": []interface{}{}, + "total": total, + "items": items, }) } func (api AuthorisedWebsiteApi) DeleteEndpoint(c echo.Context) error { + id := c.Param("id") + if err := repository.AuthorisedWebsiteRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api AuthorisedWebsiteApi) CreateEndpoint(c echo.Context) error { + var req struct { + UserIds []string `json:"userIds"` + DepartmentIds []string `json:"departmentIds"` + WebsiteIds []string `json:"websiteIds"` + CommandFilterId string `json:"commandFilterId"` + StrategyId string `json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + } + if err := c.Bind(&req); err != nil { + return err + } + + var items []model.AuthorisedWebsite + for _, userId := range req.UserIds { + for _, websiteId := range req.WebsiteIds { + items = append(items, model.AuthorisedWebsite{ + ID: utils.UUID(), + UserId: userId, + WebsiteId: websiteId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + for _, departmentId := range req.DepartmentIds { + for _, websiteId := range req.WebsiteIds { + items = append(items, model.AuthorisedWebsite{ + ID: utils.UUID(), + DepartmentId: departmentId, + WebsiteId: websiteId, + CommandFilterId: req.CommandFilterId, + StrategyId: req.StrategyId, + ExpiredAt: req.ExpiredAt, + }) + } + } + + if len(items) > 0 { + if err := repository.AuthorisedWebsiteRepository.CreateInBatches(context.TODO(), items); err != nil { + return err + } + } + return Success(c, nil) } @@ -139,9 +551,9 @@ type DnsProviderApi struct{} func (api DnsProviderApi) GetConfigEndpoint(c echo.Context) error { return Success(c, maps.Map{ - "ok": false, - "email": "", - "type": "", + "ok": false, + "email": "", + "type": "", }) } @@ -163,12 +575,12 @@ func (api LicenseApi) GetMachineIdEndpoint(c echo.Context) error { func (api LicenseApi) GetLicenseEndpoint(c echo.Context) error { return Success(c, maps.Map{ - "type": "free", - "machineId": "", - "asset": 0, + "type": "free", + "machineId": "", + "asset": 0, "concurrent": 0, - "user": 0, - "expired": 0, + "user": 0, + "expired": 0, }) } diff --git a/server/api/term.go b/server/api/term.go index bbb017d37..6bf778631 100644 --- a/server/api/term.go +++ b/server/api/term.go @@ -25,10 +25,10 @@ import ( ) const ( - Closed = 0 - Data = 1 - Resize = 2 - Ping = 9 + Closed = 0 + Data = 1 + Resize = 2 + Ping = 9 ) type WebTerminalApi struct { @@ -119,11 +119,11 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error { } sessionForUpdate := model.Session{ - ConnectionId: sessionId, - Width: cols, - Height: rows, - Status: nt.Connected, - Recording: recording, + ConnectionId: sessionId, + Width: cols, + Height: rows, + Status: nt.Connected, + Recording: recording, ConnectedTime: common.NowJsonTime(), } if sessionForUpdate.Recording == "" { @@ -247,7 +247,7 @@ func (api WebTerminalApi) AccessTerminalEndpoint(c echo.Context) error { }() sessionId := c.QueryParam("sessionId") - log.Debug("AccessTerminal: WebSocket connected, sessionId="+sessionId+", cols="+c.QueryParam("cols")+", rows="+c.QueryParam("rows")) + log.Debug("AccessTerminal: WebSocket connected, sessionId=" + sessionId + ", cols=" + c.QueryParam("cols") + ", rows=" + c.QueryParam("rows")) if sessionId == "" { return WriteMessage(ws, dto.NewMessage(Closed, "sessionId is required")) } @@ -305,11 +305,11 @@ func (api WebTerminalApi) AccessTerminalEndpoint(c echo.Context) error { log.Debug("AccessTerminal: Shell OK, starting TermHandler...") sessionForUpdate := model.Session{ - ConnectionId: sessionId, - Width: cols, - Height: rows, - Status: nt.Connected, - Recording: recording, + ConnectionId: sessionId, + Width: cols, + Height: rows, + Status: nt.Connected, + Recording: recording, ConnectedTime: common.NowJsonTime(), } if sessionForUpdate.Recording == "" { diff --git a/server/api/tools_api.go b/server/api/tools_api.go new file mode 100644 index 000000000..93423dde3 --- /dev/null +++ b/server/api/tools_api.go @@ -0,0 +1,161 @@ +package api + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net" + "net/http" + "os/exec" + "runtime" + "strconv" + "strings" + "time" + + "github.com/labstack/echo/v4" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" +) + +type ToolsApi struct{} + +func (api ToolsApi) TcpingEndpoint(c echo.Context) error { + host := c.QueryParam("host") + port := c.QueryParam("port") + attemptsStr := c.QueryParam("attempts") + + attempts := 4 + if attemptsStr != "" { + attempts, _ = strconv.Atoi(attemptsStr) + } + + c.Response().Header().Set("Content-Type", "text/event-stream") + c.Response().Header().Set("Cache-Control", "no-cache") + c.Response().Header().Set("Connection", "keep-alive") + + flusher, ok := c.Response().Writer.(http.Flusher) + if !ok { + return echo.NewHTTPError(500, "Streaming unsupported") + } + + for i := 0; i < attempts; i++ { + start := time.Now() + conn, err := net.DialTimeout("tcp", host+":"+port, 5*time.Second) + elapsed := time.Since(start) + + var result string + if err != nil { + result = fmt.Sprintf("TCP ping %s:%s - Failed: %v", host, port, err) + } else { + conn.Close() + result = fmt.Sprintf("TCP ping %s:%s - Connected, time=%v", host, port, elapsed.Round(time.Millisecond)) + } + + fmt.Fprintf(c.Response(), "data: %s\n\n", result) + flusher.Flush() + + if i < attempts-1 { + time.Sleep(1 * time.Second) + } + } + + fmt.Fprintf(c.Response(), "event: done\ndata: completed\n\n") + flusher.Flush() + + return nil +} + +func (api ToolsApi) PingEndpoint(c echo.Context) error { + host := c.QueryParam("host") + attemptsStr := c.QueryParam("attempts") + + attempts := 4 + if attemptsStr != "" { + attempts, _ = strconv.Atoi(attemptsStr) + } + + c.Response().Header().Set("Content-Type", "text/event-stream") + c.Response().Header().Set("Cache-Control", "no-cache") + c.Response().Header().Set("Connection", "keep-alive") + + flusher, ok := c.Response().Writer.(http.Flusher) + if !ok { + return echo.NewHTTPError(500, "Streaming unsupported") + } + + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("ping", "-n", strconv.Itoa(attempts), host) + } else { + cmd = exec.Command("ping", "-c", strconv.Itoa(attempts), host) + } + + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Fprintf(c.Response(), "data: Error: %v\n\n", err) + flusher.Flush() + return nil + } + + if err := cmd.Start(); err != nil { + fmt.Fprintf(c.Response(), "data: Error: %v\n\n", err) + flusher.Flush() + return nil + } + + var reader io.Reader = stdout + if runtime.GOOS == "windows" { + reader = transform.NewReader(stdout, simplifiedchinese.GBK.NewDecoder()) + } + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + if strings.TrimSpace(line) != "" { + fmt.Fprintf(c.Response(), "data: %s\n\n", line) + flusher.Flush() + } + } + + cmd.Wait() + + fmt.Fprintf(c.Response(), "event: done\ndata: completed\n\n") + flusher.Flush() + + return nil +} + +func (api ToolsApi) TelnetEndpoint(c echo.Context) error { + host := c.QueryParam("host") + port := c.QueryParam("port") + + c.Response().Header().Set("Content-Type", "text/event-stream") + c.Response().Header().Set("Cache-Control", "no-cache") + c.Response().Header().Set("Connection", "keep-alive") + + flusher, ok := c.Response().Writer.(http.Flusher) + if !ok { + return echo.NewHTTPError(500, "Streaming unsupported") + } + + start := time.Now() + conn, err := net.DialTimeout("tcp", host+":"+port, 10*time.Second) + elapsed := time.Since(start) + + var result bytes.Buffer + if err != nil { + result.WriteString(fmt.Sprintf("Telnet %s:%s - Connection failed: %v", host, port, err)) + } else { + conn.Close() + result.WriteString(fmt.Sprintf("Telnet %s:%s - Connected successfully in %v", host, port, elapsed.Round(time.Millisecond))) + } + + fmt.Fprintf(c.Response(), "data: %s\n\n", result.String()) + flusher.Flush() + + fmt.Fprintf(c.Response(), "event: done\ndata: completed\n\n") + flusher.Flush() + + return nil +} diff --git a/server/api/user.go b/server/api/user.go index 59b868cda..768f279ff 100644 --- a/server/api/user.go +++ b/server/api/user.go @@ -41,8 +41,9 @@ func (userApi UserApi) PagingEndpoint(c echo.Context) error { order := c.QueryParam("order") field := c.QueryParam("field") online := c.QueryParam("online") + userType := c.QueryParam("type") - items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, online, "", order, field) + items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, online, userType, "", order, field) if err != nil { return err } diff --git a/server/api/website_api.go b/server/api/website_api.go index 2d2938e9a..61719cafc 100644 --- a/server/api/website_api.go +++ b/server/api/website_api.go @@ -5,6 +5,7 @@ import ( "encoding/json" "strconv" + "next-terminal/server/common" "next-terminal/server/common/maps" "next-terminal/server/dto" "next-terminal/server/model" @@ -68,27 +69,27 @@ func (api WebsiteApi) CreateEndpoint(c echo.Context) error { } item := &model.Website{ - ID: utils.UUID(), - Name: req.Name, - Enabled: req.Enabled, - TargetUrl: req.TargetUrl, - TargetHost: req.TargetHost, - TargetPort: req.TargetPort, - Domain: req.Domain, - AsciiDomain: req.AsciiDomain, - Entrance: req.Entrance, - Description: req.Description, - Status: req.Status, - StatusText: req.StatusText, - GatewayType: req.GatewayType, - GatewayId: req.GatewayId, - BasicAuth: serializeJSON(req.BasicAuth), - Headers: serializeJSON(req.Headers), - Cert: serializeJSON(req.Cert), - Public: serializeJSON(req.Public), - TempAllow: serializeJSON(req.TempAllow), - GroupId: req.GroupId, - Sort: req.Sort, + ID: utils.UUID(), + Name: req.Name, + Enabled: req.Enabled, + TargetUrl: req.TargetUrl, + TargetHost: req.TargetHost, + TargetPort: req.TargetPort, + Domain: req.Domain, + AsciiDomain: req.AsciiDomain, + Entrance: req.Entrance, + Description: req.Description, + Status: req.Status, + StatusText: req.StatusText, + GatewayType: req.GatewayType, + GatewayId: req.GatewayId, + BasicAuth: serializeJSON(req.BasicAuth), + Headers: serializeJSON(req.Headers), + Cert: serializeJSON(req.Cert), + Public: serializeJSON(req.Public), + TempAllow: serializeJSON(req.TempAllow), + GroupId: req.GroupId, + Sort: req.Sort, } if err := repository.WebsiteRepository.Create(context.TODO(), item); err != nil { @@ -105,27 +106,27 @@ func (api WebsiteApi) UpdateEndpoint(c echo.Context) error { } item := &model.Website{ - ID: id, - Name: req.Name, - Enabled: req.Enabled, - TargetUrl: req.TargetUrl, - TargetHost: req.TargetHost, - TargetPort: req.TargetPort, - Domain: req.Domain, - AsciiDomain: req.AsciiDomain, - Entrance: req.Entrance, - Description: req.Description, - Status: req.Status, - StatusText: req.StatusText, - GatewayType: req.GatewayType, - GatewayId: req.GatewayId, - BasicAuth: serializeJSON(req.BasicAuth), - Headers: serializeJSON(req.Headers), - Cert: serializeJSON(req.Cert), - Public: serializeJSON(req.Public), - TempAllow: serializeJSON(req.TempAllow), - GroupId: req.GroupId, - Sort: req.Sort, + ID: id, + Name: req.Name, + Enabled: req.Enabled, + TargetUrl: req.TargetUrl, + TargetHost: req.TargetHost, + TargetPort: req.TargetPort, + Domain: req.Domain, + AsciiDomain: req.AsciiDomain, + Entrance: req.Entrance, + Description: req.Description, + Status: req.Status, + StatusText: req.StatusText, + GatewayType: req.GatewayType, + GatewayId: req.GatewayId, + BasicAuth: serializeJSON(req.BasicAuth), + Headers: serializeJSON(req.Headers), + Cert: serializeJSON(req.Cert), + Public: serializeJSON(req.Public), + TempAllow: serializeJSON(req.TempAllow), + GroupId: req.GroupId, + Sort: req.Sort, } if err := repository.WebsiteRepository.UpdateById(context.TODO(), item, id); err != nil { @@ -152,18 +153,124 @@ func (api WebsiteApi) GetEndpoint(c echo.Context) error { } func (api WebsiteApi) GroupsGetEndpoint(c echo.Context) error { - return Success(c, []interface{}{}) + groups, err := repository.WebsiteGroupRepository.FindAll(context.TODO()) + if err != nil { + return err + } + tree := buildWebsiteGroupTree(groups, "") + return Success(c, tree) +} + +func buildWebsiteGroupTree(groups []model.WebsiteGroup, parentId string) []maps.Map { + var tree []maps.Map + for _, g := range groups { + if g.ParentId == parentId { + node := maps.Map{ + "id": g.ID, + "name": g.Name, + "title": g.Name, + "key": g.ID, + "value": g.ID, + } + children := buildWebsiteGroupTree(groups, g.ID) + if len(children) > 0 { + node["children"] = children + } + tree = append(tree, node) + } + } + return tree } func (api WebsiteApi) GroupsSetEndpoint(c echo.Context) error { + var req []map[string]interface{} + if err := c.Bind(&req); err != nil { + return err + } + ctx := context.TODO() + repository.WebsiteGroupRepository.DeleteAll(ctx) + for i, item := range req { + name := "" + if v, ok := item["name"].(string); ok { + name = v + } else if v, ok := item["title"].(string); ok { + name = v + } + group := model.WebsiteGroup{ + ID: utils.UUID(), + Name: name, + ParentId: "", + Sort: i, + Created: common.NowJsonTime(), + } + repository.WebsiteGroupRepository.Create(ctx, &group) + if subChildren, ok := item["children"].([]interface{}); ok { + saveWebsiteGroupChildren(ctx, subChildren, group.ID) + } + } return Success(c, nil) } +func saveWebsiteGroupChildren(ctx context.Context, children []interface{}, parentId string) { + for i, child := range children { + m, ok := child.(map[string]interface{}) + if !ok { + continue + } + name := "" + if v, ok := m["name"].(string); ok { + name = v + } else if v, ok := m["title"].(string); ok { + name = v + } + group := model.WebsiteGroup{ + ID: utils.UUID(), + Name: name, + ParentId: parentId, + Sort: i, + Created: common.NowJsonTime(), + } + repository.WebsiteGroupRepository.Create(ctx, &group) + if subChildren, ok := m["children"].([]interface{}); ok { + saveWebsiteGroupChildren(ctx, subChildren, group.ID) + } + } +} + func (api WebsiteApi) GroupsDeleteEndpoint(c echo.Context) error { + id := c.Param("id") + + count, err := repository.WebsiteGroupRepository.CountByParentId(context.TODO(), id) + if err != nil { + return err + } + if count > 0 { + return Fail(c, -1, "该分组下存在子分组,无法删除") + } + + if err := repository.WebsiteGroupRepository.DeleteById(context.TODO(), id); err != nil { + return err + } return Success(c, nil) } func (api WebsiteApi) ChangeGroupEndpoint(c echo.Context) error { + var req struct { + WebsiteIds []string `json:"websiteIds"` + GroupId string `json:"groupId"` + } + if err := c.Bind(&req); err != nil { + return err + } + + for _, websiteId := range req.WebsiteIds { + website, err := repository.WebsiteRepository.FindById(context.TODO(), websiteId) + if err != nil { + continue + } + website.GroupId = req.GroupId + repository.WebsiteRepository.UpdateById(context.TODO(), &website, websiteId) + } return Success(c, nil) } diff --git a/server/app/middleware/auth.go b/server/app/middleware/auth.go index ff981344b..6a285e59a 100644 --- a/server/app/middleware/auth.go +++ b/server/app/middleware/auth.go @@ -57,6 +57,8 @@ var allowUrls = []urlpath.Path{ urlpath.New("/api/portal/websites"), urlpath.New("/api/portal/info"), urlpath.New("/api/license"), + urlpath.New("/api/admin/tools/tcping"), + urlpath.New("/api/admin/tools/ping"), } func Auth(next echo.HandlerFunc) echo.HandlerFunc { diff --git a/server/app/server.go b/server/app/server.go index 533f8f140..189947a67 100644 --- a/server/app/server.go +++ b/server/app/server.go @@ -95,9 +95,9 @@ func setupRoutes() *echo.Echo { adminGroup := apiGroup.Group("/admin") PortalApi := new(api.PortalApi) - AccessSettingApi := new(api.AccessSettingApi) - webTerminalApi := new(api.WebTerminalApi) - FileSystemApi := new(api.FileSystemApi) + AccessSettingApi := new(api.AccessSettingApi) + webTerminalApi := new(api.WebTerminalApi) + FileSystemApi := new(api.FileSystemApi) { @@ -105,39 +105,40 @@ func setupRoutes() *echo.Echo { UserApi := new(api.UserApi) UserGroupApi := new(api.UserGroupApi) AssetApi := new(api.AssetApi) - CommandApi := new(api.CommandApi) - CredentialApi := new(api.CredentialApi) - SessionApi := new(api.SessionApi) - LoginLogApi := new(api.LoginLogApi) - PropertyApi := new(api.PropertyApi) + CommandApi := new(api.CommandApi) + CredentialApi := new(api.CredentialApi) + SessionApi := new(api.SessionApi) + LoginLogApi := new(api.LoginLogApi) + PropertyApi := new(api.PropertyApi) LogoApi := new(api.LogoApi) - OverviewApi := new(api.OverviewApi) - JobApi := new(api.JobApi) - SecurityApi := new(api.SecurityApi) - StorageApi := new(api.StorageApi) - StrategyApi := new(api.StrategyApi) - AccessGatewayApi := new(api.AccessGatewayApi) - BackupApi := new(api.BackupApi) - TenantApi := new(api.TenantApi) - RoleApi := new(api.RoleApi) - LoginPolicyApi := new(api.LoginPolicyApi) - StorageLogApi := new(api.StorageLogApi) - AuthorisedApi := new(api.AuthorisedApi) - DashboardApi := new(api.DashboardApi) - DepartmentApi := new(api.DepartmentApi) - SnippetApi := new(api.SnippetApi) - CommandFilterApi := new(api.CommandFilterApi) - DatabaseAssetApi := new(api.DatabaseAssetApi) - AssetGroupApi := new(api.AssetGroupApi) + OverviewApi := new(api.OverviewApi) + JobApi := new(api.JobApi) + SecurityApi := new(api.SecurityApi) + StorageApi := new(api.StorageApi) + StrategyApi := new(api.StrategyApi) + AccessGatewayApi := new(api.AccessGatewayApi) + BackupApi := new(api.BackupApi) + TenantApi := new(api.TenantApi) + RoleApi := new(api.RoleApi) + LoginPolicyApi := new(api.LoginPolicyApi) + StorageLogApi := new(api.StorageLogApi) + AuthorisedApi := new(api.AuthorisedApi) + DashboardApi := new(api.DashboardApi) + DepartmentApi := new(api.DepartmentApi) + SnippetApi := new(api.SnippetApi) + CommandFilterApi := new(api.CommandFilterApi) + DatabaseAssetApi := new(api.DatabaseAssetApi) + AssetGroupApi := new(api.AssetGroupApi) SshGatewayApi := new(api.SshGatewayApi) GatewayGroupApi := new(api.GatewayGroupApi) WebsiteApi := new(api.WebsiteApi) CertificateApi := new(api.CertificateApi) + ToolsApi := new(api.ToolsApi) - adminGroup.GET("/login-status", setupApi.LoginStatusEndpoint) - adminGroup.POST("/validate-totp", setupApi.ValidateTOTPEndpoint) - adminGroup.GET("/account/password-policy", setupApi.PasswordPolicyEndpoint) - adminGroup.GET("/captcha", setupApi.GetCaptchaEndpoint) + adminGroup.GET("/login-status", setupApi.LoginStatusEndpoint) + adminGroup.POST("/validate-totp", setupApi.ValidateTOTPEndpoint) + adminGroup.GET("/account/password-policy", setupApi.PasswordPolicyEndpoint) + adminGroup.GET("/captcha", setupApi.GetCaptchaEndpoint) dashboard := adminGroup.Group("/dashboard") { @@ -279,6 +280,12 @@ func setupRoutes() *echo.Echo { adminGroup.GET("/tags", AssetApi.AssetTagsEndpoint) + tools := adminGroup.Group("/tools") + { + tools.GET("/tcping", ToolsApi.TcpingEndpoint) + tools.GET("/ping", ToolsApi.PingEndpoint) + } + commands := adminGroup.Group("/commands") { commands.GET("", CommandApi.CommandAllEndpoint) diff --git a/server/dto/website.go b/server/dto/website.go index 8655fb629..63dbbef75 100644 --- a/server/dto/website.go +++ b/server/dto/website.go @@ -1,32 +1,32 @@ package dto type WebsiteDTO struct { - ID string `json:"id"` - Name string `json:"name"` - Enabled bool `json:"enabled"` - TargetUrl string `json:"targetUrl"` - TargetHost string `json:"targetHost"` - TargetPort int `json:"targetPort"` - Domain string `json:"domain"` - AsciiDomain string `json:"asciiDomain"` - Entrance string `json:"entrance"` - Description string `json:"description"` - Status string `json:"status"` - StatusText string `json:"statusText"` - GatewayType string `json:"gatewayType"` - GatewayId string `json:"gatewayId"` - BasicAuth interface{} `json:"basicAuth"` - Headers interface{} `json:"headers"` - Cert interface{} `json:"cert"` - Public interface{} `json:"public"` - TempAllow interface{} `json:"tempAllow"` - Created int64 `json:"createdAt"` - GroupId string `json:"groupId"` - Sort string `json:"sort"` - Logo string `json:"logo"` - Scheme string `json:"scheme"` - Host string `json:"host"` - Port int `json:"port"` - PreserveHost bool `json:"preserveHost"` - DisableAccessLog bool `json:"disableAccessLog"` + ID string `json:"id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + TargetUrl string `json:"targetUrl"` + TargetHost string `json:"targetHost"` + TargetPort int `json:"targetPort"` + Domain string `json:"domain"` + AsciiDomain string `json:"asciiDomain"` + Entrance string `json:"entrance"` + Description string `json:"description"` + Status string `json:"status"` + StatusText string `json:"statusText"` + GatewayType string `json:"gatewayType"` + GatewayId string `json:"gatewayId"` + BasicAuth interface{} `json:"basicAuth"` + Headers interface{} `json:"headers"` + Cert interface{} `json:"cert"` + Public interface{} `json:"public"` + TempAllow interface{} `json:"tempAllow"` + Created int64 `json:"createdAt"` + GroupId string `json:"groupId"` + Sort string `json:"sort"` + Logo string `json:"logo"` + Scheme string `json:"scheme"` + Host string `json:"host"` + Port int `json:"port"` + PreserveHost bool `json:"preserveHost"` + DisableAccessLog bool `json:"disableAccessLog"` } diff --git a/server/env/db.go b/server/env/db.go index a34faaec2..9a616bf26 100644 --- a/server/env/db.go +++ b/server/env/db.go @@ -55,7 +55,7 @@ func setupDB() *gorm.DB { &model.Role{}, &model.RoleMenuRef{}, &model.UserRoleRef{}, &model.LoginPolicy{}, &model.LoginPolicyUserRef{}, &model.TimePeriod{}, &model.StorageLog{}, &model.Authorised{}, &model.Logo{}, &model.AssetGroup{}, - &model.AgentGateway{}, &model.SshGateway{}, &model.GatewayGroup{}, &model.Website{}, &model.Certificate{}, &model.Snippet{}, &model.SessionAudit{}, &model.Department{}, &model.UserDepartmentRef{}); err != nil { + &model.AgentGateway{}, &model.SshGateway{}, &model.GatewayGroup{}, &model.Website{}, &model.Certificate{}, &model.Snippet{}, &model.SessionAudit{}, &model.Department{}, &model.UserDepartmentRef{}, &model.DatabaseAsset{}, &model.CommandFilter{}, &model.CommandFilterRule{}, &model.AuthorisedAsset{}, &model.AuthorisedDatabaseAsset{}, &model.AuthorisedWebsite{}, &model.WebsiteGroup{}); err != nil { panic(fmt.Errorf("初始化数据库表结构异常: %v", err.Error())) } return db diff --git a/server/model/access_token.go b/server/model/access_token.go index 2b2dde682..cd00a36d5 100644 --- a/server/model/access_token.go +++ b/server/model/access_token.go @@ -5,11 +5,11 @@ import ( ) type AccessToken struct { - ID string `gorm:"primary_key,type:varchar(36)" json:"id"` - UserId string `gorm:"index,type:varchar(200)" json:"userId"` - Token string `gorm:"index,type:varchar(128)" json:"token"` - Type string `gorm:"type:varchar(32);default:'api'" json:"type"` - Created common.JsonTime `json:"createdAt"` + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + UserId string `gorm:"index,type:varchar(200)" json:"userId"` + Token string `gorm:"index,type:varchar(128)" json:"token"` + Type string `gorm:"type:varchar(32);default:'api'" json:"type"` + Created common.JsonTime `json:"createdAt"` } func (r *AccessToken) TableName() string { diff --git a/server/model/agent_gateway.go b/server/model/agent_gateway.go index a28f12e97..beb30d03d 100644 --- a/server/model/agent_gateway.go +++ b/server/model/agent_gateway.go @@ -3,16 +3,16 @@ package model import "next-terminal/server/common" type AgentGateway struct { - ID string `gorm:"primary_key,type:varchar(36)" json:"id"` - Name string `gorm:"type:varchar(500)" json:"name"` - IP string `gorm:"type:varchar(200)" json:"ip"` - OS string `gorm:"type:varchar(100)" json:"os"` - Arch string `gorm:"type:varchar(100)" json:"arch"` - Online bool `json:"online"` - Created common.JsonTime `json:"createdAt"` - Updated common.JsonTime `json:"updatedAt"` - Sort string `gorm:"type:varchar(50);default:''" json:"sort"` - Version string `gorm:"type:varchar(50)" json:"version"` + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Name string `gorm:"type:varchar(500)" json:"name"` + IP string `gorm:"type:varchar(200)" json:"ip"` + OS string `gorm:"type:varchar(100)" json:"os"` + Arch string `gorm:"type:varchar(100)" json:"arch"` + Online bool `json:"online"` + Created common.JsonTime `json:"createdAt"` + Updated common.JsonTime `json:"updatedAt"` + Sort string `gorm:"type:varchar(50);default:''" json:"sort"` + Version string `gorm:"type:varchar(50)" json:"version"` } func (r *AgentGateway) TableName() string { @@ -20,14 +20,14 @@ func (r *AgentGateway) TableName() string { } type AgentGatewayForPage struct { - ID string `json:"id"` - Name string `json:"name"` - IP string `json:"ip"` - OS string `json:"os"` - Arch string `json:"arch"` - Online bool `json:"online"` - Version string `json:"version"` - Sort string `json:"sort"` - Created int64 `json:"createdAt"` - Updated int64 `json:"updatedAt"` + ID string `json:"id"` + Name string `json:"name"` + IP string `json:"ip"` + OS string `json:"os"` + Arch string `json:"arch"` + Online bool `json:"online"` + Version string `json:"version"` + Sort string `json:"sort"` + Created int64 `json:"createdAt"` + Updated int64 `json:"updatedAt"` } diff --git a/server/model/authorised_asset.go b/server/model/authorised_asset.go new file mode 100644 index 000000000..ae553fdc7 --- /dev/null +++ b/server/model/authorised_asset.go @@ -0,0 +1,49 @@ +package model + +import "next-terminal/server/common" + +type AuthorisedAsset struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + UserId string `gorm:"index,type:varchar(36)" json:"userId"` + DepartmentId string `gorm:"index,type:varchar(36)" json:"departmentId"` + AssetId string `gorm:"index,type:varchar(36)" json:"assetId"` + AssetGroupId string `gorm:"index,type:varchar(36)" json:"assetGroupId"` + CommandFilterId string `gorm:"index,type:varchar(36)" json:"commandFilterId"` + StrategyId string `gorm:"index,type:varchar(36)" json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` +} + +func (m AuthorisedAsset) TableName() string { + return "authorised_assets" +} + +type AuthorisedDatabaseAsset struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + UserId string `gorm:"index,type:varchar(36)" json:"userId"` + DepartmentId string `gorm:"index,type:varchar(36)" json:"departmentId"` + DatabaseAssetId string `gorm:"index,type:varchar(36)" json:"databaseAssetId"` + CommandFilterId string `gorm:"index,type:varchar(36)" json:"commandFilterId"` + StrategyId string `gorm:"index,type:varchar(36)" json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` +} + +func (m AuthorisedDatabaseAsset) TableName() string { + return "authorised_database_assets" +} + +type AuthorisedWebsite struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + UserId string `gorm:"index,type:varchar(36)" json:"userId"` + DepartmentId string `gorm:"index,type:varchar(36)" json:"departmentId"` + WebsiteId string `gorm:"index,type:varchar(36)" json:"websiteId"` + CommandFilterId string `gorm:"index,type:varchar(36)" json:"commandFilterId"` + StrategyId string `gorm:"index,type:varchar(36)" json:"strategyId"` + ExpiredAt int64 `json:"expiredAt"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` +} + +func (m AuthorisedWebsite) TableName() string { + return "authorised_websites" +} diff --git a/server/model/certificate.go b/server/model/certificate.go index 77227117b..13216bd2f 100644 --- a/server/model/certificate.go +++ b/server/model/certificate.go @@ -3,22 +3,22 @@ package model import "next-terminal/server/common" type Certificate struct { - ID string `gorm:"primary_key,type:varchar(36)" json:"id"` - CommonName string `gorm:"type:varchar(500)" json:"commonName"` - Subject string `gorm:"type:varchar(500)" json:"subject"` - Issuer string `gorm:"type:varchar(500)" json:"issuer"` - NotBefore common.JsonTime `json:"notBefore"` - NotAfter common.JsonTime `json:"notAfter"` - Type string `gorm:"type:varchar(20);default:'imported'" json:"type"` - StorageKey string `gorm:"type:varchar(100)" json:"storageKey"` - Certificate string `gorm:"type:text" json:"certificate"` - PrivateKey string `gorm:"type:text" json:"privateKey"` + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + CommonName string `gorm:"type:varchar(500)" json:"commonName"` + Subject string `gorm:"type:varchar(500)" json:"subject"` + Issuer string `gorm:"type:varchar(500)" json:"issuer"` + NotBefore common.JsonTime `gorm:"type:datetime" json:"notBefore"` + NotAfter common.JsonTime `gorm:"type:datetime" json:"notAfter"` + Type string `gorm:"type:varchar(20);default:'imported'" json:"type"` + StorageKey string `gorm:"type:varchar(100)" json:"storageKey"` + Certificate string `gorm:"type:text" json:"certificate"` + PrivateKey string `gorm:"type:text" json:"privateKey"` RequireClientAuth bool `gorm:"default:false" json:"requireClientAuth"` - IssuedStatus string `gorm:"type:varchar(20);default:'success'" json:"issuedStatus"` - IssuedError string `gorm:"type:text" json:"issuedError"` - UpdatedAt common.JsonTime `json:"updatedAt"` - IsDefault bool `gorm:"default:false" json:"isDefault"` - Created common.JsonTime `json:"createdAt"` + IssuedStatus string `gorm:"type:varchar(20);default:'success'" json:"issuedStatus"` + IssuedError string `gorm:"type:text" json:"issuedError"` + UpdatedAt common.JsonTime `gorm:"type:datetime" json:"updatedAt"` + IsDefault bool `gorm:"default:false" json:"isDefault"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` } func (r *Certificate) TableName() string { diff --git a/server/model/command_filter.go b/server/model/command_filter.go new file mode 100644 index 000000000..bded8311b --- /dev/null +++ b/server/model/command_filter.go @@ -0,0 +1,27 @@ +package model + +import "next-terminal/server/common" + +type CommandFilter struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Name string `gorm:"type:varchar(200)" json:"name"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` +} + +func (r *CommandFilter) TableName() string { + return "command_filters" +} + +type CommandFilterRule struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + CommandFilterId string `gorm:"type:varchar(36);index" json:"commandFilterId"` + Type string `gorm:"type:varchar(20)" json:"type"` + Pattern string `gorm:"type:text" json:"pattern"` + Priority int `json:"priority"` + Action string `gorm:"type:varchar(20)" json:"action"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` +} + +func (r *CommandFilterRule) TableName() string { + return "command_filter_rules" +} diff --git a/server/model/database_asset.go b/server/model/database_asset.go new file mode 100644 index 000000000..adddeb097 --- /dev/null +++ b/server/model/database_asset.go @@ -0,0 +1,27 @@ +package model + +import "next-terminal/server/common" + +type DatabaseAsset struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Name string `gorm:"type:varchar(500)" json:"name"` + Type string `gorm:"type:varchar(20)" json:"type"` + Host string `gorm:"type:varchar(200)" json:"host"` + Port int `json:"port"` + Database string `gorm:"type:varchar(200)" json:"database"` + Username string `gorm:"type:varchar(200)" json:"username"` + Password string `gorm:"type:varchar(500)" json:"password"` + Description string `json:"description"` + GatewayType string `gorm:"type:varchar(20)" json:"gatewayType"` + GatewayId string `gorm:"type:varchar(36)" json:"gatewayId"` + Tags string `json:"tags"` + Owner string `gorm:"index,type:varchar(36)" json:"owner"` + Encrypted bool `json:"encrypted"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` + Updated common.JsonTime `gorm:"type:datetime" json:"updatedAt"` + Sort int `json:"sort" gorm:"default:0"` +} + +func (r *DatabaseAsset) TableName() string { + return "database_assets" +} diff --git a/server/model/department.go b/server/model/department.go index 8cdfda0f9..13a5974eb 100644 --- a/server/model/department.go +++ b/server/model/department.go @@ -3,11 +3,13 @@ package model import "next-terminal/server/common" type Department struct { - ID string `gorm:"primary_key,type:varchar(36)" json:"id"` - Name string `gorm:"type:varchar(200)" json:"name"` - ParentId string `gorm:"type:varchar(36)" json:"parentId"` - Sort int `json:"sort"` - Created common.JsonTime `json:"createdAt"` + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Name string `gorm:"type:varchar(200)" json:"name"` + ParentId string `gorm:"type:varchar(36)" json:"parentId"` + Sort int `json:"weight"` + ParentName string `gorm:"-" json:"parentName"` + UserCount int64 `gorm:"-" json:"userCount"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` } func (r *Department) TableName() string { diff --git a/server/model/job.go b/server/model/job.go index f66a81bee..74036563a 100644 --- a/server/model/job.go +++ b/server/model/job.go @@ -16,6 +16,7 @@ type Job struct { Metadata string `json:"metadata"` Created common.JsonTime `json:"created"` Updated common.JsonTime `json:"updated"` + LastExecAt common.JsonTime `json:"lastExecAt"` } func (r *Job) TableName() string { @@ -27,6 +28,7 @@ type JobLog struct { Timestamp common.JsonTime `json:"timestamp"` JobId string `json:"jobId"` Message string `json:"message"` + Results string `json:"results"` } func (r *JobLog) TableName() string { diff --git a/server/model/session.go b/server/model/session.go index 42dd2c93f..c82102009 100644 --- a/server/model/session.go +++ b/server/model/session.go @@ -47,29 +47,29 @@ func (r *Session) TableName() string { } type SessionForPage struct { - ID string `json:"id" gorm:"column:id"` - Protocol string `json:"protocol" gorm:"column:protocol"` - IP string `json:"ip" gorm:"column:ip"` - Port int `json:"port" gorm:"column:port"` - Username string `json:"username" gorm:"column:username"` - ConnectionId string `json:"connectionId" gorm:"column:connection_id"` - AssetId string `json:"assetId" gorm:"column:asset_id"` - Creator string `json:"userId" gorm:"column:creator"` - ClientIP string `json:"clientIp" gorm:"column:client_ip"` - Width int `json:"width" gorm:"column:width"` - Height int `json:"height" gorm:"column:height"` - Status string `json:"status" gorm:"column:status"` - Recording string `json:"recording" gorm:"column:recording"` - ConnectedTime common.JsonTime `json:"connectedAt" gorm:"column:connected_time"` - DisconnectedTime common.JsonTime `json:"disconnectedAt" gorm:"column:disconnected_time"` - AssetName string `json:"assetName" gorm:"column:asset_name"` - CreatorName string `json:"userAccount" gorm:"column:creator_name"` - Code int `json:"code" gorm:"column:code"` - Message string `json:"message" gorm:"column:message"` - Mode string `json:"mode" gorm:"column:mode"` - Reviewed bool `json:"reviewed" gorm:"column:reviewed"` - CommandCount int64 `json:"commandCount" gorm:"column:command_count"` - ConnectionDuration string `json:"connectionDuration" gorm:"-"` + ID string `json:"id" gorm:"column:id"` + Protocol string `json:"protocol" gorm:"column:protocol"` + IP string `json:"ip" gorm:"column:ip"` + Port int `json:"port" gorm:"column:port"` + Username string `json:"username" gorm:"column:username"` + ConnectionId string `json:"connectionId" gorm:"column:connection_id"` + AssetId string `json:"assetId" gorm:"column:asset_id"` + Creator string `json:"userId" gorm:"column:creator"` + ClientIP string `json:"clientIp" gorm:"column:client_ip"` + Width int `json:"width" gorm:"column:width"` + Height int `json:"height" gorm:"column:height"` + Status string `json:"status" gorm:"column:status"` + Recording string `json:"recording" gorm:"column:recording"` + ConnectedTime common.JsonTime `json:"connectedAt" gorm:"column:connected_time"` + DisconnectedTime common.JsonTime `json:"disconnectedAt" gorm:"column:disconnected_time"` + AssetName string `json:"assetName" gorm:"column:asset_name"` + CreatorName string `json:"userAccount" gorm:"column:creator_name"` + Code int `json:"code" gorm:"column:code"` + Message string `json:"message" gorm:"column:message"` + Mode string `json:"mode" gorm:"column:mode"` + Reviewed bool `json:"reviewed" gorm:"column:reviewed"` + CommandCount int64 `json:"commandCount" gorm:"column:command_count"` + ConnectionDuration string `json:"connectionDuration" gorm:"-"` } type SessionForAccess struct { diff --git a/server/model/user.go b/server/model/user.go index d8231123c..1af98d60d 100644 --- a/server/model/user.go +++ b/server/model/user.go @@ -5,23 +5,23 @@ import ( ) type User struct { - ID string `gorm:"primary_key,type:varchar(36)" json:"id"` - Username string `gorm:"index,type:varchar(200)" json:"username"` - Password string `gorm:"type:varchar(500)" json:"-"` - TOTPSecret string `json:"-"` - Online *bool `json:"online"` - Status string `gorm:"type:varchar(10)" json:"status"` - Created common.JsonTime `json:"createdAt"` - Type string `gorm:"type:varchar(20)" json:"type"` - Nickname string `gorm:"type:varchar(500)" json:"nickname"` - Mail string `gorm:"type:varchar(500)" json:"mail"` - Phone string `gorm:"type:varchar(50)" json:"phone"` - Source string `gorm:"type:varchar(20)" json:"source"` - Recording string `gorm:"type:varchar(20);default:'enabled'" json:"recording"` - Watermark string `gorm:"type:varchar(20);default:'enabled'" json:"watermark"` - PublicKey string `gorm:"type:text" json:"publicKey"` - Remark string `gorm:"type:text" json:"remark"` - Roles []string `gorm:"-" json:"roles"` + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Username string `gorm:"index,type:varchar(200)" json:"username"` + Password string `gorm:"type:varchar(500)" json:"-"` + TOTPSecret string `json:"-"` + Online *bool `json:"online"` + Status string `gorm:"type:varchar(10)" json:"status"` + Created common.JsonTime `json:"createdAt"` + Type string `gorm:"type:varchar(20)" json:"type"` + Nickname string `gorm:"type:varchar(500)" json:"nickname"` + Mail string `gorm:"type:varchar(500)" json:"mail"` + Phone string `gorm:"type:varchar(50)" json:"phone"` + Source string `gorm:"type:varchar(20)" json:"source"` + Recording string `gorm:"type:varchar(20);default:'enabled'" json:"recording"` + Watermark string `gorm:"type:varchar(20);default:'enabled'" json:"watermark"` + PublicKey string `gorm:"type:text" json:"publicKey"` + Remark string `gorm:"type:text" json:"remark"` + Roles []string `gorm:"-" json:"roles"` Departments []UserDepartment `gorm:"-" json:"departments"` } @@ -31,22 +31,22 @@ type UserDepartment struct { } type UserForPage struct { - ID string `json:"id"` - Username string `json:"username"` - Nickname string `json:"nickname"` - TOTPSecret string `json:"totpSecret"` - Mail string `json:"mail"` - Phone string `json:"phone"` - Online bool `json:"online"` - Status string `json:"status"` - Created common.JsonTime `json:"createdAt"` - Type string `json:"type"` - Source string `json:"source"` - Recording string `json:"recording"` - Watermark string `json:"watermark"` - Remark string `json:"remark"` - SharerAssetCount int64 `json:"sharerAssetCount"` - Departments []UserDepartment `json:"departments"` + ID string `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + TOTPSecret string `json:"totpSecret"` + Mail string `json:"mail"` + Phone string `json:"phone"` + Online bool `json:"online"` + Status string `json:"status"` + Created common.JsonTime `json:"createdAt"` + Type string `json:"type"` + Source string `json:"source"` + Recording string `json:"recording"` + Watermark string `json:"watermark"` + Remark string `json:"remark"` + SharerAssetCount int64 `json:"sharerAssetCount"` + Departments []UserDepartment `gorm:"-" json:"departments"` LastLoginAt *common.JsonTime `json:"lastLoginAt"` } diff --git a/server/model/website.go b/server/model/website.go index 29054e083..843e356e9 100644 --- a/server/model/website.go +++ b/server/model/website.go @@ -3,28 +3,28 @@ package model import "next-terminal/server/common" type Website struct { - ID string `gorm:"primary_key,type:varchar(36)" json:"id"` - Name string `gorm:"type:varchar(500)" json:"name"` - Enabled bool `json:"enabled"` - TargetUrl string `gorm:"type:text" json:"targetUrl"` - TargetHost string `gorm:"type:varchar(500)" json:"targetHost"` - TargetPort int `json:"targetPort"` - Domain string `gorm:"type:varchar(500)" json:"domain"` - AsciiDomain string `gorm:"type:varchar(500)" json:"asciiDomain"` - Entrance string `gorm:"type:varchar(50);default:'default'" json:"entrance"` - Description string `gorm:"type:varchar(1000)" json:"description"` - Status string `gorm:"type:varchar(50);default:'unknown'" json:"status"` - StatusText string `gorm:"type:text" json:"statusText"` - GatewayType string `gorm:"type:varchar(20);default:''" json:"gatewayType"` - GatewayId string `gorm:"type:varchar(36);default:''" json:"gatewayId"` - BasicAuth string `gorm:"type:text" json:"basicAuth"` - Headers string `gorm:"type:text" json:"headers"` - Cert string `gorm:"type:text" json:"cert"` - Public string `gorm:"type:text" json:"public"` - TempAllow string `gorm:"type:text" json:"tempAllow"` - Created common.JsonTime `json:"createdAt"` - GroupId string `gorm:"type:varchar(36);default:''" json:"groupId"` - Sort string `gorm:"type:varchar(50);default:''" json:"sort"` + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Name string `gorm:"type:varchar(500)" json:"name"` + Enabled bool `json:"enabled"` + TargetUrl string `gorm:"type:text" json:"targetUrl"` + TargetHost string `gorm:"type:varchar(500)" json:"targetHost"` + TargetPort int `json:"targetPort"` + Domain string `gorm:"type:varchar(500)" json:"domain"` + AsciiDomain string `gorm:"type:varchar(500)" json:"asciiDomain"` + Entrance string `gorm:"type:varchar(50);default:'default'" json:"entrance"` + Description string `gorm:"type:varchar(1000)" json:"description"` + Status string `gorm:"type:varchar(50);default:'unknown'" json:"status"` + StatusText string `gorm:"type:text" json:"statusText"` + GatewayType string `gorm:"type:varchar(20);default:''" json:"gatewayType"` + GatewayId string `gorm:"type:varchar(36);default:''" json:"gatewayId"` + BasicAuth string `gorm:"type:text" json:"basicAuth"` + Headers string `gorm:"type:text" json:"headers"` + Cert string `gorm:"type:text" json:"cert"` + Public string `gorm:"type:text" json:"public"` + TempAllow string `gorm:"type:text" json:"tempAllow"` + Created common.JsonTime `json:"createdAt"` + GroupId string `gorm:"type:varchar(36);default:''" json:"groupId"` + Sort string `gorm:"type:varchar(50);default:''" json:"sort"` } func (r *Website) TableName() string { diff --git a/server/model/website_group.go b/server/model/website_group.go new file mode 100644 index 000000000..c48ca891a --- /dev/null +++ b/server/model/website_group.go @@ -0,0 +1,15 @@ +package model + +import "next-terminal/server/common" + +type WebsiteGroup struct { + ID string `gorm:"primary_key,type:varchar(36)" json:"id"` + Name string `gorm:"type:varchar(200)" json:"name"` + ParentId string `gorm:"type:varchar(36)" json:"parentId"` + Sort int `json:"sort"` + Created common.JsonTime `gorm:"type:datetime" json:"createdAt"` +} + +func (r *WebsiteGroup) TableName() string { + return "website_groups" +} diff --git a/server/repository/authorised_asset.go b/server/repository/authorised_asset.go new file mode 100644 index 000000000..c5d27aa4e --- /dev/null +++ b/server/repository/authorised_asset.go @@ -0,0 +1,389 @@ +package repository + +import ( + "context" + "strconv" + + "next-terminal/server/common" + "next-terminal/server/model" +) + +var AuthorisedAssetRepository = new(authorisedAssetRepository) + +type authorisedAssetRepository struct { + baseRepository +} + +func (r authorisedAssetRepository) Find(c context.Context, pageIndex, pageSize int, userId, departmentId, assetGroupId, assetId string) (o []model.AuthorisedAsset, total int64, err error) { + db := r.GetDB(c).Model(&model.AuthorisedAsset{}) + + if userId != "" { + db = db.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("department_id = ?", departmentId) + } + if assetGroupId != "" { + db = db.Where("asset_group_id = ?", assetGroupId) + } + if assetId != "" { + db = db.Where("asset_id = ?", assetId) + } + + err = db.Count(&total).Error + if err != nil { + return + } + + err = db.Order("created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func (r authorisedAssetRepository) FindById(c context.Context, id string) (o model.AuthorisedAsset, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r authorisedAssetRepository) Create(c context.Context, o *model.AuthorisedAsset) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r authorisedAssetRepository) CreateInBatches(c context.Context, items []model.AuthorisedAsset) error { + for i := range items { + items[i].Created = common.NowJsonTime() + } + return r.GetDB(c).CreateInBatches(items, 100).Error +} + +func (r authorisedAssetRepository) UpdateById(c context.Context, o *model.AuthorisedAsset) error { + return r.GetDB(c).Updates(o).Error +} + +func (r authorisedAssetRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.AuthorisedAsset{}).Error +} + +func (r authorisedAssetRepository) Selected(c context.Context, expect, userId, departmentId, assetId string) (result []string, err error) { + var items []model.AuthorisedAsset + db := r.GetDB(c) + switch expect { + case "userId": + db = db.Select("user_id") + case "departmentId": + db = db.Select("department_id") + case "assetId": + db = db.Select("asset_id") + case "assetGroupId": + db = db.Select("asset_group_id") + } + + if userId != "" { + db = db.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("department_id = ?", departmentId) + } + if assetId != "" { + db = db.Where("asset_id = ?", assetId) + } + + err = db.Find(&items).Error + if err != nil { + return + } + + for _, item := range items { + switch expect { + case "userId": + if item.UserId != "" { + result = append(result, item.UserId) + } + case "departmentId": + if item.DepartmentId != "" { + result = append(result, item.DepartmentId) + } + case "assetId": + if item.AssetId != "" { + result = append(result, item.AssetId) + } + case "assetGroupId": + if item.AssetGroupId != "" { + result = append(result, item.AssetGroupId) + } + } + } + return +} + +func (r authorisedAssetRepository) FindWithDetails(c context.Context, pageIndex, pageSize int, userId, departmentId, assetGroupId, assetId string) (o []map[string]interface{}, total int64, err error) { + db := r.GetDB(c).Table("authorised_assets"). + Select(`authorised_assets.id, + strftime('%s', authorised_assets.created) * 1000 as "createdAt", + authorised_assets.expired_at as "expiredAt", + authorised_assets.user_id as "userId", users.nickname as "userName", + authorised_assets.department_id as "departmentId", departments.name as "departmentName", + authorised_assets.asset_id as "assetId", assets.name as "assetName", + authorised_assets.asset_group_id as "assetGroupId", asset_groups.name as "assetGroupName", + authorised_assets.strategy_id as "strategyId", strategies.name as "strategyName"`). + Joins("left join users on users.id = authorised_assets.user_id"). + Joins("left join departments on departments.id = authorised_assets.department_id"). + Joins("left join assets on assets.id = authorised_assets.asset_id"). + Joins("left join asset_groups on asset_groups.id = authorised_assets.asset_group_id"). + Joins("left join strategies on strategies.id = authorised_assets.strategy_id") + + dbCounter := r.GetDB(c).Model(&model.AuthorisedAsset{}) + + if userId != "" { + db = db.Where("authorised_assets.user_id = ?", userId) + dbCounter = dbCounter.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("authorised_assets.department_id = ?", departmentId) + dbCounter = dbCounter.Where("department_id = ?", departmentId) + } + if assetGroupId != "" { + db = db.Where("authorised_assets.asset_group_id = ?", assetGroupId) + dbCounter = dbCounter.Where("asset_group_id = ?", assetGroupId) + } + if assetId != "" { + db = db.Where("authorised_assets.asset_id = ?", assetId) + dbCounter = dbCounter.Where("asset_id = ?", assetId) + } + + err = dbCounter.Count(&total).Error + if err != nil { + return + } + + err = db.Order("authorised_assets.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +var AuthorisedDatabaseAssetRepository = new(authorisedDatabaseAssetRepository) + +type authorisedDatabaseAssetRepository struct { + baseRepository +} + +func (r authorisedDatabaseAssetRepository) Find(c context.Context, pageIndex, pageSize int, userId, departmentId, databaseAssetId string) (o []model.AuthorisedDatabaseAsset, total int64, err error) { + db := r.GetDB(c).Model(&model.AuthorisedDatabaseAsset{}) + + if userId != "" { + db = db.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("department_id = ?", departmentId) + } + if databaseAssetId != "" { + db = db.Where("database_asset_id = ?", databaseAssetId) + } + + err = db.Count(&total).Error + if err != nil { + return + } + + err = db.Order("created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func (r authorisedDatabaseAssetRepository) FindById(c context.Context, id string) (o model.AuthorisedDatabaseAsset, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r authorisedDatabaseAssetRepository) Create(c context.Context, o *model.AuthorisedDatabaseAsset) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r authorisedDatabaseAssetRepository) CreateInBatches(c context.Context, items []model.AuthorisedDatabaseAsset) error { + for i := range items { + items[i].Created = common.NowJsonTime() + } + return r.GetDB(c).CreateInBatches(items, 100).Error +} + +func (r authorisedDatabaseAssetRepository) UpdateById(c context.Context, o *model.AuthorisedDatabaseAsset) error { + return r.GetDB(c).Updates(o).Error +} + +func (r authorisedDatabaseAssetRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.AuthorisedDatabaseAsset{}).Error +} + +func (r authorisedDatabaseAssetRepository) Selected(c context.Context, expect, userId, departmentId, databaseAssetId string) (result []string, err error) { + var items []model.AuthorisedDatabaseAsset + db := r.GetDB(c) + switch expect { + case "userId": + db = db.Select("user_id") + case "departmentId": + db = db.Select("department_id") + case "databaseAssetId": + db = db.Select("database_asset_id") + } + + if userId != "" { + db = db.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("department_id = ?", departmentId) + } + if databaseAssetId != "" { + db = db.Where("database_asset_id = ?", databaseAssetId) + } + + err = db.Find(&items).Error + if err != nil { + return + } + + for _, item := range items { + switch expect { + case "userId": + if item.UserId != "" { + result = append(result, item.UserId) + } + case "departmentId": + if item.DepartmentId != "" { + result = append(result, item.DepartmentId) + } + case "databaseAssetId": + if item.DatabaseAssetId != "" { + result = append(result, item.DatabaseAssetId) + } + } + } + return +} + +func (r authorisedDatabaseAssetRepository) FindWithDetails(c context.Context, pageIndex, pageSize int, userId, departmentId, databaseAssetId string) (o []map[string]interface{}, total int64, err error) { + db := r.GetDB(c).Table("authorised_database_assets"). + Select(`authorised_database_assets.id, + strftime('%s', authorised_database_assets.created) * 1000 as "createdAt", + authorised_database_assets.expired_at as "expiredAt", + authorised_database_assets.user_id as "userId", users.nickname as "userName", + authorised_database_assets.department_id as "departmentId", departments.name as "departmentName", + authorised_database_assets.database_asset_id as "databaseAssetId", database_assets.name as "databaseAssetName", + authorised_database_assets.strategy_id as "strategyId", strategies.name as "strategyName"`). + Joins("left join users on users.id = authorised_database_assets.user_id"). + Joins("left join departments on departments.id = authorised_database_assets.department_id"). + Joins("left join database_assets on database_assets.id = authorised_database_assets.database_asset_id"). + Joins("left join strategies on strategies.id = authorised_database_assets.strategy_id") + + dbCounter := r.GetDB(c).Model(&model.AuthorisedDatabaseAsset{}) + + if userId != "" { + db = db.Where("authorised_database_assets.user_id = ?", userId) + dbCounter = dbCounter.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("authorised_database_assets.department_id = ?", departmentId) + dbCounter = dbCounter.Where("department_id = ?", departmentId) + } + if databaseAssetId != "" { + db = db.Where("authorised_database_assets.database_asset_id = ?", databaseAssetId) + dbCounter = dbCounter.Where("database_asset_id = ?", databaseAssetId) + } + + err = dbCounter.Count(&total).Error + if err != nil { + return + } + + err = db.Order("authorised_database_assets.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +var AuthorisedWebsiteRepository = new(authorisedWebsiteRepository) + +type authorisedWebsiteRepository struct { + baseRepository +} + +func (r authorisedWebsiteRepository) Find(c context.Context, pageIndex, pageSize int, userId, departmentId, websiteId string) (o []model.AuthorisedWebsite, total int64, err error) { + db := r.GetDB(c).Model(&model.AuthorisedWebsite{}) + + if userId != "" { + db = db.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("department_id = ?", departmentId) + } + if websiteId != "" { + db = db.Where("website_id = ?", websiteId) + } + + err = db.Count(&total).Error + if err != nil { + return + } + + err = db.Order("created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func (r authorisedWebsiteRepository) FindById(c context.Context, id string) (o model.AuthorisedWebsite, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r authorisedWebsiteRepository) Create(c context.Context, o *model.AuthorisedWebsite) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r authorisedWebsiteRepository) CreateInBatches(c context.Context, items []model.AuthorisedWebsite) error { + for i := range items { + items[i].Created = common.NowJsonTime() + } + return r.GetDB(c).CreateInBatches(items, 100).Error +} + +func (r authorisedWebsiteRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.AuthorisedWebsite{}).Error +} + +func (r authorisedWebsiteRepository) FindWithDetails(c context.Context, pageIndex, pageSize int, userId, departmentId, websiteId string) (o []map[string]interface{}, total int64, err error) { + db := r.GetDB(c).Table("authorised_websites"). + Select(`authorised_websites.id, + strftime('%s', authorised_websites.created) * 1000 as "createdAt", + authorised_websites.expired_at as "expiredAt", + authorised_websites.user_id as "userId", users.nickname as "userName", + authorised_websites.department_id as "departmentId", departments.name as "departmentName", + authorised_websites.website_id as "websiteId", websites.name as "websiteName", + authorised_websites.strategy_id as "strategyId", strategies.name as "strategyName"`). + Joins("left join users on users.id = authorised_websites.user_id"). + Joins("left join departments on departments.id = authorised_websites.department_id"). + Joins("left join websites on websites.id = authorised_websites.website_id"). + Joins("left join strategies on strategies.id = authorised_websites.strategy_id") + + dbCounter := r.GetDB(c).Model(&model.AuthorisedWebsite{}) + + if userId != "" { + db = db.Where("authorised_websites.user_id = ?", userId) + dbCounter = dbCounter.Where("user_id = ?", userId) + } + if departmentId != "" { + db = db.Where("authorised_websites.department_id = ?", departmentId) + dbCounter = dbCounter.Where("department_id = ?", departmentId) + } + if websiteId != "" { + db = db.Where("authorised_websites.website_id = ?", websiteId) + dbCounter = dbCounter.Where("website_id = ?", websiteId) + } + + err = dbCounter.Count(&total).Error + if err != nil { + return + } + + err = db.Order("authorised_websites.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func init() { + _ = strconv.Itoa(0) +} diff --git a/server/repository/command_filter.go b/server/repository/command_filter.go new file mode 100644 index 000000000..da142e288 --- /dev/null +++ b/server/repository/command_filter.go @@ -0,0 +1,110 @@ +package repository + +import ( + "context" + + "next-terminal/server/common" + "next-terminal/server/model" +) + +var CommandFilterRepository = new(commandFilterRepository) + +type commandFilterRepository struct { + baseRepository +} + +func (r commandFilterRepository) FindAll(c context.Context) (o []model.CommandFilter, err error) { + err = r.GetDB(c).Order("name asc").Find(&o).Error + return +} + +func (r commandFilterRepository) Find(c context.Context, pageIndex, pageSize int, name, order, field string) (o []model.CommandFilter, total int64, err error) { + db := r.GetDB(c).Model(&model.CommandFilter{}) + + if len(name) > 0 { + db = db.Where("name like ?", "%"+name+"%") + } + + err = db.Count(&total).Error + if err != nil { + return + } + + if order == "" { + order = "asc" + } + if field == "" { + field = "name" + } + + err = db.Order(field + " " + order).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func (r commandFilterRepository) FindById(c context.Context, id string) (o model.CommandFilter, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r commandFilterRepository) Create(c context.Context, o *model.CommandFilter) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r commandFilterRepository) UpdateById(c context.Context, o *model.CommandFilter) error { + return r.GetDB(c).Updates(o).Error +} + +func (r commandFilterRepository) DeleteById(c context.Context, id string) error { + tx := r.GetDB(c).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := tx.Where("command_filter_id = ?", id).Delete(&model.CommandFilterRule{}).Error; err != nil { + tx.Rollback() + return err + } + + if err := tx.Where("id = ?", id).Delete(&model.CommandFilter{}).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + +var CommandFilterRuleRepository = new(commandFilterRuleRepository) + +type commandFilterRuleRepository struct { + baseRepository +} + +func (r commandFilterRuleRepository) FindByCommandFilterId(c context.Context, commandFilterId string) (o []model.CommandFilterRule, err error) { + err = r.GetDB(c).Where("command_filter_id = ?", commandFilterId).Order("priority asc").Find(&o).Error + return +} + +func (r commandFilterRuleRepository) FindById(c context.Context, id string) (o model.CommandFilterRule, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r commandFilterRuleRepository) Create(c context.Context, o *model.CommandFilterRule) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r commandFilterRuleRepository) UpdateById(c context.Context, o *model.CommandFilterRule) error { + return r.GetDB(c).Updates(o).Error +} + +func (r commandFilterRuleRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.CommandFilterRule{}).Error +} + +func (r commandFilterRuleRepository) DeleteByCommandFilterId(c context.Context, commandFilterId string) error { + return r.GetDB(c).Where("command_filter_id = ?", commandFilterId).Delete(&model.CommandFilterRule{}).Error +} diff --git a/server/repository/database_asset.go b/server/repository/database_asset.go new file mode 100644 index 000000000..67e84a3fa --- /dev/null +++ b/server/repository/database_asset.go @@ -0,0 +1,148 @@ +package repository + +import ( + "context" + "strconv" + "strings" + + "next-terminal/server/model" +) + +var DatabaseAssetRepository = new(databaseAssetRepository) + +type databaseAssetRepository struct { + baseRepository +} + +func (r databaseAssetRepository) FindAll(c context.Context) (o []model.DatabaseAsset, err error) { + err = r.GetDB(c).Order("name asc").Find(&o).Error + return +} + +func (r databaseAssetRepository) FindByType(c context.Context, dbType string) (o []model.DatabaseAsset, err error) { + db := r.GetDB(c) + if dbType != "" { + db = db.Where("type = ?", dbType) + } + err = db.Order("name asc").Find(&o).Error + return +} + +func (r databaseAssetRepository) Find(c context.Context, pageIndex, pageSize int, name, dbType, order, field string) (o []model.DatabaseAsset, total int64, err error) { + db := r.GetDB(c).Model(&model.DatabaseAsset{}) + + if len(name) > 0 { + db = db.Where("name like ?", "%"+name+"%") + } + + if len(dbType) > 0 { + db = db.Where("type = ?", dbType) + } + + err = db.Count(&total).Error + if err != nil { + return + } + + if order == "" { + order = "asc" + } + if field == "" { + field = "name" + } + + orderBy := field + " " + order + if field == "created" { + orderBy = "created " + order + } + + err = db.Order(orderBy).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func (r databaseAssetRepository) FindById(c context.Context, id string) (o model.DatabaseAsset, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r databaseAssetRepository) Create(c context.Context, o *model.DatabaseAsset) error { + return r.GetDB(c).Create(o).Error +} + +func (r databaseAssetRepository) UpdateById(c context.Context, o *model.DatabaseAsset) error { + return r.GetDB(c).Updates(o).Error +} + +func (r databaseAssetRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.DatabaseAsset{}).Error +} + +func (r databaseAssetRepository) Count(c context.Context) (total int64, err error) { + err = r.GetDB(c).Model(&model.DatabaseAsset{}).Count(&total).Error + return +} + +func (r databaseAssetRepository) FindAllTags(c context.Context) (o []string, err error) { + var assets []model.DatabaseAsset + err = r.GetDB(c).Select("tags").Find(&assets).Error + if err != nil { + return + } + + tagSet := make(map[string]bool) + for _, asset := range assets { + if asset.Tags == "" { + continue + } + tags := strings.Split(asset.Tags, ",") + for _, tag := range tags { + tag = strings.TrimSpace(tag) + if tag != "" { + tagSet[tag] = true + } + } + } + + for tag := range tagSet { + o = append(o, tag) + } + return +} + +func (r databaseAssetRepository) ParseTags(tags string) []string { + if tags == "" { + return nil + } + return strings.Split(tags, ",") +} + +func (r databaseAssetRepository) FormatTags(tags []string) string { + if len(tags) == 0 { + return "" + } + return strings.Join(tags, ",") +} + +func (r databaseAssetRepository) GetDBPort(dbType string) int { + switch dbType { + case "mysql": + return 3306 + case "pg": + return 5432 + case "sqlserver": + return 1433 + case "oracle": + return 1521 + default: + return 3306 + } +} + +func (r databaseAssetRepository) UpdateOwner(c context.Context, ownerId string, newOwnerId string) error { + return r.GetDB(c).Model(&model.DatabaseAsset{}).Where("owner = ?", ownerId).Update("owner", newOwnerId).Error +} + +func init() { + _ = strconv.Itoa(0) + _ = strings.Join(nil, "") +} diff --git a/server/repository/department.go b/server/repository/department.go new file mode 100644 index 000000000..dcfa517ff --- /dev/null +++ b/server/repository/department.go @@ -0,0 +1,109 @@ +package repository + +import ( + "context" + + "next-terminal/server/common" + "next-terminal/server/model" +) + +var DepartmentRepository = new(departmentRepository) + +type departmentRepository struct { + baseRepository +} + +func (r departmentRepository) FindAll(c context.Context) (o []model.Department, err error) { + err = r.GetDB(c).Order("sort asc, name asc").Find(&o).Error + return +} + +func (r departmentRepository) Find(c context.Context, pageIndex, pageSize int, name, order, field string) (o []model.Department, total int64, err error) { + db := r.GetDB(c).Model(&model.Department{}) + + if len(name) > 0 { + db = db.Where("name like ?", "%"+name+"%") + } + + err = db.Count(&total).Error + if err != nil { + return + } + + if order == "" { + order = "asc" + } + if field == "" { + field = "sort" + } + + err = db.Order(field + " " + order).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + return +} + +func (r departmentRepository) FindById(c context.Context, id string) (o model.Department, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r departmentRepository) FindByParentId(c context.Context, parentId string) (o []model.Department, err error) { + db := r.GetDB(c) + if parentId == "" { + db = db.Where("parent_id = '' or parent_id is null") + } else { + db = db.Where("parent_id = ?", parentId) + } + err = db.Order("sort asc, name asc").Find(&o).Error + return +} + +func (r departmentRepository) Create(c context.Context, o *model.Department) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r departmentRepository) UpdateById(c context.Context, o *model.Department) error { + return r.GetDB(c).Updates(o).Error +} + +func (r departmentRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.Department{}).Error +} + +func (r departmentRepository) CountByParentId(c context.Context, parentId string) (total int64, err error) { + err = r.GetDB(c).Model(&model.Department{}).Where("parent_id = ?", parentId).Count(&total).Error + return +} + +func (r departmentRepository) BuildTree(departments []model.Department) []map[string]interface{} { + departmentMap := make(map[string][]map[string]interface{}) + var roots []map[string]interface{} + + for _, dept := range departments { + node := map[string]interface{}{ + "title": dept.Name, + "key": dept.ID, + "value": dept.ID, + "children": []map[string]interface{}{}, + } + if dept.ParentId == "" { + roots = append(roots, node) + } else { + departmentMap[dept.ParentId] = append(departmentMap[dept.ParentId], node) + } + } + + var buildChildren func(nodes []map[string]interface{}) + buildChildren = func(nodes []map[string]interface{}) { + for i := range nodes { + id := nodes[i]["key"].(string) + if children, ok := departmentMap[id]; ok { + nodes[i]["children"] = children + buildChildren(children) + } + } + } + + buildChildren(roots) + return roots +} diff --git a/server/repository/user.go b/server/repository/user.go index 5e74f3ae5..d575c0dcd 100644 --- a/server/repository/user.go +++ b/server/repository/user.go @@ -7,6 +7,8 @@ import ( "next-terminal/server/model" ) +const SuperAdminID = `abcdefghijklmnopqrstuvwxyz` + var UserRepository = new(userRepository) type userRepository struct { @@ -18,7 +20,7 @@ func (r userRepository) FindAll(c context.Context) (o []model.User, err error) { return } -func (r userRepository) Find(c context.Context, pageIndex, pageSize int, username, nickname, mail, online, loginPolicyId, order, field string) (o []model.UserForPage, total int64, err error) { +func (r userRepository) Find(c context.Context, pageIndex, pageSize int, username, nickname, mail, online, userType, loginPolicyId, order, field string) (o []model.UserForPage, total int64, err error) { db := r.GetDB(c).Table("users").Select("users.id,users.username,users.nickname,users.mail,users.phone,users.online,users.created,users.type,users.status,users.source,users.recording,users.watermark, users.totp_secret") dbCounter := r.GetDB(c).Table("users") @@ -54,6 +56,17 @@ func (r userRepository) Find(c context.Context, pageIndex, pageSize int, usernam dbCounter = dbCounter.Where("users.online = ?", _online) } + if userType == "super-admin" { + db = db.Where("users.id = ?", SuperAdminID) + dbCounter = dbCounter.Where("id = ?", SuperAdminID) + } else if userType == "admin" { + db = db.Where("users.type = ? and users.id != ?", "admin", SuperAdminID) + dbCounter = dbCounter.Where("type = ? and id != ?", "admin", SuperAdminID) + } else if userType == "user" { + db = db.Where("users.type = ?", "user") + dbCounter = dbCounter.Where("type = ?", "user") + } + err = dbCounter.Count(&total).Error if err != nil { return nil, 0, err diff --git a/server/repository/user_department.go b/server/repository/user_department.go new file mode 100644 index 000000000..9c3c31b0b --- /dev/null +++ b/server/repository/user_department.go @@ -0,0 +1,106 @@ +package repository + +import ( + "context" + + "next-terminal/server/model" +) + +var UserDepartmentRepository = new(userDepartmentRepository) + +type userDepartmentRepository struct { + baseRepository +} + +func (r userDepartmentRepository) FindByUserId(c context.Context, userId string) (o []model.UserDepartmentRef, err error) { + err = r.GetDB(c).Where("user_id = ?", userId).Find(&o).Error + return +} + +func (r userDepartmentRepository) FindByDepartmentId(c context.Context, departmentId string) (o []model.UserDepartmentRef, err error) { + err = r.GetDB(c).Where("department_id = ?", departmentId).Find(&o).Error + return +} + +func (r userDepartmentRepository) FindUsersByDepartmentId(c context.Context, departmentId string) (userIds []string, err error) { + var refs []model.UserDepartmentRef + err = r.GetDB(c).Where("department_id = ?", departmentId).Find(&refs).Error + if err != nil { + return + } + for _, ref := range refs { + userIds = append(userIds, ref.UserId) + } + return +} + +func (r userDepartmentRepository) SaveUserDepartments(c context.Context, userId string, departmentIds []string) error { + tx := r.GetDB(c).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := tx.Where("user_id = ?", userId).Delete(&model.UserDepartmentRef{}).Error; err != nil { + tx.Rollback() + return err + } + + for _, deptId := range departmentIds { + ref := model.UserDepartmentRef{ + UserId: userId, + DepartmentId: deptId, + } + if err := tx.Create(&ref).Error; err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit().Error +} + +func (r userDepartmentRepository) SaveDepartmentUsers(c context.Context, departmentId string, userIds []string) error { + tx := r.GetDB(c).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := tx.Where("department_id = ?", departmentId).Delete(&model.UserDepartmentRef{}).Error; err != nil { + tx.Rollback() + return err + } + + for _, userId := range userIds { + ref := model.UserDepartmentRef{ + UserId: userId, + DepartmentId: departmentId, + } + if err := tx.Create(&ref).Error; err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit().Error +} + +func (r userDepartmentRepository) RemoveUsersFromDepartment(c context.Context, departmentId string, userIds []string) error { + return r.GetDB(c).Where("department_id = ? AND user_id IN ?", departmentId, userIds).Delete(&model.UserDepartmentRef{}).Error +} + +func (r userDepartmentRepository) DeleteByUserId(c context.Context, userId string) error { + return r.GetDB(c).Where("user_id = ?", userId).Delete(&model.UserDepartmentRef{}).Error +} + +func (r userDepartmentRepository) DeleteByDepartmentId(c context.Context, departmentId string) error { + return r.GetDB(c).Where("department_id = ?", departmentId).Delete(&model.UserDepartmentRef{}).Error +} + +func (r userDepartmentRepository) CountByDepartmentId(c context.Context, departmentId string) (total int64, err error) { + err = r.GetDB(c).Model(&model.UserDepartmentRef{}).Where("department_id = ?", departmentId).Count(&total).Error + return +} diff --git a/server/repository/website.go b/server/repository/website.go index d7fe6b050..2fc7344a3 100644 --- a/server/repository/website.go +++ b/server/repository/website.go @@ -3,6 +3,7 @@ package repository import ( "context" + "next-terminal/server/common" "next-terminal/server/model" ) @@ -18,7 +19,8 @@ func (r websiteRepository) FindAll(c context.Context) (o []model.Website, err er } func (r websiteRepository) Find(c context.Context, pageIndex, pageSize int, keyword string) (o []model.WebsiteForPage, total int64, err error) { - db := r.GetDB(c).Table("websites").Select("id,name,enabled,target_url,target_host,target_port,domain,status,status_text,created,group_id,sort") + db := r.GetDB(c).Table("websites").Select(`id,name,enabled,target_url,target_host,target_port,domain,status,status_text, + strftime('%s', created) * 1000 as created,group_id,sort`) dbCounter := r.GetDB(c).Table("websites") if len(keyword) > 0 { @@ -39,6 +41,7 @@ func (r websiteRepository) Find(c context.Context, pageIndex, pageSize int, keyw } func (r websiteRepository) Create(c context.Context, o *model.Website) error { + o.Created = common.NowJsonTime() return r.GetDB(c).Create(o).Error } @@ -55,3 +58,8 @@ func (r websiteRepository) FindById(c context.Context, id string) (o model.Websi err = r.GetDB(c).Where("id = ?", id).First(&o).Error return } + +func (r websiteRepository) Count(c context.Context) (total int64, err error) { + err = r.GetDB(c).Model(&model.Website{}).Count(&total).Error + return +} diff --git a/server/repository/website_group.go b/server/repository/website_group.go new file mode 100644 index 000000000..617701ad1 --- /dev/null +++ b/server/repository/website_group.go @@ -0,0 +1,50 @@ +package repository + +import ( + "context" + + "next-terminal/server/common" + "next-terminal/server/model" +) + +var WebsiteGroupRepository = new(websiteGroupRepository) + +type websiteGroupRepository struct { + baseRepository +} + +func (r websiteGroupRepository) FindAll(c context.Context) (o []model.WebsiteGroup, err error) { + err = r.GetDB(c).Order("sort asc, name asc").Find(&o).Error + return +} + +func (r websiteGroupRepository) FindById(c context.Context, id string) (o model.WebsiteGroup, err error) { + err = r.GetDB(c).Where("id = ?", id).First(&o).Error + return +} + +func (r websiteGroupRepository) Create(c context.Context, o *model.WebsiteGroup) error { + o.Created = common.NowJsonTime() + return r.GetDB(c).Create(o).Error +} + +func (r websiteGroupRepository) UpdateById(c context.Context, o *model.WebsiteGroup) error { + return r.GetDB(c).Updates(o).Error +} + +func (r websiteGroupRepository) DeleteById(c context.Context, id string) error { + return r.GetDB(c).Where("id = ?", id).Delete(&model.WebsiteGroup{}).Error +} + +func (r websiteGroupRepository) DeleteByParentId(c context.Context, parentId string) error { + return r.GetDB(c).Where("parent_id = ?", parentId).Delete(&model.WebsiteGroup{}).Error +} + +func (r websiteGroupRepository) CountByParentId(c context.Context, parentId string) (total int64, err error) { + err = r.GetDB(c).Model(&model.WebsiteGroup{}).Where("parent_id = ?", parentId).Count(&total).Error + return +} + +func (r websiteGroupRepository) DeleteAll(c context.Context) error { + return r.GetDB(c).Where("1 = 1").Delete(&model.WebsiteGroup{}).Error +} diff --git a/server/service/job_exec_shell.go b/server/service/job_exec_shell.go index c00a3dc1f..20757dce2 100644 --- a/server/service/job_exec_shell.go +++ b/server/service/job_exec_shell.go @@ -30,6 +30,14 @@ type MetadataShell struct { Shell string } +type ExecScriptResult struct { + Name string `json:"name"` + Success bool `json:"success"` + UsedTime int64 `json:"usedTime"` + UsedTimeStr string `json:"usedTimeStr"` + Result string `json:"result"` +} + func (r ShellJob) Run() { if r.ID == "" { return @@ -49,6 +57,14 @@ func (r ShellJob) Run() { func (r ShellJob) executeShellByAssets(assets []model.Asset) { if len(assets) == 0 { + jobLog := model.JobLog{ + ID: utils.UUID(), + JobId: r.ID, + Timestamp: common.NowJsonTime(), + Message: "没有找到符合条件的SSH资产", + Results: "[]", + } + _ = repository.JobLogRepository.Create(context.TODO(), &jobLog) return } @@ -59,12 +75,23 @@ func (r ShellJob) executeShellByAssets(assets []model.Asset) { return } - msgChan := make(chan string) + type execResult struct { + msg string + result ExecScriptResult + } + resultChan := make(chan execResult) for i := range assets { asset, err := AssetService.FindByIdAndDecrypt(context.TODO(), assets[i].ID) if err != nil { - msgChan <- fmt.Sprintf("资产「%v」Shell执行失败,查询数据异常「%v」", assets[i].Name, err.Error()) - return + resultChan <- execResult{ + msg: fmt.Sprintf("资产「%v」Shell执行失败,查询数据异常「%v」", assets[i].Name, err.Error()), + result: ExecScriptResult{ + Name: assets[i].Name, + Success: false, + Result: err.Error(), + }, + } + continue } var ( @@ -79,8 +106,15 @@ func (r ShellJob) executeShellByAssets(assets []model.Asset) { if asset.AccountType == "credential" { credential, err := CredentialService.FindByIdAndDecrypt(context.TODO(), asset.CredentialId) if err != nil { - msgChan <- fmt.Sprintf("资产「%v」Shell执行失败,查询授权凭证数据异常「%v」", assets[i].Name, err.Error()) - return + resultChan <- execResult{ + msg: fmt.Sprintf("资产「%v」Shell执行失败,查询授权凭证数据异常「%v」", assets[i].Name, err.Error()), + result: ExecScriptResult{ + Name: asset.Name, + Success: false, + Result: err.Error(), + }, + } + continue } if credential.Type == nt.Custom { @@ -98,33 +132,54 @@ func (r ShellJob) executeShellByAssets(assets []model.Asset) { result, err := execute(metadataShell.Shell, asset.AccessGatewayId, ip, port, username, password, privateKey, passphrase) elapsed := time.Since(t1) var msg string + var execRes ExecScriptResult if err != nil { if errors.Is(gorm.ErrRecordNotFound, err) { msg = fmt.Sprintf("资产「%v」Shell执行失败,请检查资产所关联接入网关是否存在,耗时「%v」", asset.Name, elapsed) } else { msg = fmt.Sprintf("资产「%v」Shell执行失败,错误内容为:「%v」,耗时「%v」", asset.Name, err.Error(), elapsed) } + execRes = ExecScriptResult{ + Name: asset.Name, + Success: false, + UsedTime: elapsed.Milliseconds(), + UsedTimeStr: elapsed.String(), + Result: err.Error(), + } log.Debug(msg) } else { msg = fmt.Sprintf("资产「%v」Shell执行成功,返回值「%v」,耗时「%v」", asset.Name, result, elapsed) + execRes = ExecScriptResult{ + Name: asset.Name, + Success: true, + UsedTime: elapsed.Milliseconds(), + UsedTimeStr: elapsed.String(), + Result: result, + } log.Debug(msg) } - msgChan <- msg + resultChan <- execResult{msg: msg, result: execRes} }() } var message = "" + var results []ExecScriptResult for i := 0; i < len(assets); i++ { - message += <-msgChan + "\n" + res := <-resultChan + message += res.msg + "\n" + results = append(results, res.result) } + resultsJSON, _ := json.Marshal(results) + _ = repository.JobRepository.UpdateLastUpdatedById(context.TODO(), r.ID) jobLog := model.JobLog{ ID: utils.UUID(), JobId: r.ID, Timestamp: common.NowJsonTime(), Message: message, + Results: string(resultsJSON), } _ = repository.JobLogRepository.Create(context.TODO(), &jobLog) diff --git a/web/src/pages/account/MultiFactorAuthentication.tsx b/web/src/pages/account/MultiFactorAuthentication.tsx index 534d35739..58b758c77 100644 --- a/web/src/pages/account/MultiFactorAuthentication.tsx +++ b/web/src/pages/account/MultiFactorAuthentication.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import {Button, Card, Input, Modal, Result, Space, Spin} from "antd"; import accountApi, {AuthType} from "@/api/account-api"; import {startAuthentication} from "@simplewebauthn/browser"; @@ -26,7 +26,8 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = const [loading, setLoading] = useState(false); const [otpValue, setOtpValue] = useState(''); const [showSelector, setShowSelector] = useState(false); - const [otpKey, setOtpKey] = useState(0); // 用于强制重新挂载 InputOTP + const [otpKey, setOtpKey] = useState(0); + const handledRef = useRef(false); // 查询支持的认证类型 const {data: supportedAuthTypes = []} = useQuery({ @@ -58,6 +59,7 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = setOtpValue(''); setShowSelector(false); setAuthType(''); + handledRef.current = false; }; // 清除输入 @@ -81,12 +83,10 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = const validateSecurityToken = () => { const securityToken = sessionStorage.getItem('securityToken'); if (securityToken) { - // 检查 Token 是否有效,有效则直接使用 accountApi.validateSecurityToken(securityToken).then((ok) => { if (ok) { storeSecurityToken(securityToken); } else { - // 无效则清除 sessionStorage.removeItem('securityToken'); } }); @@ -121,7 +121,7 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = onSuccess: (token) => token && storeSecurityToken(token), onError: (e: any) => { setOtpValue(''); - setOtpKey(prev => prev + 1); // 强制重新挂载以重新获得焦点 + setOtpKey(prev => prev + 1); const code = typeof e?.code === 'number' ? e.code : 1; setError({code, message: e?.message || String(e)}); } @@ -141,8 +141,13 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = return; } + if (handledRef.current) { + return; + } + // 没有可用的认证方式,直接通过 if (supportedAuthTypes.length === 0) { + handledRef.current = true; handleOk(''); return; } @@ -153,7 +158,6 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = setAuthType(supportedAuthTypes[0]); setShowSelector(false); } else { - // 有可用的认证方式 if (!authType || authType === 'none') { setAuthType(''); setShowSelector(true); @@ -170,8 +174,7 @@ const MultiFactorAuthentication = ({open, handleOk, handleCancel, forceReauth = }, [authType, showSelector, open]); useEffect(() => { - if (open && !forceReauth) { - // 检查是否有存储的 securityToken + if (open && !forceReauth && !handledRef.current) { validateSecurityToken(); } }, [open, forceReauth]); diff --git a/web/src/pages/authorised/AuthorisedAssetPost.tsx b/web/src/pages/authorised/AuthorisedAssetPost.tsx index 10440d274..889b2c07b 100644 --- a/web/src/pages/authorised/AuthorisedAssetPost.tsx +++ b/web/src/pages/authorised/AuthorisedAssetPost.tsx @@ -93,24 +93,14 @@ const AuthorisedAssetPost = () => { }} request={async () => { let items = await assetApi.getGroups(); - // let selected = await authorisedAssetApi.selected('assetId', userId, userGroupId, ''); - // 递归把 key 字段设置为 value,并且非叶子节点全部 disabled - function setKeyAndDisabled(item: any) { - item.value = item.key; - if (!item.isLeaf) { - // 递归处理子节点 - if (item.children) { - item.children.forEach(setKeyAndDisabled); - } + function processNode(item: any) { + item.value = item.id; + if (item.children) { + item.children.forEach(processNode); } - // if (selected.includes(item.key)) { - // item.disabled = true; - // } } - - // 对获取到的所有节点进行处理 items.forEach((item: any) => { - setKeyAndDisabled(item); + processNode(item); }); return items; }} @@ -127,28 +117,19 @@ const AuthorisedAssetPost = () => { }} request={async () => { let items = await assetApi.tree(); - // let selected = await authorisedAssetApi.selected('assetId', userId, userGroupId, ''); - - // 递归把 key 字段设置为 value,并且非叶子节点全部 disabled - function setKeyAndDisabled(item: any) { - item.value = item.key; + function processNode(item: any) { + item.value = item.id; if (!item.isLeaf) { item.disabled = true; - // 递归处理子节点 - if (item.children) { - item.children.forEach(setKeyAndDisabled); - } } else { item.title = item.title + ' (' + item.extra?.network + ')'; } - // if (selected.includes(item.key)) { - // item.disabled = true; - // } + if (item.children) { + item.children.forEach(processNode); + } } - - // 对获取到的所有节点进行处理 items.forEach((item: any) => { - setKeyAndDisabled(item); + processNode(item); }); return items; }} diff --git a/web/src/pages/gateway/GatewayGroupDrawer.tsx b/web/src/pages/gateway/GatewayGroupDrawer.tsx index 97a82df2e..5d8b3d759 100644 --- a/web/src/pages/gateway/GatewayGroupDrawer.tsx +++ b/web/src/pages/gateway/GatewayGroupDrawer.tsx @@ -29,7 +29,18 @@ const GatewayGroupDrawer: React.FC = ({open, group, onClose}) => { useEffect(() => { if (open && group) { - form.setFieldsValue(group); + let members = group.members; + if (typeof members === 'string') { + try { + members = JSON.parse(members); + } catch { + members = []; + } + } + form.setFieldsValue({ + ...group, + members, + }); } else if (open) { form.resetFields(); form.setFieldsValue({ @@ -41,10 +52,14 @@ const GatewayGroupDrawer: React.FC = ({open, group, onClose}) => { const handleSubmit = async (values: any) => { try { + const submitData = { + ...values, + members: JSON.stringify(values.members || []), + }; if (group?.id) { - await gatewayGroupApi.updateById(group.id, values); + await gatewayGroupApi.updateById(group.id, submitData); } else { - await gatewayGroupApi.create(values); + await gatewayGroupApi.create(submitData); } message.success(t('general.success')); onClose(true); diff --git a/web/src/pages/gateway/GatewayGroupPage.tsx b/web/src/pages/gateway/GatewayGroupPage.tsx index cdc04bd8d..20d55c33b 100644 --- a/web/src/pages/gateway/GatewayGroupPage.tsx +++ b/web/src/pages/gateway/GatewayGroupPage.tsx @@ -43,6 +43,16 @@ const GatewayGroupPage: React.FC = () => { } }; + const parseMembers = (members: string | undefined): any[] => { + if (!members) return []; + if (Array.isArray(members)) return members; + try { + return JSON.parse(members); + } catch { + return []; + } + }; + const columns: ProColumns[] = [ { title: t('gateway_group.name'), @@ -69,8 +79,9 @@ const GatewayGroupPage: React.FC = () => { dataIndex: 'members', width: 100, render: (_, record) => { - const enabledCount = record.members?.filter(m => m.enabled).length || 0; - const totalCount = record.members?.length || 0; + const members = parseMembers(record.members as unknown as string); + const enabledCount = members.filter(m => m.enabled).length; + const totalCount = members.length; return `${enabledCount}/${totalCount}`; }, }, diff --git a/web/src/pages/sysops/ScheduledTaskLogPage.tsx b/web/src/pages/sysops/ScheduledTaskLogPage.tsx index e5e70760f..d07b3f2f4 100644 --- a/web/src/pages/sysops/ScheduledTaskLogPage.tsx +++ b/web/src/pages/sysops/ScheduledTaskLogPage.tsx @@ -194,7 +194,25 @@ const ScheduledTaskLogPage = ({open, jobId, handleCancel}: Props) => { title: t('sysops.logs.result'), dataIndex: 'result', key: 'result', - valueType: 'code', + width: 200, + render: (text: string) => { + if (!text) return - + return ( +
+ {text} +
+ ) + } }, ] case 'renew-certificate': @@ -244,6 +262,7 @@ const ScheduledTaskLogPage = ({open, jobId, handleCancel}: Props) => { options={false} dataSource={record.results} pagination={false} + rowKey={(r: any, index: number) => r.name || r.id || index} /> } diff --git a/web/src/pages/sysops/ScheduledTaskModal.tsx b/web/src/pages/sysops/ScheduledTaskModal.tsx index 328d5f995..b423e1e3e 100644 --- a/web/src/pages/sysops/ScheduledTaskModal.tsx +++ b/web/src/pages/sysops/ScheduledTaskModal.tsx @@ -13,6 +13,7 @@ import {Col, Modal, Popover, Row} from "antd"; import scheduledTaskApi, {ScheduledTask} from "@/api/scheduled-task-api"; import assetApi from "@/api/asset-api"; import ScheduledTaskRuntime from "@/pages/sysops/ScheduledTaskRuntime"; +import {useQuery} from "@tanstack/react-query"; const ScheduledTaskModal = ({ open, @@ -23,13 +24,33 @@ const ScheduledTaskModal = ({ }: Props) => { let {t} = useTranslation(); const formRef = useRef(null); - let [spec, setSpec] = useState(''); let [runtimeOpen, setRuntimeOpen] = useState(false); + let [specForRuntime, setSpecForRuntime] = useState(''); + + const {data: assetTreeData} = useQuery({ + queryKey: ['asset-tree', 'ssh'], + queryFn: async () => { + let items = await assetApi.tree('ssh'); + function processItem(item: any) { + item.value = item.value || item.key || item.id; + item.title = item.title || item.name || ''; + if (item.isLeaf && item.extra?.network) { + item.title = item.title + ' (' + item.extra.network + ')'; + } + if (item.children && item.children.length > 0) { + item.children.forEach(processItem); + } + } + if (items && items.length > 0) { + items.forEach(processItem); + } + return items || []; + }, + }); const get = async () => { if (id) { let data = await scheduledTaskApi.getById(id); - setSpec(data.spec); return data } return { @@ -38,6 +59,14 @@ const ScheduledTaskModal = ({ } as ScheduledTask; } + const handleRuntimeOpenChange = (open: boolean) => { + if (open) { + const currentSpec = formRef.current?.getFieldValue('spec') || ''; + setSpecForRuntime(currentSpec); + } + setRuntimeOpen(open); + } + return ( { - setSpec(e.target.value); - }, addonAfter:
} + content={runtimeOpen ? : null} title={t('sysops.spec_run_time')} trigger="click" placement="rightTop" open={runtimeOpen} - onOpenChange={(open) => { - setRuntimeOpen(open); - }} + onOpenChange={handleRuntimeOpenChange} > {t('sysops.spec_run')} @@ -135,34 +158,12 @@ const ScheduledTaskModal = ({ name='assetIdList' rules={[{required: true}]} fieldProps={{ + treeData: assetTreeData || [], multiple: true, showSearch: true, treeDefaultExpandAll: true, treeNodeFilterProp: "title", }} - request={async () => { - let items = await assetApi.tree('ssh'); - - // 递归把 key 字段设置为 value,并且非叶子节点全部 disabled - function setKeyAndDisabled(item: any) { - item.value = item.key; - if (!item.isLeaf) { - // item.disabled = true; - // 递归处理子节点 - if (item.children) { - item.children.forEach(setKeyAndDisabled); - } - } else { - item.title = item.title + ' (' + item.extra?.network + ')'; - } - } - - // 对获取到的所有节点进行处理 - items.forEach((item: any) => { - setKeyAndDisabled(item); - }); - return items; - }} /> } return <> diff --git a/web/src/pages/sysops/ScheduledTaskRuntime.tsx b/web/src/pages/sysops/ScheduledTaskRuntime.tsx index 566cce876..2ab82674b 100644 --- a/web/src/pages/sysops/ScheduledTaskRuntime.tsx +++ b/web/src/pages/sysops/ScheduledTaskRuntime.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React from 'react'; import {useQuery} from "@tanstack/react-query"; import scheduledTaskApi from '@/api/scheduled-task-api'; @@ -14,26 +14,20 @@ const ScheduledTaskRuntime = ({open, spec}: Props) => { queryFn: () => { return scheduledTaskApi.getNextTenRuns(spec); }, - enabled: open, + enabled: open && !!spec, retry: false, }); - useEffect(() => { - if (open) { - query.refetch(); - } - }, [open]); - return (
{query.isError &&
Error: {query.error?.message}
}
- {Array.isArray(query.data) ? query.data.map((item: any) => { - return
{item}
+ {Array.isArray(query.data) ? query.data.map((item: any, index: number) => { + return
{item}
}) : []}
); }; -export default ScheduledTaskRuntime; \ No newline at end of file +export default ScheduledTaskRuntime; diff --git a/web/src/pages/sysops/ToolsPing.tsx b/web/src/pages/sysops/ToolsPing.tsx index a178e8f37..cfcf1879f 100644 --- a/web/src/pages/sysops/ToolsPing.tsx +++ b/web/src/pages/sysops/ToolsPing.tsx @@ -23,6 +23,11 @@ const ToolsPing = () => { setLogs((prevLogs) => [...prevLogs, event.data]); }; + eventSource.addEventListener('done', () => { + eventSource?.close(); + setRunning(false); + }); + eventSource.onerror = () => { eventSource?.close(); setRunning(false); @@ -67,11 +72,11 @@ const ToolsPing = () => { -
-
{logs.join("\n")}
+
+
{logs.join("\n")}
); }; -export default ToolsPing; \ No newline at end of file +export default ToolsPing; diff --git a/web/src/pages/sysops/ToolsTcping.tsx b/web/src/pages/sysops/ToolsTcping.tsx index 7afe4d474..ed7677d2b 100644 --- a/web/src/pages/sysops/ToolsTcping.tsx +++ b/web/src/pages/sysops/ToolsTcping.tsx @@ -1,4 +1,4 @@ -import {Button, Form, Input, InputNumber, Space} from 'antd'; +import {Button, Form, Input, InputNumber} from 'antd'; import React, {useState} from 'react'; import {baseUrl, getToken} from "@/api/core/requests"; import {useTranslation} from "react-i18next"; @@ -15,7 +15,7 @@ const ToolsTcping = () => { let eventSource: EventSource | null = null; const onSearch = (host: string, port: number) => { - if (running) return; // 防止重复启动 + if (running) return; setRunning(true); setLogs([]); @@ -25,6 +25,11 @@ const ToolsTcping = () => { setLogs((prevLogs) => [...prevLogs, event.data]); }; + eventSource.addEventListener('done', () => { + eventSource?.close(); + setRunning(false); + }); + eventSource.onerror = () => { eventSource?.close(); setRunning(false); @@ -79,11 +84,11 @@ const ToolsTcping = () => { -
-
{logs.join("\n")}
+
+
{logs.join("\n")}
); }; -export default ToolsTcping; \ No newline at end of file +export default ToolsTcping; diff --git a/web/src/react-i18next/locales/zh-CN.json b/web/src/react-i18next/locales/zh-CN.json index c7851fd45..ff39fc556 100644 --- a/web/src/react-i18next/locales/zh-CN.json +++ b/web/src/react-i18next/locales/zh-CN.json @@ -50,7 +50,8 @@ "upload": "上传", "offline": "离线", "online": "在线", - "search_placeholder": "关键词搜索" + "search_placeholder": "关键词搜索", + "max_length": "最大长度为 {{max}} 个字符" }, "actions": { "new": "新建", @@ -1834,5 +1835,101 @@ "403": "没有权限执行此操作", "404": "请求的资源不存在", "500": "服务器内部错误" + }, + "permissions": { + "dashboard": "数据概览", + "asset-access": "访问资产", + "asset-add": "新增资产", + "asset-edit": "编辑资产", + "asset-del": "删除资产", + "asset-copy": "复制资产", + "asset-conn-test": "连接测试", + "asset-import": "导入资产", + "asset-change-owner": "修改所有者", + "asset-authorised-user-add": "授权用户", + "asset-authorised-user-del": "取消授权用户", + "asset-authorised-user-group-add": "授权用户组", + "asset-authorised-user-group-del": "取消授权用户组", + "credential-add": "新增凭证", + "credential-del": "删除凭证", + "credential-edit": "编辑凭证", + "command-add": "新增命令", + "command-edit": "编辑命令", + "command-del": "删除命令", + "command-exec": "执行命令", + "command-change-owner": "修改所有者", + "access-gateway-add": "新增网关", + "access-gateway-del": "删除网关", + "access-gateway-edit": "编辑网关", + "online-session-disconnect": "断开会话", + "online-session-monitor": "监控会话", + "offline-session-playback": "回放会话", + "offline-session-del": "删除会话", + "offline-session-clear": "清空会话", + "offline-session-command": "命令记录", + "offline-session-reviewed": "标记已阅", + "offline-session-unreviewed": "标记未读", + "offline-session-reviewed-all": "全部标记已阅", + "login-log-del": "删除登录日志", + "login-log-clear": "清空登录日志", + "storage-log-del": "删除文件日志", + "storage-log-clear": "清空文件日志", + "session-command": "命令日志", + "job-add": "新增任务", + "job-del": "删除任务", + "job-edit": "编辑任务", + "job-run": "执行任务", + "job-change-status": "修改状态", + "job-log": "任务日志", + "job-log-clear": "清空日志", + "storage-add": "新增存储", + "storage-del": "删除存储", + "storage-edit": "编辑存储", + "storage-browse-download": "下载文件", + "storage-browse-upload": "上传文件", + "storage-browse-mkdir": "创建目录", + "storage-browse-rm": "删除文件", + "storage-browse-rename": "重命名", + "storage-browse-edit": "编辑文件", + "monitoring": "系统监控", + "access-security-add": "新增规则", + "access-security-del": "删除规则", + "access-security-edit": "编辑规则", + "login-policy-add": "新增策略", + "login-policy-del": "删除策略", + "login-policy-edit": "编辑策略", + "login-policy-bind-user": "绑定用户", + "user-add": "新增用户", + "user-del": "删除用户", + "user-edit": "编辑用户", + "user-change-password": "修改密码", + "user-enable-disable": "启用/禁用", + "user-reset-totp": "重置OTP", + "user-bind-asset": "绑定资产", + "user-unbind-asset": "解绑资产", + "login-policy-unbind-user": "解绑用户", + "user-unbind-login-policy": "解绑策略", + "role-add": "新增角色", + "role-del": "删除角色", + "role-edit": "编辑角色", + "role-detail": "角色详情", + "user-group-add": "新增用户组", + "user-group-del": "删除用户组", + "user-group-edit": "编辑用户组", + "user-group-detail": "用户组详情", + "user-group-bind-asset": "绑定资产", + "user-group-unbind-asset": "解绑资产", + "command-filter-add": "新增拦截器", + "command-filter-del": "删除拦截器", + "command-filter-edit": "编辑拦截器", + "command-filter-rule-add": "新增规则", + "command-filter-rule-put": "修改规则", + "command-filter-rule-del": "删除规则", + "strategy-add": "新增策略", + "strategy-edit": "编辑策略", + "strategy-del": "删除策略", + "strategy-detail": "策略详情", + "setting": "系统设置", + "info": "系统信息" } } \ No newline at end of file