提交 v1.3.0 beta

This commit is contained in:
dushixiang 2022-10-23 20:05:13 +08:00
parent 4ff4d37442
commit 112435199a
329 changed files with 18340 additions and 58458 deletions

View File

@ -49,7 +49,7 @@ https://next.typesafe.cn/ 账号test 密码test
## 问题反馈
- Issues
- 微信群 加我微信拉你进群
- 微信群 加我微信拉你进群 (请备注 next-terminal)
<img src="wx.png" width="300" height="auto"/>

View File

@ -1,5 +1,9 @@
#!/bin/bash
cp build/resources/logo.png web/src/images/logo.png
cp build/resources/logo-with-name.png web/src/images/logo-with-name.png
cp build/resources/favicon.ico web/public/favicon.ico
rm -rf server/resource/build
echo "clean build history"

20
go.mod
View File

@ -5,7 +5,7 @@ go 1.17
require (
github.com/glebarez/sqlite v1.3.5
github.com/gliderlabs/ssh v0.3.3
github.com/gofrs/uuid v4.2.0+incompatible
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/labstack/echo/v4 v4.7.0
@ -17,10 +17,13 @@ require (
github.com/pkg/sftp v1.13.4
github.com/pquerna/otp v1.3.0
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.22.9
github.com/sirupsen/logrus v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.0
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb
go.uber.org/zap v1.23.0
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/text v0.3.7
@ -36,33 +39,40 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/glebarez/go-sqlite v1.14.8 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/spf13/afero v1.8.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.14.6 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.0.5 // indirect

43
go.sum
View File

@ -65,6 +65,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -133,12 +135,12 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
@ -189,6 +191,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -283,6 +287,8 @@ github.com/labstack/echo/v4 v4.7.0 h1:8wHgZhoE9OT1NSLw6sfrX7ZGpWMtO5Zlfr68+BIo18
github.com/labstack/echo/v4 v4.7.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
@ -339,6 +345,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -362,6 +370,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil/v3 v3.22.9 h1:yibtJhIVEMcdw+tCTbOPiF1VcsuDeTE4utJ8Dm4c5eA=
github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
@ -381,16 +391,25 @@ github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb h1:Ywfo8sUltxogBpFuMOFRrrSifO788kAFxmvVw31PtQQ=
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
@ -400,6 +419,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
@ -411,9 +432,15 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -552,6 +579,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -579,6 +607,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -608,8 +637,9 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -853,8 +883,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=

70
server/api/abi/abi.go Normal file
View File

@ -0,0 +1,70 @@
package abi
import (
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
)
type Abi struct {
}
func (r *Abi) Fail(c echo.Context, code int, message string) error {
return c.JSON(200, maps.Map{
"code": code,
"message": message,
})
}
func (r *Abi) FailWithData(c echo.Context, code int, message string, data interface{}) error {
return c.JSON(200, maps.Map{
"code": code,
"message": message,
"data": data,
})
}
func (r *Abi) Success(c echo.Context, data interface{}) error {
return c.JSON(200, maps.Map{
"code": 1,
"message": "success",
"data": data,
})
}
func (r *Abi) GetToken(c echo.Context) string {
token := c.Request().Header.Get(nt.Token)
if len(token) > 0 {
return token
}
return c.QueryParam(nt.Token)
}
func (r *Abi) GetCurrentAccount(c echo.Context) (*model.User, bool) {
token := r.GetToken(c)
get, b := cache.TokenManager.Get(token)
if b {
return get.(dto.Authorization).User, true
}
return nil, false
}
func (r *Abi) HasPermission(c echo.Context, owner string) bool {
// 检测是否登录
account, found := r.GetCurrentAccount(c)
if !found {
return false
}
// 检测是否为管理人员
if nt.TypeAdmin == account.Type {
return true
}
// 检测是否为所有者
if owner == account.ID {
return true
}
return false
}

View File

@ -2,6 +2,8 @@ package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -23,7 +25,7 @@ func (api AccessGatewayApi) AccessGatewayCreateEndpoint(c echo.Context) error {
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.GatewayRepository.Create(context.TODO(), &item); err != nil {
return err
@ -38,11 +40,14 @@ func (api AccessGatewayApi) AccessGatewayAllEndpoint(c echo.Context) error {
if err != nil {
return err
}
var simpleGateways = make([]model.AccessGatewayForPage, 0)
for i := 0; i < len(gateways); i++ {
simpleGateways = append(simpleGateways, model.AccessGatewayForPage{ID: gateways[i].ID, Name: gateways[i].Name})
items := make([]maps.Map, len(gateways))
for i, e := range gateways {
items[i] = maps.Map{
"id": e.ID,
"name": e.Name,
}
return Success(c, simpleGateways)
}
return Success(c, items)
}
func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
@ -66,7 +71,7 @@ func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
}
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})

View File

@ -3,17 +3,17 @@ package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"path"
"strconv"
"strings"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/totp"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
@ -50,7 +50,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
if user.Status == constant.StatusDisabled {
if user.Status == nt.StatusDisabled {
return Fail(c, -1, "该账户已停用")
}
@ -64,11 +64,24 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
// 账号密码正确,需要进行两步验证
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
return Fail(c, 0, "")
if loginAccount.TOTP == "" {
return Fail(c, 100, "")
} else {
if !common.Validate(loginAccount.TOTP, user.TOTPSecret) {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
}
}
}
token, err := api.LoginSuccess(loginAccount, user)
token, err := api.LoginSuccess(loginAccount, user, c.RealIP())
if err != nil {
return err
}
@ -80,12 +93,17 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
return Success(c, token)
}
func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.User) (string, error) {
func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.User, ip string) (string, error) {
// 判断当前时间是否允许该用户登录
if err := service.LoginPolicyService.Check(user.ID, ip); err != nil {
return "", err
}
token := utils.LongUUID()
authorization := dto.Authorization{
Token: token,
Type: constant.LoginToken,
Type: nt.LoginToken,
Remember: loginAccount.Remember,
User: &user,
}
@ -97,75 +115,12 @@ func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.Use
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
b := true
// 修改登录状态
err := repository.UserRepository.Update(context.TODO(), &model.User{Online: true, ID: user.ID})
err := repository.UserRepository.Update(context.TODO(), &model.User{Online: &b, ID: user.ID})
return token, err
}
func (api AccountApi) LoginWithTotpEndpoint(c echo.Context) error {
var loginAccount dto.LoginAccount
if err := c.Bind(&loginAccount); err != nil {
return err
}
// 存储登录失败次数信息
loginFailCountKey := c.RealIP() + loginAccount.Username
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
if !ok {
v = 1
}
count := v.(int)
if count >= 5 {
return Fail(c, -1, "登录失败次数过多请等待5分钟后再试")
}
user, err := repository.UserRepository.FindByUsername(context.TODO(), loginAccount.Username)
if err != nil {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
if user.Status == constant.StatusDisabled {
return Fail(c, -1, "该账户已停用")
}
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
}
token, err := api.LoginSuccess(loginAccount, user)
if err != nil {
return err
}
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
return err
}
return Success(c, token)
}
func (api AccountApi) LogoutEndpoint(c echo.Context) error {
token := GetToken(c)
service.UserService.Logout(token)
@ -183,7 +138,7 @@ func (api AccountApi) ConfirmTOTPEndpoint(c echo.Context) error {
return err
}
if !totp.Validate(confirmTOTP.TOTP, confirmTOTP.Secret) {
if !common.Validate(confirmTOTP.TOTP, confirmTOTP.Secret) {
return Fail(c, -1, "TOTP 验证失败,请重试")
}
@ -202,7 +157,7 @@ func (api AccountApi) ConfirmTOTPEndpoint(c echo.Context) error {
func (api AccountApi) ReloadTOTPEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
key, err := totp.NewTOTP(totp.GenerateOpts{
key, err := common.NewTOTP(common.GenerateOpts{
Issuer: c.Request().Host,
AccountName: account.Username,
})
@ -275,54 +230,63 @@ type AccountInfo struct {
Nickname string `json:"nickname"`
Type string `json:"type"`
EnableTotp bool `json:"enableTotp"`
Roles []string `json:"roles"`
Menus []string `json:"menus"`
}
func (api AccountApi) InfoEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
if strings.EqualFold("anonymous", account.Type) {
return Success(c, account)
}
user, err := repository.UserRepository.FindById(context.TODO(), account.ID)
user, err := service.UserService.FindById(account.ID)
if err != nil {
return err
}
var menus []string
if service.UserService.IsSuperAdmin(account.ID) {
menus = service.MenuService.GetMenus()
} else {
roles, err := service.RoleService.GetRolesByUserId(account.ID)
if err != nil {
return err
}
for _, role := range roles {
items := service.RoleService.GetMenuListByRole(role)
menus = append(menus, items...)
}
}
info := AccountInfo{
Id: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Type: user.Type,
EnableTotp: user.TOTPSecret != "" && user.TOTPSecret != "-",
Roles: user.Roles,
Menus: menus,
}
return Success(c, info)
}
func (api AccountApi) AccountAssetEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
protocol := c.QueryParam("protocol")
tags := c.QueryParam("tags")
owner := c.QueryParam("owner")
sharer := c.QueryParam("sharer")
userGroupId := c.QueryParam("userGroupId")
ip := c.QueryParam("ip")
order := c.QueryParam("order")
field := c.QueryParam("field")
func (api AccountApi) MenuEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
items, total, err := repository.AssetRepository.Find(context.TODO(), pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
if service.UserService.IsSuperAdmin(account.ID) {
items := service.MenuService.GetMenus()
return Success(c, items)
}
roles, err := service.RoleService.GetRolesByUserId(account.ID)
if err != nil {
return err
}
for i := range items {
items[i].IP = ""
items[i].Port = 0
var menus []string
for _, role := range roles {
items := service.RoleService.GetMenuListByRole(role)
menus = append(menus, items...)
}
return Success(c, Map{
"total": total,
"items": items,
})
return Success(c, menus)
}
func (api AccountApi) AccountStorageEndpoint(c echo.Context) error {
@ -364,3 +328,11 @@ func (api AccountApi) AccessTokenGenEndpoint(c echo.Context) error {
}
return Success(c, nil)
}
func (api AccountApi) AccessTokenDelEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
if err := service.AccessTokenService.DelAccessToken(context.Background(), account.ID); err != nil {
return err
}
return Success(c, nil)
}

View File

@ -1,25 +1,23 @@
package api
import (
"next-terminal/server/constant"
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
"github.com/labstack/echo/v4"
)
type Map map[string]interface{}
func Fail(c echo.Context, code int, message string) error {
return c.JSON(200, Map{
return c.JSON(200, maps.Map{
"code": code,
"message": message,
})
}
func FailWithData(c echo.Context, code int, message string, data interface{}) error {
return c.JSON(200, Map{
return c.JSON(200, maps.Map{
"code": code,
"message": message,
"data": data,
@ -27,7 +25,7 @@ func FailWithData(c echo.Context, code int, message string, data interface{}) er
}
func Success(c echo.Context, data interface{}) error {
return c.JSON(200, Map{
return c.JSON(200, maps.Map{
"code": 1,
"message": "success",
"data": data,
@ -35,11 +33,11 @@ func Success(c echo.Context, data interface{}) error {
}
func GetToken(c echo.Context) string {
token := c.Request().Header.Get(constant.Token)
token := c.Request().Header.Get(nt.Token)
if len(token) > 0 {
return token
}
return c.QueryParam(constant.Token)
return c.QueryParam(nt.Token)
}
func GetCurrentAccount(c echo.Context) (*model.User, bool) {
@ -50,20 +48,3 @@ func GetCurrentAccount(c echo.Context) (*model.User, bool) {
}
return nil, false
}
func HasPermission(c echo.Context, owner string) bool {
// 检测是否登录
account, found := GetCurrentAccount(c)
if !found {
return false
}
// 检测是否为管理人员
if constant.TypeAdmin == account.Type {
return true
}
// 检测是否为所有者
if owner == account.ID {
return true
}
return false
}

View File

@ -5,10 +5,12 @@ import (
"context"
"encoding/csv"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -75,13 +77,13 @@ func (assetApi AssetApi) AssetImportEndpoint(c echo.Context) error {
Protocol: record[1],
IP: record[2],
Port: port,
AccountType: constant.Custom,
AccountType: nt.Custom,
Username: record[4],
Password: record[5],
PrivateKey: record[6],
Passphrase: record[7],
Description: record[8],
Created: utils.NowJsonTime(),
Created: common.NowJsonTime(),
Owner: account.ID,
Active: true,
}
@ -114,22 +116,18 @@ func (assetApi AssetApi) AssetPagingEndpoint(c echo.Context) error {
name := c.QueryParam("name")
protocol := c.QueryParam("protocol")
tags := c.QueryParam("tags")
owner := c.QueryParam("owner")
sharer := c.QueryParam("sharer")
userGroupId := c.QueryParam("userGroupId")
ip := c.QueryParam("ip")
active := c.QueryParam("active")
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := GetCurrentAccount(c)
items, total, err := repository.AssetRepository.Find(context.TODO(), pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
items, total, err := repository.AssetRepository.Find(context.Background(), pageIndex, pageSize, name, protocol, tags, ip, active, order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -137,16 +135,22 @@ func (assetApi AssetApi) AssetPagingEndpoint(c echo.Context) error {
func (assetApi AssetApi) AssetAllEndpoint(c echo.Context) error {
protocol := c.QueryParam("protocol")
items, _ := repository.AssetRepository.FindByProtocol(context.TODO(), protocol)
assets, err := repository.AssetRepository.FindByProtocol(context.TODO(), protocol)
if err != nil {
return err
}
items := make([]maps.Map, len(assets))
for i, e := range assets {
items[i] = maps.Map{
"id": e.ID,
"name": e.Name,
}
}
return Success(c, items)
}
func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
return err
}
m := echo.Map{}
if err := c.Bind(&m); err != nil {
return err
@ -161,9 +165,6 @@ func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := assetApi.PreCheckAssetPermission(c, split[i]); err != nil {
return err
}
if err := service.AssetService.DeleteById(split[i]); err != nil {
return err
}
@ -174,9 +175,6 @@ func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
func (assetApi AssetApi) AssetGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
return err
}
var item model.Asset
if item, err = service.AssetService.FindByIdAndDecrypt(context.TODO(), id); err != nil {
@ -202,20 +200,17 @@ func (assetApi AssetApi) AssetTcpingEndpoint(c echo.Context) (err error) {
return err
}
active, err := service.AssetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
if item.Active != active {
if err := repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.ID); err != nil {
return err
}
}
active, err := service.AssetService.CheckStatus(&item, item.IP, item.Port)
var message = ""
if err != nil {
message = err.Error()
}
if err := repository.AssetRepository.UpdateActiveById(context.TODO(), active, message, item.ID); err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"active": active,
"message": message,
})
@ -232,25 +227,9 @@ func (assetApi AssetApi) AssetTagsEndpoint(c echo.Context) (err error) {
func (assetApi AssetApi) AssetChangeOwnerEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
return err
}
owner := c.QueryParam("owner")
if err := repository.AssetRepository.UpdateById(context.TODO(), &model.Asset{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
func (assetApi AssetApi) PreCheckAssetPermission(c echo.Context, id string) error {
item, err := repository.AssetRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return nil
}

136
server/api/authorised.go Normal file
View File

@ -0,0 +1,136 @@
package api
import (
"context"
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/dto"
"next-terminal/server/repository"
"next-terminal/server/service"
"strconv"
)
type AuthorisedApi struct {
}
func (api AuthorisedApi) Selected(c echo.Context) error {
userId := c.QueryParam("userId")
userGroupId := c.QueryParam("userGroupId")
assetId := c.QueryParam("assetId")
key := c.QueryParam("key")
items, err := repository.AuthorisedRepository.FindAll(context.Background(), userId, userGroupId, assetId)
if err != nil {
return err
}
var result = make([]string, 0)
switch key {
case "userId":
for _, item := range items {
result = append(result, item.UserId)
}
case "userGroupId":
for _, item := range items {
result = append(result, item.UserGroupId)
}
case "assetId":
for _, item := range items {
result = append(result, item.AssetId)
}
}
return Success(c, result)
}
func (api AuthorisedApi) Delete(c echo.Context) error {
id := c.Param("id")
if err := repository.AuthorisedRepository.DeleteById(context.Background(), id); err != nil {
return err
}
return Success(c, "")
}
func (api AuthorisedApi) PagingAsset(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
assetName := c.QueryParam("assetName")
userId := c.QueryParam("userId")
userGroupId := c.QueryParam("userGroupId")
items, total, err := repository.AuthorisedRepository.FindAssetPage(context.Background(), pageIndex, pageSize, assetName, userId, userGroupId)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AuthorisedApi) PagingUser(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
userName := c.QueryParam("userName")
assetId := c.QueryParam("assetId")
items, total, err := repository.AuthorisedRepository.FindUserPage(context.Background(), pageIndex, pageSize, userName, assetId)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AuthorisedApi) PagingUserGroup(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
userGroupName := c.QueryParam("userGroupName")
assetId := c.QueryParam("assetId")
items, total, err := repository.AuthorisedRepository.FindUserGroupPage(context.Background(), pageIndex, pageSize, userGroupName, assetId)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AuthorisedApi) AuthorisedAssets(c echo.Context) error {
var item dto.AuthorisedAsset
if err := c.Bind(&item); err != nil {
return err
}
if err := service.AuthorisedService.AuthorisedAssets(context.Background(), &item); err != nil {
return err
}
return Success(c, nil)
}
func (api AuthorisedApi) AuthorisedUsers(c echo.Context) error {
var item dto.AuthorisedUser
if err := c.Bind(&item); err != nil {
return err
}
if err := service.AuthorisedService.AuthorisedUsers(context.Background(), &item); err != nil {
return err
}
return Success(c, nil)
}
func (api AuthorisedApi) AuthorisedUserGroups(c echo.Context) error {
var item dto.AuthorisedUserGroup
if err := c.Bind(&item); err != nil {
return err
}
if err := service.AuthorisedService.AuthorisedUserGroups(context.Background(), &item); err != nil {
return err
}
return Success(c, nil)
}

15
server/api/branding.go Normal file
View File

@ -0,0 +1,15 @@
package api
import (
"next-terminal/server/branding"
"next-terminal/server/common/maps"
"github.com/labstack/echo/v4"
)
func Branding(c echo.Context) error {
return Success(c, maps.Map{
"name": branding.Name,
"copyright": branding.Copyright,
})
}

View File

@ -2,7 +2,8 @@ package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -24,7 +25,7 @@ func (api CommandApi) CommandCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.CommandRepository.Create(context.TODO(), &item); err != nil {
return err
@ -34,8 +35,7 @@ func (api CommandApi) CommandCreateEndpoint(c echo.Context) error {
}
func (api CommandApi) CommandAllEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
items, err := repository.CommandRepository.FindByUser(context.TODO(), account)
items, err := repository.CommandRepository.FindAll(context.Background())
if err != nil {
return err
}
@ -47,17 +47,16 @@ func (api CommandApi) CommandPagingEndpoint(c echo.Context) error {
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
content := c.QueryParam("content")
account, _ := GetCurrentAccount(c)
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.CommandRepository.Find(context.TODO(), pageIndex, pageSize, name, content, order, field, account)
items, total, err := repository.CommandRepository.Find(context.TODO(), pageIndex, pageSize, name, content, order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -65,9 +64,6 @@ func (api CommandApi) CommandPagingEndpoint(c echo.Context) error {
func (api CommandApi) CommandUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCommandPermission(c, id); err != nil {
return err
}
var item model.Command
if err := c.Bind(&item); err != nil {
@ -85,9 +81,6 @@ func (api CommandApi) CommandDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := api.PreCheckCommandPermission(c, split[i]); err != nil {
return err
}
if err := repository.CommandRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
@ -97,11 +90,6 @@ func (api CommandApi) CommandDeleteEndpoint(c echo.Context) error {
func (api CommandApi) CommandGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := api.PreCheckCommandPermission(c, id); err != nil {
return err
}
var item model.Command
if item, err = repository.CommandRepository.FindById(context.TODO(), id); err != nil {
return err
@ -111,26 +99,9 @@ func (api CommandApi) CommandGetEndpoint(c echo.Context) (err error) {
func (api CommandApi) CommandChangeOwnerEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := api.PreCheckCommandPermission(c, id); err != nil {
return err
}
owner := c.QueryParam("owner")
if err := repository.CommandRepository.UpdateById(context.TODO(), &model.Command{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
func (api CommandApi) PreCheckCommandPermission(c echo.Context, id string) error {
item, err := repository.CommandRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return nil
}

View File

@ -3,12 +3,13 @@ package api
import (
"context"
"encoding/base64"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -20,7 +21,7 @@ import (
type CredentialApi struct{}
func (api CredentialApi) CredentialAllEndpoint(c echo.Context) error {
items, err := repository.CredentialRepository.FindByUser(context.TODO())
items, err := repository.CredentialRepository.FindByAll(context.TODO())
if err != nil {
return err
}
@ -35,10 +36,10 @@ func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
switch item.Type {
case constant.Custom:
case nt.Custom:
item.PrivateKey = "-"
item.Passphrase = "-"
if item.Username == "" {
@ -47,7 +48,7 @@ func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
if item.Password == "" {
item.Password = "-"
}
case constant.PrivateKey:
case nt.PrivateKey:
item.Password = "-"
if item.Username == "" {
item.Username = "-"
@ -79,13 +80,12 @@ func (api CredentialApi) CredentialPagingEndpoint(c echo.Context) error {
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := GetCurrentAccount(c)
items, total, err := repository.CredentialRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field, account)
items, total, err := repository.CredentialRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -94,17 +94,13 @@ func (api CredentialApi) CredentialPagingEndpoint(c echo.Context) error {
func (api CredentialApi) CredentialUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCredentialPermission(c, id); err != nil {
return err
}
var item model.Credential
if err := c.Bind(&item); err != nil {
return err
}
switch item.Type {
case constant.Custom:
case nt.Custom:
item.PrivateKey = "-"
item.Passphrase = "-"
if item.Username == "" {
@ -120,7 +116,7 @@ func (api CredentialApi) CredentialUpdateEndpoint(c echo.Context) error {
}
item.Password = base64.StdEncoding.EncodeToString(encryptedCBC)
}
case constant.PrivateKey:
case nt.PrivateKey:
item.Password = "-"
if item.Username == "" {
item.Username = "-"
@ -161,9 +157,6 @@ func (api CredentialApi) CredentialDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := api.PreCheckCredentialPermission(c, split[i]); err != nil {
return err
}
if err := repository.CredentialRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
@ -174,44 +167,21 @@ func (api CredentialApi) CredentialDeleteEndpoint(c echo.Context) error {
func (api CredentialApi) CredentialGetEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCredentialPermission(c, id); err != nil {
return err
}
item, err := service.CredentialService.FindByIdAndDecrypt(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return Success(c, item)
}
func (api CredentialApi) CredentialChangeOwnerEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCredentialPermission(c, id); err != nil {
return err
}
owner := c.QueryParam("owner")
if err := repository.CredentialRepository.UpdateById(context.TODO(), &model.Credential{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
func (api CredentialApi) PreCheckCredentialPermission(c echo.Context, id string) error {
item, err := repository.CredentialRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return nil
}

View File

@ -4,13 +4,13 @@ import (
"context"
"fmt"
"net/http"
"next-terminal/server/common/guacamole"
"next-terminal/server/common/nt"
"path"
"strconv"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/global/session"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
@ -34,6 +34,8 @@ const (
)
var UpGrader = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
@ -46,7 +48,7 @@ type GuacamoleApi struct {
func (api GuacamoleApi) Guacamole(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
log.Warn("升级为WebSocket协议失败", log.NamedError("err", err))
return err
}
ctx := context.TODO()
@ -58,7 +60,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
intWidth, _ := strconv.Atoi(width)
intHeight, _ := strconv.Atoi(height)
configuration := guacd.NewConfiguration()
configuration := guacamole.NewConfiguration()
propertyMap := repository.PropertyRepository.FindAllMap(ctx)
@ -74,14 +76,14 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
if err != nil {
utils.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
guacamole.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
return nil
}
defer g.CloseSshTunnel(s.ID)
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, s.IP, s.Port)
if err != nil {
utils.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败"+err.Error())
guacamole.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败"+err.Error())
return nil
}
s.IP = exposedIP
@ -108,12 +110,12 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port)
asset := fmt.Sprintf("%s:%s", configuration.GetParameter("hostname"), configuration.GetParameter("port"))
log.Debugf("[%v] 新建 guacd 会话, guacd=%v, asset=%v", sessionId, addr, asset)
log.Debug("新建 guacd 会话", log.String("sessionId", sessionId), log.String("addr", addr), log.String("asset", asset))
guacdTunnel, err := guacd.NewTunnel(addr, configuration)
guacdTunnel, err := guacamole.NewTunnel(addr, configuration)
if err != nil {
utils.Disconnect(ws, NewTunnelError, err.Error())
log.Printf("[%v] 建立连接失败: %v", sessionId, err.Error())
guacamole.Disconnect(ws, NewTunnelError, err.Error())
log.Error("建立连接失败", log.String("sessionId", sessionId), log.NamedError("err", err))
return err
}
@ -125,11 +127,11 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
GuacdTunnel: guacdTunnel,
}
if configuration.Protocol == constant.SSH {
if configuration.Protocol == nt.SSH {
nextTerminal, err := CreateNextTerminalBySession(s)
if err != nil {
utils.Disconnect(ws, NewSshClientError, "建立SSH客户端失败: "+err.Error())
log.Printf("[%v] 建立 ssh 客户端失败: %v", sessionId, err.Error())
guacamole.Disconnect(ws, NewSshClientError, "建立SSH客户端失败: "+err.Error())
log.Debug("建立 ssh 客户端失败", log.String("sessionId", sessionId), log.NamedError("err", err))
return err
}
nextSession.NextTerminal = nextTerminal
@ -141,15 +143,15 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
ConnectionId: guacdTunnel.UUID,
Width: intWidth,
Height: intHeight,
Status: constant.Connecting,
Recording: configuration.GetParameter(guacd.RecordingPath),
Status: nt.Connecting,
Recording: configuration.GetParameter(guacamole.RecordingPath),
}
if sess.Recording == "" {
// 未录屏时无需审计
sess.Reviewed = true
}
// 创建新会话
log.Debugf("[%v] 新建会话成功: %v", sessionId, sess.ConnectionId)
log.Debug("新建会话成功", log.String("sessionId", sessionId))
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
return err
}
@ -161,7 +163,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Debugf("[%v] WebSocket已关闭, %v", sessionId, err.Error())
log.Debug("WebSocket已关闭", log.String("sessionId", sessionId), log.NamedError("err", err))
// guacdTunnel.Read() 会阻塞所以要先把guacdTunnel客户端关闭才能退出Guacd循环
_ = guacdTunnel.Close()
@ -176,23 +178,22 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
}
}
func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Session, configuration *guacd.Configuration) {
func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Session, configuration *guacamole.Configuration) {
for key, value := range attributes {
if guacd.DrivePath == key {
if guacamole.DrivePath == key {
// 忽略该参数
continue
}
if guacd.EnableDrive == key && value == "true" {
storageId := attributes[guacd.DrivePath]
if guacamole.EnableDrive == key && value == "true" {
storageId := attributes[guacamole.DrivePath]
if storageId == "" || storageId == "-" {
// 默认空间ID和用户ID相同
storageId = s.Creator
}
realPath := path.Join(service.StorageService.GetBaseDrivePath(), storageId)
configuration.SetParameter(guacd.EnableDrive, "true")
configuration.SetParameter(guacd.DriveName, "Filesystem")
configuration.SetParameter(guacd.DrivePath, realPath)
log.Debugf("[%v] 会话 %v:%v 映射目录地址为 %v", s.ID, s.IP, s.Port, realPath)
configuration.SetParameter(guacamole.EnableDrive, "true")
configuration.SetParameter(guacamole.DriveName, "Filesystem")
configuration.SetParameter(guacamole.DrivePath, realPath)
} else {
configuration.SetParameter(key, value)
}
@ -202,7 +203,7 @@ func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Ses
func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
log.Warn("升级为WebSocket协议失败", log.NamedError("err", err))
return err
}
ctx := context.TODO()
@ -212,12 +213,12 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
if err != nil {
return err
}
if s.Status != constant.Connected {
utils.Disconnect(ws, AssetNotActive, "会话离线")
if s.Status != nt.Connected {
guacamole.Disconnect(ws, AssetNotActive, "会话离线")
return nil
}
connectionId := s.ConnectionId
configuration := guacd.NewConfiguration()
configuration := guacamole.NewConfiguration()
configuration.ConnectionID = connectionId
sessionId = s.ID
configuration.SetParameter("width", strconv.Itoa(s.Width))
@ -225,13 +226,10 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
configuration.SetParameter("dpi", "96")
addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port)
asset := fmt.Sprintf("%s:%s", configuration.GetParameter("hostname"), configuration.GetParameter("port"))
log.Debugf("[%v] 新建 guacd 会话, guacd=%v, asset=%v", sessionId, addr, asset)
guacdTunnel, err := guacd.NewTunnel(addr, configuration)
guacdTunnel, err := guacamole.NewTunnel(addr, configuration)
if err != nil {
utils.Disconnect(ws, NewTunnelError, err.Error())
log.Printf("[%v] 建立连接失败: %v", sessionId, err.Error())
guacamole.Disconnect(ws, NewTunnelError, err.Error())
return err
}
@ -246,12 +244,11 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
// 要监控会话
forObsSession := session.GlobalSessionManager.GetById(sessionId)
if forObsSession == nil {
utils.Disconnect(ws, NotFoundSession, "获取会话失败")
guacamole.Disconnect(ws, NotFoundSession, "获取会话失败")
return nil
}
nextSession.ID = utils.UUID()
forObsSession.Observer.Add(nextSession)
log.Debugf("[%v:%v] 观察者[%v]加入会话[%v]", sessionId, connectionId, nextSession.ID, s.ConnectionId)
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
guacamoleHandler.Start()
@ -260,13 +257,11 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Debugf("[%v:%v] WebSocket已关闭, %v", sessionId, connectionId, err.Error())
// guacdTunnel.Read() 会阻塞所以要先把guacdTunnel客户端关闭才能退出Guacd循环
_ = guacdTunnel.Close()
observerId := nextSession.ID
forObsSession.Observer.Del(observerId)
log.Debugf("[%v:%v] 观察者[%v]退出会话", sessionId, connectionId, observerId)
return nil
}
_, err = guacdTunnel.WriteAndFlush(message)
@ -277,12 +272,12 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
}
}
func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session, configuration *guacd.Configuration) {
if propertyMap[guacd.EnableRecording] == "true" {
configuration.SetParameter(guacd.RecordingPath, path.Join(config.GlobalCfg.Guacd.Recording, s.ID))
configuration.SetParameter(guacd.CreateRecordingPath, "true")
func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session, configuration *guacamole.Configuration) {
if propertyMap[guacamole.EnableRecording] == "true" {
configuration.SetParameter(guacamole.RecordingPath, path.Join(config.GlobalCfg.Guacd.Recording, s.ID))
configuration.SetParameter(guacamole.CreateRecordingPath, "true")
} else {
configuration.SetParameter(guacd.RecordingPath, "")
configuration.SetParameter(guacamole.RecordingPath, "")
}
configuration.Protocol = s.Protocol
@ -295,18 +290,18 @@ func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session
configuration.SetParameter("ignore-cert", "true")
configuration.SetParameter("create-drive-path", "true")
configuration.SetParameter("resize-method", "reconnect")
configuration.SetParameter(guacd.EnableWallpaper, propertyMap[guacd.EnableWallpaper])
configuration.SetParameter(guacd.EnableTheming, propertyMap[guacd.EnableTheming])
configuration.SetParameter(guacd.EnableFontSmoothing, propertyMap[guacd.EnableFontSmoothing])
configuration.SetParameter(guacd.EnableFullWindowDrag, propertyMap[guacd.EnableFullWindowDrag])
configuration.SetParameter(guacd.EnableDesktopComposition, propertyMap[guacd.EnableDesktopComposition])
configuration.SetParameter(guacd.EnableMenuAnimations, propertyMap[guacd.EnableMenuAnimations])
configuration.SetParameter(guacd.DisableBitmapCaching, propertyMap[guacd.DisableBitmapCaching])
configuration.SetParameter(guacd.DisableOffscreenCaching, propertyMap[guacd.DisableOffscreenCaching])
configuration.SetParameter(guacd.ColorDepth, propertyMap[guacd.ColorDepth])
configuration.SetParameter(guacd.ForceLossless, propertyMap[guacd.ForceLossless])
configuration.SetParameter(guacd.PreConnectionId, propertyMap[guacd.PreConnectionId])
configuration.SetParameter(guacd.PreConnectionBlob, propertyMap[guacd.PreConnectionBlob])
configuration.SetParameter(guacamole.EnableWallpaper, propertyMap[guacamole.EnableWallpaper])
configuration.SetParameter(guacamole.EnableTheming, propertyMap[guacamole.EnableTheming])
configuration.SetParameter(guacamole.EnableFontSmoothing, propertyMap[guacamole.EnableFontSmoothing])
configuration.SetParameter(guacamole.EnableFullWindowDrag, propertyMap[guacamole.EnableFullWindowDrag])
configuration.SetParameter(guacamole.EnableDesktopComposition, propertyMap[guacamole.EnableDesktopComposition])
configuration.SetParameter(guacamole.EnableMenuAnimations, propertyMap[guacamole.EnableMenuAnimations])
configuration.SetParameter(guacamole.DisableBitmapCaching, propertyMap[guacamole.DisableBitmapCaching])
configuration.SetParameter(guacamole.DisableOffscreenCaching, propertyMap[guacamole.DisableOffscreenCaching])
configuration.SetParameter(guacamole.ColorDepth, propertyMap[guacamole.ColorDepth])
configuration.SetParameter(guacamole.ForceLossless, propertyMap[guacamole.ForceLossless])
configuration.SetParameter(guacamole.PreConnectionId, propertyMap[guacamole.PreConnectionId])
configuration.SetParameter(guacamole.PreConnectionBlob, propertyMap[guacamole.PreConnectionBlob])
case "ssh":
if len(s.PrivateKey) > 0 && s.PrivateKey != "-" {
configuration.SetParameter("username", s.Username)
@ -317,11 +312,11 @@ func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session
configuration.SetParameter("password", s.Password)
}
configuration.SetParameter(guacd.FontSize, propertyMap[guacd.FontSize])
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
configuration.SetParameter(guacd.Backspace, propertyMap[guacd.Backspace])
configuration.SetParameter(guacd.TerminalType, propertyMap[guacd.TerminalType])
configuration.SetParameter(guacamole.FontSize, propertyMap[guacamole.FontSize])
configuration.SetParameter(guacamole.FontName, propertyMap[guacamole.FontName])
configuration.SetParameter(guacamole.ColorScheme, propertyMap[guacamole.ColorScheme])
configuration.SetParameter(guacamole.Backspace, propertyMap[guacamole.Backspace])
configuration.SetParameter(guacamole.TerminalType, propertyMap[guacamole.TerminalType])
case "vnc":
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
@ -329,17 +324,17 @@ func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
configuration.SetParameter(guacd.FontSize, propertyMap[guacd.FontSize])
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
configuration.SetParameter(guacd.Backspace, propertyMap[guacd.Backspace])
configuration.SetParameter(guacd.TerminalType, propertyMap[guacd.TerminalType])
configuration.SetParameter(guacamole.FontSize, propertyMap[guacamole.FontSize])
configuration.SetParameter(guacamole.FontName, propertyMap[guacamole.FontName])
configuration.SetParameter(guacamole.ColorScheme, propertyMap[guacamole.ColorScheme])
configuration.SetParameter(guacamole.Backspace, propertyMap[guacamole.Backspace])
configuration.SetParameter(guacamole.TerminalType, propertyMap[guacamole.TerminalType])
case "kubernetes":
configuration.SetParameter(guacd.FontSize, propertyMap[guacd.FontSize])
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
configuration.SetParameter(guacd.Backspace, propertyMap[guacd.Backspace])
configuration.SetParameter(guacd.TerminalType, propertyMap[guacd.TerminalType])
configuration.SetParameter(guacamole.FontSize, propertyMap[guacamole.FontSize])
configuration.SetParameter(guacamole.FontName, propertyMap[guacamole.FontName])
configuration.SetParameter(guacamole.ColorScheme, propertyMap[guacamole.ColorScheme])
configuration.SetParameter(guacamole.Backspace, propertyMap[guacamole.Backspace])
configuration.SetParameter(guacamole.TerminalType, propertyMap[guacamole.TerminalType])
default:
}

View File

@ -2,22 +2,19 @@ package api
import (
"context"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/utils"
"next-terminal/server/common/guacamole"
"github.com/gorilla/websocket"
)
type GuacamoleHandler struct {
ws *websocket.Conn
tunnel *guacd.Tunnel
tunnel *guacamole.Tunnel
ctx context.Context
cancel context.CancelFunc
}
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacd.Tunnel) *GuacamoleHandler {
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacamole.Tunnel) *GuacamoleHandler {
ctx, cancel := context.WithCancel(context.Background())
return &GuacamoleHandler{
ws: ws,
@ -36,7 +33,7 @@ func (r GuacamoleHandler) Start() {
default:
instruction, err := r.tunnel.Read()
if err != nil {
utils.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
guacamole.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
return
}
if len(instruction) == 0 {
@ -44,7 +41,6 @@ func (r GuacamoleHandler) Start() {
}
err = r.ws.WriteMessage(websocket.TextMessage, instruction)
if err != nil {
log.Debugf("WebSocket写入失败即将关闭Guacd连接...")
return
}
}

View File

@ -2,7 +2,8 @@ package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -23,7 +24,7 @@ func (api JobApi) JobCreateEndpoint(c echo.Context) error {
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := service.JobService.Create(context.TODO(), &item); err != nil {
return err
@ -45,7 +46,7 @@ func (api JobApi) JobPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -110,13 +111,17 @@ func (api JobApi) JobGetEndpoint(c echo.Context) error {
func (api JobApi) JobGetLogsEndpoint(c echo.Context) error {
id := c.Param("id")
items, err := repository.JobLogRepository.FindByJobId(context.TODO(), id)
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
items, total, err := repository.JobLogRepository.FindByJobId(context.TODO(), id, pageIndex, pageSize)
if err != nil {
return err
}
return Success(c, items)
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api JobApi) JobDeleteLogsEndpoint(c echo.Context) error {

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -26,7 +27,7 @@ func (api LoginLogApi) LoginLogPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})

143
server/api/login_policy.go Normal file
View File

@ -0,0 +1,143 @@
package api
import (
"context"
"strconv"
"strings"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type LoginPolicyApi struct{}
func (api LoginPolicyApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
userId := c.QueryParam("userId")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.LoginPolicyRepository.Find(context.TODO(), pageIndex, pageSize, name, userId, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api LoginPolicyApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.LoginPolicyService.FindById(context.Background(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api LoginPolicyApi) CreateEndpoint(c echo.Context) error {
var item model.LoginPolicy
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
if err := service.LoginPolicyService.Create(context.Background(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
if err := service.LoginPolicyService.DeleteByIds(context.Background(), split); err != nil {
return err
}
return Success(c, nil)
}
func (api LoginPolicyApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.LoginPolicy
if err := c.Bind(&item); err != nil {
return err
}
if err := service.LoginPolicyService.UpdateById(context.Background(), &item, id); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) GetUserPageEndpoint(c echo.Context) error {
id := c.Param("id")
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username")
nickname := c.QueryParam("nickname")
mail := c.QueryParam("mail")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, "", id, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api LoginPolicyApi) BindEndpoint(c echo.Context) error {
var items []model.LoginPolicyUserRef
if err := c.Bind(&items); err != nil {
return err
}
id := c.Param("id")
if err := service.LoginPolicyService.Bind(context.Background(), id, items); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) UnbindEndpoint(c echo.Context) error {
var items []model.LoginPolicyUserRef
if err := c.Bind(&items); err != nil {
return err
}
id := c.Param("id")
if err := service.LoginPolicyService.Unbind(context.Background(), id, items); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) GetUserIdEndpoint(c echo.Context) error {
id := c.Param("id")
refs, err := repository.LoginPolicyUserRefRepository.FindByLoginPolicyId(context.Background(), id)
if err != nil {
return err
}
var ids = make([]string, 0)
for _, ref := range refs {
ids = append(ids, ref.UserId)
}
return Success(c, ids)
}

View File

@ -2,10 +2,11 @@ package api
import (
"context"
"next-terminal/server/constant"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/stat"
"next-terminal/server/repository"
"time"
"github.com/labstack/echo/v4"
)
@ -14,21 +15,27 @@ type OverviewApi struct{}
func (api OverviewApi) OverviewCounterEndPoint(c echo.Context) error {
var (
countUser int64
countOnlineSession int64
credential int64
asset int64
totalUser int64
onlineUser int64
countOfflineSession int64
totalAsset int64
activeAsset int64
failLoginCount int64
)
countUser, _ = repository.UserRepository.CountOnlineUser(context.TODO())
countOnlineSession, _ = repository.SessionRepository.CountOnlineSession(context.TODO())
credential, _ = repository.CredentialRepository.Count(context.TODO())
asset, _ = repository.AssetRepository.Count(context.TODO())
totalUser, _ = repository.UserRepository.Count(context.TODO())
onlineUser, _ = repository.UserRepository.CountOnlineUser(context.TODO())
countOfflineSession, _ = repository.SessionRepository.CountOfflineSession(context.TODO())
totalAsset, _ = repository.AssetRepository.Count(context.TODO())
activeAsset, _ = repository.AssetRepository.CountByActive(context.TODO(), true)
failLoginCount, _ = repository.LoginLogRepository.CountByState(context.TODO(), "0")
counter := dto.Counter{
User: countUser,
OnlineSession: countOnlineSession,
Credential: credential,
Asset: asset,
TotalUser: totalUser,
OnlineUser: onlineUser,
OfflineSession: countOfflineSession,
TotalAsset: totalAsset,
ActiveAsset: activeAsset,
FailLoginCount: failLoginCount,
}
return Success(c, counter)
@ -43,11 +50,11 @@ func (api OverviewApi) OverviewAssetEndPoint(c echo.Context) error {
kubernetes int64
)
ssh, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.SSH)
rdp, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.RDP)
vnc, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.VNC)
telnet, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.Telnet)
kubernetes, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.K8s)
ssh, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.SSH)
rdp, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.RDP)
vnc, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.VNC)
telnet, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.Telnet)
kubernetes, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.K8s)
m := echo.Map{
"ssh": ssh,
@ -55,14 +62,168 @@ func (api OverviewApi) OverviewAssetEndPoint(c echo.Context) error {
"vnc": vnc,
"telnet": telnet,
"kubernetes": kubernetes,
"all": ssh + rdp + vnc + telnet + kubernetes,
}
return Success(c, m)
}
func (api OverviewApi) OverviewAccessEndPoint(c echo.Context) error {
access, err := repository.SessionRepository.OverviewAccess(context.TODO())
func (api OverviewApi) OverviewDateCounterEndPoint(c echo.Context) error {
d := c.QueryParam("d")
var days = 7
if d == "month" {
days = 30
}
now := time.Now()
lastDate := now.AddDate(0, 0, -days)
// 最近一月登录次数
loginLogCounters, err := repository.LoginLogRepository.CountWithGroupByLoginTime(context.TODO(), lastDate)
if err != nil {
return err
}
return Success(c, access)
// 最近一月活跃用户
userCounters, err := repository.LoginLogRepository.CountWithGroupByLoginTimeAndUsername(context.TODO(), lastDate)
if err != nil {
return err
}
// 最近一月活跃资产
sessionCounters, err := repository.SessionRepository.CountWithGroupByLoginTime(context.TODO(), lastDate)
if err != nil {
return err
}
var counters []dto.DateCounter
for i := 0; i < days; i++ {
day := lastDate.AddDate(0, 0, i).Format("2006-01-02")
var exist = false
for _, counter := range loginLogCounters {
if counter.Date == day {
exist = true
counters = append(counters, dto.DateCounter{
Type: "登录次数",
Date: day,
Value: counter.Value,
})
break
}
}
if !exist {
counters = append(counters, dto.DateCounter{
Type: "登录次数",
Date: day,
Value: 0,
})
}
exist = false
for _, counter := range userCounters {
if counter.Date == day {
exist = true
counters = append(counters, dto.DateCounter{
Type: "活跃用户",
Date: day,
Value: counter.Value,
})
break
}
}
if !exist {
counters = append(counters, dto.DateCounter{
Type: "活跃用户",
Date: day,
Value: 0,
})
}
exist = false
for _, counter := range sessionCounters {
if counter.Date == day {
exist = true
counters = append(counters, dto.DateCounter{
Type: "活跃资产",
Date: day,
Value: counter.Value,
})
break
}
}
if !exist {
counters = append(counters, dto.DateCounter{
Type: "活跃资产",
Date: day,
Value: 0,
})
}
}
return Success(c, counters)
}
func (api OverviewApi) OverviewPS(c echo.Context) error {
//memoryStat, err := mem.VirtualMemory()
//if err != nil {
// return err
//}
//avgStat, err := load.Avg()
//if err != nil {
// return err
//}
//
//cpuCount, err := cpu.Counts(true)
//if err != nil {
// return err
//}
//
//percent, err := cpu.Percent(time.Second, false)
//if err != nil {
// return err
//}
//
//var bytesRead uint64 = 0
//var bytesWrite uint64 = 0
//
//diskIO, err := disk.IOCounters()
//if err != nil {
// return err
//}
//for _, v := range diskIO {
// bytesRead += v.ReadBytes
// bytesWrite += v.WriteBytes
//}
//
//var bytesSent uint64 = 0
//var bytesRecv uint64 = 0
//netIO, err := net.IOCounters(true)
//if err != nil {
// return err
//}
//for _, v := range netIO {
// bytesSent += v.BytesSent
// bytesRecv += v.BytesRecv
//}
//return Success(c, Map{
// "mem": Map{
// "total": memoryStat.Total,
// "usedPercent": memoryStat.UsedPercent,
// },
// "cpu": Map{
// "count": cpuCount,
// "loadAvg": avgStat,
// "usedPercent": percent[0],
// },
// "diskIO": Map{
// "bytesRead": bytesRead,
// "bytesWrite": bytesWrite,
// },
// "netIO": Map{
// "bytesSent": bytesSent,
// "bytesRecv": bytesRecv,
// },
//})
return Success(c, stat.SystemLoad)
}

View File

@ -1,51 +0,0 @@
package api
import (
"context"
"next-terminal/server/dto"
"next-terminal/server/repository"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
)
type ResourceSharerApi struct{}
func (api ResourceSharerApi) RSGetSharersEndPoint(c echo.Context) error {
resourceId := c.QueryParam("resourceId")
resourceType := c.QueryParam("resourceType")
userId := c.QueryParam("userId")
userGroupId := c.QueryParam("userGroupId")
userIds, err := repository.ResourceSharerRepository.Find(context.TODO(), resourceId, resourceType, userId, userGroupId)
if err != nil {
return err
}
return Success(c, userIds)
}
func (api ResourceSharerApi) ResourceRemoveByUserIdAssignEndPoint(c echo.Context) error {
var ru dto.RU
if err := c.Bind(&ru); err != nil {
return err
}
if err := repository.ResourceSharerRepository.DeleteByUserIdAndResourceTypeAndResourceIdIn(context.TODO(), ru.UserGroupId, ru.UserId, ru.ResourceType, ru.ResourceIds); err != nil {
return err
}
return Success(c, "")
}
func (api ResourceSharerApi) ResourceAddByUserIdAssignEndPoint(c echo.Context) error {
var ru dto.RU
if err := c.Bind(&ru); err != nil {
return err
}
if err := service.UserService.AddSharerResources(context.TODO(), ru.UserGroupId, ru.UserId, ru.StrategyId, ru.ResourceType, ru.ResourceIds); err != nil {
return err
}
return Success(c, "")
}

100
server/api/role.go Normal file
View File

@ -0,0 +1,100 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/service"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type RoleApi struct{}
func (api RoleApi) AllEndpoint(c echo.Context) error {
items, err := repository.RoleRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api RoleApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
_type := c.QueryParam("type")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.RoleRepository.Find(context.TODO(), pageIndex, pageSize, name, _type, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api RoleApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.RoleService.FindById(context.Background(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api RoleApi) CreateEndpoint(c echo.Context) error {
var item model.Role
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
item.Deletable = true
item.Modifiable = true
item.Type = "new"
if err := service.RoleService.Create(context.Background(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api RoleApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
if err := service.RoleService.DeleteByIds(context.Background(), split, false); err != nil {
return err
}
return Success(c, nil)
}
func (api RoleApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Role
if err := c.Bind(&item); err != nil {
return err
}
if err := service.RoleService.UpdateById(context.Background(), &item, id, false); err != nil {
return err
}
return Success(c, "")
}
func (api RoleApi) TreeMenus(c echo.Context) error {
return Success(c, service.MenuService.GetTreeMenus())
}

View File

@ -2,7 +2,7 @@ package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -54,7 +54,7 @@ func (api SecurityApi) SecurityPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})

View File

@ -13,9 +13,10 @@ import (
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/global/session"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -44,10 +45,10 @@ func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
}
for i := 0; i < len(items); i++ {
if status == constant.Disconnected && len(items[i].Recording) > 0 {
if status == nt.Disconnected && len(items[i].Recording) > 0 {
var recording string
if items[i].Mode == constant.Native || items[i].Mode == constant.Terminal {
if items[i].Mode == nt.Native || items[i].Mode == nt.Terminal {
recording = items[i].Recording
} else {
recording = items[i].Recording + "/recording"
@ -63,7 +64,7 @@ func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
}
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -71,7 +72,7 @@ func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
func (api SessionApi) SessionDeleteEndpoint(c echo.Context) error {
sessionIds := strings.Split(c.Param("id"), ",")
err := repository.SessionRepository.DeleteByIds(context.TODO(), sessionIds)
err := service.SessionService.DeleteByIds(context.TODO(), sessionIds)
if err != nil {
return err
}
@ -115,8 +116,8 @@ func (api SessionApi) SessionConnectEndpoint(c echo.Context) error {
s := model.Session{}
s.ID = sessionId
s.Status = constant.Connected
s.ConnectedTime = utils.NowJsonTime()
s.Status = nt.Connected
s.ConnectedTime = common.NowJsonTime()
if err := repository.SessionRepository.UpdateById(context.TODO(), &s, sessionId); err != nil {
return err
@ -170,10 +171,10 @@ func (api SessionApi) SessionCreateEndpoint(c echo.Context) error {
assetId := c.QueryParam("assetId")
mode := c.QueryParam("mode")
if mode == constant.Native {
mode = constant.Native
if mode == nt.Native {
mode = nt.Native
} else {
mode = constant.Guacd
mode = nt.Guacd
}
user, _ := GetCurrentAccount(c)
@ -221,6 +222,10 @@ func (api SessionApi) SessionUploadEndpoint(c echo.Context) error {
remoteDir := c.QueryParam("dir")
remoteFile := path.Join(remoteDir, filename)
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionUpload, remoteFile)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
@ -313,6 +318,11 @@ func (api SessionApi) SessionDownloadEndpoint(c echo.Context) error {
return errors.New("禁止操作")
}
file := c.QueryParam("file")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionDownload, file)
// 获取带后缀的文件名称
filenameWithSuffix := path.Base(file)
if "ssh" == s.Protocol {
@ -360,7 +370,6 @@ func (api SessionApi) SessionLsEndpoint(c echo.Context) error {
if nextSession.NextTerminal.SftpClient == nil {
sftpClient, err := sftp.NewClient(nextSession.NextTerminal.SshClient)
if err != nil {
log.Errorf("创建sftp客户端失败%v", err.Error())
return err
}
nextSession.NextTerminal.SftpClient = sftpClient
@ -374,18 +383,13 @@ func (api SessionApi) SessionLsEndpoint(c echo.Context) error {
var files = make([]service.File, 0)
for i := range fileInfos {
// 忽略隐藏文件
if strings.HasPrefix(fileInfos[i].Name(), ".") {
continue
}
file := service.File{
Name: fileInfos[i].Name(),
Path: path.Join(remoteDir, fileInfos[i].Name()),
IsDir: fileInfos[i].IsDir(),
Mode: fileInfos[i].Mode().String(),
IsLink: fileInfos[i].Mode()&os.ModeSymlink == os.ModeSymlink,
ModTime: utils.NewJsonTime(fileInfos[i].ModTime()),
ModTime: common.NewJsonTime(fileInfos[i].ModTime()),
Size: fileInfos[i].Size(),
}
@ -415,6 +419,11 @@ func (api SessionApi) SessionMkDirEndpoint(c echo.Context) error {
return errors.New("禁止操作")
}
remoteDir := c.QueryParam("dir")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionMkdir, remoteDir)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
@ -445,6 +454,11 @@ func (api SessionApi) SessionRmEndpoint(c echo.Context) error {
}
// 文件夹或者文件
file := c.FormValue("file")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionRm, file)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
@ -502,6 +516,11 @@ func (api SessionApi) SessionRenameEndpoint(c echo.Context) error {
}
oldName := c.QueryParam("oldName")
newName := c.QueryParam("newName")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionRename, oldName)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
@ -533,15 +552,13 @@ func (api SessionApi) SessionRecordingEndpoint(c echo.Context) error {
}
var recording string
if s.Mode == constant.Native || s.Mode == constant.Terminal {
if s.Mode == nt.Native || s.Mode == nt.Terminal {
recording = s.Recording
} else {
recording = s.Recording + "/recording"
}
_ = repository.SessionRepository.UpdateReadByIds(context.TODO(), true, []string{sessionId})
log.Debugf("读取录屏文件:%v,是否存在: %v, 是否为文件: %v", recording, utils.FileExists(recording), utils.IsFile(recording))
http.ServeFile(c.Response(), c.Request(), recording)
return nil
}
@ -570,8 +587,8 @@ func (api SessionApi) SessionStatsEndpoint(c echo.Context) error {
if nextSession == nil {
return errors.New("获取会话失败")
}
sshClient := nextSession.NextTerminal.SshClient
stats, err := GetAllStats(sshClient)
stats, err := GetAllStats(nextSession)
if err != nil {
return err
}

View File

@ -1,6 +0,0 @@
package api
func DealCommand(enterKeys []rune) {
println(string(enterKeys))
}

View File

@ -3,6 +3,8 @@ package api
import (
"bufio"
"fmt"
"next-terminal/server/common/taskrunner"
"next-terminal/server/global/session"
"strconv"
"strings"
"time"
@ -70,40 +72,56 @@ type Stat struct {
CPU CPU `json:"cpu"`
}
func GetAllStats(client *ssh.Client) (*Stat, error) {
func GetAllStats(nextSession *session.Session) (*Stat, error) {
client := nextSession.NextTerminal.SshClient
start := time.Now()
stats := &Stat{}
stats := &Stat{
Uptime: nextSession.Uptime,
Hostname: nextSession.Hostname,
}
if stats.Uptime == 0 {
if err := getUptime(client, stats); err != nil {
return nil, err
}
nextSession.Uptime = stats.Uptime
}
if stats.Hostname == "" {
if err := getHostname(client, stats); err != nil {
return nil, err
}
if err := getLoad(client, stats); err != nil {
return nil, err
}
if err := getMem(client, stats); err != nil {
return nil, err
}
if err := getFileSystems(client, stats); err != nil {
return nil, err
}
if err := getInterfaces(client, stats); err != nil {
return nil, err
}
if err := getInterfaceInfo(client, stats); err != nil {
return nil, err
}
if err := getCPU(client, stats); err != nil {
return nil, err
nextSession.Hostname = stats.Hostname
}
runner := taskrunner.Runner{}
runner.Add(func() error {
return getLoad(client, stats)
})
runner.Add(func() error {
return getMem(client, stats)
})
runner.Add(func() error {
return getFileSystems(client, stats)
})
runner.Add(func() error {
return getInterfaces(client, stats)
})
runner.Add(func() error {
return getInterfaceInfo(client, stats)
})
runner.Add(func() error {
return getCPU(client, stats)
})
runner.Wait()
cost := time.Since(start)
fmt.Printf("%s: %v\n", "GetAllStats", cost)
return stats, nil
}
func getHostname(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getHostname")
defer utils.TimeWatcher("getHostname")
hostname, err := utils.RunCommand(client, "/bin/hostname -f")
if err != nil {
return
@ -113,7 +131,7 @@ func getHostname(client *ssh.Client, stat *Stat) (err error) {
}
func getUptime(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getUptime")
defer utils.TimeWatcher("getUptime")
uptime, err := utils.RunCommand(client, "/bin/cat /proc/uptime")
if err != nil {
return
@ -132,7 +150,7 @@ func getUptime(client *ssh.Client, stat *Stat) (err error) {
}
func getLoad(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getLoad")
defer utils.TimeWatcher("getLoad")
line, err := utils.RunCommand(client, "/bin/cat /proc/loadavg")
if err != nil {
return
@ -154,7 +172,7 @@ func getLoad(client *ssh.Client, stat *Stat) (err error) {
}
func getMem(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getMem")
defer utils.TimeWatcher("getMem")
lines, err := utils.RunCommand(client, "/bin/cat /proc/meminfo")
if err != nil {
return
@ -192,7 +210,7 @@ func getMem(client *ssh.Client, stat *Stat) (err error) {
}
func getFileSystems(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getFileSystems")
defer utils.TimeWatcher("getFileSystems")
lines, err := utils.RunCommand(client, "/bin/df -B1")
if err != nil {
return
@ -228,7 +246,7 @@ func getFileSystems(client *ssh.Client, stat *Stat) (err error) {
}
func getInterfaces(client *ssh.Client, stats *Stat) (err error) {
//defer utils.TimeWatcher("getInterfaces")
defer utils.TimeWatcher("getInterfaces")
var lines string
lines, err = utils.RunCommand(client, "/bin/ip -o addr")
if err != nil {
@ -273,16 +291,16 @@ func getInterfaces(client *ssh.Client, stats *Stat) (err error) {
}
func getInterfaceInfo(client *ssh.Client, stats *Stat) (err error) {
//defer utils.TimeWatcher("getInterfaceInfo")
lines, err := utils.RunCommand(client, "/bin/cat /proc/net/dev")
if err != nil {
return
}
defer utils.TimeWatcher("getInterfaceInfo")
if stats.Network == nil {
return
} // should have been here already
lines, err := utils.RunCommand(client, "/bin/cat /proc/net/dev")
if err != nil {
return
}
scanner := bufio.NewScanner(strings.NewReader(lines))
for scanner.Scan() {
line := scanner.Text()
@ -345,7 +363,7 @@ func parseCPUFields(fields []string, stat *cpuRaw) {
var preCPU cpuRaw
func getCPU(client *ssh.Client, stats *Stat) (err error) {
//defer utils.TimeWatcher("getCPU")
defer utils.TimeWatcher("getCPU")
lines, err := utils.RunCommand(client, "/bin/cat /proc/stat")
if err != nil {
return

View File

@ -3,12 +3,14 @@ package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"os"
"path"
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -44,7 +46,7 @@ func (api StorageApi) StoragePagingEndpoint(c echo.Context) error {
}
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -59,7 +61,7 @@ func (api StorageApi) StorageCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
item.Owner = account.ID
// 创建对应的目录文件夹
drivePath := service.StorageService.GetBaseDrivePath()
@ -147,7 +149,7 @@ func (api StorageApi) PermissionCheck(c echo.Context, id string) error {
return err
}
account, _ := GetCurrentAccount(c)
if account.Type != constant.TypeAdmin {
if account.Type != nt.TypeAdmin {
if storage.Owner != account.ID {
return errors.New("您没有权限访问此地址 :(")
}

48
server/api/storage_log.go Normal file
View File

@ -0,0 +1,48 @@
package api
import (
"context"
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/repository"
"strconv"
)
type StorageLogApi struct {
}
func (api StorageLogApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
assetId := c.QueryParam("assetId")
userId := c.QueryParam("userId")
action := c.QueryParam("action")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.StorageLogRepository.Find(context.TODO(), pageIndex, pageSize, assetId, userId, action, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api StorageLogApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.StorageLogRepository.DeleteById(context.Background(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api StorageLogApi) ClearEndpoint(c echo.Context) error {
if err := repository.StorageLogRepository.DeleteAll(context.Background()); err != nil {
return err
}
return Success(c, nil)
}

View File

@ -2,7 +2,8 @@ package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -36,7 +37,7 @@ func (api StrategyApi) StrategyPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -48,7 +49,7 @@ func (api StrategyApi) StrategyCreateEndpoint(c echo.Context) error {
return err
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.StrategyRepository.Create(context.TODO(), &item); err != nil {
return err
@ -80,3 +81,12 @@ func (api StrategyApi) StrategyUpdateEndpoint(c echo.Context) error {
}
return Success(c, "")
}
func (api StrategyApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
strategy, err := repository.StrategyRepository.FindById(context.Background(), id)
if err != nil {
return err
}
return Success(c, strategy)
}

83
server/api/tenant.go Normal file
View File

@ -0,0 +1,83 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type TenantApi struct{}
func (api TenantApi) AllEndpoint(c echo.Context) error {
items, err := repository.TenantRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api TenantApi) 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.TenantRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api TenantApi) CreateEndpoint(c echo.Context) error {
var item model.Tenant
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.TenantRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api TenantApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := repository.TenantRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
}
return Success(c, nil)
}
func (api TenantApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Tenant
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.TenantRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, "")
}

View File

@ -4,20 +4,18 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"next-terminal/server/common/nt"
"path"
"strconv"
"next-terminal/server/common/guacamole"
"next-terminal/server/common/term"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/dto"
"next-terminal/server/global/session"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/term"
"next-terminal/server/utils"
"github.com/gorilla/websocket"
@ -38,7 +36,6 @@ type WebTerminalApi struct {
func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
return err
}
@ -86,7 +83,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
recording := ""
var isRecording = false
property, err := repository.PropertyRepository.FindByName(ctx, guacd.EnableRecording)
property, err := repository.PropertyRepository.FindByName(ctx, guacamole.EnableRecording)
if err == nil && property.Value == "true" {
isRecording = true
}
@ -102,8 +99,8 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
var xterm = "xterm-256color"
var nextTerminal *term.NextTerminal
if "true" == attributes[constant.SocksProxyEnable] {
nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[constant.SocksProxyHost], attributes[constant.SocksProxyPort], attributes[constant.SocksProxyUsername], attributes[constant.SocksProxyPassword])
if "true" == attributes[nt.SocksProxyEnable] {
nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[nt.SocksProxyHost], attributes[nt.SocksProxyPort], attributes[nt.SocksProxyUsername], attributes[nt.SocksProxyPassword])
} else {
nextTerminal, err = term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true)
}
@ -120,20 +117,19 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
return err
}
sess := model.Session{
sessionForUpdate := model.Session{
ConnectionId: sessionId,
Width: cols,
Height: rows,
Status: constant.Connecting,
Status: nt.Connecting,
Recording: recording,
}
if sess.Recording == "" {
if sessionForUpdate.Recording == "" {
// 未录屏时无需审计
sess.Reviewed = true
sessionForUpdate.Reviewed = true
}
// 创建新会话
log.Debugf("创建新会话 %v", sess.ConnectionId)
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
if err := repository.SessionRepository.UpdateById(ctx, &sessionForUpdate, sessionId); err != nil {
return err
}
@ -152,7 +148,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
}
session.GlobalSessionManager.Add(nextSession)
termHandler := NewTermHandler(sessionId, isRecording, ws, nextTerminal)
termHandler := NewTermHandler(s.Creator, s.AssetId, sessionId, isRecording, ws, nextTerminal)
termHandler.Start()
defer termHandler.Stop()
@ -160,14 +156,12 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
_, message, err := ws.ReadMessage()
if err != nil {
// web socket会话关闭后主动关闭ssh会话
log.Debugf("WebSocket已关闭")
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
break
}
msg, err := dto.ParseMessage(string(message))
if err != nil {
log.Warnf("消息解码失败: %v, 原始字符串:%v", err, string(message))
continue
}
@ -175,31 +169,28 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
case Resize:
decodeString, err := base64.StdEncoding.DecodeString(msg.Content)
if err != nil {
log.Warnf("Base64解码失败: %v原始字符串%v", err, msg.Content)
continue
}
var winSize dto.WindowSize
err = json.Unmarshal(decodeString, &winSize)
if err != nil {
log.Warnf("解析SSH会话窗口大小失败: %v原始字符串%v", err, msg.Content)
continue
}
if err := nextTerminal.WindowChange(winSize.Rows, winSize.Cols); err != nil {
log.Warnf("更改SSH会话窗口大小失败: %v", err)
if err := termHandler.WindowChange(winSize.Rows, winSize.Cols); err != nil {
}
_ = repository.SessionRepository.UpdateWindowSizeById(ctx, winSize.Rows, winSize.Cols, sessionId)
case Data:
input := []byte(msg.Content)
_, err := nextTerminal.Write(input)
err := termHandler.Write(input)
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
}
case Ping:
_, _, err := nextTerminal.SshClient.Conn.SendRequest("helloworld1024@foxmail.com", true, nil)
err := termHandler.SendRequest()
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
} else {
_ = termHandler.WriteMessage(dto.NewMessage(Ping, ""))
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
}
}
@ -210,7 +201,6 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
return err
}
@ -238,12 +228,10 @@ func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
WebSocket: ws,
}
nextSession.Observer.Add(obSession)
log.Debugf("会话 %v 观察者 %v 进入", sessionId, obId)
for {
_, _, err := ws.ReadMessage()
if err != nil {
log.Debugf("会话 %v 观察者 %v 退出", sessionId, obId)
nextSession.Observer.Del(obId)
break
}
@ -253,16 +241,16 @@ func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
func (api WebTerminalApi) permissionCheck(c echo.Context, assetId string) error {
user, _ := GetCurrentAccount(c)
if constant.TypeUser == user.Type {
// 检测是否有访问权限
assetIds, err := repository.ResourceSharerRepository.FindAssetIdsByUserId(context.TODO(), user.ID)
if err != nil {
return err
}
if !utils.Contains(assetIds, assetId) {
return errors.New("您没有权限访问此资产")
}
if nt.TypeUser == user.Type {
// 检测是否有访问权限 TODO
//assetIds, err := repository.ResourceSharerRepository.FindAssetIdsByUserId(context.TODO(), user.ID)
//if err != nil {
// return err
//}
//
//if !utils.Contains(assetIds, assetId) {
// return errors.New("您没有权限访问此资产")
//}
}
return nil
}

View File

@ -1,16 +1,16 @@
package api
import (
"bytes"
"context"
"sync"
"time"
"unicode/utf8"
"github.com/gorilla/websocket"
"next-terminal/server/common/term"
"next-terminal/server/dto"
"next-terminal/server/global/session"
"next-terminal/server/term"
"github.com/gorilla/websocket"
)
type TermHandler struct {
@ -23,11 +23,13 @@ type TermHandler struct {
dataChan chan rune
tick *time.Ticker
mutex sync.Mutex
buf bytes.Buffer
}
func NewTermHandler(sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
ctx, cancel := context.WithCancel(context.Background())
tick := time.NewTicker(time.Millisecond * time.Duration(60))
return &TermHandler{
sessionId: sessionId,
isRecording: isRecording,
@ -46,6 +48,7 @@ func (r *TermHandler) Start() {
}
func (r *TermHandler) Stop() {
// 会话结束时记录最后一个命令
r.tick.Stop()
r.cancel()
}
@ -68,15 +71,13 @@ func (r *TermHandler) readFormTunnel() {
}
func (r *TermHandler) writeToWebsocket() {
var buf []byte
for {
select {
case <-r.ctx.Done():
return
case <-r.tick.C:
if len(buf) > 0 {
s := string(buf)
if err := r.WriteMessage(dto.NewMessage(Data, s)); err != nil {
s := r.buf.String()
if err := r.SendMessageToWebSocket(dto.NewMessage(Data, s)); err != nil {
return
}
// 录屏
@ -85,21 +86,35 @@ func (r *TermHandler) writeToWebsocket() {
}
// 监控
SendObData(r.sessionId, s)
buf = []byte{}
}
r.buf.Reset()
case data := <-r.dataChan:
if data != utf8.RuneError {
p := make([]byte, utf8.RuneLen(data))
utf8.EncodeRune(p, data)
buf = append(buf, p...)
r.buf.Write(p)
} else {
buf = append(buf, []byte("@")...)
r.buf.Write([]byte("@"))
}
}
}
}
func (r *TermHandler) WriteMessage(msg dto.Message) error {
func (r *TermHandler) Write(input []byte) error {
// 正常的字符输入
_, err := r.nextTerminal.Write(input)
return err
}
func (r *TermHandler) WindowChange(h int, w int) error {
return r.nextTerminal.WindowChange(h, w)
}
func (r *TermHandler) SendRequest() error {
_, _, err := r.nextTerminal.SshClient.Conn.SendRequest("helloworld1024@foxmail.com", true, nil)
return err
}
func (r *TermHandler) SendMessageToWebSocket(msg dto.Message) error {
if r.webSocket == nil {
return nil
}

View File

@ -1,66 +0,0 @@
package main
import (
"fmt"
"strings"
"github.com/manifoldco/promptui"
)
type pepper struct {
Name string
HeatUnit int
Peppers int
}
func main() {
peppers := []pepper{
{Name: "Bell Pepper", HeatUnit: 0, Peppers: 0},
{Name: "Banana Pepper", HeatUnit: 100, Peppers: 1},
{Name: "Poblano", HeatUnit: 1000, Peppers: 2},
{Name: "Jalapeño", HeatUnit: 3500, Peppers: 3},
{Name: "Aleppo", HeatUnit: 10000, Peppers: 4},
{Name: "Tabasco", HeatUnit: 30000, Peppers: 5},
{Name: "Malagueta", HeatUnit: 50000, Peppers: 6},
{Name: "Habanero", HeatUnit: 100000, Peppers: 7},
{Name: "Red Savina Habanero", HeatUnit: 350000, Peppers: 8},
{Name: "Dragons Breath", HeatUnit: 855000, Peppers: 9},
}
templates := &promptui.SelectTemplates{
Label: "{{ . }}?",
Active: "\U0001F336 {{ .Name | cyan }} ({{ .HeatUnit | red }})",
Inactive: " {{ .Name | cyan }} ({{ .HeatUnit | red }})",
Selected: "\U0001F336 {{ .Name | red | cyan }}",
Details: `
--------- Pepper ----------
{{ "Name:" | faint }} {{ .Name }}/
{{ "Heat Unit:" | faint }} {{ .HeatUnit }}
{{ "Peppers:" | faint }} {{ .Peppers }}`,
}
searcher := func(input string, index int) bool {
pepper := peppers[index]
name := strings.Replace(strings.ToLower(pepper.Name), " ", "", -1)
input = strings.Replace(strings.ToLower(input), " ", "", -1)
return strings.Contains(name, input)
}
prompt := promptui.Select{
Label: "Spicy Level",
Items: peppers,
Templates: templates,
Size: 4,
Searcher: searcher,
}
i, _, err := prompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
fmt.Printf("You choose number %d: %s\n", i+1, peppers[i].Name)
}

View File

@ -2,21 +2,19 @@ package api
import (
"context"
"fmt"
"next-terminal/server/common/maps"
"strconv"
"strings"
"github.com/labstack/echo/v4"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type UserApi struct{}
func (userApi UserApi) UserCreateEndpoint(c echo.Context) (err error) {
func (userApi UserApi) CreateEndpoint(c echo.Context) (err error) {
var item model.User
if err := c.Bind(&item); err != nil {
return err
@ -29,7 +27,7 @@ func (userApi UserApi) UserCreateEndpoint(c echo.Context) (err error) {
return Success(c, item)
}
func (userApi UserApi) UserPagingEndpoint(c echo.Context) error {
func (userApi UserApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username")
@ -38,25 +36,26 @@ func (userApi UserApi) UserPagingEndpoint(c echo.Context) error {
order := c.QueryParam("order")
field := c.QueryParam("field")
online := c.QueryParam("online")
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, order, field)
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, online, "", order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (userApi UserApi) UserUpdateEndpoint(c echo.Context) error {
func (userApi UserApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
account, _ := GetCurrentAccount(c)
if account.ID == id {
return Fail(c, -1, "cannot modify itself")
}
//account, _ := GetCurrentAccount(c)
//if account.ID == id {
// return Fail(c, -1, "cannot modify itself")
//}
var item model.User
if err := c.Bind(&item); err != nil {
@ -70,7 +69,7 @@ func (userApi UserApi) UserUpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (userApi UserApi) UserUpdateStatusEndpoint(c echo.Context) error {
func (userApi UserApi) UpdateStatusEndpoint(c echo.Context) error {
id := c.Param("id")
status := c.QueryParam("status")
account, _ := GetCurrentAccount(c)
@ -85,7 +84,7 @@ func (userApi UserApi) UserUpdateStatusEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (userApi UserApi) UserDeleteEndpoint(c echo.Context) error {
func (userApi UserApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
account, found := GetCurrentAccount(c)
if !found {
@ -105,10 +104,10 @@ func (userApi UserApi) UserDeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (userApi UserApi) UserGetEndpoint(c echo.Context) error {
func (userApi UserApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.UserRepository.FindById(context.TODO(), id)
item, err := service.UserService.FindById(id)
if err != nil {
return err
}
@ -116,49 +115,41 @@ func (userApi UserApi) UserGetEndpoint(c echo.Context) error {
return Success(c, item)
}
func (userApi UserApi) UserChangePasswordEndpoint(c echo.Context) error {
func (userApi UserApi) ChangePasswordEndpoint(c echo.Context) error {
id := c.Param("id")
password := c.FormValue("password")
if password == "" {
return Fail(c, -1, "请输入密码")
}
user, err := repository.UserRepository.FindById(context.TODO(), id)
if err != nil {
ids := strings.Split(id, ",")
if err := service.UserService.ChangePassword(ids, password); err != nil {
return err
}
passwd, err := utils.Encoder.Encode([]byte(password))
if err != nil {
return err
}
u := &model.User{
Password: string(passwd),
ID: id,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
if user.Mail != "" {
subject := "密码修改通知"
text := fmt.Sprintf(`您好%s
管理员已将你的密码修改为%s
`, user.Username, password)
go service.MailService.SendMail(user.Mail, subject, text)
}
return Success(c, "")
}
func (userApi UserApi) UserResetTotpEndpoint(c echo.Context) error {
func (userApi UserApi) ResetTotpEndpoint(c echo.Context) error {
id := c.Param("id")
u := &model.User{
TOTPSecret: "-",
ID: id,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
ids := strings.Split(id, ",")
if err := service.UserService.ResetTotp(ids); err != nil {
return err
}
return Success(c, "")
}
func (userApi UserApi) AllEndpoint(c echo.Context) error {
users, err := repository.UserRepository.FindAll(context.Background())
if err != nil {
return err
}
items := make([]maps.Map, len(users))
for i, user := range users {
items[i] = maps.Map{
"id": user.ID,
"nickname": user.Nickname,
}
}
return Success(c, items)
}

View File

@ -2,6 +2,8 @@ package api
import (
"context"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"strconv"
"strings"
@ -15,7 +17,7 @@ import (
type UserGroupApi struct{}
func (userGroupApi UserGroupApi) UserGroupCreateEndpoint(c echo.Context) error {
var item dto.UserGroup
var item model.UserGroup
if err := c.Bind(&item); err != nil {
return err
}
@ -40,7 +42,7 @@ func (userGroupApi UserGroupApi) UserGroupPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -49,7 +51,7 @@ func (userGroupApi UserGroupApi) UserGroupPagingEndpoint(c echo.Context) error {
func (userGroupApi UserGroupApi) UserGroupUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item dto.UserGroup
var item model.UserGroup
if err := c.Bind(&item); err != nil {
return err
}
@ -82,7 +84,7 @@ func (userGroupApi UserGroupApi) UserGroupGetEndpoint(c echo.Context) error {
return err
}
members, err := repository.UserGroupMemberRepository.FindUserIdsByUserGroupId(context.TODO(), id)
members, err := repository.UserGroupMemberRepository.FindByUserGroupId(context.TODO(), id)
if err != nil {
return err
}
@ -90,8 +92,17 @@ func (userGroupApi UserGroupApi) UserGroupGetEndpoint(c echo.Context) error {
userGroup := dto.UserGroup{
Id: item.ID,
Name: item.Name,
Created: item.Created,
Members: members,
}
return Success(c, userGroup)
}
func (userGroupApi UserGroupApi) UserGroupAllEndpoint(c echo.Context) error {
userGroups, err := repository.UserGroupRepository.FindAll(context.Background())
if err != nil {
return err
}
return Success(c, userGroups)
}

View File

@ -0,0 +1,49 @@
package worker
import (
"context"
"github.com/labstack/echo/v4"
"next-terminal/server/api/abi"
"next-terminal/server/common/maps"
"next-terminal/server/service"
"strconv"
)
type WorkAssetApi struct {
abi.Abi
}
func (api WorkAssetApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
protocol := c.QueryParam("protocol")
tags := c.QueryParam("tags")
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := api.GetCurrentAccount(c)
items, total, err := service.WorkerService.FindMyAssetPaging(pageIndex, pageSize, name, protocol, tags, account.ID, order, field)
if err != nil {
return err
}
for i := range items {
items[i].IP = ""
items[i].Port = 0
}
return api.Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api WorkAssetApi) TagsEndpoint(c echo.Context) (err error) {
account, _ := api.GetCurrentAccount(c)
var items []string
if items, err = service.WorkerService.FindMyAssetTags(context.TODO(), account.ID); err != nil {
return err
}
return api.Success(c, items)
}

View File

@ -0,0 +1,132 @@
package worker
import (
"context"
"errors"
"gorm.io/gorm"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/api/abi"
"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"
)
type WorkCommandApi struct {
abi.Abi
}
func (api WorkCommandApi) CommandCreateEndpoint(c echo.Context) error {
var item model.Command
if err := c.Bind(&item); err != nil {
return err
}
account, _ := api.GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.CommandRepository.Create(context.TODO(), &item); err != nil {
return err
}
return api.Success(c, item)
}
func (api WorkCommandApi) CommandAllEndpoint(c echo.Context) error {
account, _ := api.GetCurrentAccount(c)
userId := account.ID
items, err := repository.CommandRepository.FindByUserId(context.Background(), userId)
if err != nil {
return err
}
return api.Success(c, items)
}
func (api WorkCommandApi) CommandPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
content := c.QueryParam("content")
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := api.GetCurrentAccount(c)
userId := account.ID
items, total, err := repository.CommandRepository.WorkerFind(context.TODO(), pageIndex, pageSize, name, content, order, field, userId)
if err != nil {
return err
}
return api.Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api WorkCommandApi) CommandUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if !api.checkPermission(c, id) {
return nt.ErrPermissionDenied
}
var item model.Command
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.CommandRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return api.Success(c, nil)
}
func (api WorkCommandApi) CommandDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if !api.checkPermission(c, id) {
return nt.ErrPermissionDenied
}
if err := repository.CommandRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
}
return api.Success(c, nil)
}
func (api WorkCommandApi) CommandGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if !api.checkPermission(c, id) {
return nt.ErrPermissionDenied
}
var item model.Command
if item, err = repository.CommandRepository.FindById(context.TODO(), id); err != nil {
return err
}
return api.Success(c, item)
}
func (api WorkCommandApi) checkPermission(c echo.Context, commandId string) bool {
command, err := repository.CommandRepository.FindById(context.Background(), commandId)
if err != nil {
if errors.Is(gorm.ErrRecordNotFound, err) {
return true
}
return false
}
account, _ := api.GetCurrentAccount(c)
userId := account.ID
return command.Owner == userId
}

View File

@ -3,16 +3,12 @@ package app
import (
"encoding/json"
"fmt"
"net/http"
_ "net/http/pprof"
"next-terminal/server/log"
"next-terminal/server/cli"
"next-terminal/server/branding"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/service"
"next-terminal/server/sshd"
"next-terminal/server/task"
"github.com/labstack/echo/v4"
)
@ -66,7 +62,12 @@ func (app App) InitDBData() (err error) {
if err := service.StorageService.InitStorages(); err != nil {
return err
}
if err := service.MenuService.Init(); err != nil {
return err
}
if err := service.RoleService.Init(); err != nil {
return err
}
// 修复数据
if err := service.AssetService.FixSshMode(); err != nil {
return err
@ -93,7 +94,7 @@ func (app App) ReloadData() error {
func Run() error {
fmt.Printf(constant.AppBanner, constant.AppVersion)
fmt.Printf(branding.Hi)
if err := app.InitDBData(); err != nil {
panic(err)
@ -108,13 +109,10 @@ func Run() error {
if err != nil {
return err
}
go func() {
log.Fatal(http.ListenAndServe("localhost:8099", nil))
}()
fmt.Printf("当前配置为: %v\n", string(jsonBytes))
}
_cli := cli.NewCli()
_cli := service.NewCli()
if config.GlobalCfg.ResetPassword != "" {
return _cli.ResetPassword(config.GlobalCfg.ResetPassword)
@ -127,6 +125,9 @@ func Run() error {
return _cli.ChangeEncryptionKey(config.GlobalCfg.EncryptionKey, config.GlobalCfg.NewEncryptionKey)
}
ticker := task.NewTicker()
ticker.SetupTicker()
if config.GlobalCfg.Sshd.Enable {
go sshd.Sshd.Serve()
}

View File

@ -1,145 +0,0 @@
package app
import (
"fmt"
"net"
"strings"
"next-terminal/server/api"
"next-terminal/server/constant"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/global/security"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil {
if he, ok := err.(*echo.HTTPError); ok {
message := fmt.Sprintf("%v", he.Message)
return api.Fail(c, he.Code, message)
}
return api.Fail(c, 0, err.Error())
}
return nil
}
}
func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
securities := security.GlobalSecurityManager.Values()
if len(securities) == 0 {
return next(c)
}
ip := c.RealIP()
for _, s := range securities {
if strings.Contains(s.IP, "/") {
// CIDR
_, ipNet, err := net.ParseCIDR(s.IP)
if err != nil {
continue
}
if !ipNet.Contains(net.ParseIP(ip)) {
continue
}
} else if strings.Contains(s.IP, "-") {
// 范围段
split := strings.Split(s.IP, "-")
if len(split) < 2 {
continue
}
start := split[0]
end := split[1]
intReqIP := utils.IpToInt(ip)
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
continue
}
} else {
// IP
if s.IP != ip {
continue
}
}
if s.Rule == constant.AccessRuleAllow {
return next(c)
}
if s.Rule == constant.AccessRuleReject {
if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(constant.Token) != "" {
return api.Fail(c, 0, "您的访问请求被拒绝 :(")
} else {
return c.HTML(666, "您的访问请求被拒绝 :(")
}
}
}
return next(c)
}
}
var anonymousUrls = []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"}
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
uri := c.Request().RequestURI
if uri == "/" || strings.HasPrefix(uri, "/#") {
return next(c)
}
// 路由拦截 - 登录身份、资源权限判断等
for i := range anonymousUrls {
if strings.HasPrefix(uri, anonymousUrls[i]) {
return next(c)
}
}
token := api.GetToken(c)
if token == "" {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
v, found := cache.TokenManager.Get(token)
if !found {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
authorization := v.(dto.Authorization)
if strings.EqualFold(constant.LoginToken, authorization.Type) {
if authorization.Remember {
// 记住登录有效期两周
cache.TokenManager.Set(token, authorization, cache.RememberMeExpiration)
} else {
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
}
return next(c)
}
}
func Admin(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
account, found := api.GetCurrentAccount(c)
if !found {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
if account.Type != constant.TypeAdmin {
return api.Fail(c, 403, "permission denied")
}
return next(c)
}
}

View File

@ -0,0 +1,138 @@
package middleware
import (
"next-terminal/server/common/nt"
"strings"
"next-terminal/server/api"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
"github.com/ucarion/urlpath"
)
var anonymousUrls = []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/branding"}
var allowUrls = []urlpath.Path{
urlpath.New("/account/info"),
urlpath.New("/share-sessions/:id"),
urlpath.New("/sessions"),
urlpath.New("/sessions/:id/tunnel"),
urlpath.New("/sessions/:id/connect"),
urlpath.New("/sessions/:id/resize"),
urlpath.New("/sessions/:id/stats"),
urlpath.New("/sessions/:id/ls"),
urlpath.New("/sessions/:id/download"),
urlpath.New("/sessions/:id/upload"),
urlpath.New("/sessions/:id/edit"),
urlpath.New("/sessions/:id/mkdir"),
urlpath.New("/sessions/:id/rm"),
urlpath.New("/sessions/:id/rename"),
urlpath.New("/sessions/:id/ssh"),
}
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
uri := c.Request().RequestURI
if uri == "/" || strings.HasPrefix(uri, "/#") {
return next(c)
}
// 路由拦截 - 登录身份、资源权限判断等
for i := range anonymousUrls {
if strings.HasPrefix(uri, anonymousUrls[i]) {
return next(c)
}
}
token := api.GetToken(c)
if token == "" {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
v, found := cache.TokenManager.Get(token)
if !found {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
authorization := v.(dto.Authorization)
if strings.EqualFold(nt.LoginToken, authorization.Type) {
if authorization.Remember {
// 记住登录有效期两周
cache.TokenManager.Set(token, authorization, cache.RememberMeExpiration)
} else {
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
}
if strings.HasPrefix(uri, "/account") {
return next(c)
}
if strings.HasPrefix(uri, "/worker") {
return next(c)
}
// 放行接入相关接口
uri = strings.Split(uri, "?")[0]
for _, url := range allowUrls {
_, ok := url.Match(uri)
if ok {
return next(c)
}
}
account, _ := api.GetCurrentAccount(c)
if service.UserService.IsSuperAdmin(account.ID) {
return next(c)
}
var roles []string
v, ok := cache.UserRolesManager.Get(account.ID)
if ok {
roles = v.([]string)
if len(roles) == 0 {
roles, _ = service.RoleService.GetRolesByUserId(account.ID)
cache.UserRolesManager.SetDefault(account.ID, roles)
}
} else {
roles, _ = service.RoleService.GetRolesByUserId(account.ID)
cache.UserRolesManager.SetDefault(account.ID, roles)
}
urlPath := c.Request().URL.Path
for _, role := range roles {
menus := service.RoleService.GetMenuListByRole(role)
for _, menu := range menus {
permissions := service.MenuService.GetPermissionByMenu(menu)
for _, perm := range permissions {
_, ok := perm.Match(urlPath)
if ok {
return next(c)
}
}
}
}
return api.Fail(c, 403, "permission denied")
}
}
func Admin(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
account, found := api.GetCurrentAccount(c)
if !found {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
if account.Type != nt.TypeAdmin {
return api.Fail(c, 403, "permission denied.")
}
return next(c)
}
}

View File

@ -0,0 +1,28 @@
package middleware
import (
"fmt"
"next-terminal/server/log"
"next-terminal/server/api"
"github.com/labstack/echo/v4"
)
func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil {
fmt.Printf("%+v\n", err)
log.Error("api error", log.NamedError("err", err))
if he, ok := err.(*echo.HTTPError); ok {
message := fmt.Sprintf("%v", he.Message)
return api.Fail(c, he.Code, message)
}
return api.Fail(c, -1, err.Error())
}
return nil
}
}

View File

@ -0,0 +1,72 @@
package middleware
import (
"net"
"next-terminal/server/common/nt"
"strings"
"next-terminal/server/api"
"next-terminal/server/global/security"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
securities := security.GlobalSecurityManager.Values()
if len(securities) == 0 {
return next(c)
}
ip := c.RealIP()
var pass = true
for _, s := range securities {
ipGroups := strings.Split(s.IP, ",")
for _, ipGroup := range ipGroups {
if strings.Contains(ipGroup, "/") {
// CIDR
_, ipNet, err := net.ParseCIDR(ipGroup)
if err != nil {
continue
}
if !ipNet.Contains(net.ParseIP(ip)) {
continue
}
} else if strings.Contains(ipGroup, "-") {
// 范围段
split := strings.Split(ipGroup, "-")
if len(split) < 2 {
continue
}
start := split[0]
end := split[1]
intReqIP := utils.IpToInt(ip)
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
continue
}
} else {
// IP
if ipGroup != ip {
continue
}
}
pass = s.Rule == nt.AccessRuleAllow
}
}
if !pass {
if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(nt.Token) != "" {
return api.Fail(c, -1, "您的访问请求被拒绝 :(")
} else {
return c.HTML(666, "您的访问请求被拒绝 :(")
}
}
return next(c)
}
}

View File

@ -6,6 +6,8 @@ import (
"os"
"next-terminal/server/api"
"next-terminal/server/api/worker"
mw "next-terminal/server/app/middleware"
"next-terminal/server/config"
"next-terminal/server/log"
"next-terminal/server/resource"
@ -48,7 +50,7 @@ func setupRoutes() *echo.Echo {
fileServer := http.FileServer(http.FS(fsys))
handler := WrapHandler(fileServer)
e.GET("/", handler)
e.GET("/asciinema.html", handler)
e.GET("/branding", api.Branding)
e.GET("/favicon.ico", handler)
e.GET("/static/*", handler)
@ -58,9 +60,11 @@ func setupRoutes() *echo.Echo {
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))
e.Use(ErrorHandler)
e.Use(TcpWall)
e.Use(Auth)
e.Use(mw.ErrorHandler)
e.Use(mw.TcpWall)
e.Use(mw.Auth)
//e.Use(RBAC)
e.Use(middleware.Gzip())
accountApi := new(api.AccountApi)
guacamoleApi := new(api.GuacamoleApi)
@ -71,7 +75,6 @@ func setupRoutes() *echo.Echo {
CommandApi := new(api.CommandApi)
CredentialApi := new(api.CredentialApi)
SessionApi := new(api.SessionApi)
ResourceSharerApi := new(api.ResourceSharerApi)
LoginLogApi := new(api.LoginLogApi)
PropertyApi := new(api.PropertyApi)
OverviewApi := new(api.OverviewApi)
@ -81,14 +84,17 @@ func setupRoutes() *echo.Echo {
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)
e.POST("/login", accountApi.LoginEndpoint)
e.POST("/loginWithTotp", accountApi.LoginWithTotpEndpoint)
account := e.Group("/account")
{
account.GET("/info", accountApi.InfoEndpoint)
account.GET("/assets", accountApi.AccountAssetEndpoint)
account.GET("/storage", accountApi.AccountStorageEndpoint)
account.POST("/logout", accountApi.LogoutEndpoint)
account.POST("/change-password", accountApi.ChangePasswordEndpoint)
@ -97,30 +103,54 @@ func setupRoutes() *echo.Echo {
account.POST("/confirm-totp", accountApi.ConfirmTOTPEndpoint)
account.GET("/access-token", accountApi.AccessTokenGetEndpoint)
account.POST("/access-token", accountApi.AccessTokenGenEndpoint)
account.DELETE("/access-token", accountApi.AccessTokenDelEndpoint)
}
users := e.Group("/users", Admin)
_worker := e.Group("/worker")
{
users.POST("", UserApi.UserCreateEndpoint)
users.GET("/paging", UserApi.UserPagingEndpoint)
users.PUT("/:id", UserApi.UserUpdateEndpoint)
users.PATCH("/:id/status", UserApi.UserUpdateStatusEndpoint)
users.DELETE("/:id", UserApi.UserDeleteEndpoint)
users.GET("/:id", UserApi.UserGetEndpoint)
users.POST("/:id/change-password", UserApi.UserChangePasswordEndpoint)
users.POST("/:id/reset-totp", UserApi.UserResetTotpEndpoint)
commands := _worker.Group("/commands")
{
workerCommandApi := new(worker.WorkCommandApi)
commands.GET("", workerCommandApi.CommandAllEndpoint)
commands.GET("/paging", workerCommandApi.CommandPagingEndpoint)
commands.POST("", workerCommandApi.CommandCreateEndpoint)
commands.PUT("/:id", workerCommandApi.CommandUpdateEndpoint)
commands.DELETE("/:id", workerCommandApi.CommandDeleteEndpoint)
commands.GET("/:id", workerCommandApi.CommandGetEndpoint)
}
userGroups := e.Group("/user-groups", Admin)
assets := _worker.Group("/assets")
{
workAssetApi := new(worker.WorkAssetApi)
assets.GET("/paging", workAssetApi.PagingEndpoint)
assets.GET("/tags", workAssetApi.TagsEndpoint)
}
}
users := e.Group("/users", mw.Admin)
{
users.GET("", UserApi.AllEndpoint)
users.GET("/paging", UserApi.PagingEndpoint)
users.POST("", UserApi.CreateEndpoint)
users.PUT("/:id", UserApi.UpdateEndpoint)
users.PATCH("/:id/status", UserApi.UpdateStatusEndpoint)
users.DELETE("/:id", UserApi.DeleteEndpoint)
users.GET("/:id", UserApi.GetEndpoint)
users.POST("/:id/change-password", UserApi.ChangePasswordEndpoint)
users.POST("/:id/reset-totp", UserApi.ResetTotpEndpoint)
}
userGroups := e.Group("/user-groups", mw.Admin)
{
userGroups.POST("", UserGroupApi.UserGroupCreateEndpoint)
userGroups.GET("", UserGroupApi.UserGroupAllEndpoint)
userGroups.GET("/paging", UserGroupApi.UserGroupPagingEndpoint)
userGroups.PUT("/:id", UserGroupApi.UserGroupUpdateEndpoint)
userGroups.DELETE("/:id", UserGroupApi.UserGroupDeleteEndpoint)
userGroups.GET("/:id", UserGroupApi.UserGroupGetEndpoint)
}
assets := e.Group("/assets", Admin)
assets := e.Group("/assets", mw.Admin)
{
assets.GET("", AssetApi.AssetAllEndpoint)
assets.POST("", AssetApi.AssetCreateEndpoint)
@ -135,7 +165,7 @@ func setupRoutes() *echo.Echo {
e.GET("/tags", AssetApi.AssetTagsEndpoint)
commands := e.Group("/commands")
commands := e.Group("/commands", mw.Admin)
{
commands.GET("", CommandApi.CommandAllEndpoint)
commands.GET("/paging", CommandApi.CommandPagingEndpoint)
@ -143,12 +173,12 @@ func setupRoutes() *echo.Echo {
commands.PUT("/:id", CommandApi.CommandUpdateEndpoint)
commands.DELETE("/:id", CommandApi.CommandDeleteEndpoint)
commands.GET("/:id", CommandApi.CommandGetEndpoint)
commands.POST("/:id/change-owner", CommandApi.CommandChangeOwnerEndpoint, Admin)
commands.POST("/:id/change-owner", CommandApi.CommandChangeOwnerEndpoint, mw.Admin)
}
credentials := e.Group("/credentials", Admin)
credentials := e.Group("/credentials", mw.Admin)
{
credentials.GET("", CredentialApi.CredentialAllEndpoint)
//credentials.GET("", CredentialApi.CredentialAllEndpoint)
credentials.GET("/paging", CredentialApi.CredentialPagingEndpoint)
credentials.POST("", CredentialApi.CredentialCreateEndpoint)
credentials.PUT("/:id", CredentialApi.CredentialUpdateEndpoint)
@ -159,15 +189,15 @@ func setupRoutes() *echo.Echo {
sessions := e.Group("/sessions")
{
sessions.GET("/paging", Admin(SessionApi.SessionPagingEndpoint))
sessions.POST("/:id/disconnect", Admin(SessionApi.SessionDisconnectEndpoint))
sessions.DELETE("/:id", Admin(SessionApi.SessionDeleteEndpoint))
sessions.GET("/:id/recording", Admin(SessionApi.SessionRecordingEndpoint))
sessions.GET("/:id", Admin(SessionApi.SessionGetEndpoint))
sessions.POST("/:id/reviewed", Admin(SessionApi.SessionReviewedEndpoint))
sessions.POST("/:id/unreviewed", Admin(SessionApi.SessionUnViewedEndpoint))
sessions.POST("/clear", Admin(SessionApi.SessionClearEndpoint))
sessions.POST("/reviewed", Admin(SessionApi.SessionReviewedAllEndpoint))
sessions.GET("/paging", mw.Admin(SessionApi.SessionPagingEndpoint))
sessions.POST("/:id/disconnect", mw.Admin(SessionApi.SessionDisconnectEndpoint))
sessions.DELETE("/:id", mw.Admin(SessionApi.SessionDeleteEndpoint))
sessions.GET("/:id/recording", mw.Admin(SessionApi.SessionRecordingEndpoint))
sessions.GET("/:id", mw.Admin(SessionApi.SessionGetEndpoint))
sessions.POST("/:id/reviewed", mw.Admin(SessionApi.SessionReviewedEndpoint))
sessions.POST("/:id/unreviewed", mw.Admin(SessionApi.SessionUnViewedEndpoint))
sessions.POST("/clear", mw.Admin(SessionApi.SessionClearEndpoint))
sessions.POST("/reviewed", mw.Admin(SessionApi.SessionReviewedAllEndpoint))
sessions.POST("", SessionApi.SessionCreateEndpoint)
sessions.POST("/:id/connect", SessionApi.SessionConnectEndpoint)
@ -187,34 +217,35 @@ func setupRoutes() *echo.Echo {
sessions.POST("/:id/rename", SessionApi.SessionRenameEndpoint)
}
resourceSharers := e.Group("/resource-sharers", Admin)
{
resourceSharers.GET("", ResourceSharerApi.RSGetSharersEndPoint)
resourceSharers.POST("/remove-resources", ResourceSharerApi.ResourceRemoveByUserIdAssignEndPoint)
resourceSharers.POST("/add-resources", ResourceSharerApi.ResourceAddByUserIdAssignEndPoint)
}
loginLogs := e.Group("login-logs", Admin)
loginLogs := e.Group("login-logs", mw.Admin)
{
loginLogs.GET("/paging", LoginLogApi.LoginLogPagingEndpoint)
loginLogs.DELETE("/:id", LoginLogApi.LoginLogDeleteEndpoint)
loginLogs.POST("/clear", LoginLogApi.LoginLogClearEndpoint)
}
properties := e.Group("properties", Admin)
storageLogs := e.Group("storage-logs", mw.Admin)
{
storageLogs.GET("/paging", StorageLogApi.PagingEndpoint)
storageLogs.DELETE("/:id", StorageLogApi.DeleteEndpoint)
storageLogs.POST("/clear", StorageLogApi.ClearEndpoint)
}
properties := e.Group("properties", mw.Admin)
{
properties.GET("", PropertyApi.PropertyGetEndpoint)
properties.PUT("", PropertyApi.PropertyUpdateEndpoint)
}
overview := e.Group("overview", Admin)
overview := e.Group("overview", mw.Admin)
{
overview.GET("/counter", OverviewApi.OverviewCounterEndPoint)
overview.GET("/asset", OverviewApi.OverviewAssetEndPoint)
overview.GET("/access", OverviewApi.OverviewAccessEndPoint)
overview.GET("/date-counter", OverviewApi.OverviewDateCounterEndPoint)
overview.GET("/ps", OverviewApi.OverviewPS)
}
jobs := e.Group("/jobs", Admin)
jobs := e.Group("/jobs", mw.Admin)
{
jobs.POST("", JobApi.JobCreateEndpoint)
jobs.GET("/paging", JobApi.JobPagingEndpoint)
@ -223,11 +254,12 @@ func setupRoutes() *echo.Echo {
jobs.POST("/:id/exec", JobApi.JobExecEndpoint)
jobs.DELETE("/:id", JobApi.JobDeleteEndpoint)
jobs.GET("/:id", JobApi.JobGetEndpoint)
jobs.GET("/:id/logs", JobApi.JobGetLogsEndpoint)
jobs.GET("/:id/logs/paging", JobApi.JobGetLogsEndpoint)
jobs.DELETE("/:id/logs", JobApi.JobDeleteLogsEndpoint)
}
securities := e.Group("/securities", Admin)
securities := e.Group("/securities", mw.Admin)
{
securities.POST("", SecurityApi.SecurityCreateEndpoint)
securities.GET("/paging", SecurityApi.SecurityPagingEndpoint)
@ -238,12 +270,12 @@ func setupRoutes() *echo.Echo {
storages := e.Group("/storages")
{
storages.GET("/paging", StorageApi.StoragePagingEndpoint, Admin)
storages.POST("", StorageApi.StorageCreateEndpoint, Admin)
storages.DELETE("/:id", StorageApi.StorageDeleteEndpoint, Admin)
storages.PUT("/:id", StorageApi.StorageUpdateEndpoint, Admin)
storages.GET("/shares", StorageApi.StorageSharesEndpoint, Admin)
storages.GET("/:id", StorageApi.StorageGetEndpoint, Admin)
storages.GET("/paging", StorageApi.StoragePagingEndpoint, mw.Admin)
storages.POST("", StorageApi.StorageCreateEndpoint, mw.Admin)
storages.DELETE("/:id", StorageApi.StorageDeleteEndpoint, mw.Admin)
storages.PUT("/:id", StorageApi.StorageUpdateEndpoint, mw.Admin)
storages.GET("/shares", StorageApi.StorageSharesEndpoint, mw.Admin)
storages.GET("/:id", StorageApi.StorageGetEndpoint, mw.Admin)
storages.POST("/:storageId/ls", StorageApi.StorageLsEndpoint)
storages.GET("/:storageId/download", StorageApi.StorageDownloadEndpoint)
@ -254,16 +286,17 @@ func setupRoutes() *echo.Echo {
storages.POST("/:storageId/edit", StorageApi.StorageEditEndpoint)
}
strategies := e.Group("/strategies", Admin)
strategies := e.Group("/strategies", mw.Admin)
{
strategies.GET("", StrategyApi.StrategyAllEndpoint)
strategies.GET("/paging", StrategyApi.StrategyPagingEndpoint)
strategies.POST("", StrategyApi.StrategyCreateEndpoint)
strategies.DELETE("/:id", StrategyApi.StrategyDeleteEndpoint)
strategies.PUT("/:id", StrategyApi.StrategyUpdateEndpoint)
strategies.GET("/:id", StrategyApi.GetEndpoint)
}
accessGateways := e.Group("/access-gateways", Admin)
accessGateways := e.Group("/access-gateways", mw.Admin)
{
accessGateways.GET("", AccessGatewayApi.AccessGatewayAllEndpoint)
accessGateways.POST("", AccessGatewayApi.AccessGatewayCreateEndpoint)
@ -273,11 +306,57 @@ func setupRoutes() *echo.Echo {
accessGateways.GET("/:id", AccessGatewayApi.AccessGatewayGetEndpoint)
}
backup := e.Group("/backup", Admin)
backup := e.Group("/backup", mw.Admin)
{
backup.GET("/export", BackupApi.BackupExportEndpoint)
backup.POST("/import", BackupApi.BackupImportEndpoint)
}
tenants := e.Group("/tenants", mw.Admin)
{
tenants.GET("", TenantApi.AllEndpoint)
tenants.GET("/paging", TenantApi.PagingEndpoint)
tenants.POST("", TenantApi.CreateEndpoint)
tenants.DELETE("/:id", TenantApi.DeleteEndpoint)
tenants.PUT("/:id", TenantApi.UpdateEndpoint)
}
roles := e.Group("/roles", mw.Admin)
{
roles.GET("", RoleApi.AllEndpoint)
roles.GET("/paging", RoleApi.PagingEndpoint)
roles.GET("/:id", RoleApi.GetEndpoint)
roles.POST("", RoleApi.CreateEndpoint)
roles.DELETE("/:id", RoleApi.DeleteEndpoint)
roles.PUT("/:id", RoleApi.UpdateEndpoint)
}
loginPolicies := e.Group("/login-policies", mw.Admin)
{
loginPolicies.GET("/paging", LoginPolicyApi.PagingEndpoint)
loginPolicies.GET("/:id", LoginPolicyApi.GetEndpoint)
loginPolicies.GET("/:id/users/paging", LoginPolicyApi.GetUserPageEndpoint)
loginPolicies.GET("/:id/users/id", LoginPolicyApi.GetUserIdEndpoint)
loginPolicies.POST("", LoginPolicyApi.CreateEndpoint)
loginPolicies.DELETE("/:id", LoginPolicyApi.DeleteEndpoint)
loginPolicies.PUT("/:id", LoginPolicyApi.UpdateEndpoint)
loginPolicies.POST("/:id/bind", LoginPolicyApi.BindEndpoint)
loginPolicies.POST("/:id/unbind", LoginPolicyApi.UnbindEndpoint)
}
authorised := e.Group("/authorised", mw.Admin)
{
authorised.GET("/assets/paging", AuthorisedApi.PagingAsset)
authorised.GET("/users/paging", AuthorisedApi.PagingUser)
authorised.GET("/user-groups/paging", AuthorisedApi.PagingUserGroup)
authorised.GET("/selected", AuthorisedApi.Selected)
authorised.POST("/assets", AuthorisedApi.AuthorisedAssets)
authorised.POST("/users", AuthorisedApi.AuthorisedUsers)
authorised.POST("/user-groups", AuthorisedApi.AuthorisedUserGroups)
authorised.DELETE("/:id", AuthorisedApi.Delete)
}
e.GET("/menus", RoleApi.TreeMenus, mw.Admin)
return e
}

View File

@ -0,0 +1,13 @@
package branding
var Name = "Next Terminal"
var Copyright = "Copyright © 2020-2022 dushixiang, All Rights Reserved."
var Banner = ` ___ ___
/\__\ /\ \
/:| _|_ \:\ \
/::|/\__\ /::\__\
\/|::/ / /:/\/__/
|:/ / \/__/
\/__/ `
var Version = `v1.3.0-beta1`
var Hi = Banner + Version

View File

@ -1,12 +1,16 @@
package guacd
package guacamole
import (
"bufio"
"encoding/base64"
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
)
const (
@ -288,3 +292,12 @@ func (opt *Tunnel) Close() error {
opt.IsOpen = false
return opt.conn.Close()
}
func Disconnect(ws *websocket.Conn, code int, reason string) {
// guacd 无法处理中文字符所以进行了base64编码。
encodeReason := base64.StdEncoding.EncodeToString([]byte(reason))
err := NewInstruction("error", encodeReason, strconv.Itoa(code))
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.String()))
disconnect := NewInstruction("disconnect")
_ = ws.WriteMessage(websocket.TextMessage, []byte(disconnect.String()))
}

View File

@ -1,4 +1,4 @@
package utils
package common
import (
"database/sql/driver"

View File

@ -0,0 +1,3 @@
package maps
type Map map[string]interface{}

View File

@ -1,21 +1,7 @@
package constant
package nt
import (
"next-terminal/server/guacd"
)
const (
AppVersion = "v1.2.7"
AppName = "Next Terminal"
AppBanner = `
_______ __ ___________ .__ .__
\ \ ____ ___ ____/ |_ \__ ___/__________ _____ |__| ____ _____ | |
/ | \_/ __ \\ \/ /\ __\ | |_/ __ \_ __ \/ \| |/ \\__ \ | |
/ | \ ___/ > < | | | |\ ___/| | \/ Y Y \ | | \/ __ \| |__
\____|__ /\___ >__/\_ \ |__| |____| \___ >__| |__|_| /__|___| (____ /____/
\/ \/ \/ \/ \/ \/ \/ %s
`
"next-terminal/server/common/guacamole"
)
const Token = "X-Auth-Token"
@ -78,10 +64,16 @@ const (
ShareSession = "share-session"
Anonymous = "anonymous"
StorageLogActionRm = "rm" // 删除
StorageLogActionUpload = "upload" // 上传
StorageLogActionDownload = "download" // 下载
StorageLogActionMkdir = "mkdir" // 创建文件夹
StorageLogActionRename = "rename" // 重命名
)
var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode, SocksProxyEnable, SocksProxyHost, SocksProxyPort, SocksProxyUsername, SocksProxyPassword}
var RDPParameterNames = []string{guacd.Domain, guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs, guacd.EnableDrive, guacd.DrivePath, guacd.ColorDepth, guacd.ForceLossless, guacd.PreConnectionId, guacd.PreConnectionBlob}
var VNCParameterNames = []string{guacd.ColorDepth, guacd.Cursor, guacd.SwapRedBlue, guacd.DestHost, guacd.DestPort}
var TelnetParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.UsernameRegex, guacd.PasswordRegex, guacd.LoginSuccessRegex, guacd.LoginFailureRegex}
var KubernetesParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.Namespace, guacd.Pod, guacd.Container, guacd.UesSSL, guacd.ClientCert, guacd.ClientKey, guacd.CaCert, guacd.IgnoreCert}
var SSHParameterNames = []string{guacamole.FontName, guacamole.FontSize, guacamole.ColorScheme, guacamole.Backspace, guacamole.TerminalType, SshMode, SocksProxyEnable, SocksProxyHost, SocksProxyPort, SocksProxyUsername, SocksProxyPassword}
var RDPParameterNames = []string{guacamole.Domain, guacamole.RemoteApp, guacamole.RemoteAppDir, guacamole.RemoteAppArgs, guacamole.EnableDrive, guacamole.DrivePath, guacamole.ColorDepth, guacamole.ForceLossless, guacamole.PreConnectionId, guacamole.PreConnectionBlob}
var VNCParameterNames = []string{guacamole.ColorDepth, guacamole.Cursor, guacamole.SwapRedBlue, guacamole.DestHost, guacamole.DestPort}
var TelnetParameterNames = []string{guacamole.FontName, guacamole.FontSize, guacamole.ColorScheme, guacamole.Backspace, guacamole.TerminalType, guacamole.UsernameRegex, guacamole.PasswordRegex, guacamole.LoginSuccessRegex, guacamole.LoginFailureRegex}
var KubernetesParameterNames = []string{guacamole.FontName, guacamole.FontSize, guacamole.ColorScheme, guacamole.Backspace, guacamole.TerminalType, guacamole.Namespace, guacamole.Pod, guacamole.Container, guacamole.UesSSL, guacamole.ClientCert, guacamole.ClientKey, guacamole.CaCert, guacamole.IgnoreCert}

View File

@ -0,0 +1,8 @@
package nt
import "errors"
var (
ErrNameAlreadyUsed = errors.New("name already used")
ErrPermissionDenied = errors.New("permission denied")
)

34
server/common/sets/set.go Normal file
View File

@ -0,0 +1,34 @@
package sets
func NewStringSet() *Set {
return &Set{data: make(map[string]struct{})}
}
type Set struct {
data map[string]struct{}
}
func (s *Set) Add(key ...string) {
for _, k := range key {
s.data[k] = struct{}{}
}
}
func (s *Set) Remove(key ...string) {
for _, k := range key {
delete(s.data, k)
}
}
func (s *Set) Contains(key string) bool {
_, ok := s.data[key]
return ok
}
func (s *Set) ToArray() []string {
var keys []string
for key, _ := range s.data {
keys = append(keys, key)
}
return keys
}

View File

@ -0,0 +1 @@
package slices

View File

@ -0,0 +1,30 @@
package taskrunner
import "sync"
type Runner struct {
wg sync.WaitGroup
errors []error
mux sync.Mutex
}
func (r *Runner) Add(f func() error) {
r.wg.Add(1)
go func() {
defer r.wg.Done()
if err := f(); err != nil {
r.addError(err)
}
}()
}
func (r *Runner) addError(err error) {
r.mux.Lock()
defer r.mux.Unlock()
r.errors = append(r.errors, err)
}
func (r *Runner) Wait() []error {
r.wg.Wait()
return r.errors
}

View File

@ -2,6 +2,7 @@ package term
import (
"bufio"
"errors"
"io"
"github.com/pkg/sftp"
@ -76,6 +77,9 @@ func newNT(sshClient *ssh.Client, pipe bool, recording string, term string, rows
}
func (ret *NextTerminal) Write(p []byte) (int, error) {
if ret.StdinPipe == nil {
return 0, errors.New("pipe is not open")
}
return ret.StdinPipe.Write(p)
}

View File

@ -1,4 +1,4 @@
package totp
package common
import (
otp_t "github.com/pquerna/otp"

View File

@ -108,9 +108,7 @@ func SetupConfig() (*Config, error) {
if err := viper.BindPFlags(pflag.CommandLine); err != nil {
return nil, err
}
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
_ = viper.ReadInConfig()
sshdKey, err := homedir.Expand(viper.GetString("sshd.key"))
if err != nil {

View File

@ -1,7 +0,0 @@
package constant
import "errors"
var (
ErrNameAlreadyUsed = errors.New("name already used")
)

View File

@ -7,6 +7,7 @@ type Authorization struct {
Remember bool
Type string // LoginToken: 登录令牌, AccessToken: 授权令牌, ShareSession: 会话分享, AccessSession: 只允许访问特定的会话
User *model.User
Roles []string
}
type LoginAccount struct {

52
server/dto/authorised.go Normal file
View File

@ -0,0 +1,52 @@
package dto
import "next-terminal/server/common"
type AuthorisedAsset struct {
AssetIds []string `json:"assetIds"`
CommandFilterId string `json:"commandFilterId"`
StrategyId string `json:"strategyId"`
UserId string `json:"userId"`
UserGroupId string `json:"userGroupId"`
}
type AuthorisedUser struct {
UserIds []string `json:"userIds"`
CommandFilterId string `json:"commandFilterId"`
StrategyId string `json:"strategyId"`
AssetId string `json:"assetId"`
}
type AuthorisedUserGroup struct {
UserGroupIds []string `json:"UserGroupIds"`
CommandFilterId string `json:"commandFilterId"`
StrategyId string `json:"strategyId"`
AssetId string `json:"assetId"`
}
type AssetPageForAuthorised struct {
Id string `json:"id"`
AssetId string `json:"assetId"`
AssetName string `json:"assetName"`
StrategyId string `json:"strategyId"`
StrategyName string `json:"strategyName"`
Created common.JsonTime `json:"created"`
}
type UserPageForAuthorised struct {
Id string `json:"id"`
UserId string `json:"userId"`
UserName string `json:"userName"`
StrategyId string `json:"strategyId"`
StrategyName string `json:"strategyName"`
Created common.JsonTime `json:"created"`
}
type UserGroupPageForAuthorised struct {
Id string `json:"id"`
UserGroupId string `json:"userGroupId"`
UserGroupName string `json:"userGroupName"`
StrategyId string `json:"strategyId"`
StrategyName string `json:"strategyName"`
Created common.JsonTime `json:"created"`
}

View File

@ -1,8 +1,10 @@
package dto
type Counter struct {
User int64 `json:"user"`
Asset int64 `json:"asset"`
Credential int64 `json:"credential"`
OnlineSession int64 `json:"onlineSession"`
TotalUser int64 `json:"totalUser"`
OnlineUser int64 `json:"onlineUser"`
TotalAsset int64 `json:"totalAsset"`
ActiveAsset int64 `json:"activeAsset"`
OfflineSession int64 `json:"offlineSession"`
FailLoginCount int64 `json:"failLoginCount"`
}

View File

@ -1,7 +1,15 @@
package dto
import "next-terminal/server/common"
type UserGroup struct {
Id string `json:"id"`
Name string `json:"name"`
Members []string `json:"members"`
Members []UserGroupMember `json:"members"`
Created common.JsonTime `json:"created"`
}
type UserGroupMember struct {
Id string `json:"id"`
Name string `json:"name"`
}

7
server/dto/overview.go Normal file
View File

@ -0,0 +1,7 @@
package dto
type DateCounter struct {
Date string `json:"date"`
Value uint64 `json:"value"`
Type string `json:"type"`
}

7
server/dto/permission.go Normal file
View File

@ -0,0 +1,7 @@
package dto
type TreeMenu struct {
Title string `json:"title"`
Key string `json:"key"`
Children []TreeMenu `json:"children"`
}

View File

@ -27,6 +27,5 @@ type Backup struct {
Commands []model.Command `json:"commands"`
Credentials []model.Credential `json:"credentials"`
Assets []map[string]interface{} `json:"assets"`
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
Jobs []model.Job `json:"jobs"`
}

15
server/dto/storage_log.go Normal file
View File

@ -0,0 +1,15 @@
package dto
import "next-terminal/server/common"
type StorageLogForPage struct {
ID string `json:"id"`
AssetId string `json:"assetId"`
AssetName string `json:"assetName"`
SessionId string `json:"sessionId"`
UserId string `json:"userId"`
UserName string `json:"userName"`
Action string `json:"action"` // 操作类型: 上传、下载、删除、重命名、编辑
FileName string `json:"fileName"` // 文件名称
Created common.JsonTime `json:"created"` // 操作时间
}

8
server/env/db.go vendored
View File

@ -48,9 +48,13 @@ func setupDB() *gorm.DB {
}
if err := db.AutoMigrate(&model.User{}, &model.Asset{}, &model.AssetAttribute{}, &model.Session{}, &model.Command{},
&model.Credential{}, &model.Property{}, &model.ResourceSharer{}, &model.UserGroup{}, &model.UserGroupMember{},
&model.Credential{}, &model.Property{}, &model.UserGroup{}, &model.UserGroupMember{},
&model.LoginLog{}, &model.Job{}, &model.JobLog{}, &model.AccessSecurity{}, &model.AccessGateway{},
&model.Storage{}, &model.Strategy{}, &model.AccessToken{}); err != nil {
&model.Storage{}, &model.Strategy{},
&model.AccessToken{}, &model.ShareSession{},
&model.Role{}, &model.RoleMenuRef{}, &model.UserRoleRef{},
&model.LoginPolicy{}, &model.LoginPolicyUserRef{}, &model.TimePeriod{},
&model.StorageLog{}, &model.Authorised{}); err != nil {
panic(fmt.Errorf("初始化数据库表结构异常: %v", err.Error()))
}
return db

View File

@ -15,8 +15,10 @@ const (
var TokenManager *cache.Cache
var LoginFailedKeyManager *cache.Cache
var UserRolesManager *cache.Cache
func init() {
TokenManager = cache.New(5*time.Minute, 10*time.Minute)
LoginFailedKeyManager = cache.New(5*time.Minute, 10*time.Minute)
UserRolesManager = cache.New(5*time.Minute, 10*time.Minute)
}

View File

@ -4,10 +4,10 @@ import (
"errors"
"fmt"
"net"
"next-terminal/server/common/term"
"os"
"sync"
"next-terminal/server/term"
"next-terminal/server/utils"
"golang.org/x/crypto/ssh"
@ -90,7 +90,9 @@ func (g *Gateway) CloseSshTunnel(id string) {
}
if len(g.tunnels) == 0 {
if g.SshClient != nil {
_ = g.SshClient.Close()
}
g.Connected = false
g.Message = "暂未使用"
}

View File

@ -3,7 +3,6 @@ package gateway
import (
"sync"
"next-terminal/server/log"
"next-terminal/server/model"
)
@ -33,7 +32,6 @@ func (m *manager) Add(model *model.AccessGateway) *Gateway {
tunnels: make(map[string]*Tunnel),
}
m.gateways.Store(g.ID, g)
log.Infof("add Gateway: %s", g.ID)
return g
}
@ -43,7 +41,6 @@ func (m *manager) Del(id string) {
g.Close()
}
m.gateways.Delete(id)
log.Infof("del Gateway: %s", id)
}
var GlobalGatewayManager *manager

View File

@ -5,8 +5,6 @@ import (
"io"
"net"
"next-terminal/server/log"
"golang.org/x/crypto/ssh"
)
@ -22,34 +20,23 @@ type Tunnel struct {
}
func (r *Tunnel) Open(sshClient *ssh.Client) {
localAddr := fmt.Sprintf("%s:%d", r.localHost, r.localPort)
for {
log.Debugf("隧道 %v 等待客户端访问 %v", r.id, localAddr)
localConn, err := r.listener.Accept()
if err != nil {
log.Debugf("隧道 %v 接受连接失败 %v, 退出循环", r.id, err.Error())
log.Debug("-------------------------------------------------")
return
}
r.localConnections = append(r.localConnections, localConn)
log.Debugf("隧道 %v 新增本地连接 %v", r.id, localConn.RemoteAddr().String())
remoteAddr := fmt.Sprintf("%s:%d", r.remoteHost, r.remotePort)
log.Debugf("隧道 %v 连接远程地址 %v ...", r.id, remoteAddr)
remoteConn, err := sshClient.Dial("tcp", remoteAddr)
if err != nil {
log.Debugf("隧道 %v 连接远程地址 %v, 退出循环", r.id, err.Error())
log.Debug("-------------------------------------------------")
return
}
r.remoteConnections = append(r.remoteConnections, remoteConn)
log.Debugf("隧道 %v 连接远程主机成功", r.id)
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
log.Debugf("隧道 %v 开始转发数据 [%v]->[%v]", r.id, localAddr, remoteAddr)
log.Debug("~~~~~~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~~~~~")
}
}
@ -63,7 +50,6 @@ func (r *Tunnel) Close() {
}
r.remoteConnections = nil
_ = r.listener.Close()
log.Debugf("隧道 %v 监听器关闭", r.id)
}
func copyConn(writer, reader net.Conn) {

View File

@ -0,0 +1,12 @@
package license
type License struct {
Type string `json:"type"` // 类型:免费版 free,会员版 vip, 旗舰版 ultimate, 企业版 enterprise
MachineId string `json:"machineId"` // 唯一机器码:免费版为空
Assert int64 `json:"assert"` // 资产数量
Concurrent int64 `json:"concurrent"` // 并发数量
User int64 `json:"user"` // 用户数量
Expired int64 `json:"expired"` // 过期时间
}
var CurrentLicense *License

View File

@ -3,8 +3,6 @@ package security
import (
"sort"
"sync"
"next-terminal/server/log"
)
type Security struct {
@ -54,13 +52,11 @@ func (m *Manager) Values() []*Security {
func (m *Manager) Add(s *Security) {
m.securities.Store(s.ID, s)
m.LoadData()
log.Infof("add security: %s", s.ID)
}
func (m *Manager) Del(id string) {
m.securities.Delete(id)
m.LoadData()
log.Infof("del security: %s", id)
}
var GlobalSecurityManager *Manager

View File

@ -1,14 +1,12 @@
package session
import (
"next-terminal/server/common/guacamole"
"next-terminal/server/common/term"
"sync"
"next-terminal/server/dto"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/term"
"github.com/gorilla/websocket"
"next-terminal/server/dto"
)
type Session struct {
@ -16,10 +14,13 @@ type Session struct {
Protocol string
Mode string
WebSocket *websocket.Conn
GuacdTunnel *guacd.Tunnel
GuacdTunnel *guacamole.Tunnel
NextTerminal *term.NextTerminal
Observer *Manager
mutex sync.Mutex
Uptime int64
Hostname string
}
func (s *Session) WriteMessage(msg dto.Message) error {
@ -79,7 +80,6 @@ func (m *Manager) GetById(id string) *Session {
func (m *Manager) Add(s *Session) {
m.sessions.Store(s.ID, s)
log.Infof("add session: %s", s.ID)
}
func (m *Manager) Del(id string) {
@ -91,7 +91,6 @@ func (m *Manager) Del(id string) {
}
}
m.sessions.Delete(id)
log.Infof("del session: %s", id)
}
func (m *Manager) Clear() {

104
server/global/stat/load.go Normal file
View File

@ -0,0 +1,104 @@
package stat
type systemLoad struct {
LoadStat *LoadStat `json:"loadStat"`
Mem *Mem `json:"mem"`
MemStat []*entry `json:"memStat"`
Cpu *Cpu `json:"cpu"`
CpuStat []*entry `json:"cpuStat"`
Disk *Disk `json:"disk"`
DiskIOStat []*ioEntry `json:"diskIO"`
NetIOStat []*ioEntry `json:"netIO"`
}
type Mem struct {
Total uint64 `json:"total"`
Available uint64 `json:"available"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
}
type Cpu struct {
Count int `json:"count"`
PhyCount int `json:"phyCount"`
UsedPercent float64 `json:"usedPercent"`
Info []*CpuInfo `json:"info"`
}
type Disk struct {
Total uint64 `json:"total"`
Used uint64 `json:"used"`
Available uint64 `json:"available"`
UsedPercent float64 `json:"usedPercent"`
}
type CpuInfo struct {
ModelName string `json:"modelName"`
CacheSize int32 `json:"cacheSize"`
MHZ float64 `json:"mhz"`
}
type LoadStat struct {
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
Percent float64 `json:"percent"`
}
type entry struct {
Time string `json:"time"`
Value float64 `json:"value"`
}
func NewStat(time string, value float64) *entry {
return &entry{
Time: time,
Value: value,
}
}
func NewIOStat(time string, read, write uint64) *ioEntry {
return &ioEntry{
Time: time,
Read: read,
Write: write,
}
}
type ioEntry struct {
Time string `json:"time"`
Read uint64 `json:"read"`
Write uint64 `json:"write"`
}
var SystemLoad *systemLoad
func init() {
SystemLoad = &systemLoad{
LoadStat: &LoadStat{
Load1: 0,
Load5: 0,
Load15: 0,
Percent: 0,
},
Mem: &Mem{
Total: 0,
Available: 0,
Used: 0,
UsedPercent: 0,
},
MemStat: make([]*entry, 0),
Cpu: &Cpu{
Count: 0,
UsedPercent: 0,
},
CpuStat: make([]*entry, 0),
Disk: &Disk{
Total: 0,
Used: 0,
UsedPercent: 0,
},
DiskIOStat: make([]*ioEntry, 0),
NetIOStat: make([]*ioEntry, 0),
}
}

View File

@ -1,266 +1,151 @@
package log
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"next-terminal/server/config"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
type Formatter struct{}
var (
_logger *zap.Logger // zap ensure that zap.Logger is safe for concurrent use
)
func (s *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
timestamp := time.Now().Local().Format("2006-01-02 15:04:05")
var file string
var l int
if entry.HasCaller() {
file = filepath.Base(entry.Caller.Function)
l = entry.Caller.Line
func init() {
cfg := zap.NewProductionConfig()
cfg.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}
msg := fmt.Sprintf("%s %s [%s:%d]%s\n", timestamp, strings.ToUpper(entry.Level.String()), file, l, entry.Message)
return []byte(msg), nil
}
var stdOut = NewLogger()
// Trace logs a message at level Trace on the standard logger.
func Trace(args ...interface{}) {
stdOut.Trace(args...)
}
// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
stdOut.Debug(args...)
}
// Print logs a message at level Info on the standard logger.
func Print(args ...interface{}) {
stdOut.Print(args...)
}
// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
stdOut.Info(args...)
}
// Warn logs a message at level Warn on the standard logger.
func Warn(args ...interface{}) {
stdOut.Warn(args...)
}
// Warning logs a message at level Warn on the standard logger.
func Warning(args ...interface{}) {
stdOut.Warning(args...)
}
// Error logs a message at level Error on the standard logger.
func Error(args ...interface{}) {
stdOut.Error(args...)
}
// Panic logs a message at level Panic on the standard logger.
func Panic(args ...interface{}) {
stdOut.Panic(args...)
}
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
func Fatal(args ...interface{}) {
stdOut.Fatal(args...)
}
// Tracef logs a message at level Trace on the standard logger.
func Tracef(format string, args ...interface{}) {
stdOut.Tracef(format, args...)
}
// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
stdOut.Debugf(format, args...)
}
// Printf logs a message at level Info on the standard logger.
func Printf(format string, args ...interface{}) {
stdOut.Printf(format, args...)
}
// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
stdOut.Infof(format, args...)
}
// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
stdOut.Warnf(format, args...)
}
// Warningf logs a message at level Warn on the standard logger.
func Warningf(format string, args ...interface{}) {
stdOut.Warningf(format, args...)
}
// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
stdOut.Errorf(format, args...)
}
// Panicf logs a message at level Panic on the standard logger.
func Panicf(format string, args ...interface{}) {
stdOut.Panicf(format, args...)
}
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
func Fatalf(format string, args ...interface{}) {
stdOut.Fatalf(format, args...)
}
// Traceln logs a message at level Trace on the standard logger.
func Traceln(args ...interface{}) {
stdOut.Traceln(args...)
}
// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
stdOut.Debugln(args...)
}
// Println logs a message at level Info on the standard logger.
func Println(args ...interface{}) {
stdOut.Println(args...)
}
// Infoln logs a message at level Info on the standard logger.
func Infoln(args ...interface{}) {
stdOut.Infoln(args...)
}
// Warnln logs a message at level Warn on the standard logger.
func Warnln(args ...interface{}) {
stdOut.Warnln(args...)
}
// Warningln logs a message at level Warn on the standard logger.
func Warningln(args ...interface{}) {
stdOut.Warningln(args...)
}
// Errorln logs a message at level Error on the standard logger.
func Errorln(args ...interface{}) {
stdOut.Errorln(args...)
}
// Panicln logs a message at level Panic on the standard logger.
func Panicln(args ...interface{}) {
stdOut.Panicln(args...)
}
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
func Fatalln(args ...interface{}) {
stdOut.Fatalln(args...)
}
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
func WithError(err error) *logrus.Entry {
return stdOut.WithField(logrus.ErrorKey, err)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithField(key string, value interface{}) *logrus.Entry {
return stdOut.WithField(key, value)
}
// Logrus : implement log
type Logrus struct {
*logrus.Logger
}
// GetEchoLogger for e.l
func NewLogger() Logrus {
logFilePath := ""
if dir, err := os.Getwd(); err == nil {
logFilePath = dir + "/logs/"
}
if err := os.MkdirAll(logFilePath, 0755); err != nil {
fmt.Println(err.Error())
}
logFileName := "next-terminal.log"
//日志文件
fileName := path.Join(logFilePath, logFileName)
if _, err := os.Stat(fileName); err != nil {
if _, err := os.Create(fileName); err != nil {
fmt.Println(err.Error())
}
}
//实例化
logger := logrus.New()
//设置输出
logger.SetOutput(io.MultiWriter(&lumberjack.Logger{
Filename: fileName,
MaxSize: 100, // megabytes
var cores = []zapcore.Core{
zapcore.NewCore(
zapcore.NewConsoleEncoder(cfg.EncoderConfig),
zapcore.Lock(os.Stdout),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level <= zapcore.InfoLevel
}),
),
zapcore.NewCore(
zapcore.NewJSONEncoder(cfg.EncoderConfig),
zapcore.AddSync(&lumberjack.Logger{
Filename: "logs/next-terminal.log",
MaxSize: 100,
MaxAge: 7,
MaxBackups: 3,
MaxAge: 7, //days
Compress: true, // disabled by default
}, os.Stdout))
logger.SetReportCaller(true)
//设置日志级别
if config.GlobalCfg.Debug {
logger.SetLevel(logrus.DebugLevel)
} else {
logger.SetLevel(logrus.InfoLevel)
Compress: true,
}),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level <= zapcore.InfoLevel
}),
),
zapcore.NewCore(
zapcore.NewConsoleEncoder(cfg.EncoderConfig),
zapcore.Lock(os.Stdout),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level > zapcore.InfoLevel
}),
),
zapcore.NewCore(
zapcore.NewJSONEncoder(cfg.EncoderConfig),
zapcore.AddSync(&lumberjack.Logger{
Filename: "logs/next-terminal-error.log",
MaxSize: 100,
MaxAge: 7,
MaxBackups: 3,
Compress: true,
}),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level > zapcore.InfoLevel
}),
),
}
//设置日志格式
logger.SetFormatter(new(Formatter))
return Logrus{Logger: logger}
_logger = zap.New(zapcore.NewTee(cores...), zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
}
func logrusMiddlewareHandler(c echo.Context, next echo.HandlerFunc) error {
l := NewLogger()
req := c.Request()
res := c.Response()
start := time.Now()
if err := next(c); err != nil {
c.Error(err)
}
stop := time.Now()
type Field = zap.Field
l.Debugf("%s %s %s %s %s %3d %s %13v %s %s",
c.RealIP(),
req.Host,
req.Method,
req.RequestURI,
req.URL.Path,
res.Status,
strconv.FormatInt(res.Size, 10),
stop.Sub(start).String(),
req.Referer(),
req.UserAgent(),
)
// function variables for all field types
// in github.com/uber-go/zap/field.go
return nil
var (
Skip = zap.Skip
Binary = zap.Binary
Bool = zap.Bool
Boolp = zap.Boolp
ByteString = zap.ByteString
Complex128 = zap.Complex128
Complex128p = zap.Complex128p
Complex64 = zap.Complex64
Complex64p = zap.Complex64p
Float64 = zap.Float64
Float64p = zap.Float64p
Float32 = zap.Float32
Float32p = zap.Float32p
Int = zap.Int
Intp = zap.Intp
Int64 = zap.Int64
Int64p = zap.Int64p
Int32 = zap.Int32
Int32p = zap.Int32p
Int16 = zap.Int16
Int16p = zap.Int16p
Int8 = zap.Int8
Int8p = zap.Int8p
String = zap.String
Stringp = zap.Stringp
Uint = zap.Uint
Uintp = zap.Uintp
Uint64 = zap.Uint64
Uint64p = zap.Uint64p
Uint32 = zap.Uint32
Uint32p = zap.Uint32p
Uint16 = zap.Uint16
Uint16p = zap.Uint16p
Uint8 = zap.Uint8
Uint8p = zap.Uint8p
Uintptr = zap.Uintptr
Uintptrp = zap.Uintptrp
Reflect = zap.Reflect
Namespace = zap.Namespace
Stringer = zap.Stringer
Time = zap.Time
Timep = zap.Timep
Stack = zap.Stack
StackSkip = zap.StackSkip
Duration = zap.Duration
Durationp = zap.Durationp
Any = zap.Any
NamedError = zap.NamedError
)
func Debug(msg string, fields ...Field) {
_logger.Debug(msg, fields...)
}
func logger(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return logrusMiddlewareHandler(c, next)
}
func Info(msg string, fields ...Field) {
_logger.Info(msg, fields...)
}
// Hook is a function to process log.
func Hook() echo.MiddlewareFunc {
return logger
func Warn(msg string, fields ...Field) {
_logger.Warn(msg, fields...)
}
func Error(msg string, fields ...Field) {
_logger.Error(msg, fields...)
}
func DPanic(msg string, fields ...Field) {
_logger.DPanic(msg, fields...)
}
func Panic(msg string, fields ...Field) {
_logger.Panic(msg, fields...)
}
func Fatal(msg string, fields ...Field) {
_logger.Fatal(msg, fields...)
}
func Sync() error {
return _logger.Sync()
}

View File

@ -0,0 +1,17 @@
package model
import "next-terminal/server/common"
type StorageLog struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
AssetId string `gorm:"index,type:varchar(36)" json:"assetId"`
SessionId string `gorm:"index,type:varchar(36)" json:"sessionId"`
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
Action string `gorm:"type:varchar(20)" json:"action"` // 操作类型: 上传、下载、删除、重命名、编辑
FileName string `gorm:"type:varchar(200)" json:"fileName"` // 文件名称
Created common.JsonTime `json:"created"` // 操作时间
}
func (s StorageLog) TableName() string {
return "storage_logs"
}

View File

@ -1,6 +1,8 @@
package model
import "next-terminal/server/utils"
import (
"next-terminal/server/common"
)
// AccessGateway 接入网关
type AccessGateway struct {
@ -13,7 +15,7 @@ type AccessGateway struct {
Password string `gorm:"type:varchar(500)" json:"password"`
PrivateKey string `gorm:"type:text" json:"privateKey"`
Passphrase string `gorm:"type:varchar(500)" json:"passphrase"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
}
func (r *AccessGateway) TableName() string {
@ -27,7 +29,7 @@ type AccessGatewayForPage struct {
Port int `json:"port"`
AccountType string `json:"accountType"`
Username string `json:"username"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Connected bool `json:"connected"`
Message string `json:"message"`
}

View File

@ -1,12 +1,14 @@
package model
import "next-terminal/server/utils"
import (
"next-terminal/server/common"
)
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"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
}
func (r *AccessToken) TableName() string {

View File

@ -1,7 +1,7 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type AssetProto string
@ -20,7 +20,8 @@ type Asset struct {
Passphrase string `gorm:"type:varchar(500)" json:"passphrase"`
Description string `json:"description"`
Active bool `json:"active"`
Created utils.JsonTime `json:"created"`
ActiveMessage string `gorm:"type:varchar(200)" json:"activeMessage"`
Created common.JsonTime `json:"created"`
Tags string `json:"tags"`
Owner string `gorm:"index,type:varchar(36)" json:"owner"`
Encrypted bool `json:"encrypted"`
@ -30,15 +31,16 @@ type Asset struct {
type AssetForPage struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
IP string `json:"ip"`
Protocol string `json:"protocol"`
Port int `json:"port"`
Active bool `json:"active"`
Created utils.JsonTime `json:"created"`
ActiveMessage string `json:"activeMessage"`
Created common.JsonTime `json:"created"`
Tags string `json:"tags"`
Owner string `json:"owner"`
OwnerName string `json:"ownerName"`
SshMode string `json:"sshMode"`
}
func (r *Asset) TableName() string {

View File

@ -0,0 +1,18 @@
package model
import "next-terminal/server/common"
// Authorised 资产授权
type Authorised struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
AssetId string `gorm:"index,type:varchar(36)" json:"assetId"`
CommandFilterId string `gorm:"index,type:varchar(36)" json:"commandFilterId"`
StrategyId string `gorm:"index,type:varchar(36)" json:"strategyId"`
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
UserGroupId string `gorm:"index,type:varchar(36)" json:"userGroupId"`
Created common.JsonTime `json:"created"`
}
func (m Authorised) TableName() string {
return "authorised"
}

View File

@ -1,14 +1,14 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type Command struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
Content string `json:"content"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Owner string `gorm:"index,type:varchar(36)" json:"owner"`
}
@ -16,10 +16,9 @@ type CommandForPage struct {
ID string `gorm:"primary_key" json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Owner string `json:"owner"`
OwnerName string `json:"ownerName"`
SharerCount int64 `json:"sharerCount"`
}
func (r *Command) TableName() string {

View File

@ -1,7 +1,7 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type Credential struct {
@ -12,7 +12,7 @@ type Credential struct {
Password string `gorm:"type:varchar(500)" json:"password"`
PrivateKey string `gorm:"type:text" json:"privateKey"`
Passphrase string `gorm:"type:varchar(500)" json:"passphrase"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Owner string `gorm:"index,type:varchar(36)" json:"owner"`
Encrypted bool `json:"encrypted"`
}
@ -26,10 +26,9 @@ type CredentialForPage struct {
Name string `json:"name"`
Type string `json:"type"`
Username string `json:"username"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Owner string `json:"owner"`
OwnerName string `json:"ownerName"`
SharerCount int64 `json:"sharerCount"`
}
type CredentialSimpleVo struct {

View File

@ -1,7 +1,7 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type Job struct {
@ -14,8 +14,8 @@ type Job struct {
ResourceIds string `json:"resourceIds"`
Status string `gorm:"type:varchar(20)" json:"status"`
Metadata string `json:"metadata"`
Created utils.JsonTime `json:"created"`
Updated utils.JsonTime `json:"updated"`
Created common.JsonTime `json:"created"`
Updated common.JsonTime `json:"updated"`
}
func (r *Job) TableName() string {
@ -24,7 +24,7 @@ func (r *Job) TableName() string {
type JobLog struct {
ID string `json:"id"`
Timestamp utils.JsonTime `json:"timestamp"`
Timestamp common.JsonTime `json:"timestamp"`
JobId string `json:"jobId"`
Message string `json:"message"`
}

View File

@ -1,7 +1,7 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type LoginLog struct {
@ -9,8 +9,8 @@ type LoginLog struct {
Username string `gorm:"index,type:varchar(200)" json:"username"`
ClientIP string `gorm:"type:varchar(200)" json:"clientIp"`
ClientUserAgent string `gorm:"type:varchar(500)" json:"clientUserAgent"`
LoginTime utils.JsonTime `json:"loginTime"`
LogoutTime utils.JsonTime `json:"logoutTime"`
LoginTime common.JsonTime `json:"loginTime"`
LogoutTime common.JsonTime `json:"logoutTime"`
Remember bool `json:"remember"`
State string `gorm:"type:varchar(1)" json:"state"` // 成功 1 失败 0
Reason string `gorm:"type:varchar(500)" json:"reason"`

View File

@ -0,0 +1,36 @@
package model
type LoginPolicy struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"` // 名称
IpGroup string `json:"ipGroup"` // IP组 格式为逗号分隔的字符串, 0.0.0.0 匹配所有。例如: 192.168.0.1, 192.168.1.0/24, 192.168.2.0-192.168.2.20
Priority int64 `json:"priority"` // 优先级 越小优先级越高
Enabled bool `json:"enabled"` // 是否激活
Rule string `gorm:"type:varchar(20)" json:"rule"` // 规则 允许或拒绝
TimePeriod []TimePeriod `gorm:"-" json:"timePeriod"` // 时间区间
}
func (r *LoginPolicy) TableName() string {
return "login_policies"
}
type LoginPolicyUserRef struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
LoginPolicyId string `gorm:"index,type:varchar(36)" json:"loginPolicyId"`
}
func (r *LoginPolicyUserRef) TableName() string {
return "login_policies_ref"
}
type TimePeriod struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
LoginPolicyId string `gorm:"index,type:varchar(36)" json:"loginPolicyId"`
Key int `json:"key"`
Value string `json:"value"`
}
func (r *TimePeriod) TableName() string {
return "time_periods"
}

View File

@ -1,14 +0,0 @@
package model
type ResourceSharer struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
ResourceId string `gorm:"index,type:varchar(36)" json:"resourceId"`
ResourceType string `gorm:"index,type:varchar(36)" json:"resourceType"`
StrategyId string `gorm:"index,type:varchar(36)" json:"strategyId"`
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
UserGroupId string `gorm:"index,type:varchar(36)" json:"userGroupId"`
}
func (r *ResourceSharer) TableName() string {
return "resource_sharers"
}

90
server/model/role.go Normal file
View File

@ -0,0 +1,90 @@
package model
import (
"next-terminal/server/common"
"next-terminal/server/utils"
"strings"
)
// Role 角色
type Role struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
Type string `gorm:"type:varchar(10)" json:"type"`
Deletable bool `json:"deletable"`
Modifiable bool `json:"modifiable"`
Created common.JsonTime `json:"created"`
Menus []RoleMenuRef `gorm:"-" json:"menus"`
}
func (r *Role) TableName() string {
return "roles"
}
func NewRole(id, name, _type string, deletable, modifiable bool, menus []RoleMenuRef) *Role {
return &Role{
ID: id,
Name: name,
Type: _type,
Deletable: deletable,
Modifiable: modifiable,
Created: common.NowJsonTime(),
Menus: menus,
}
}
// Menu 菜单
type Menu struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
ParentId string `gorm:"index,type:varchar(36)" json:"parentId"`
Permissions []*Permission `gorm:"-"`
}
func NewMenu(id, name, parentId string, permissions ...*Permission) *Menu {
return &Menu{
ID: id,
Name: name,
ParentId: parentId,
Permissions: permissions,
}
}
// Permission 权限
type Permission struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
Method string `gorm:"type:varchar(10)" json:"method"`
Path string `gorm:"type:varchar(200)" json:"path"`
RequiredParams string `gorm:"type:varchar(200)" json:"params"`
}
func NewPermission(method, path string, requiredParams ...string) *Permission {
return &Permission{
ID: utils.Sign([]string{method, path}),
Method: method,
Path: path,
RequiredParams: strings.Join(requiredParams, ","),
}
}
type RoleMenuRef struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
RoleId string `gorm:"index,type:varchar(36)" json:"roleId"`
MenuId string `gorm:"index,type:varchar(36)" json:"menuId"`
Checked bool `json:"checked"`
}
func (r *RoleMenuRef) TableName() string {
return "roles_menus_ref"
}
type UserRoleRef struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
RoleId string `gorm:"index,type:varchar(36)" json:"roleId"`
}
func (r *UserRoleRef) TableName() string {
return "users_roles_ref"
}

View File

@ -1,7 +1,7 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type Session struct {
@ -23,8 +23,9 @@ type Session struct {
Passphrase string `gorm:"type:varchar(500)" json:"passphrase"`
Code int `json:"code"`
Message string `json:"message"`
ConnectedTime utils.JsonTime `json:"connectedTime"`
DisconnectedTime utils.JsonTime `json:"disconnectedTime"`
ConnectedTime common.JsonTime `json:"connectedTime"`
DisconnectedTime common.JsonTime `json:"disconnectedTime"`
Mode string `gorm:"type:varchar(10)" json:"mode"`
FileSystem string `gorm:"type:varchar(1)" json:"fileSystem"` // 1 = true, 0 = false
Upload string `gorm:"type:varchar(1)" json:"upload"`
@ -38,6 +39,7 @@ type Session struct {
StorageId string `gorm:"type:varchar(36)" json:"storageId"`
AccessGatewayId string `gorm:"type:varchar(36)" json:"accessGatewayId"`
Reviewed bool `gorm:"type:tinyint(1)" json:"reviewed"`
CommandCount int64 `json:"commandCount"`
}
func (r *Session) TableName() string {
@ -58,14 +60,15 @@ type SessionForPage struct {
Height int `json:"height"`
Status string `json:"status"`
Recording string `json:"recording"`
ConnectedTime utils.JsonTime `json:"connectedTime"`
DisconnectedTime utils.JsonTime `json:"disconnectedTime"`
ConnectedTime common.JsonTime `json:"connectedTime"`
DisconnectedTime common.JsonTime `json:"disconnectedTime"`
AssetName string `json:"assetName"`
CreatorName string `json:"creatorName"`
Code int `json:"code"`
Message string `json:"message"`
Mode string `json:"mode"`
Reviewed bool `json:"reviewed"`
CommandCount int64 `json:"commandCount"`
}
type SessionForAccess struct {

View File

@ -0,0 +1,21 @@
package model
import (
"next-terminal/server/common"
)
type ShareSession struct {
ID string `gorm:"primary_key,type:varchar(130)" json:"id"`
AssetId string `gorm:"index,type:varchar(36)" json:"assetId"`
FileSystem string `gorm:"type:varchar(1)" json:"fileSystem"` // 1 = true, 0 = false
Upload string `gorm:"type:varchar(1)" json:"upload"`
Download string `gorm:"type:varchar(1)" json:"download"`
Delete string `gorm:"type:varchar(1)" json:"delete"`
Rename string `gorm:"type:varchar(1)" json:"rename"`
Edit string `gorm:"type:varchar(1)" json:"edit"`
CreateDir string `gorm:"type:varchar(1)" json:"createDir"`
Mode string `gorm:"type:varchar(20)" json:"mode"`
Creator string `gorm:"type:varchar(36)" json:"creator"`
Created common.JsonTime `json:"created"` // 创建时间
Expiration common.JsonTime `json:"expiration"` // 过期时间
}

View File

@ -1,6 +1,8 @@
package model
import "next-terminal/server/utils"
import (
"next-terminal/server/common"
)
type Storage struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
@ -9,7 +11,7 @@ type Storage struct {
LimitSize int64 `json:"limitSize"` // 大小限制,单位字节
IsDefault bool `json:"isDefault"` // 是否为用户默认的
Owner string `gorm:"index,type:varchar(36)" json:"owner"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
}
func (r *Storage) TableName() string {
@ -25,5 +27,5 @@ type StorageForPage struct {
IsDefault bool `json:"isDefault"` // 是否为用户默认的
Owner string `gorm:"index" json:"owner"`
OwnerName string `json:"ownerName"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
}

View File

@ -1,19 +1,21 @@
package model
import "next-terminal/server/utils"
import (
"next-terminal/server/common"
)
type Strategy struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
Upload string `gorm:"type:varchar(1)" json:"upload"` // 1 = true, 0 = false
Download string `gorm:"type:varchar(1)" json:"download"`
Delete string `gorm:"type:varchar(1)" json:"delete"`
Rename string `gorm:"type:varchar(1)" json:"rename"`
Edit string `gorm:"type:varchar(1)" json:"edit"`
CreateDir string `gorm:"type:varchar(1)" json:"createDir"`
Copy string `gorm:"type:varchar(1)" json:"copy"`
Paste string `gorm:"type:varchar(1)" json:"paste"`
Created utils.JsonTime `json:"created"`
Upload *bool `gorm:"type:tinyint;default:false" json:"upload"` // 1 = true, 0 = false
Download *bool `gorm:"type:tinyint;default:false" json:"download"`
Delete *bool `gorm:"type:tinyint;default:false" json:"delete"`
Rename *bool `gorm:"type:tinyint;default:false" json:"rename"`
Edit *bool `gorm:"type:tinyint;default:false" json:"edit"`
CreateDir *bool `gorm:"type:tinyint;default:false" json:"createDir"`
Copy *bool `gorm:"type:tinyint;default:false" json:"copy"`
Paste *bool `gorm:"type:tinyint;default:false" json:"paste"`
Created common.JsonTime `json:"created"`
}
func (r *Strategy) TableName() string {

15
server/model/tenant.go Normal file
View File

@ -0,0 +1,15 @@
package model
import (
"next-terminal/server/common"
)
type Tenant struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
Created common.JsonTime `json:"created"`
}
func (r *Tenant) TableName() string {
return "tenants"
}

View File

@ -1,7 +1,7 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type User struct {
@ -10,12 +10,13 @@ type User struct {
Password string `gorm:"type:varchar(500)" json:"password"`
Nickname string `gorm:"type:varchar(500)" json:"nickname"`
TOTPSecret string `json:"-"`
Online bool `json:"online"`
Online *bool `json:"online"`
Status string `gorm:"type:varchar(10)" json:"status"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Type string `gorm:"type:varchar(20)" json:"type"`
Mail string `gorm:"type:varchar(500)" json:"mail"`
Source string `gorm:"type:varchar(20)" json:"source"`
Roles []string `gorm:"-" json:"roles"`
}
type UserForPage struct {
@ -26,7 +27,7 @@ type UserForPage struct {
Mail string `json:"mail"`
Online bool `json:"online"`
Status string `json:"status"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Type string `json:"type"`
Source string `json:"source"`
SharerAssetCount int64 `json:"sharerAssetCount"`

View File

@ -1,20 +1,20 @@
package model
import (
"next-terminal/server/utils"
"next-terminal/server/common"
)
type UserGroup struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
Members []string `gorm:"-" json:"members"`
}
type UserGroupForPage struct {
ID string `json:"id"`
Name string `json:"name"`
Created utils.JsonTime `json:"created"`
Created common.JsonTime `json:"created"`
AssetCount int64 `json:"assetCount"`
}

View File

@ -6,6 +6,8 @@ import (
"next-terminal/server/model"
)
var AccessTokenRepository = new(accessTokenRepository)
type accessTokenRepository struct {
baseRepository
}

View File

@ -3,16 +3,19 @@ package repository
import (
"context"
"fmt"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
var AssetRepository = new(assetRepository)
type assetRepository struct {
baseRepository
}
@ -28,7 +31,11 @@ func (r assetRepository) FindByIds(c context.Context, assetIds []string) (o []mo
}
func (r assetRepository) FindByProtocol(c context.Context, protocol string) (o []model.Asset, err error) {
err = r.GetDB(c).Where("protocol = ?", protocol).Find(&o).Error
db := r.GetDB(c)
if protocol != "" {
db = db.Where("protocol = ?", protocol)
}
err = db.Order("name asc").Find(&o).Error
return
}
@ -37,65 +44,9 @@ func (r assetRepository) FindByProtocolAndIds(c context.Context, protocol string
return
}
func (r assetRepository) FindByProtocolAndUser(c context.Context, protocol string, account model.User) (o []model.Asset, err error) {
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created,assets.tags,assets.description, users.nickname as owner_name").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
if constant.TypeUser == account.Type {
owner := account.ID
db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner)
// 查询用户所在用户组列表
userGroupIds, err := UserGroupMemberRepository.FindUserGroupIdsByUserId(c, account.ID)
if err != nil {
return nil, err
}
if len(userGroupIds) > 0 {
db = db.Or("resource_sharers.user_group_id in ?", userGroupIds)
}
}
if len(protocol) > 0 {
db = db.Where("assets.protocol = ?", protocol)
}
err = db.Find(&o).Error
return
}
func (r assetRepository) Find(c context.Context, pageIndex, pageSize int, name, protocol, tags string, account *model.User, owner, sharer, userGroupId, ip, order, field string) (o []model.AssetForPage, total int64, err error) {
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created,assets.tags,assets.description, users.nickname as owner_name").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
dbCounter := r.GetDB(c).Table("assets").Select("DISTINCT assets.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
if constant.TypeUser == account.Type {
owner := account.ID
db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner)
dbCounter = dbCounter.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner)
// 查询用户所在用户组列表
userGroupIds, err := UserGroupMemberRepository.FindUserGroupIdsByUserId(c, account.ID)
if err != nil {
return nil, 0, err
}
if len(userGroupIds) > 0 {
db = db.Or("resource_sharers.user_group_id in ?", userGroupIds)
dbCounter = dbCounter.Or("resource_sharers.user_group_id in ?", userGroupIds)
}
} else {
if len(owner) > 0 {
db = db.Where("assets.owner = ?", owner)
dbCounter = dbCounter.Where("assets.owner = ?", owner)
}
if len(sharer) > 0 {
db = db.Where("resource_sharers.user_id = ?", sharer)
dbCounter = dbCounter.Where("resource_sharers.user_id = ?", sharer)
}
if len(userGroupId) > 0 {
db = db.Where("resource_sharers.user_group_id = ?", userGroupId)
dbCounter = dbCounter.Where("resource_sharers.user_group_id = ?", userGroupId)
}
}
func (r assetRepository) Find(c context.Context, pageIndex, pageSize int, name, protocol, tags, ip, active, order, field string) (o []model.AssetForPage, total int64, err error) {
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.active_message,assets.owner,assets.created,assets.tags,assets.description, users.nickname as owner_name").Joins("left join users on assets.owner = users.id")
dbCounter := r.GetDB(c).Table("assets")
if len(name) > 0 {
db = db.Where("assets.name like ?", "%"+name+"%")
@ -125,6 +76,14 @@ func (r assetRepository) Find(c context.Context, pageIndex, pageSize int, name,
}
}
if active != "" {
_active, err := strconv.ParseBool(active)
if err == nil {
db = db.Where("assets.active = ?", _active)
dbCounter = dbCounter.Where("assets.active = ?", _active)
}
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
@ -146,22 +105,6 @@ func (r assetRepository) Find(c context.Context, pageIndex, pageSize int, name,
if o == nil {
o = make([]model.AssetForPage, 0)
} else {
for i := 0; i < len(o); i++ {
if o[i].Protocol == "ssh" {
attributes, err := r.FindAttrById(c, o[i].ID)
if err != nil {
continue
}
for j := range attributes {
if attributes[j].Name == constant.SshMode {
o[i].SshMode = attributes[j].Value
break
}
}
}
}
}
return
}
@ -180,9 +123,9 @@ func (r assetRepository) UpdateById(c context.Context, o *model.Asset, id string
return r.GetDB(c).Updates(o).Error
}
func (r assetRepository) UpdateActiveById(c context.Context, active bool, id string) error {
sql := "update assets set active = ? where id = ?"
return r.GetDB(c).Exec(sql, active, id).Error
func (r assetRepository) UpdateActiveById(c context.Context, active bool, message, id string) error {
sql := "update assets set active = ?, active_message = ? where id = ?"
return r.GetDB(c).Exec(sql, active, message, id).Error
}
func (r assetRepository) DeleteById(c context.Context, assetId string) (err error) {
@ -198,47 +141,16 @@ func (r assetRepository) Count(c context.Context) (total int64, err error) {
return
}
func (r assetRepository) CountByActive(c context.Context, active bool) (total int64, err error) {
err = r.GetDB(c).Find(&model.Asset{}).Where("active = ?", active).Count(&total).Error
return
}
func (r assetRepository) CountByProtocol(c context.Context, protocol string) (total int64, err error) {
err = r.GetDB(c).Find(&model.Asset{}).Where("protocol = ?", protocol).Count(&total).Error
return
}
func (r assetRepository) CountByUserId(c context.Context, userId string) (total int64, err error) {
db := r.GetDB(c).Joins("left join resource_sharers on assets.id = resource_sharers.resource_id")
db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", userId, userId)
// 查询用户所在用户组列表
userGroupIds, err := UserGroupMemberRepository.FindUserGroupIdsByUserId(c, userId)
if err != nil {
return 0, err
}
if len(userGroupIds) > 0 {
db = db.Or("resource_sharers.user_group_id in ?", userGroupIds)
}
err = db.Find(&model.Asset{}).Count(&total).Error
return
}
func (r assetRepository) CountByUserIdAndProtocol(c context.Context, userId, protocol string) (total int64, err error) {
db := r.GetDB(c).Joins("left join resource_sharers on assets.id = resource_sharers.resource_id")
db = db.Where("( assets.owner = ? or resource_sharers.user_id = ? ) and assets.protocol = ?", userId, userId, protocol)
// 查询用户所在用户组列表
userGroupIds, err := UserGroupMemberRepository.FindUserGroupIdsByUserId(c, userId)
if err != nil {
return 0, err
}
if len(userGroupIds) > 0 {
db = db.Or("resource_sharers.user_group_id in ?", userGroupIds)
}
err = db.Find(&model.Asset{}).Count(&total).Error
return
}
func (r assetRepository) FindTags(c context.Context) (o []string, err error) {
var assets []model.Asset
err = r.GetDB(c).Not("tags = '' or tags = '-' ").Find(&assets).Error
@ -265,15 +177,15 @@ func (r assetRepository) UpdateAttributes(c context.Context, assetId, protocol s
var parameterNames []string
switch protocol {
case "ssh":
parameterNames = constant.SSHParameterNames
parameterNames = nt.SSHParameterNames
case "rdp":
parameterNames = constant.RDPParameterNames
parameterNames = nt.RDPParameterNames
case "vnc":
parameterNames = constant.VNCParameterNames
parameterNames = nt.VNCParameterNames
case "telnet":
parameterNames = constant.TelnetParameterNames
parameterNames = nt.TelnetParameterNames
case "kubernetes":
parameterNames = constant.KubernetesParameterNames
parameterNames = nt.KubernetesParameterNames
}
for i := range parameterNames {
@ -322,15 +234,15 @@ func (r assetRepository) FindAssetAttrMapByAssetId(c context.Context, assetId st
var parameterNames []string
switch asset.Protocol {
case "ssh":
parameterNames = constant.SSHParameterNames
parameterNames = nt.SSHParameterNames
case "rdp":
parameterNames = constant.RDPParameterNames
parameterNames = nt.RDPParameterNames
case "vnc":
parameterNames = constant.VNCParameterNames
parameterNames = nt.VNCParameterNames
case "telnet":
parameterNames = constant.TelnetParameterNames
parameterNames = nt.TelnetParameterNames
case "kubernetes":
parameterNames = constant.KubernetesParameterNames
parameterNames = nt.KubernetesParameterNames
}
propertiesMap := PropertyRepository.FindAllMap(c)
var attributeMap = make(map[string]string)
@ -350,3 +262,91 @@ func (r assetRepository) UpdateAttrs(c context.Context, name, value, newValue st
sql := "update asset_attributes set value = ? where name = ? and value = ?"
return r.GetDB(c).Exec(sql, newValue, name, value).Error
}
func (r assetRepository) ExistById(c context.Context, id string) (bool, error) {
m := model.Asset{}
var count uint64
err := r.GetDB(c).Table(m.TableName()).Select("count(*)").
Where("id = ?", id).
Find(&count).
Error
if err != nil {
return false, err
}
return count > 0, nil
}
func (r assetRepository) FindMyAssets(c context.Context, pageIndex, pageSize int, name, protocol, tags string, assetIds []string, order, field string) (o []model.AssetForPage, total int64, err error) {
db := r.GetDB(c).Table("assets").Select("assets.id,assets.name,assets.protocol,assets.active,assets.active_message,assets.tags,assets.description").
Where("id in ?", assetIds)
dbCounter := r.GetDB(c).Table("assets").Where("id in ?", assetIds)
if len(name) > 0 {
db = db.Where("assets.name like ?", "%"+name+"%")
dbCounter = dbCounter.Where("assets.name like ?", "%"+name+"%")
}
if len(protocol) > 0 {
db = db.Where("assets.protocol = ?", protocol)
dbCounter = dbCounter.Where("assets.protocol = ?", protocol)
}
if len(tags) > 0 {
tagArr := strings.Split(tags, ",")
for i := range tagArr {
if config.GlobalCfg.DB == "sqlite" {
db = db.Where("(',' || assets.tags || ',') LIKE ?", "%,"+tagArr[i]+",%")
dbCounter = dbCounter.Where("(',' || assets.tags || ',') LIKE ?", "%,"+tagArr[i]+",%")
} else {
db = db.Where("find_in_set(?, assets.tags)", tagArr[i])
dbCounter = dbCounter.Where("find_in_set(?, assets.tags)", tagArr[i])
}
}
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
}
if order == "ascend" {
order = "asc"
} else {
order = "desc"
}
if field == "name" {
field = "name"
} else {
field = "created"
}
err = db.Order("assets." + field + " " + order).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
if o == nil {
o = make([]model.AssetForPage, 0)
}
return
}
func (r assetRepository) FindMyAssetTags(c context.Context, assetIds []string) (o []string, err error) {
var assets []model.Asset
err = r.GetDB(c).Not("tags = '' or tags = '-' ").Where("id in ?", assetIds).Find(&assets).Error
if err != nil {
return nil, err
}
o = make([]string, 0)
for i := range assets {
if len(assets[i].Tags) == 0 {
continue
}
split := strings.Split(assets[i].Tags, ",")
o = append(o, split...)
}
return utils.Distinct(o), nil
}

View File

@ -0,0 +1,167 @@
package repository
import (
"context"
"next-terminal/server/dto"
"next-terminal/server/model"
)
var AuthorisedRepository = new(authorisedRepository)
type authorisedRepository struct {
baseRepository
}
func (r authorisedRepository) Create(c context.Context, m *model.Authorised) error {
return r.GetDB(c).Create(m).Error
}
func (r authorisedRepository) CreateInBatches(c context.Context, m []model.Authorised) error {
return r.GetDB(c).CreateInBatches(m, 100).Error
}
func (r authorisedRepository) DeleteByUserId(c context.Context, userId string) error {
return r.GetDB(c).Where("user_id = ?", userId).Delete(model.Authorised{}).Error
}
func (r authorisedRepository) DeleteByUserGroupId(c context.Context, userGroupId string) error {
return r.GetDB(c).Where("user_group_id = ?", userGroupId).Delete(model.Authorised{}).Error
}
func (r authorisedRepository) DeleteByAssetId(c context.Context, assetId string) error {
return r.GetDB(c).Where("asset_id = ?", assetId).Delete(model.Authorised{}).Error
}
func (r authorisedRepository) FindByUserId(c context.Context, userId string) (items []model.Authorised, err error) {
err = r.GetDB(c).Where("user_id = ?", userId).Find(&items).Error
return
}
func (r authorisedRepository) FindById(c context.Context, id string) (item model.Authorised, err error) {
err = r.GetDB(c).Where("id = ?", id).Find(&item).Error
return
}
func (r authorisedRepository) FindByUserGroupId(c context.Context, userGroupId string) (items []model.Authorised, err error) {
err = r.GetDB(c).Where("user_group_id = ?", userGroupId).Find(&items).Error
return
}
func (r authorisedRepository) FindByUserGroupIdIn(c context.Context, userGroupIds []string) (items []model.Authorised, err error) {
err = r.GetDB(c).Where("user_group_id in ?", userGroupIds).Find(&items).Error
return
}
func (r authorisedRepository) FindAll(c context.Context, userId, userGroupId, assetId string) (items []model.Authorised, err error) {
db := r.GetDB(c)
if userId != "" {
db = db.Where("user_id = ?", userId)
}
if userGroupId != "" {
db = db.Where("user_group_id = ?", userGroupId)
}
if assetId != "" {
db = db.Where("asset_id = ?", assetId)
}
err = db.Find(&items).Error
return
}
func (r authorisedRepository) FindAssetPage(c context.Context, pageIndex, pageSize int, assetName, userId, userGroupId string) (o []dto.AssetPageForAuthorised, total int64, err error) {
db := r.GetDB(c).Table("assets").
Select("authorised.id, authorised.created, assets.id as asset_id, assets.name as asset_name, strategies.id as strategy_id, strategies.name as strategy_name ").
Joins("left join authorised on authorised.asset_id = assets.id").
Joins("left join strategies on strategies.id = authorised.strategy_id").
Group("assets.id")
dbCounter := r.GetDB(c).Table("assets").Joins("left join authorised on assets.id = authorised.asset_id").Group("assets.id")
if assetName != "" {
db = db.Where("assets.name like ?", "%"+assetName+"%")
dbCounter = dbCounter.Where("assets.name like ?", "%"+assetName+"%")
}
if userId != "" {
db = db.Where("authorised.user_id = ?", userId)
dbCounter = dbCounter.Where("authorised.user_id = ?", userId)
}
if userGroupId != "" {
db = db.Where("authorised.user_group_id = ?", userGroupId)
dbCounter = dbCounter.Where("authorised.user_group_id = ?", userGroupId)
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
}
err = db.Order("authorised.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
if o == nil {
o = make([]dto.AssetPageForAuthorised, 0)
}
return
}
func (r authorisedRepository) DeleteById(c context.Context, id string) error {
return r.GetDB(c).Where("id = ?", id).Delete(model.Authorised{}).Error
}
func (r authorisedRepository) FindUserPage(c context.Context, pageIndex, pageSize int, userName, assetId string) (o []dto.UserPageForAuthorised, total int64, err error) {
db := r.GetDB(c).Table("users").
Select("authorised.id, authorised.created, users.id as user_id, users.nickname as user_name, strategies.id as strategy_id, strategies.name as strategy_name ").
Joins("left join authorised on authorised.user_id = users.id").
Joins("left join strategies on strategies.id = authorised.strategy_id").
Group("users.id")
dbCounter := r.GetDB(c).Table("assets").Joins("left join authorised on assets.id = authorised.asset_id").Group("assets.id")
if userName != "" {
db = db.Where("users.nickname like ?", "%"+userName+"%")
dbCounter = dbCounter.Where("users.nickname like ?", "%"+userName+"%")
}
if assetId != "" {
db = db.Where("authorised.asset_id = ?", assetId)
dbCounter = dbCounter.Where("authorised.asset_id = ?", assetId)
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
}
err = db.Order("authorised.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
if o == nil {
o = make([]dto.UserPageForAuthorised, 0)
}
return
}
func (r authorisedRepository) FindUserGroupPage(c context.Context, pageIndex, pageSize int, userName, assetId string) (o []dto.UserGroupPageForAuthorised, total int64, err error) {
db := r.GetDB(c).Table("user_groups").
Select("authorised.id, authorised.created, user_groups.id as user_group_id, user_groups.name as user_group_name, strategies.id as strategy_id, strategies.name as strategy_name ").
Joins("left join authorised on authorised.user_group_id = user_groups.id").
Joins("left join strategies on strategies.id = authorised.strategy_id").
Group("user_groups.id")
dbCounter := r.GetDB(c).Table("assets").Joins("left join authorised on assets.id = authorised.asset_id").Group("assets.id")
if userName != "" {
db = db.Where("user_groups.name like ?", "%"+userName+"%")
dbCounter = dbCounter.Where("user_groups.name like ?", "%"+userName+"%")
}
if assetId != "" {
db = db.Where("authorised.asset_id = ?", assetId)
dbCounter = dbCounter.Where("authorised.asset_id = ?", assetId)
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
}
err = db.Order("authorised.created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
if o == nil {
o = make([]dto.UserGroupPageForAuthorised, 0)
}
return
}

View File

@ -2,8 +2,8 @@ package repository
import (
"context"
"next-terminal/server/common/nt"
"next-terminal/server/constant"
"next-terminal/server/env"
"gorm.io/gorm"
@ -13,7 +13,7 @@ type baseRepository struct {
}
func (b *baseRepository) GetDB(c context.Context) *gorm.DB {
db, ok := c.Value(constant.DB).(*gorm.DB)
db, ok := c.Value(nt.DB).(*gorm.DB)
if !ok {
return env.GetDB()
}

View File

@ -3,24 +3,57 @@ package repository
import (
"context"
"next-terminal/server/constant"
"next-terminal/server/model"
)
var CommandRepository = new(commandRepository)
type commandRepository struct {
baseRepository
}
func (r commandRepository) Find(c context.Context, pageIndex, pageSize int, name, content, order, field string, account *model.User) (o []model.CommandForPage, total int64, err error) {
db := r.GetDB(c).Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on commands.owner = users.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id")
dbCounter := r.GetDB(c).Table("commands").Select("DISTINCT commands.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id")
func (r commandRepository) Find(c context.Context, pageIndex, pageSize int, name, content, order, field string) (o []model.CommandForPage, total int64, err error) {
db := r.GetDB(c).Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created, users.nickname as owner_name").Joins("left join users on commands.owner = users.id").Group("commands.id")
dbCounter := r.GetDB(c).Table("commands")
if constant.TypeUser == account.Type {
owner := account.ID
db = db.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner)
dbCounter = dbCounter.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner)
if len(name) > 0 {
db = db.Where("commands.name like ?", "%"+name+"%")
dbCounter = dbCounter.Where("commands.name like ?", "%"+name+"%")
}
if len(content) > 0 {
db = db.Where("commands.content like ?", "%"+content+"%")
dbCounter = dbCounter.Where("commands.content like ?", "%"+content+"%")
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
}
if order == "ascend" {
order = "asc"
} else {
order = "desc"
}
if field == "name" {
field = "name"
} else {
field = "created"
}
err = db.Order("commands." + field + " " + order).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
if o == nil {
o = make([]model.CommandForPage, 0)
}
return
}
func (r commandRepository) WorkerFind(c context.Context, pageIndex, pageSize int, name, content, order, field, userId string) (o []model.CommandForPage, total int64, err error) {
db := r.GetDB(c).Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created").Where("commands.owner = ?", userId)
dbCounter := r.GetDB(c).Table("commands").Where("commands.owner = ?", userId)
if len(name) > 0 {
db = db.Where("commands.name like ?", "%"+name+"%")
dbCounter = dbCounter.Where("commands.name like ?", "%"+name+"%")
@ -76,21 +109,12 @@ func (r commandRepository) DeleteById(c context.Context, id string) error {
return r.GetDB(c).Where("id = ?", id).Delete(&model.Command{}).Error
}
func (r commandRepository) FindByUser(c context.Context, account *model.User) (o []model.CommandForPage, err error) {
db := r.GetDB(c).Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on commands.owner = users.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id")
if constant.TypeUser == account.Type {
owner := account.ID
db = db.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner)
}
err = db.Order("commands.name asc").Find(&o).Error
if o == nil {
o = make([]model.CommandForPage, 0)
}
return
}
func (r commandRepository) FindAll(c context.Context) (o []model.Command, err error) {
err = r.GetDB(c).Find(&o).Error
return
}
func (r commandRepository) FindByUserId(c context.Context, userId string) (o []model.Command, err error) {
err = r.GetDB(c).Where("owner = ?", userId).First(&o).Error
return
}

Some files were not shown because too many files have changed in this diff Show More