Compare commits

...

425 Commits

Author SHA1 Message Date
lejianwen
e318f1fd58 feat: WebClient 1.4.2 2025-09-12 15:11:49 +08:00
nomoneynolife
ee19eb9729 feat:ldap allow-group (#388) 2025-09-05 12:53:45 +08:00
Tom
fcdea1ad6d feat: add TLS configuration option for MySQL (#384) 2025-09-03 19:43:16 +08:00
lejianwen
c88d8cc359 docs: readme 2025-09-01 21:36:27 +08:00
lejianwen
ade6e6355a feat(peer): add alias field and support filtering by alias 2025-08-31 13:39:22 +08:00
lejianwen
9b769b99dc fix!: Update peer to use ID instead of UUID 2025-08-31 12:46:54 +08:00
lejianwen
c14c4d478b fix: The callback URL is based on the configured API SERVER because the project might be behind an Nginx reverse proxy. If the Origin/Host is forgotten to configure the reverse proxy, it will be incorrect 2025-08-10 15:48:11 +08:00
lejianwen
9d08c61390 fix: Normal user can not change the name of their own address book (#341) 2025-08-10 11:17:51 +08:00
Tao Chen
6f092472b1 feat: Optimize login workflow (#345)
* add "disable_pwd" and "auto_oidc" at /admin/login-options

* fix: build RedirectURL by host and scheme, not Origin
2025-07-31 10:46:11 +08:00
caicob
4876746f7a docs: README_EN.md (#340) 2025-07-31 10:42:56 +08:00
startgo
05d2d1642a feat: Update zh_TW.toml (#322)
Corrected translation to match Taiwanese Traditional Chinese usage
2025-07-19 21:08:40 +08:00
lejianwen
59fdd6424b feat(captcha): The captcha generates uppercase letter images, but it can only recognize them as lowercase (#319) 2025-07-14 20:36:33 +08:00
lejianwen
0feee5115f fix: Oauth callback url is fixed to host+/api/oidc/callback (#314) 2025-07-11 09:55:48 +08:00
Plynksiy Nikita
65f0a9e3cf docs: add note about requiring conf and resources directories (or specifying paths via -c and RUSTDESK_API_GIN_RESOURCES_PATH) when running without docker (#311) 2025-07-09 09:43:56 +08:00
lejianwen
77836a4e56 feat(user): add remark field to User model and form (#307) 2025-07-08 12:14:46 +08:00
lejianwen
09f8316bf1 feat: Update database version constant to 264 2025-07-08 12:13:31 +08:00
k3-cat
c52706e621 feat: Improve oauth redirect (#303)
* fix: redirects after oauth can potentially misalign with server's actually hostname

* feat: remove `RedirectURL` from oauth config, as it should checked by provider rather than client

* feat: align oauth endpoint with the hostname in requests
2025-07-04 14:30:07 +08:00
k3-cat
17dcff4f43 feat: better autorenewal (#305) 2025-07-04 14:27:47 +08:00
Plynksiy Nikita
0b39c4e104 feat(password): Password hashing with bcrypt (#290)
* feat(password): add configurable password hashing with md5 and bcrypt

* docs: add password hashing algorithm configuration (bcrypt/md5)

* feat(password): better bcrypt fallback and minor refactoring

* feat(password): handle errors in password encryption and verification

* feat(password): remove password hashing algorithm configuration
2025-06-24 17:23:36 +08:00
Plynksiy Nikita
ee176b314e feat: Use crypto/rand for secure random string generation (#293) 2025-06-18 20:47:24 +08:00
Plynksiy Nikita
1ffc9c4a5b fix: correct typo in build arg FRONTEND_GIT_REPO (#292) 2025-06-18 20:42:13 +08:00
Plynksiy Nikita
1257246552 feat(i18n): replace hardcoded messages with translated strings (#289) 2025-06-17 09:05:10 +08:00
lejianwen
2948eaaa5c chore: Update Go version to 1.23 in build configurations 2025-06-16 15:41:16 +08:00
lejianwen
8641ba5c0c docs: Update swagger docs 2025-06-16 12:31:48 +08:00
lejianwen
60b7a18fe7 feat: Add PostgreSQL support and refactor MySQL DSN handling (#284) 2025-06-16 12:26:08 +08:00
lejianwen
ca068816ae feat: Add start time in /api/sysinfover 2025-06-16 12:23:48 +08:00
lejianwen
06648d9a6c fix(admin): Use admin-hello first
(#274) (#255)
2025-06-15 15:33:12 +08:00
puyujian
8a8abd5163 feat(oauth): 支持linux.do登录 (#280)
* 支持linux.do登录

* 修正
2025-06-15 15:32:20 +08:00
lejianwen
97f98cd6ce chore: update download links for musl cross-compilers 2025-06-05 12:14:17 +08:00
lejianwen
51f2920661 fix: Init sqlite fail(#266) 2025-06-04 09:31:43 +08:00
lejianwen
7a5d141ce8 fix(server): Port custom (#257) 2025-05-30 12:27:37 +08:00
lejianwen
3cef02a0bb fix(webclient): Peer online status 2025-05-29 18:51:37 +08:00
lejianwen
46a7ecc1ba fix: Captcha some problem when users login with same ip 2025-05-27 17:36:20 +08:00
lejianwen
4d2b037f5e docs: Readme 2025-05-25 17:44:29 +08:00
lejianwen
323364b24e feat(register): Register status can be set (#223) 2025-05-25 17:03:13 +08:00
lejianwen
f19109cdf8 feat(login): Captcha upgrade and add the function to ban IP addresses (#250) 2025-05-25 16:52:58 +08:00
Tao Chen
527260d60a fix: dn should be case-insensitive (#250) 2025-05-21 09:07:08 +08:00
lejianwen
46bb44f0ab fix(webclient): DefaultIdServerPort undefined (#238) 2025-05-16 20:14:36 +08:00
lejianwen
2f1380f24a fix(webclient): Remove license warning (#235) 2025-05-13 13:11:19 +08:00
lejianwen
ece3328e94 feat(webclient): Web client to 1.4.0 2025-05-12 20:16:08 +08:00
lejianwen
fdd26d87be fix: PageSize (#225) 2025-05-06 19:08:18 +08:00
lejianwen
2ade0dda42 chore: Noelware/docker-manifest-action 2025-04-25 16:20:36 +08:00
lejianwen
a87ae5cf65 chore: Noelware/docker-manifest-action 2025-04-25 14:34:45 +08:00
lejianwen
fe7b8b53a6 style: Oauth page languages 2025-04-24 21:52:43 +08:00
lejianwen
b929f3efdb style: Remove useless configurations 2025-04-15 10:52:46 +08:00
lejianwen
f847fc076f fix: Low case (#149) 2025-04-15 10:46:21 +08:00
lejianwen
60d0a701ce fix: Share pwd 2025-04-15 10:09:56 +08:00
lejianwen
0dedaf6824 feat: Peer share to group 2025-04-14 19:12:40 +08:00
lejianwen
ab231b3fed feat: Add SysInfoVer endpoint and AppService for version retrieval 2025-04-07 16:38:21 +08:00
lejianwen
e7f28cca36 fix: Update peer based on the UUID (#180) 2025-04-02 09:50:16 +08:00
lejianwen
505e8aac4b feat: Add Korean translations validator (#168) 2025-04-02 09:42:29 +08:00
lejianwen
746e2a6052 fix: Get Uuids 2025-03-15 21:02:47 +08:00
lejianwen
dc03d5d83d style: Update peer last online time logic (#173) 2025-03-15 21:02:08 +08:00
lejianwen
b770ab178d feat(admin): Add filter by ip and username (#172) 2025-03-15 19:49:49 +08:00
Tao Chen
fd7e022e88 fix: rm varify password accidentally (#176) 2025-03-15 19:40:02 +08:00
lejianwen
ac5df6826b fix: Init database err (#166) 2025-03-04 18:14:25 +08:00
lejianwen
91908859bc docs: Readme 2025-03-04 16:30:12 +08:00
lejianwen
9c8822f857 fix: templates 2025-03-04 16:14:34 +08:00
lejianwen
9709da7fb6 style(webclient): ws-host 2025-03-04 15:48:25 +08:00
lejianwen
1852f10131 feat(config): add ws-host configuration (#156) 2025-03-04 15:43:25 +08:00
Tao Chen
77d7b43e21 fix: Fix/ldap tls (#162)
* optimize and fix tls of LDAP

* fix
2025-03-02 22:59:01 +08:00
lejianwen
7410b539a0 style(service): refactor global dependencies to use local variables 2025-02-26 19:15:38 +08:00
lejianwen
0403d71502 feat(oauth): Oauth nonce (#148) 2025-02-26 16:36:53 +08:00
lejianwen
2af1d93a7d feat(oauth): Oauth callback page beautification (#115) 2025-02-25 13:51:49 +08:00
lejianwen
76281ad761 feat(api): Add device group for 1.3.8 2025-02-23 21:35:42 +08:00
lejianwen
d1ec9b4916 feat(api): Add /device-group/accessible for 1.3.8 2025-02-23 15:32:44 +08:00
lejianwen
448e7460a9 style(oidc): Oidc style 2025-02-21 09:49:41 +08:00
lejianwen
ee0cbabffc fix(admin): Admin hello 2025-02-20 19:37:34 +08:00
lejianwen
d6a5af890a style: No need exec sql on version 261 2025-02-20 19:22:22 +08:00
lejianwen
dc313441e5 fix: Js content-type 2025-02-19 16:04:03 +08:00
Tao Chen
c75320f4f4 feat(oidc): add pkce (#150) 2025-02-19 09:31:25 +08:00
lejianwen
c788f78416 docs: Readme 2025-02-17 10:59:38 +08:00
lejianwen
49cf954d4a fix(config)!: Token expire time (#145)
将配置中的过期时间单位统一为time.Duration,可以设置为`h`,`m`,`s`
2025-02-17 10:49:59 +08:00
lejianwen
014e3db54f docs: Readme 2025-02-16 21:06:55 +08:00
lejianwen
6d9c245c81 fix(webclient): port 2025-02-16 14:17:37 +08:00
lejianwen
7fa9b79f31 fix(webclient): port 2025-02-16 14:11:36 +08:00
lejianwen
c7f3d13b7f fix(webclient): port 2025-02-16 13:33:06 +08:00
lejianwen
46f08a89d2 feat: Login by pwd can be disable
---

Closes: #141
2025-02-16 13:06:45 +08:00
lejianwen
0dcfedb4dc fix(webclient)!: Webclient path is /ws/(relay|id) (#73 #143 #140)
Webclient 的反代引发了很多问题,现在将在HTTPS下的path固定为`/ws/(relay|id)`

---

Closes: #143 #140
2025-02-16 12:41:32 +08:00
lejianwen
918bf85a2d style: middleware name 2025-02-12 22:09:52 +08:00
lejianwen
99db5f7190 fix(admin): Admin web title 2025-02-12 21:10:07 +08:00
lejianwen
18eff791b2 style: Module name 2025-02-12 19:46:39 +08:00
lejianwen
624dcacac5 style: generate 2025-02-12 16:56:41 +08:00
lejianwen
878d5fd27c style: Remove generate 2025-02-12 16:25:04 +08:00
lejianwen
4b893ce0e8 docs: Docs 2025-02-12 16:14:51 +08:00
lejianwen
472524f836 style: Module name 2025-02-12 16:07:51 +08:00
lejianwen
dbf8b23b15 fix: Config watchConfig (#135)
---
Closes: #135
2025-02-10 10:13:34 +08:00
lejianwen
79a5dd53ae fix: User disabled can not work (#133)
---
Closes: #133
2025-02-10 10:13:15 +08:00
Tao Chen
8a5b20685c fix: When OIDC and LDAP work togethar (#132 #134)
* fix OIDC create user if LDAP enable

* `newUser.GroupId = 1` for ldap

* fix
2025-02-10 10:08:49 +08:00
lejianwen
5a9c972de0 docs: Readme 2025-02-09 21:13:01 +08:00
Tao Chen
fc0e67122d docs: add LDAP info (#130) 2025-02-09 19:36:31 +08:00
lejianwen
eb642f66ca docs: Readme 2025-02-07 18:14:12 +08:00
lejianwen
8cac15f7dd style: Log time 2025-02-07 17:57:28 +08:00
lejianwen
5011e2b7c1 feat: Web sso env (#125) 2025-02-07 17:52:40 +08:00
lejianwen
b0008143b1 docs: Up readme 2025-02-07 17:52:40 +08:00
lejianwen
a3c3ab5a72 style: Up conf 2025-02-07 17:52:40 +08:00
lejianwen
3a16269215 docs: Up readme 2025-02-07 17:52:40 +08:00
lejianwen
151145b0c3 feat: Random Initial Password for Admin (#117) 2025-02-07 17:52:39 +08:00
lejianwen
9c794e9d4b fix(build): Fix no admin in deb (#119 #120) 2025-02-03 13:29:35 +08:00
lejianwen
01f697d279 fix(api): Add Default Token Expire (#113) 2025-02-03 13:18:08 +08:00
lejianwen
6cdc37333b style: webclient 2025-02-03 00:01:10 +08:00
Tao Chen
ae32915565 feat(ldap): Add LDAP
* rename: Admin to AdminGroup

* update

* cleanup

* tmp save group mapping

* add enableControl(not-test)

* verify username exist before create(for LDAP)

* add getAllGroupsDn()

* rename

* adminGroup

* enable TLS Verify

* init for ldap

---------

Co-authored-by: Tao Chen <iamtaochen@outlook.com>
2025-02-02 23:59:52 +08:00
lejianwen
f49457dc5b feat(webclient): Up to 1.3.7 2025-01-21 19:12:28 +08:00
lejianwen
d9e2e247ea feat(api): Add api token expire
Resolves #109
2025-01-21 18:23:28 +08:00
lejianwen
c6f2f2f150 feat(api): Add api/version
Resolves #110
2025-01-20 20:04:22 +08:00
lejianwen
56b9c66cb8 docs: Up Swagger docs 2025-01-20 19:35:47 +08:00
lejianwen
5d8a0d0e1f style: Add Start Tips 2025-01-20 13:12:45 +08:00
lejianwen
f4cb9beda5 fix(api): Change tag to alphabetical sorting
Fixes: #108
2025-01-20 13:07:41 +08:00
lejianwen
b66fc3c06d fix(docs): Api Route doc 2025-01-19 13:10:43 +08:00
lejianwen
ab2e1a9236 feat(i18n): Add ZH_TW 2025-01-19 13:10:19 +08:00
Jia-Bin
ab77b400a1 Add Traditional Chinese 2025-01-18 23:04:39 +08:00
lejianwen
eb7ab63563 docs: Up readme 2025-01-16 22:01:23 +08:00
lejianwen
4cf7d01622 docs: Up readme 2025-01-16 21:59:46 +08:00
lejianwen
a876078a9c feat(server): Rustdesk Id Server Port & Relay Server Port #104 2025-01-16 20:57:00 +08:00
lejianwen
495f2ae3c6 refactor(config): Up Config Load 2025-01-16 20:40:42 +08:00
lejianwen
4e6d11baf0 docs: Up readme 2025-01-15 21:56:04 +08:00
lejianwen
a951b982b3 fix: Jwt 2025-01-15 20:26:26 +08:00
lejianwen
a33be66504 docs: Up readme 2025-01-15 20:09:08 +08:00
lejianwen
f41b9d5887 feat!: Add JWT
- `RUSTDESK_API_JWT_KEY`如果设置,将会启用JWT,token自动续期功能将失效
- 此功能是为了server端校验token的合法性
2025-01-15 19:25:28 +08:00
lejianwen
3c608463e6 docs: Up readme 2025-01-12 23:12:00 +08:00
lejianwen
eeffbe124a docs: Up readme 2025-01-12 21:35:34 +08:00
lejianwen
d7f2d54faa feat(server): Add Rustdesk Relay Server Commands 2025-01-04 20:49:44 +08:00
lejianwen
7db4b03634 style(server): fmt print to log 2025-01-02 21:49:37 +08:00
lejianwen
77760a681a docs: Up readme 2025-01-02 17:03:07 +08:00
lejianwen
f9c1447ceb fix: Fix Dockerfile_full_s6 2024-12-31 23:33:17 +08:00
lejianwen
fb749c1902 fix(server): Fix Rustdesk Sys Command 2024-12-31 23:29:05 +08:00
lejianwen
240c44aa07 feat(server): Add Rustdesk Command
And add build full s6 image for rustdesk command
2024-12-31 23:16:15 +08:00
lejianwen
92cd8642c8 docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:36:57 +08:00
lejianwen
89d90cf919 docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:35:06 +08:00
lejianwen
920c6b6d8b docs(readme): Up readme
Connection timeout issues move to #92
2024-12-30 13:33:11 +08:00
lejianwen
1dd4df3a1c chore(buildTest): add start.bat to run on windows 2024-12-27 20:13:55 +08:00
lejianwen
c7d44cc253 fix(build): add start.bat to run on windows(#89) 2024-12-27 20:09:08 +08:00
lejianwen
5082ab1893 refactor(admin): Move Admin Web Route to user model 2024-12-27 19:27:33 +08:00
lejianwen
e8b2425222 feat(admin): Add My Login log 2024-12-27 19:25:59 +08:00
lejianwen
09d12cefd8 feat(admin): Support Markdown to welcome msg 2024-12-25 19:21:50 +08:00
lejianwen
5f1166965d fix(api): Get ab list when personal is disabled (#86) 2024-12-25 19:04:42 +08:00
lejianwen
0dbab182e9 fix(webclient): share fail when expire is 0
Closes: #88
2024-12-25 15:01:03 +08:00
lejianwen
512f3f99fd fix(build): fix build_test.yml 2024-12-25 14:06:29 +08:00
lejianwen
6fb4fad705 fix(build): up build.yml to build deb 2024-12-25 13:20:12 +08:00
lejianwen
fa92529e9b chore(build): up build.yml to build deb 2024-12-25 12:57:11 +08:00
Follow the wind
d6c6051a6c feat(build): 添加构建deb包相关基础 (#87)
* 添加构建deb包相关基础

* 补齐工作流,等待验证

* 修复构建时没有创建的data目录保障deb包构建

* 修复其余架构deb包构建中的依赖错误

* 修复:由于小改工作流导致写错架构的问题

* 修复拼写错误导致的目录错误

* 添加上传工件,和发布rel工作流,完成相关事务

---------

Co-authored-by: ymwl <ymwlpoolc@qq.com>
2024-12-25 12:28:51 +08:00
lejianwen
ce063bd3ac feat(webclient): v1.3.5 -> v1.3.6 2024-12-24 10:25:47 +08:00
lejianwen
4468894dfb chore(changelog): up build.yml to generate changelog 2024-12-22 14:14:51 +08:00
lejianwen
8b00b919ad chore(changelog): up build.yml to generate changelog 2024-12-22 14:04:23 +08:00
64f4a6dfac feat(i18n): Merge pull request #85 from jimmyGALLAND/trans-fr
add locale french
2024-12-22 13:57:56 +08:00
jimmyGALLAND
6faa5153b6 add locale french 2024-12-21 22:56:48 +01:00
lejianwen
a771b1e9b0 fix(webclient): remove console.log when query online by new 2024-12-21 21:49:03 +08:00
lejianwen
7750f9c54d chore(changelog): up build.yml to add changelog 2024-12-21 21:39:11 +08:00
lejianwen
b2d24ee67b docs(webclient): up readme 2024-12-21 21:31:54 +08:00
lejianwen
589a2a5123 feat(webclient): add new query_online function
There may be a loss of performance
Therefore, it is not enabled by default
2024-12-21 21:15:06 +08:00
lejianwen
184d3d357d optimize add ab from peer #84 2024-12-20 11:11:32 +08:00
lejianwen
50b3d85270 up docs and readme 2024-12-18 14:28:06 +08:00
lejianwen
09fdd34ba3 fix captcha 2024-12-18 13:51:06 +08:00
lejianwen
bba10261c5 fix captcha 2024-12-18 13:36:01 +08:00
lejianwen
46bfe54097 add show-swagger config #83 2024-12-18 12:50:09 +08:00
lejianwen
503e7a307e up docs 2024-12-18 12:44:06 +08:00
lejianwen
821b0a6faf add captcha #82 2024-12-18 12:43:55 +08:00
lejianwen
d60fdff179 split my from admin 2024-12-17 21:41:56 +08:00
lejianwen
fdd841e82a add batch add ab from peer and up my 2024-12-13 16:27:12 +08:00
lejianwen
2d6f0a116a add share record manage 2024-12-13 12:32:36 +08:00
lejianwen
bd13fe4ef4 up web client v2 2024-12-10 18:26:08 +08:00
lejianwen
6e1b208464 up README.md 2024-12-10 15:28:05 +08:00
lejianwen
76433a409e up README.md 2024-12-09 13:43:04 +08:00
lejianwen
9b4fa679c2 add batch add ab from peer
add batch update ab tags
2024-12-06 19:45:41 +08:00
lejianwen
c2ae95c4cc up api docs 2024-12-06 10:36:40 +08:00
lejianwen
b2b7f60fd5 add batch delete user token 2024-12-06 10:36:27 +08:00
lejianwen
a465888b31 up username length to 32 #70 2024-12-06 10:22:28 +08:00
lejianwen
d368bdc84c up web client v2 2024-12-04 13:43:04 +08:00
lejianwen
cdc1150505 up readme 2024-11-28 12:42:15 +08:00
32d525c53c Create LICENSE 2024-11-26 17:29:46 +08:00
lejianwen
a89b40c607 add es lang 2024-11-26 10:43:01 +08:00
lejianwen
b6bd9150d9 up web client v2 from rustdesk 2024-11-22 19:54:19 +08:00
lejianwen
96e3e3bc86 up docs 2024-11-22 19:53:49 +08:00
lejianwen
41377f41bb Split the language 2024-11-22 19:39:28 +08:00
lejianwen
fb744f81e2 up img 2024-11-20 19:33:27 +08:00
lejianwen
750c3bcbcd fix #62 2024-11-20 19:32:44 +08:00
lejianwen
d4015d7284 fix 2024-11-20 09:20:29 +08:00
lejianwen
a9bf3fda73 fix https://github.com/lejianwen/rustdesk-api/discussions/59#discussioncomment-11306760 2024-11-20 09:17:29 +08:00
lejianwen
7f467a4814 up web client v2 2024-11-18 21:39:18 +08:00
lejianwen
9f10b5e983 up readme 2024-11-17 18:27:15 +08:00
lejianwen
5291270e6a up ws connect in https #12 2024-11-17 17:34:51 +08:00
lejianwen
56bba381d8 fix 2024-11-16 22:08:06 +08:00
lejianwen
2ff276b162 up package lock 2024-11-16 20:56:26 +08:00
lejianwen
d77191ce0f up ws js 2024-11-16 20:03:30 +08:00
lejianwen
f99803aef4 up 2024-11-16 19:42:13 +08:00
lejianwen
e09fa17013 add webclientv2 2024-11-16 18:33:02 +08:00
lejianwen
c7e69aa8a5 fix gitignore 2024-11-15 21:43:23 +08:00
ljw
9548068283 up readme 2024-11-12 11:54:21 +08:00
ljw
09958c78f3 up readme 2024-11-12 11:53:57 +08:00
ljw
aced098982 add batch delete log #57 2024-11-12 09:08:10 +08:00
ljw
7862a34760 up admin conf 2024-11-12 08:43:49 +08:00
ljw
1384d28cac add admin conf 2024-11-11 22:26:15 +08:00
ljw
24b7338153 up readme 2024-11-09 20:38:11 +08:00
ljw
30d254eaef fix #55 2024-11-09 20:14:14 +08:00
ljw
bb8a936ade add build_test.yml 2024-11-08 16:09:50 +08:00
ljw
61044fd30b add build_test.yml 2024-11-08 16:05:43 +08:00
ljw
22a4546d0f add cmd 2024-11-08 15:54:49 +08:00
ljw
07450416ed fix #52 & add auto refresh token #53 2024-11-07 10:46:00 +08:00
0d6db0d2a1 Merge pull request #52 from IamTaoChen/fix/bug
fix: cannot delete user
2024-11-07 10:21:50 +08:00
Tao Chen
ab30b3407b add error information 2024-11-06 15:06:15 +08:00
Tao Chen
7a4c735803 fix: cannot delete user 2024-11-06 14:36:12 +08:00
ljw
654c764019 fix migrate 2024-11-05 21:07:39 +08:00
ljw
7101139250 fix migrate 2024-11-05 21:07:31 +08:00
ljw
793614841a fix migrate 2024-11-05 21:03:32 +08:00
ljw
94f2ac5b2a fix username length #48 2024-11-05 11:57:16 +08:00
ljw
d4d39eecaa up oauth re 2024-11-05 11:48:35 +08:00
ljw
8af01c859c Merge branch 'oauth_re' of https://github.com/IamTaoChen/rustdesk-api into IamTaoChen-oauth_re 2024-11-05 08:24:48 +08:00
Tao Chen
3af92d03e8 modify google ro re-use oidc 2024-11-04 21:30:58 +08:00
Tao Chen
f17d891302 fix: delete check 2024-11-03 22:23:24 +08:00
Tao Chen
51623436b0 fix: call us.IsAdmin(u) to check admin 2024-11-03 21:59:17 +08:00
Tao Chen
1b7c7eef8f fix google 2024-11-03 18:04:28 +08:00
Tao Chen
15e4c7e522 re-use responseLoginSuccess 2024-11-03 17:25:27 +08:00
Tao Chen
120ab132a9 fix: last admin shouldn't be deleted, disabled or demoted 2024-11-03 17:19:05 +08:00
Tao Chen
0b52e8faa8 fix: Github AvatarUrl to OauthUser 2024-11-03 16:49:28 +08:00
Tao Chen
60eaaf390a add err for RegisterByOauth 2024-11-03 16:49:03 +08:00
Tao Chen
9101badb1c fronted for docker-dev 2024-11-03 16:34:50 +08:00
Tao Chen
7082111b34 optimize /admin/login-options 2024-11-03 05:37:34 +08:00
Tao Chen
a156f2681e chroe 2024-11-03 05:34:19 +08:00
Tao Chen
7cb823c957 add Avatar to OauthUser 2024-11-03 05:33:59 +08:00
Tao Chen
817f612224 low case email 2024-11-03 05:25:10 +08:00
Tao Chen
1cbaf9d6bc const var for op name 2024-11-03 05:13:22 +08:00
Tao Chen
d353f9837f fix: email from github 2024-11-03 05:11:31 +08:00
Tao Chen
14beef72fc fix: Email of Register 2024-11-03 05:07:17 +08:00
Tao Chen
91a33fe7f6 fix: RegisterByOauth without Email 2024-11-03 04:35:39 +08:00
ljw
7ad7a0ff41 fix #45 2024-11-02 18:49:16 +08:00
Tao Chen
d646469f71 set user_id=0 at peers, when the user is deleted 2024-11-02 08:24:07 +08:00
Tao Chen
4f616ffff1 When login, peer doesn't exist, it should create 2024-11-02 08:19:44 +08:00
Tao Chen
64400fba61 delete the token when delete a peer 2024-11-02 08:02:03 +08:00
Tao Chen
fc15e8c63d add MyPeers for user 2024-11-02 07:35:26 +08:00
Tao Chen
cbba1e5317 add email for register 2024-11-02 05:43:55 +08:00
Tao Chen
75b997dcc4 add DeviceId to userToken 2024-11-02 05:42:47 +08:00
Tao Chen
853063c59b logout should unbind uuid and uid of peer 2024-11-02 05:07:41 +08:00
Tao Chen
5c65edb8fa fix bug ValidateOauthProvider location 2024-11-02 04:20:00 +08:00
Tao Chen
7707cc116f re-construct oauth 2024-11-02 04:01:28 +08:00
ljw
737fe749de load key from file 2024-11-01 14:56:11 +08:00
b9c6f17e3f Merge pull request #42 from IamTaoChen/docker
Docker Optimize
2024-11-01 13:10:59 +08:00
Tao Chen
af6a340003 add more 2024-11-01 01:33:08 +08:00
Tao Chen
8ba3bee944 optimize scripts 2024-11-01 01:33:05 +08:00
Tao Chen
153c3566b6 optimize build speed, like cache and mirror 2024-11-01 01:32:45 +08:00
Tao Chen
97a4753f4f add ARG CONTRY=CN to improve the alpinelinux install speed 2024-11-01 00:29:33 +08:00
Tao Chen
74c3899b55 add bash to run dev-docker 2024-11-01 00:28:43 +08:00
ljw
273ac6d1a8 add remove user token #34 2024-10-31 22:29:12 +08:00
5edbb39a63 Merge pull request #40 from IamTaoChen/resetEmptyPassWD
Reset empty password
2024-10-31 18:46:34 +08:00
Tao Chen
0c974c4113 Merge branch 'master' into resetEmptyPassWD 2024-10-31 16:35:32 +08:00
Tao Chen
46657a525d ommit check old passwd if password is empty 2024-10-31 16:23:06 +08:00
Tao Chen
b36aa6f917 add IsPasswordEmpty... 2024-10-31 16:22:42 +08:00
ljw
cddb0ebef9 up readme #28 2024-10-31 15:48:36 +08:00
ljw
788c4e3531 up readme #28 2024-10-31 15:47:39 +08:00
ljw
47f9ad8274 add register 2024-10-31 15:14:30 +08:00
ljw
855beb7fa9 up oauth 2024-10-31 14:03:48 +08:00
f57816b1b0 Merge pull request #36 from IamTaoChen/oidc-for-web
OIDC for web
2024-10-31 11:10:46 +08:00
Tao Chen
ff08fefc30 rename build stage 2024-10-31 09:21:43 +08:00
Tao Chen
f792ab9055 add some /admin/ to surport web OIDC 2024-10-31 09:21:30 +08:00
ljw
63af103a4e fix buidconfirm 2024-10-30 20:59:51 +08:00
ljw
0a36d44cec up del user 2024-10-30 19:34:56 +08:00
a1f4e1de84 Merge pull request #32 from IamTaoChen/bug/odic-user
delete user from user_thirds and update README
2024-10-30 19:08:50 +08:00
Tao Chen
05b20d47db modify delete user 2024-10-30 16:33:01 +08:00
Tao Chen
6b746f13d1 update README 2024-10-30 16:31:47 +08:00
Tao Chen
e838d5bcd2 update README for OIDC 2024-10-30 16:29:49 +08:00
Tao Chen
0dcc21260e delete user from user_thirds, too 2024-10-30 15:59:33 +08:00
ljw
3c30ad145c up v 2024-10-30 15:46:12 +08:00
ljw
06b0a8e873 add docker-compose-dev.yaml 2024-10-30 15:34:45 +08:00
b7de2ccadd Merge pull request #30 from IamTaoChen/oidc
Add General OIDC Login
2024-10-30 14:40:10 +08:00
Tao Chen
b52c5cfca1 bind oidc ThirdEmail 2024-10-29 23:09:54 +08:00
Tao Chen
fe910c37cf fix: spelling 2024-10-29 23:00:17 +08:00
Tao Chen
337ef330eb fix bug 2024-10-29 18:48:37 +08:00
Tao Chen
ffa47177aa fix bug - oidc scopes 2024-10-29 18:46:45 +08:00
ljw
46a76853c3 fix oauth register #26 #23 2024-10-29 15:31:27 +08:00
Tao Chen
2cd7dfb2b3 fix bug 2024-10-29 14:27:15 +08:00
Tao Chen
fee2808bca try add oidc 2024-10-29 11:51:01 +08:00
Tao Chen
49e5eb186a optimize docker 2024-10-29 11:50:55 +08:00
Tao Chen
dee2865466 optimize build.sh 2024-10-29 10:58:17 +08:00
ljw
eb340b2615 add last online ip #24 2024-10-28 20:24:34 +08:00
ljw
e714549a95 up address book add version #20 2024-10-28 19:48:47 +08:00
ljw
a1367bcd3d up peer update 2024-10-28 19:15:13 +08:00
ljw
642351dafd fix bug #27 2024-10-28 16:08:33 +08:00
ljw
90b9b5adba up 2024-10-28 14:54:00 +08:00
ljw
5bf4bbe45f add address book name &
add share address book
2024-10-28 14:51:07 +08:00
ljw
036f928fa3 up readme 2024-10-23 11:09:28 +08:00
ljw
94e7b31fb6 fix group 2024-10-23 11:09:13 +08:00
ljw
be4742382d up build.yml 2024-10-23 09:19:02 +08:00
ljw
70d2f1a055 add armv7l build #21 2024-10-23 09:03:01 +08:00
ljw
877fe50049 add ru lang 2024-10-22 19:46:38 +08:00
ljw
7d505705ee add ko lang,but validator dont have translations ko 2024-10-22 12:21:32 +08:00
ljw
38f81a03b5 Merge branch 'master' of https://github.com/lejianwen/rustdesk-api 2024-10-22 11:31:01 +08:00
d549d23819 Merge pull request #19 from jkh0kr/master
Add Korean language file
2024-10-22 11:30:49 +08:00
ljw
934675e0f0 up 2024-10-22 11:28:27 +08:00
진기환
2be397aa38 Add Korean language file 2024-10-22 10:35:58 +09:00
ljw
a0a422ed45 up readme 2024-10-21 21:35:27 +08:00
ljw
fcce10c695 add file conn log 2024-10-21 21:08:25 +08:00
ljw
30eb14702f up build.yml 2024-10-20 20:26:08 +08:00
ljw
3679fcc874 fix group create type 2024-10-20 20:15:12 +08:00
ljw
d085b4e3c2 up readme 2024-10-20 19:40:49 +08:00
dc8fcdf214 Merge pull request #17 from Ogannesson/master
Add proxy option for Google Oauthon
2024-10-20 19:05:01 +08:00
Oganneson
8bab23b65b Add oauth callback via proxy
Improved support for environment variables and configuration files, and standardized default behaviors
2024-10-20 17:56:11 +08:00
ljw
f64022e411 add conn log 2024-10-18 15:05:58 +08:00
ljw
2d37302cf9 fix write when heartbeat #14 2024-10-17 10:24:02 +08:00
ljw
1a1856257d fix write when heartbeat #14 2024-10-16 21:32:55 +08:00
ljw
24f570b64f fix pc add #13 2024-10-16 10:10:20 +08:00
ljw
6322177b71 fix pc add #13 2024-10-16 09:28:39 +08:00
ljw
d2390d1cb3 Revert "add webclient v2 preview"
This reverts commit 399c32da7d.
2024-10-15 16:35:34 +08:00
ljw
6fe6f6b708 Revert "up readme"
This reverts commit a656f4fec3.
2024-10-15 16:35:34 +08:00
ljw
a656f4fec3 up readme 2024-10-15 16:13:42 +08:00
ljw
399c32da7d add webclient v2 preview 2024-10-15 16:11:40 +08:00
ljw
62167836dc fix docs 2024-10-15 14:51:19 +08:00
ljw
caef3897a0 add login fail warn &
add web client on/off &
up admin peer filter &
upgrade web client
2024-10-14 10:43:29 +08:00
ljw
5ef6810e3f up readme 2024-10-12 10:02:04 +08:00
ljw
ae2079f583 Revert "Revert "up readme""
This reverts commit aa65382a0f.
2024-10-11 22:45:35 +08:00
ljw
aa65382a0f Revert "up readme"
This reverts commit 18c61a2bfc.
2024-10-11 22:06:20 +08:00
ljw
18c61a2bfc up readme 2024-10-11 21:39:58 +08:00
ljw
7cc1a8a58a up readme 2024-10-11 10:11:42 +08:00
ljw
cf9feac702 up readme 2024-10-10 13:00:29 +08:00
ljw
a963cd0209 fix readme 2024-10-10 12:40:54 +08:00
ljw
6a5408f9b8 up gorm logger & add share to guest by web client 2024-10-09 15:53:08 +08:00
ljw
9aad62d1e4 build tag 2024-09-29 12:47:04 +08:00
ljw
867eab40f8 build default push to docker 2024-09-29 12:39:33 +08:00
ljw
eb5c7efc4c fix build 2024-09-29 12:23:34 +08:00
857abc16e7 Merge pull request #5 from gigaion/new-build-1
build.yml - Add GHCR & Dynamic Inputs
2024-09-29 12:12:17 +08:00
ljw
28b9866c42 upgrade: init by i18n
add: batch delete peer
add: batch peer to addressbook
2024-09-29 11:53:58 +08:00
Gigaion
a27deb0a41 build.yml - Add GHCR & Dynamic Inputs
build.yml - Add GHCR & Dynamic Inputs
2024-09-28 13:30:44 -07:00
ljw
8e026de20b test 2024-09-28 11:01:23 +08:00
ljw
718ecc2372 test 2024-09-28 10:12:56 +08:00
ljw
56d46722f4 test 2024-09-28 10:05:04 +08:00
ljw
b6463cd715 test 2024-09-28 09:57:32 +08:00
ljw
bd3ae0cbfe fix build docker image 2024-09-28 09:39:26 +08:00
ljw
83c3aa894f fix build docker image 2024-09-28 09:33:59 +08:00
ljw
1b88d26fea up build 2024-09-27 22:08:36 +08:00
ljw
588287fdb4 up build 2024-09-27 22:06:04 +08:00
ljw
688e544b07 up build 2024-09-27 22:03:06 +08:00
ljw
3e3f812e83 up build 2024-09-27 21:48:48 +08:00
ljw
b551c7abe4 up build 2024-09-27 21:37:12 +08:00
ljw
6d1e7a4c05 up build 2024-09-27 21:29:19 +08:00
ljw
3341a4bc8e up build docker echo manifest 2024-09-27 21:20:54 +08:00
ljw
1c84980d36 up build docker 2024-09-27 20:10:17 +08:00
ljw
833b25881d up build docker 2024-09-27 20:06:49 +08:00
ljw
9dbf58903c up build docker 2024-09-27 19:40:48 +08:00
ljw
ad007f0d91 up build docker 2024-09-27 18:49:22 +08:00
ljw
4b06973a52 up build docker 2024-09-27 18:48:09 +08:00
ljw
159a67f15d up build docker 2024-09-27 17:49:17 +08:00
ljw
7c03b9953b up build docker 2024-09-27 17:32:25 +08:00
ljw
f90987de8d up build docker 2024-09-27 17:26:40 +08:00
ljw
70e4ff7820 up build docker 2024-09-27 17:25:30 +08:00
ljw
a99356f54b up build docker 2024-09-27 17:24:25 +08:00
ljw
c5bc9534cc up build docker 2024-09-27 17:15:06 +08:00
ljw
a40733424f up build docker 2024-09-27 16:52:02 +08:00
ljw
a937efc60e up build docker 2024-09-27 16:07:06 +08:00
ljw
6adb0e8415 up build docker 2024-09-27 15:59:12 +08:00
ljw
ff9ffb2f12 up build docker 2024-09-27 15:31:43 +08:00
ljw
9be4f472ae up build docker 2024-09-27 15:23:19 +08:00
ljw
8581d74b08 up build docker 2024-09-27 15:18:45 +08:00
ljw
dafe9bd6b6 up build docker 2024-09-27 14:58:17 +08:00
ljw
3ae5772360 up build docker 2024-09-27 14:54:11 +08:00
ljw
4628dbccfb up build docker 2024-09-27 14:46:39 +08:00
ljw
572b1d4c14 up build docker 2024-09-27 14:35:43 +08:00
ljw
bdb70e9859 up build docker 2024-09-27 14:29:55 +08:00
ljw
38bda17271 up build docker 2024-09-27 14:24:17 +08:00
ljw
455e1d2e5b up build docker 2024-09-27 14:19:56 +08:00
ljw
89cd724bab up build docker 2024-09-27 14:18:36 +08:00
ljw
b9109b4d0e up build docker 2024-09-27 14:16:59 +08:00
ljw
945958f552 up build docker 2024-09-27 14:10:50 +08:00
ljw
78eb0d5c06 up release_arm64.yml 2024-09-27 10:43:48 +08:00
ljw
bc6eae711e up release_arm64.yml 2024-09-27 10:38:14 +08:00
ljw
f0a4bf6164 up docker_arm64.yml 2024-09-26 14:36:15 +08:00
ljw
fc3b5e3ac3 up docker_arm64.yml 2024-09-26 14:33:01 +08:00
ljw
f7235ac847 up docker_arm64.yml 2024-09-26 14:26:15 +08:00
ljw
231f4ddb7f up docker_arm64.yml 2024-09-26 14:24:14 +08:00
ljw
3cad3994cb add docker_arm64.yml 2024-09-26 14:18:57 +08:00
ljw
8c97cc8686 up README 2024-09-26 13:38:43 +08:00
ljw
7ae976ee5d up README_EN.md 2024-09-26 13:21:17 +08:00
ljw
e91b53eb32 test release_arm64.yml 2024-09-26 11:49:11 +08:00
ljw
90311536a7 test release_arm64.yml 2024-09-26 11:46:17 +08:00
ljw
e951b7f2f9 fix Dockerfile 2024-09-26 09:29:02 +08:00
ljw
e0f94b62cf add i18n 2024-09-25 22:42:36 +08:00
ljw
8cb701ec85 add i18n 2024-09-25 22:41:57 +08:00
ljw
9afd11e3b8 up readme 2024-09-24 21:16:03 +08:00
ljw
a1d495f2db fix group peers 2024-09-24 19:35:20 +08:00
ljw
652fa93910 up readme 2024-09-24 15:22:21 +08:00
ljw
ef414855f0 up docs 2024-09-24 15:16:07 +08:00
ljw
76cf35cdf0 up readme 2024-09-24 15:15:26 +08:00
ljw
9d7aa05032 add personal apis 2024-09-24 14:43:27 +08:00
ljw
62a22c697d up web client can get pwd if exist 2024-09-24 10:15:04 +08:00
ljw
be497a5aa7 add docker.yml 2024-09-23 14:43:43 +08:00
ljw
fc3e16bc63 up README.md 2024-09-23 10:21:43 +08:00
ljw
df35912461 up release.yml 2024-09-23 10:13:44 +08:00
ljw
0ddc66a854 up release.yml 2024-09-23 10:10:29 +08:00
ljw
716d557d66 up test.yml 2024-09-23 10:07:10 +08:00
ljw
f9edcb9d47 up test.yml 2024-09-22 21:40:17 +08:00
ljw
d4623c5bc9 up test.yml 2024-09-22 21:28:43 +08:00
ljw
02ba4a3330 up test.yml 2024-09-22 20:30:12 +08:00
ljw
a72d4eaf78 up test.yml 2024-09-22 19:46:37 +08:00
ljw
4d3abb2dc0 up test.yml 2024-09-22 17:58:42 +08:00
ad6a8d1f7a Update test.yml 2024-09-21 22:49:06 +08:00
a5c8c2ac97 Update test.yml 2024-09-21 21:55:36 +08:00
1237004cb1 Update test.yml 2024-09-21 21:44:43 +08:00
c2bcd17df7 Update go.yml 2024-09-21 21:43:31 +08:00
bb49cbdd50 Update test.yml 2024-09-21 21:40:36 +08:00
6ad770a824 Create test.yml 2024-09-21 21:35:55 +08:00
593eeb3ac3 Update go.yml 2024-09-21 21:17:03 +08:00
6a7be3ef84 Create go.yml 2024-09-21 21:13:54 +08:00
ljw
3addc12f45 bug fix 2024-09-20 20:46:32 +08:00
ljw
1e3403e3c5 add google oauth 2024-09-20 12:13:15 +08:00
ljw
ae4672174a up 2024-09-20 10:14:38 +08:00
ljw
f935fed5f7 up readme 2024-09-20 10:12:25 +08:00
ljw
989990c869 up Docker image add tzdata 2024-09-20 10:10:04 +08:00
ljw
8b8a44e46b fix 2024-09-20 09:05:15 +08:00
ljw
2ed2884118 up README_EN.md 2024-09-19 12:05:53 +08:00
ljw
03bd34cadd up README_EN.md 2024-09-19 12:03:18 +08:00
ljw
1c3f87fecb up README.md 2024-09-19 11:07:54 +08:00
ljw
d45e85d29b up README.md 2024-09-19 11:02:54 +08:00
ljw
a4b413dadb add oauth loginlog & fix bugs 2024-09-19 10:44:49 +08:00
ljw
ebd1feb8d1 update README.md 2024-09-18 21:27:44 +08:00
ljw
815ec0f75d update Dockerfile 2024-09-18 10:48:04 +08:00
ljw
4c7919e6b7 add .gitattributes 2024-09-14 15:15:15 +08:00
ljw
f3218d481c add .gitattributes 2024-09-14 15:13:18 +08:00
ljw
5e6e90788b add .gitattributes 2024-09-14 15:11:33 +08:00
ljw
579a6400ca add .gitattributes 2024-09-14 15:10:55 +08:00
ljw
db4ad69d8f add .gitattributes 2024-09-14 15:07:35 +08:00
ljw
b1293189ad add .gitattributes 2024-09-14 15:06:50 +08:00
ljw
c8b3d41d9d add README_EN.md 2024-09-14 08:57:47 +08:00
ljw
fefb679cfc up docker 2024-09-14 07:59:35 +08:00
ljw
352941a20f up README.md 2024-09-13 22:51:13 +08:00
ljw
3006f5bd7c up env 2024-09-13 22:39:00 +08:00
ljw
d9ed2127da up env 2024-09-13 22:09:46 +08:00
ljw
f2ee00d49e up readme 2024-09-13 21:18:18 +08:00
ljw
c827a4e335 add Dockerfile & up readme 2024-09-13 21:01:37 +08:00
291 changed files with 256005 additions and 2183 deletions

29
.dockerignore Normal file
View File

@@ -0,0 +1,29 @@
# Ignore Docker Compose configuration files
docker-compose.yaml
docker-compose-dev.yaml
# Ignore development Dockerfile
Dockerfile
Dockerfile.dev
docker-dev.sh
# Ignore the data directory
data/
# Ignore version control system directories
.git/
# Ignore log and temporary files
*.log
*.tmp
*.swp
# Ignore editor/IDE configuration files
.vscode/
.idea/
# Ignore binaries and build cache
release/
bin/
*.exe
*.out

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
resources/web/**/* linguist-vendored
resources/web2/**/* linguist-vendored

444
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,444 @@
name: Build
on:
workflow_dispatch:
inputs:
BASE_IMAGE_NAMESPACE:
description: 'Base image namespace (Default: Your Github username)'
required: false
default: ''
DOCKERHUB_IMAGE_NAMESPACE:
description: 'Docker Hub image namespace (Default: Your Github username)'
required: false
default: ''
GHCR_IMAGE_NAMESPACE:
description: 'GitHub Container Registry image namespace (Default: Your Github username)'
required: false
default: ''
SKIP_DOCKER_HUB:
description: 'Set to true to skip pushing to Docker Hub (default: false)'
required: false
default: 'false'
SKIP_GHCR:
description: 'Set to true to skip pushing to GHCR (default: false)'
required: false
default: 'false'
WEBCLIENT_SOURCE_LOCATION:
description: 'Web Client API Repository'
required: true
default: 'https://github.com/lejianwen/rustdesk-api-web'
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag例如 v1.0.0)时触发工作流
- 'test*'
env:
LATEST_TAG: latest
WEBCLIENT_SOURCE_LOCATION: ${{ github.event.inputs.WEBCLIENT_SOURCE_LOCATION || 'https://github.com/lejianwen/rustdesk-api-web' }}
BASE_IMAGE_NAMESPACE: ${{ github.event.inputs.BASE_IMAGE_NAMESPACE || github.actor }}
DOCKERHUB_IMAGE_NAMESPACE: ${{ github.event.inputs.DOCKERHUB_IMAGE_NAMESPACE || github.actor }}
GHCR_IMAGE_NAMESPACE: ${{ github.event.inputs.GHCR_IMAGE_NAMESPACE || github.actor }}
SKIP_DOCKER_HUB: ${{ github.event.inputs.SKIP_DOCKER_HUB || 'false' }}
SKIP_GHCR: ${{ github.event.inputs.SKIP_GHCR || 'false' }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", file_ext: "tar.gz" }
- { platform: "arm64", goos: "linux", file_ext: "tar.gz" }
- { platform: "armv7l", goos: "linux", file_ext: "tar.gz" }
- { platform: "amd64", goos: "windows", file_ext: "zip" }
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: lejianwen/rustdesk-api-web
path: rustdesk-api-web
ref: master
- name: Set up Go environment
uses: actions/setup-go@v4
with:
go-version: '1.23' # 选择 Go 版本
- name: Set up npm
uses: actions/setup-node@v2
with:
node-version: '20'
- name: build rustdesk-api-web
working-directory: rustdesk-api-web
run: |
npm install
npm run build
mkdir -p ../resources/admin/
cp -ar dist/* ../resources/admin/
- name: tidy
run: go mod tidy
- name: Get tag version
run: |
TAG_VERSION="${GITHUB_REF##*/}"
VERSION="${TAG_VERSION#v}"
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Write version to resources/version
run: echo $VERSION > resources/version
- name: swag
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
- name: Build for ${{ matrix.job.goos }}-${{ matrix.job.platform }}
run: |
mkdir release -p
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir -p release/data
mkdir -p release/runtime
if [ "${{ matrix.job.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
echo @echo off > release/start.bat
echo cmd /c \"%~dp0apimain.exe\" >> release/start.bat
zip -r ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
else
if [ "${{ matrix.job.platform }}" = "arm64" ]; then
wget https://musl.ljw.red/aarch64-linux-musl-cross.tgz
tar -xf aarch64-linux-musl-cross.tgz
export PATH=$PATH:$PWD/aarch64-linux-musl-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=aarch64-linux-musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
elif [ "${{ matrix.job.platform }}" = "armv7l" ]; then
wget https://musl.ljw.red/armv7l-linux-musleabihf-cross.tgz
tar -xf armv7l-linux-musleabihf-cross.tgz
export PATH=$PATH:$PWD/armv7l-linux-musleabihf-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=arm GOARM=7 CC=armv7l-linux-musleabihf-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
else
sudo apt-get install musl musl-dev musl-tools -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
fi
tar -czf ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate Changelog
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
deb-package:
name: debian package - ${{ matrix.job.platform }}
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", debian_platform: "amd64", crossbuild_package: ""}
- { platform: "arm64", goos: "linux", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" }
- { platform: "armv7l", goos: "linux", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" }
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Create packaging env
run: |
sudo apt update
DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }}
mkdir -p debian-build/${{ matrix.job.platform }}/bin
- name: Get tag version
id: get_tag
run: |
TAG_VERSION="${GITHUB_REF##*/}"
VERSION="${TAG_VERSION#v}"
echo "TAG_VERSION=$TAG_VERSION" >> $GITHUB_ENV
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Update changelog
run: |
DATE=$(date -R)
sed -i "1i rustdesk-api-server (${VERSION}) stable; urgency=medium\n\n * Automatically generated release for version ${VERSION}.\n\n -- GitHub Actions <actions@github.com> ${DATE}\n" debian/changelog
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: .
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
- name: Build package for ${{ matrix.job.platform }} arch
run: |
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
mv ${{ matrix.job.platform }}/release/resources/admin resources
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
mkdir -p data
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.platform }}/debian/control
cd debian-build/${{ matrix.job.platform }}/
debuild -i -us -uc -b -a${{ matrix.job.debian_platform}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.debian_platform }}
path: |
debian-build/*.deb
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
debian-build/rustdesk-api-server_*_${{ matrix.job.debian_platform }}.deb
docker:
name: Push Docker Image
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", docker_platform: "linux/amd64" }
- { platform: "arm64", goos: "linux", docker_platform: "linux/arm64" }
- { platform: "armv7l", goos: "linux", docker_platform: "linux/arm/v7" }
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if SKIP_DOCKER_HUB is false
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=latest" >> $GITHUB_ENV # Default to 'latest' if not a tag
fi
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: ./
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.LATEST_TAG }}-${{ matrix.job.platform }},
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker Full S6 image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile_full_s6
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.LATEST_TAG }}-${{ matrix.job.platform }},
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker Full S6 image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
#
docker-manifest:
name: Push Docker Manifest
needs: docker
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=latest" >> $GITHUB_ENV # Default to 'latest' if not a tag
fi
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if Docker Hub push is enabled
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
- name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
amend: true
- name: Create and push manifest Docker Hub (:latest)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
push: true
- name: Create and push manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
push: true
amend: true
- name: Create and push Full S6 manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
push: true
amend: true
- name: Create and push Full S6 manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
push: true
amend: true

337
.github/workflows/build_test.yml vendored Normal file
View File

@@ -0,0 +1,337 @@
name: Build Test
on:
workflow_dispatch:
inputs:
BASE_IMAGE_NAMESPACE:
description: 'Base image namespace (Default: Your Github username)'
required: false
default: ''
DOCKERHUB_IMAGE_NAMESPACE:
description: 'Docker Hub image namespace (Default: Your Github username)'
required: false
default: ''
GHCR_IMAGE_NAMESPACE:
description: 'GitHub Container Registry image namespace (Default: Your Github username)'
required: false
default: ''
SKIP_DOCKER_HUB:
description: 'Set to true to skip pushing to Docker Hub (default: false)'
required: false
default: 'false'
SKIP_GHCR:
description: 'Set to true to skip pushing to GHCR (default: false)'
required: false
default: 'false'
WEBCLIENT_SOURCE_LOCATION:
description: 'Web Client API Repository'
required: true
default: 'https://github.com/lejianwen/rustdesk-api-web'
env:
LATEST_TAG: latest
WEBCLIENT_SOURCE_LOCATION: ${{ github.event.inputs.WEBCLIENT_SOURCE_LOCATION || 'https://github.com/lejianwen/rustdesk-api-web' }}
BASE_IMAGE_NAMESPACE: ${{ github.event.inputs.BASE_IMAGE_NAMESPACE || github.actor }}
DOCKERHUB_IMAGE_NAMESPACE: ${{ github.event.inputs.DOCKERHUB_IMAGE_NAMESPACE || github.actor }}
GHCR_IMAGE_NAMESPACE: ${{ github.event.inputs.GHCR_IMAGE_NAMESPACE || github.actor }}
SKIP_DOCKER_HUB: ${{ github.event.inputs.SKIP_DOCKER_HUB || 'false' }}
SKIP_GHCR: ${{ github.event.inputs.SKIP_GHCR || 'false' }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", file_ext: "tar.gz" }
- { platform: "arm64", goos: "linux", file_ext: "tar.gz" }
- { platform: "armv7l", goos: "linux", file_ext: "tar.gz" }
- { platform: "amd64", goos: "windows", file_ext: "zip" }
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: lejianwen/rustdesk-api-web
path: rustdesk-api-web
ref: master
- name: Set up Go environment
uses: actions/setup-go@v4
with:
go-version: '1.23' # 选择 Go 版本
- name: Set up npm
uses: actions/setup-node@v2
with:
node-version: '20'
- name: build rustdesk-api-web
working-directory: rustdesk-api-web
run: |
npm install
npm run build
mkdir -p ../resources/admin/
cp -ar dist/* ../resources/admin/
- name: tidy
run: go mod tidy
- name: swag
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
- name: Build for ${{ matrix.job.goos }}-${{ matrix.job.platform }}
run: |
mkdir release -p
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir -p release/data
mkdir -p release/runtime
if [ "${{ matrix.job.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
echo @echo off > release/start.bat
echo cmd /c \"%~dp0apimain.exe\" >> release/start.bat
zip -r ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
else
if [ "${{ matrix.job.platform }}" = "arm64" ]; then
wget https://musl.ljw.red/aarch64-linux-musl-cross.tgz
tar -xf aarch64-linux-musl-cross.tgz
export PATH=$PATH:$PWD/aarch64-linux-musl-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=aarch64-linux-musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
elif [ "${{ matrix.job.platform }}" = "armv7l" ]; then
wget https://musl.ljw.red/armv7l-linux-musleabihf-cross.tgz
tar -xf armv7l-linux-musleabihf-cross.tgz
export PATH=$PATH:$PWD/armv7l-linux-musleabihf-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=arm GOARM=7 CC=armv7l-linux-musleabihf-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
else
sudo apt-get install musl musl-dev musl-tools -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
fi
tar -czf ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
tag_name: test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
deb-package:
name: debian package - ${{ matrix.job.platform }}
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", debian_platform: "amd64", crossbuild_package: ""}
- { platform: "arm64", goos: "linux", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" }
- { platform: "armv7l", goos: "linux", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" }
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Create packaging env
run: |
sudo apt update
DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }}
mkdir -p debian-build/${{ matrix.job.platform }}/bin
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: .
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
- name: Build package for ${{ matrix.job.platform }} arch
run: |
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
mkdir -p data
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.platform }}/debian/control
cd debian-build/${{ matrix.job.platform }}/
debuild -i -us -uc -b -a${{ matrix.job.debian_platform}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.debian_platform }}
path: |
debian-build/*.deb
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: test
files: |
debian-build/rustdesk-api-server_*_${{ matrix.job.debian_platform }}.deb
docker:
name: Push Docker Image
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", docker_platform: "linux/amd64" }
- { platform: "arm64", goos: "linux", docker_platform: "linux/arm64" }
- { platform: "armv7l", goos: "linux", docker_platform: "linux/arm/v7" }
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if SKIP_DOCKER_HUB is false
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=test" >> $GITHUB_ENV # Default to 'test' if not a tag
fi
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: ./
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
#
docker-manifest:
name: Push Docker Manifest
needs: docker
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=test" >> $GITHUB_ENV # Default to 'test' if not a tag
fi
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if Docker Hub push is enabled
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
- name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@v0.2.3
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
amend: true

9
.gitignore vendored
View File

@@ -1,9 +1,8 @@
.idea
runtime/*
!runtime/cache/.gitignore
!runtime
!runtime/cache/.gitkeep
go.sum
resources/*
!resources/public/upload/.gitignore
!resources/web
resources/admin
release
data
data/rustdeskapi.db

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM alpine
ARG BUILDARCH
WORKDIR /app
RUN apk add --no-cache tzdata
COPY ./${BUILDARCH}/release /app/
VOLUME /app/data
EXPOSE 21114
CMD ["./apimain"]

94
Dockerfile.dev Normal file
View File

@@ -0,0 +1,94 @@
# Use build arguments for Go version and architecture
ARG GO_VERSION=1.22
ARG BUILDARCH=amd64
# Stage 1: Builder Stage
# FROM golang:${GO_VERSION}-alpine AS builder
FROM crazymax/xgo:${GO_VERSION} AS builder-backend
# Set up working directory
WORKDIR /app
# Step 1: Copy the source code
COPY . .
# use --mount=type=cache,target=/go/pkg/mod to cache the go mod
# Step 2: Download dependencies
RUN --mount=type=cache,target=/go/pkg/mod \
go mod tidy && go mod download && go install github.com/swaggo/swag/cmd/swag@latest
# Step 3: Run swag build script
RUN --mount=type=cache,target=/go/pkg/mod \
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin && \
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
# Step 4: Build the Go application with CGO enabled and specified ldflags
RUN --mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=1 GOOS=linux go build -a \
-ldflags "-s -w --extldflags '-static -fpic'" \
-installsuffix cgo -o release/apimain cmd/apimain.go
# Stage 2: Frontend Build Stage (builder2)
FROM node:18-alpine AS builder-admin-frontend
# Set working directory
WORKDIR /frontend
ARG COUNTRY
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache git
ARG FRONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
ARG FRONTEND_GIT_BRANCH=master
# Clone the frontend repository
RUN git clone -b $FRONTEND_GIT_BRANCH $FRONTEND_GIT_REPO .
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating NPM_CONFIG_REGISTRY"; \
export NPM_CONFIG_REGISTRY="https://mirrors.huaweicloud.com/repository/npm/"; \
fi && \
npm install && npm run build
# Stage 2: Final Image
FROM alpine:latest
# Set up working directory
WORKDIR /app
# Install necessary runtime dependencies
# Install required tools without caching index to minimize image size
ARG COUNTRY
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache tzdata file
# Copy the built application and resources from the builder stage
COPY --from=builder-backend /app/release /app/
COPY --from=builder-backend /app/conf /app/conf/
COPY --from=builder-backend /app/resources /app/resources/
COPY --from=builder-backend /app/docs /app/docs/
# Copy frontend build from builder2 stage
COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/
# Ensure the binary is correctly built and linked
RUN file /app/apimain && \
mkdir -p /app/data && \
mkdir -p /app/runtime
# Set up a volume for persistent data
VOLUME /app/data
# Expose the necessary port
EXPOSE 21114
# Define the command to run the application
CMD ["./apimain"]

38
Dockerfile_full_s6 Normal file
View File

@@ -0,0 +1,38 @@
FROM rustdesk/rustdesk-server-s6:latest AS server
FROM alpine
ARG BUILDARCH
WORKDIR /app
RUN apk add --no-cache tzdata
COPY ./${BUILDARCH}/release /app/
COPY --from=server /init /init
COPY --from=server /etc/s6-overlay /etc/s6-overlay
COPY --from=server /package /package
COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh
COPY --from=server /usr/bin/hbbr /usr/bin/hbbr
COPY --from=server /usr/bin/hbbs /usr/bin/hbbs
COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils
COPY --from=server /command /command
RUN \
mkdir -p /etc/s6-overlay/s6-rc.d/api && \
echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \
echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \
echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \
echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \
ln -s /run /var/run
ENV RELAY=relay.example.com
ENV ENCRYPTED_ONLY=0
VOLUME /data
VOLUME /app/data
EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119
ENTRYPOINT ["/init"]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-present Lejianwen and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

319
README.md
View File

@@ -1,89 +1,237 @@
# RustDesk API
本项目使用 Go 实现了 RustDesk 的 API并包含了 Web UI 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
[English Doc](README_EN.md)
## 使用前准备
本项目使用 Go 实现了 RustDesk 的 API并包含了 Web Admin 和 Web 客户端。
### Rustdesk
1. PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
2. server端必须指定key不能用自带的生成的key,否则可能链接不上或者超时
<div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/>
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div>
```bash
hbbs -r <relay-server-ip[:port]> -k 123456789
hbbr -k 123456789
```
## 搭配[lejianwen/rustdesk-server]使用更佳。
> [lejianwen/rustdesk-server]fork自RustDesk Server官方仓库
> 1. 解决了使用API链接超时问题
> 2. 可以强制登录后才能发起链接
> 3. 支持客户端websocket
# 特性
- PC端API
- 个人版API
- 登录
- 地址簿
- 群组
- 授权登录
- 支持`github`, `google``OIDC` 登录,
- 支持`web后台`授权登录
- 支持`LDAP`(AD和OpenLDAP已测试), 如果API Server配置了LDAP
- i18n
- Web Admin
- 用户管理
- 设备管理
- 地址簿管理
- 标签管理
- 群组管理
- Oauth 管理
- 配置LDAP, 配置文件或者环境变量
- 登录日志
- 链接日志
- 文件传输日志
- 快速使用web client
- i18n
- 通过 web client 分享给游客
- server控制(一些官方的简单的指令 [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client
- 自动获取API server
- 自动获取ID服务器和KEY
- 自动获取地址簿
- 游客通过临时分享链接直接远程到设备
- v2 Preview
- CLI
- 重置管理员密码
## 功能
### **API 服务**: 基本实现了PC端基础的接口。
![pc_ab](docs/pc_ab.png)
![pc_gr](docs/pc_gr.png)
### API 服务
基本实现了PC端基础的接口。支持Personal版本接口可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用
### **Web UI**: 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。
<table>
<tr>
<td width="50%" align="center" colspan="2"><b>登录</b></td>
</tr>
<tr>
<td width="50%" align="center" colspan="2"><img src="docs/pc_login.png"></td>
</tr>
<tr>
<td width="50%" align="center"><b>地址簿</b></td>
<td width="50%" align="center"><b>群组</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="docs/pc_ab.png"></td>
<td width="50%" align="center"><img src="docs/pc_gr.png"></td>
</tr>
</table>
***前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
### Web Admin:
***初次安装管理员为用户名密码为admin admin请即时更改密码***
* 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)
* 后台访问地址是`http://<your server>[:port]/_admin/`
* 初次安装管理员为用户名为`admin`,密码将在控制台打印,可以通过[命令行](#CLI)更改密码
![img.png](./docs/init_admin_pwd.png)
1. 管理员界面
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_user.png)
3. 更改密码在右上角
![web_resetpwd](docs/web_resetpwd.png)
![web_user](docs/web_admin_user.png)
3. 每个用户可以多个地址簿,也可以将地址簿共享给其他用户
4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组``普通组`
![web_admin_gr](docs/web_admin_gr.png)
5. 可以直接打开webclient方便使用也可以分享给游客游客可以直接通过webclient远程到设备
6. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
- 对于`Google``Github`, `Issuer``Scopes`不需要填写.
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email``preferred_username`
- `github oauth app``Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oidc/callback`
,比如`http://127.0.0.1:21114/api/oidc/callback`
7. 登录日志
8. 链接日志
9. 文件传输日志
10. server控制
### **Web 客户端**:
- `简易模式`,已经界面化了一些简单的指令,可以直接在后台执行
![rustdesk_command_simple](./docs/rustdesk_command_simple.png)
- `高级模式`,直接在后台执行指令
* 可以官方指令
* 可以添加自定义指令
* 可以执行自定义指令
11. **LDAP 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败返回本地用户
### Web Client:
1. 如果已经登录了后台web client将自动直接登录
2. 如果没登录后台点击右上角登录即可api server已经自动配置好了
3. 登录后台后会将地址簿自动保存到web client中方便使用
![webclient_conf](docs/webclient_conf.png)
3. 登录后会自动同步ID服务器和KEY
4. 登录后,会将地址簿自动保存到web client中,方便使用
5. 现已支持`v2 Preview`,访问路径是`/webclient2`
![webclientv2](./docs/webclientv2.png)
6. `v2 preview` 部署,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
### **自动化文档**: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
1. 后台文档 <youer server>/admin/swagger/index.html
2. PC端文档 <youer server>/swagger/index.html
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
1. 后台文档 `<youer server[:port]>/admin/swagger/index.html`
2. PC端文档 `<youer server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png)
### CLI
```bash
# 查看帮助
./apimain -h
```
#### 重置管理员密码
```bash
./apimain reset-admin-pwd <pwd>
```
## 安装与运行
### 相关配置
* 参考`conf/config.yaml`配置文件,修改相关配置。如果`gorm.type``sqlite`则不需要配置mysql相关配置。
* [配置文件](./conf/config.yaml)
* 参考`conf/config.yaml`配置文件,修改相关配置。
* 如果`gorm.type``sqlite`则不需要配置mysql相关配置。
* 语言如果不设置默认为`zh-CN`
```yaml
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
```
### 环境变量
环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API`
下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。
### 安装步骤
| 变量名 | 说明 | 示例 |
|--------------------------------------------------------|--------------------------------------------------------------------------------|------------------------------|
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长 | `168h` |
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | 是否禁用密码登录; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_REGISTER_STATUS | 注册用户默认状态; 1 启用2 禁用, 默认 1 | `1` |
| RUSTDESK_API_APP_CAPTCHA_THRESHOLD | 验证码触发次数; -1 不启用, 0 一直启用, >0 登录错误次数后启用 ;默认 `3` | `3` |
| RUSTDESK_API_APP_BAN_THRESHOLD | 封禁IP触发次数; 0 不启用, >0 登录错误次数后封禁IP; 默认 `0` | `0` |
| -----ADMIN配置----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----GORM配置----- | ---------- | --------------------------- |
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
| RUSTDESK_API_MYSQL_TLS | 是否启用TLS, 可选值: `true`, `false`, `skip-verify`, `custom` | `false` |
| -----RUSTDESK配置----- | ---------- | ---------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT<br/>_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` |
| RUSTDESK_API_RUSTDESK_WS_HOST | 自定义Websocket Host | `wss://192.168.1.123:1234` |
| ----PROXY配置----- | ---------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| ----JWT配置---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | `168h` |
### 运行
#### docker运行
1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置
```bash
docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e TZ=Asia/Shanghai \
-e RUSTDESK_API_LANG=zh-CN \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=<key> \
lejianwen/rustdesk-api
```
2. 使用`docker compose`,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
#### 下载release直接运行
[下载地址](https://github.com/lejianwen/rustdesk-api/releases)
#### 源码安装
1. 克隆仓库
@@ -91,15 +239,19 @@ rustdesk:
git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api
```
2. 安装依赖
```bash
go mod tidy
#安装swag如果不需要生成文档可以不安装
go install github.com/swaggo/swag/cmd/swag@latest
```
3. 编译后台前端,前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)中
```bash
cd resources
mkdir -p admin
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
@@ -113,6 +265,75 @@ rustdesk:
#或者使用generate_api.go生成api并运行
go generate generate_api.go
```
> 注意:使用 `go run` 或编译后的二进制时,当前目录下必须存在 `conf` 和 `resources`
> 目录。如果在其他目录运行,可通过 `-c` 和环境变量
> `RUSTDESK_API_GIN_RESOURCES_PATH` 指定绝对路径,例如:
> ```bash
> RUSTDESK_API_GIN_RESOURCES_PATH=/opt/rustdesk-api/resources ./apimain -c /opt/rustdesk-api/conf/config.yaml
> ```
5. 编译,如果想自己编译,先cd到项目根目录然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
目录下生成对应的可执行文件。
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
#### 使用`lejianwen/server-s6`镜像运行
- 已解决链接超时问题
- 可以强制登录后才能发起链接
- github https://github.com/lejianwen/rustdesk-server
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk:
ports:
- 21114:21114
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: lejianwen/rustdesk-server-s6:latest
environment:
- RELAY=<relay_server[:port]>
- ENCRYPTED_ONLY=1
- MUST_LOGIN=N
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
## 其他
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [链接超时问题](https://github.com/lejianwen/rustdesk-api/issues/92)
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
## 鸣谢
感谢所有做过贡献的人!
<a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a>
## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢!
[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server

338
README_EN.md Normal file
View File

@@ -0,0 +1,338 @@
# RustDesk API
This project implements the RustDesk API using Go, and includes both a web UI and web client. RustDesk is a remote
desktop software that provides self-hosted solutions.
<div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/>
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div>
## Better used with [lejianwen/rustdesk-server].
> [lejianwen/rustdesk-server] is a fork of the official RustDesk Server repository.
> 1. Solves the API connection timeout issue.
> 2. Can enforce login before initiating a connection.
> 3. Supports client websocket.
# Features
- PC API
- Personal API
- Login
- Address Book
- Groups
- Authorized login,
- supports `GitHub`, `Google` and `OIDC` login,
- supports `web admin` authorized login,
- supports LDAP(test AD and openladp) if API Server config
- i18n
- Web Admin
- User Management
- Device Management
- Address Book Management
- Tag Management
- Group Management
- OAuth Management
- LDAP Config by config file or ENV
- Login Logs
- Connection Logs
- File Transfer Logs
- Quick access to web client
- i18n
- Share to guest by web client
- Server control (some simple official commands [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client
- Automatically obtain API server
- Automatically obtain ID server and KEY
- Automatically obtain address book
- Visitors are remotely to the device via a temporary sharing link
- CLI
- Reset admin password
## Overview
### API Service
Basic implementation of the PC client's primary interfaces.Supports the Personal version api, which can be enabled by configuring the `rustdesk.personal` file or the `RUSTDESK_API_RUSTDESK_PERSONAL` environment variable.
<table>
<tr>
<td width="50%" align="center" colspan="2"><b>Login</b></td>
</tr>
<tr>
<td width="50%" align="center" colspan="2"><img src="docs/en_img/pc_login.png"></td>
</tr>
<tr>
<td width="50%" align="center"><b>Address Book</b></td>
<td width="50%" align="center"><b>Groups</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="docs/en_img/pc_ab.png"></td>
<td width="50%" align="center"><img src="docs/en_img/pc_gr.png"></td>
</tr>
</table>
### Web Admin
* The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and
displaying data.Frontend code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)
* Admin panel URL: `http://<your server[:port]>/_admin/`
* For the initial installation, the admin username is `admin`, and the password will be printed in the console. You can change the password via the [command line](#CLI).
![img.png](./docs/init_admin_pwd.png)
1. Admin interface:
![web_admin](docs/en_img/web_admin.png)
2. Regular user interface:
![web_user](docs/en_img/web_admin_user.png)
3. Each user can have multiple address books, which can also be shared with other users.
4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
5. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client.
6. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in
the admin panel.
- For `Google` and `Github`, you don't need to fill the `Issuer` and `Scpoes`
- For `OIDC`, you must set the `Issuer`. And `Scopes` is optional which default is `openid,email,profile`, please make sure this `Oauth App` can access `sub`, `email` and `preferred_username`
- Create a `GitHub OAuth App`
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oidc/callback`,
e.g., `http://127.0.0.1:21114/api/oidc/callback`.
7. Login logs
8. Connection logs
9. File transfer logs
10. Server control
- `Simple mode`, some simple commands have been GUI-ized and can be executed directly in the backend
![rustdesk_command_simple](./docs/en_img/rustdesk_command_simple.png)
- `Advanced mode`, commands can be executed directly in the backend
* Official commands can be used
* Custom commands can be added
* Custom commands can be executed
11. **LDAP Support**, When you setup the LDAP(test for OpenLDAP and AD), you can login with the LDAP's user. https://github.com/lejianwen/rustdesk-api/issues/114 , if LDAP fail fallback local user
### Web Client:
1. If you're already logged into the admin panel, the web client will log in automatically.
2. If you're not logged in, simply click the login button in the top right corner, and the API server will be
pre-configured.
3. After logging in, the ID server and key will be automatically synced.
4. The address book will also be automatically saved to the web client for convenient use.
5. Now supports `v2 Preview`, accessible at `/webclient2`
![webclientv2](./docs/webclientv2.png)
6. `v2 preview` deployment, [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API.
1. Admin panel docs: `<your server[:port]>/admin/swagger/index.html`
2. PC client docs: `<your server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png)
### CLI
```bash
# help
./apimain -h
```
#### Reset admin password
```bash
./apimain reset-admin-pwd <pwd>
```
## Installation and Setup
### Configuration
* [Config File](./conf/config.yaml)
* Modify the configuration in `conf/config.yaml`.
* If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required.
* Language support: `en` and `zh-CN` are supported. The default is `zh-CN`.
### Environment Variables
The environment variables correspond one-to-one with the configurations in the `conf/config.yaml` file. The prefix for variable names is `RUSTDESK_API`.
The table below does not list all configurations. Please refer to the configurations in `conf/config.yaml`.
| Variable Name | Description | Example |
|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration | `168h` |
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | disable password login | `false` |
| RUSTDESK_API_APP_REGISTER_STATUS | register user default status ; 1 enabled , 2 disabled ; default 1 | `1` |
| RUSTDESK_API_APP_CAPTCHA_THRESHOLD | captcha threshold; -1 disabled, 0 always enable, >0 threshold ;default `3` | `3` |
| RUSTDESK_API_APP_BAN_THRESHOLD | ban ip threshold; 0 disabled, >0 threshold ; default `0` | `0` |
| ----- ADMIN Configuration----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| RUSTDESK_API_MYSQL_TLS | Whether to enable TLS, optional values: `true`, `false`, `skip-verify`, `custom` | `false` |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK<br/>_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| RUSTDESK_API_RUSTDESK_WS_HOST | Custom Websocket Host | `wss://192.168.1.123:1234` |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| ----JWT---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | Custom JWT KEY, if empty JWT is not enabled.<br/>If `MUST_LOGIN` from `lejianwen/rustdesk-server` is not used, it is recommended to leave it empty. | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | `168h` |
### Installation Steps
#### Running via Docker
1. Run directly with Docker. Configuration can be modified by mounting the config file `/app/conf/config.yaml`, or by
using environment variables to override settings.
```bash
docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e RUSTDESK_API_LANG=en \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=abc123456 \
lejianwen/rustdesk-api
```
2. Using `docker-compose`,look [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
#### Running from Release
Download the release from [release](https://github.com/lejianwen/rustdesk-api/releases).
#### Source Installation
1. Clone the repository:
```bash
git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api
```
2. Install dependencies:
```bash
go mod tidy
# Install Swag if you need to generate documentation; otherwise, you can skip this step
go install github.com/swaggo/swag/cmd/swag@latest
```
3. Build the admin front-end (the front-end code is
in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash
cd resources
mkdir -p admin
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
npm run build
cp -ar dist/* ../admin/
```
4. Run:
```bash
# Run directly
go run cmd/apimain.go
# Or generate and run the API using generate_api.go
go generate generate_api.go
```
> **Note:** When using `go run` or the compiled binary, the `conf` and `resources`
> directories must exist relative to the current working directory. If you run
> the program from another location, specify absolute paths with `-c` and the
> `RUSTDESK_API_GIN_RESOURCES_PATH` environment variable. Example:
> ```bash
> RUSTDESK_API_GIN_RESOURCES_PATH=/opt/rustdesk-api/resources ./apimain -c /opt/rustdesk-api/conf/config.yaml
> ```
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After
compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables
directly.
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please
change the password promptly.
#### Running with my forked server-s6 image
- Connection timeout issue resolved
- Can enforce login before initiating a connection
- github https://github.com/lejianwen/rustdesk-server
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk:
ports:
- 21114:21114
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: lejianwen/rustdesk-server-s6:latest
environment:
- RELAY=<relay_server[:port]>
- ENCRYPTED_ONLY=1
- MUST_LOGIN=N
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
## Others
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [Connection Timeout](https://github.com/lejianwen/rustdesk-api/issues/92)
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
## Acknowledgements
Thanks to everyone who contributed!
<a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a>
## Thanks for your support! If you find this project useful, please give it a ⭐️. Thank you!
[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server

View File

@@ -1,4 +1,5 @@
@echo off
rmdir /s /q release
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1

41
build.sh Normal file → Executable file
View File

@@ -1,15 +1,46 @@
#!/bin/sh
set -e
# Automatically get the current environment's GOARCH; if not defined, use the detected system architecture
GOARCH=${GOARCH:-$(go env GOARCH)}
DOCS="true"
# Safely remove the old release directory
rm -rf release
# Set Go environment variables
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1
go env -w GOOS=linux
go env -w GOARCH=amd64
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
go env -w GOARCH=${GOARCH}
# Generate Swagger documentation if DOCS is not empty
if [ -n "${DOCS}" ]; then
# Check if swag is installed
if ! command -v swag &> /dev/null; then
echo "swag command not found. Please install it using:"
echo "go install github.com/swaggo/swag/cmd/swag@latest"
echo "Skipping Swagger documentation generation due to missing swag tool."
else
echo "Generating Swagger documentation..."
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
fi
else
echo "Skipping Swagger documentation generation due to DOCS is empty."
fi
# Compile the Go code and output it to the release directory
go build -o release/apimain cmd/apimain.go
# Copy resource files to the release directory
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir release/data
mkdir release/runtime
# Create necessary directory structures
mkdir -p release/data
mkdir -p release/runtime
echo "Build and setup completed successfully."

View File

@@ -1,26 +1,30 @@
package main
import (
"Gwen/config"
"Gwen/global"
"Gwen/http"
"Gwen/lib/cache"
"Gwen/lib/lock"
"Gwen/lib/logger"
"Gwen/lib/orm"
"Gwen/lib/upload"
"Gwen/model"
"Gwen/service"
"fmt"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh_Hans_CN"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
"os"
"strconv"
"time"
"github.com/go-redis/redis/v8"
"reflect"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http"
"github.com/lejianwen/rustdesk-api/v2/lib/cache"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/logger"
"github.com/lejianwen/rustdesk-api/v2/lib/orm"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/spf13/cobra"
)
const DatabaseVersion = 265
// @title 管理系统API
// @version 1.0
// @description 接口
@@ -31,11 +35,84 @@ import (
// @securitydefinitions.apikey BearerAuth
// @in header
// @name Authorization
var rootCmd = &cobra.Command{
Use: "apimain",
Short: "RUSTDESK API SERVER",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
InitGlobal()
},
Run: func(cmd *cobra.Command, args []string) {
global.Logger.Info("API SERVER START")
http.ApiInit()
},
}
var resetPwdCmd = &cobra.Command{
Use: "reset-admin-pwd [pwd]",
Example: "reset-admin-pwd 123456",
Short: "Reset Admin Password",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
pwd := args[0]
admin := service.AllService.UserService.InfoById(1)
if admin.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err := service.AllService.UserService.UpdatePassword(admin, pwd)
if err != nil {
global.Logger.Error("reset password fail! ", err)
return
}
global.Logger.Info("reset password success! ")
},
}
var resetUserPwdCmd = &cobra.Command{
Use: "reset-pwd [userId] [pwd]",
Example: "reset-pwd 2 123456",
Short: "Reset User Password",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
userId := args[0]
pwd := args[1]
uid, err := strconv.Atoi(userId)
if err != nil {
global.Logger.Warn("userId must be int!")
return
}
if uid <= 0 {
global.Logger.Warn("userId must be greater than 0! ")
return
}
u := service.AllService.UserService.InfoById(uint(uid))
if u.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err = service.AllService.UserService.UpdatePassword(u, pwd)
if err != nil {
global.Logger.Warn("reset password fail! ", err)
return
}
global.Logger.Info("reset password success!")
},
}
func init() {
rootCmd.PersistentFlags().StringVarP(&global.ConfigPath, "config", "c", "./conf/config.yaml", "choose config file")
rootCmd.AddCommand(resetPwdCmd, resetUserPwdCmd)
}
func main() {
if err := rootCmd.Execute(); err != nil {
global.Logger.Error(err)
os.Exit(1)
}
}
func InitGlobal() {
//配置解析
global.Viper = config.Init(&global.Config, func() {
fmt.Println(global.Config)
})
global.Viper = config.Init(&global.Config, global.ConfigPath)
//日志
global.Logger = logger.New(&logger.Config{
@@ -44,6 +121,8 @@ func main() {
ReportCaller: global.Config.Logger.ReportCaller,
})
global.InitI18n()
//redis
global.Redis = redis.NewClient(&redis.Options{
Addr: global.Config.Redis.Addr,
@@ -65,23 +144,45 @@ func main() {
}
//gorm
if global.Config.Gorm.Type == config.TypeMysql {
dns := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/" + global.Config.Mysql.Dbname + "?charset=utf8mb4&parseTime=True&loc=Local"
dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
global.Config.Mysql.Username,
global.Config.Mysql.Password,
global.Config.Mysql.Addr,
global.Config.Mysql.Dbname,
global.Config.Mysql.Tls,
)
global.DB = orm.NewMysql(&orm.MysqlConfig{
Dns: dns,
Dsn: dsn,
MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns,
})
}, global.Logger)
} else if global.Config.Gorm.Type == config.TypePostgresql {
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",
global.Config.Postgresql.Host,
global.Config.Postgresql.Port,
global.Config.Postgresql.User,
global.Config.Postgresql.Password,
global.Config.Postgresql.Dbname,
global.Config.Postgresql.Sslmode,
global.Config.Postgresql.TimeZone,
)
global.DB = orm.NewPostgresql(&orm.PostgresqlConfig{
Dsn: dsn,
MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns,
}, global.Logger)
} else {
//sqlite
global.DB = orm.NewSqlite(&orm.SqliteConfig{
MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns,
})
}, global.Logger)
}
DatabaseAutoUpdate()
//validator
ApiInitValidator()
global.ApiInitValidator()
//oss
global.Oss = &upload.Oss{
@@ -95,101 +196,60 @@ func main() {
//jwt
//fmt.Println(global.Config.Jwt.PrivateKey)
//global.Jwt = jwt.NewJwt(global.Config.Jwt.PrivateKey, global.Config.Jwt.ExpireDuration*time.Second)
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration)
//locker
global.Lock = lock.NewLocal()
//gin
http.ApiInit()
//service
service.New(&global.Config, global.DB, global.Logger, global.Jwt, global.Lock)
}
func ApiInitValidator() {
validate := validator.New()
enT := en.New()
cn := zh_Hans_CN.New()
uni := ut.New(enT, cn)
trans, _ := uni.GetTranslator("cn")
err := zh_translations.RegisterDefaultTranslations(validate, trans)
if err != nil {
//退出
panic(err)
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
label := field.Tag.Get("label")
if label == "" {
return field.Name
}
return label
global.LoginLimiter = utils.NewLoginLimiter(utils.SecurityPolicy{
CaptchaThreshold: global.Config.App.CaptchaThreshold,
BanThreshold: global.Config.App.BanThreshold,
AttemptsWindow: 10 * time.Minute,
BanDuration: 30 * time.Minute,
})
global.Validator.Validate = validate
global.Validator.VTrans = trans
global.Validator.ValidStruct = func(i interface{}) []string {
err := global.Validator.Validate.Struct(i)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(global.Validator.VTrans))
}
}
return errList
}
global.Validator.ValidVar = func(field interface{}, tag string) []string {
err := global.Validator.Validate.Var(field, tag)
fmt.Println(err)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(global.Validator.VTrans))
}
}
return errList
}
global.LoginLimiter.RegisterProvider(utils.B64StringCaptchaProvider{})
DatabaseAutoUpdate()
}
func DatabaseAutoUpdate() {
version := 100
version := DatabaseVersion
db := global.DB
if global.Config.Gorm.Type == config.TypeMysql {
//检查存不存在数据库,不存在则创建
dbName := db.Migrator().CurrentDatabase()
fmt.Println("dbName", dbName)
if dbName == "" {
dbName = global.Config.Mysql.Dbname
// 移除 DSN 中的数据库名称,以便初始连接时不指定数据库
dsnWithoutDB := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/?charset=utf8mb4&parseTime=True&loc=Local"
dsnWithoutDB := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
global.Config.Mysql.Username,
global.Config.Mysql.Password,
global.Config.Mysql.Addr,
"",
)
//新链接
dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{
Dns: dsnWithoutDB,
})
Dsn: dsnWithoutDB,
}, global.Logger)
// 获取底层的 *sql.DB 对象,并确保在程序退出时关闭连接
sqlDBWithoutDB, err := dbWithoutDB.DB()
if err != nil {
fmt.Printf("获取底层 *sql.DB 对象失败: %v\n", err)
global.Logger.Errorf("获取底层 *sql.DB 对象失败: %v", err)
return
}
defer func() {
if err := sqlDBWithoutDB.Close(); err != nil {
fmt.Printf("关闭连接失败: %v\n", err)
global.Logger.Errorf("关闭连接失败: %v", err)
}
}()
err = dbWithoutDB.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARSET utf8mb4").Error
if err != nil {
fmt.Println(err)
global.Logger.Error(err)
return
}
}
@@ -204,11 +264,30 @@ func DatabaseAutoUpdate() {
if v.Version < uint(version) {
Migrate(uint(version))
}
// 245迁移
if v.Version < 245 {
//oauths 表的 oauth_type 字段设置为 op同样的值
db.Exec("update oauths set oauth_type = op")
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google'")
db.Exec("update user_thirds set oauth_type = third_type, op = third_type")
//通过email迁移旧的google授权
uts := make([]model.UserThird, 0)
db.Where("oauth_type = ?", "google").Find(&uts)
for _, ut := range uts {
if ut.UserId > 0 {
db.Model(&model.User{}).Where("id = ?", ut.UserId).Update("email", ut.OpenId)
}
}
}
if v.Version < 246 {
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google' and issuer is null")
}
}
}
func Migrate(version uint) {
fmt.Println("migrating....", version)
global.Logger.Info("Migrating....", version)
err := global.DB.AutoMigrate(
&model.Version{},
&model.User{},
@@ -217,22 +296,40 @@ func Migrate(version uint) {
&model.AddressBook{},
&model.Peer{},
&model.Group{},
&model.UserThird{},
&model.Oauth{},
&model.LoginLog{},
&model.ShareRecord{},
&model.AuditConn{},
&model.AuditFile{},
&model.AddressBookCollection{},
&model.AddressBookCollectionRule{},
&model.ServerCmd{},
&model.DeviceGroup{},
)
if err != nil {
fmt.Println("migrate err :=>", err)
global.Logger.Error("migrate err :=>", err)
}
global.DB.Create(&model.Version{Version: version})
//如果是初次则创建一个默认用户
var vc int64
global.DB.Model(&model.Version{}).Count(&vc)
if vc == 1 {
localizer := global.Localizer("")
defaultGroup, _ := localizer.LocalizeMessage(&i18n.Message{
ID: "DefaultGroup",
})
group := &model.Group{
Name: "默认组",
Name: defaultGroup,
Type: model.GroupTypeDefault,
}
service.AllService.GroupService.Create(group)
shareGroup, _ := localizer.LocalizeMessage(&i18n.Message{
ID: "ShareGroup",
})
groupShare := &model.Group{
Name: "共享组",
Name: shareGroup,
Type: model.GroupTypeShare,
}
service.AllService.GroupService.Create(groupShare)
@@ -240,12 +337,20 @@ func Migrate(version uint) {
is_admin := true
admin := &model.User{
Username: "admin",
Nickname: "管理员",
Nickname: "Admin",
Status: model.COMMON_STATUS_ENABLE,
IsAdmin: &is_admin,
GroupId: 1,
}
admin.Password = service.AllService.UserService.EncryptPassword("admin")
// 生成随机密码
pwd := utils.RandomString(8)
global.Logger.Info("Admin Password Is: ", pwd)
var err error
admin.Password, err = utils.EncryptPassword(pwd)
if err != nil {
global.Logger.Fatalf("failed to generate admin password: %v", err)
}
global.DB.Create(admin)
}

1
conf/admin/hello.html Normal file
View File

@@ -0,0 +1 @@
### 👏👏👏 你好 ***{{username}}*** 欢迎使用 [RustDesk API](https://github.com/lejianwen/rustdesk-api)

View File

@@ -1,42 +1,84 @@
lang: "zh-CN"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
register-status: 1 # 注册用户默认状态 1:启用 2:禁用
captcha-threshold: 3 # <0:disabled, 0 always, >0:enabled
ban-threshold: 0 # 0:disabled, >0:enabled
show-swagger: 0 # 1:启用 0:禁用
token-expire: 168h
web-sso: true #web auth sso
disable-pwd-login: false #禁用密码登录
admin:
title: "RustDesk API Admin"
hello-file: "./conf/admin/hello.html" #优先使用file
hello: ""
# ID Server and Relay Server ports https://github.com/lejianwen/rustdesk-api/issues/257
id-server-port: 21116 # ID Server port (for server cmd)
relay-server-port: 21117 # ID Server port (for server cmd)
gin:
api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test
resources-path: 'resources' #对外静态文件目录
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk2"
rustdesk:
id-server: "124.220.134.240:21116"
relay-server: "124.220.134.240:21117"
api-server: "http://127.0.0.1:21114"
key: "ljw19891989"
redis:
addr: "127.0.0.1:6379"
username: ""
password: ""
db: 0
addr: ""
dbname: ""
tls: "false" # true / false / skip-verify / custom
postgresql:
host: "127.0.0.1"
port: "5432"
user: ""
password: ""
dbname: "postgres"
sslmode: "disable" # disable, require, verify-ca, verify-full
time-zone: "Asia/Shanghai" # Time zone for PostgreSQL connection
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://127.0.0.1:21114"
key: ""
key-file: "/data/id_ed25519.pub"
personal: 1
webclient-magic-queryonline: 0
ws-host: "" #eg: wss://192.168.1.3:4443
logger:
path: "./runtime/log.txt"
level: "error" #trace,debug,info,warn,error,fatal
level: "info" #trace,debug,info,warn,error,fatal
report-caller: true
cache:
type: "file"
file-dir: "./runtime/cache"
redis-addr: "127.0.0.1:6379"
redis-pwd: "ljw19891989"
redis-db: 0
oss:
access-key-id: ""
access-key-secret: ""
host: ""
callback-url: ""
expire-time: 30
max-byte: 10240
proxy:
enable: false
host: "http://127.0.0.1:1080"
jwt:
private-key: "./conf/jwt_pri.pem"
expire-duration: 360000
key: ""
expire-duration: 168h
ldap:
enable: false
url: "ldap://ldap.example.com:389"
tls-ca-file: ""
tls-verify: false
base-dn: "dc=example,dc=com"
bind-dn: "cn=admin,dc=example,dc=com"
bind-password: "password"
user:
base-dn: "ou=users,dc=example,dc=com"
enable-attr: "" #The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
enable-attr-value: "" # The value of the enable attribute when the user is enabled. If you are using AD, just set random value, it will be ignored.
filter: "(cn=*)"
username: "uid" # The attribute name of the user for usernamem if you are using AD, it should be "sAMAccountName"
email: "mail"
first-name: "givenName"
last-name: "sn"
sync: false # If true, the user will be synchronized to the database when the user logs in. If false, the user will be synchronized to the database when the user be created.
admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin.
allow-group: "cn=users,dc=example,dc=com" # The group name of the users group, if the user is in this group, the user will be an login.

View File

@@ -1,10 +1,10 @@
package config
import (
"flag"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"strings"
"time"
)
const (
@@ -13,42 +13,93 @@ const (
DefaultConfig = "conf/config.yaml"
)
type App struct {
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
RegisterStatus int `mapstructure:"register-status"`
ShowSwagger int `mapstructure:"show-swagger"`
TokenExpire time.Duration `mapstructure:"token-expire"`
WebSso bool `mapstructure:"web-sso"`
DisablePwdLogin bool `mapstructure:"disable-pwd-login"`
CaptchaThreshold int `mapstructure:"captcha-threshold"`
BanThreshold int `mapstructure:"ban-threshold"`
}
type Admin struct {
Title string `mapstructure:"title"`
Hello string `mapstructure:"hello"`
HelloFile string `mapstructure:"hello-file"`
IdServerPort int `mapstructure:"id-server-port"`
RelayServerPort int `mapstructure:"relay-server-port"`
}
type Config struct {
Gorm Gorm
Mysql Mysql
Gin Gin
Logger Logger
Redis Redis
Cache Cache
Oss Oss
Jwt Jwt
Rustdesk Rustdesk
Lang string `mapstructure:"lang"`
App App
Admin Admin
Gorm Gorm
Mysql Mysql
Postgresql Postgresql
Gin Gin
Logger Logger
Redis Redis
Cache Cache
Oss Oss
Jwt Jwt
Rustdesk Rustdesk
Proxy Proxy
Ldap Ldap
}
func (a *Admin) Init() {
if a.IdServerPort == 0 {
a.IdServerPort = DefaultIdServerPort
}
if a.RelayServerPort == 0 {
a.RelayServerPort = DefaultRelayServerPort
}
}
// Init 初始化配置
func Init(rowVal interface{}, cb func()) *viper.Viper {
var config string
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config == "" { // 优先级: 命令行 > 默认值
config = DefaultConfig
func Init(rowVal *Config, path string) *viper.Viper {
if path == "" {
path = DefaultConfig
}
v := viper.New()
v.SetConfigFile(config)
v := viper.GetViper()
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
v.SetEnvPrefix("RUSTDESK_API")
v.SetConfigFile(path)
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
v.WatchConfig()
v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听
fmt.Println("config file changed:", e.Name)
if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2)
}
cb()
})
/*
v.WatchConfig()
//监听配置修改没什么必要
v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听
fmt.Println("config file changed:", e.Name)
if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2)
}
rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort()
})
*/
if err := v.Unmarshal(rowVal); err != nil {
panic(fmt.Errorf("Fatal error config: %s \n", err))
}
rowVal.Rustdesk.LoadKeyFile()
rowVal.Admin.Init()
return v
}
// ReadEnv 读取环境变量
func ReadEnv(rowVal interface{}) *viper.Viper {
v := viper.New()
v.AutomaticEnv()
if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err)
}

View File

@@ -5,4 +5,5 @@ type Gin struct {
AdminAddr string `mapstructure:"admin-addr"`
Mode string
ResourcesPath string `mapstructure:"resources-path"`
TrustProxy string `mapstructure:"trust-proxy"`
}

View File

@@ -1,8 +1,9 @@
package config
const (
TypeSqlite = "sqlite"
TypeMysql = "mysql"
TypeSqlite = "sqlite"
TypeMysql = "mysql"
TypePostgresql = "postgresql"
)
type Gorm struct {
@@ -16,4 +17,15 @@ type Mysql struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"`
Tls string `mapstructure:"tls"` // true / false / skip-verify / custom
}
type Postgresql struct {
Host string `mapstructure:"host"`
Port string `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"`
Sslmode string `mapstructure:"sslmode"` // "disable", "require", "verify-ca", "verify-full"
TimeZone string `mapstructure:"time-zone"` // e.g., "Asia/Shanghai"
}

View File

@@ -3,6 +3,6 @@ package config
import "time"
type Jwt struct {
PrivateKey string `mapstructure:"private-key"`
Key string `mapstructure:"key"`
ExpireDuration time.Duration `mapstructure:"expire-duration"`
}

37
config/ldap.go Normal file
View File

@@ -0,0 +1,37 @@
package config
type LdapUser struct {
BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching
EnableAttr string `mapstructure:"enable-attr"` // The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
EnableAttrValue string `mapstructure:"enable-attr-value"` // The value of the enable attribute when the user is enabled. If you are using AD, just leave it random str, it will be ignored.
Filter string `mapstructure:"filter"`
Username string `mapstructure:"username"`
Email string `mapstructure:"email"`
FirstName string `mapstructure:"first-name"`
LastName string `mapstructure:"last-name"`
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
AllowGroup string `mapstructure:"allow-group"` // Which group is allowed to login
}
// type LdapGroup struct {
// BaseDn string `mapstructure:"base-dn"` // The base DN of the group for searching
// Name string `mapstructure:"name"` // The attribute name of the group
// Filter string `mapstructure:"filter"`
// Admin string `mapstructure:"admin"` // Which group is the admin group
// Member string `mapstructure:"member"` // How to get the member of the group: member, uniqueMember, or memberOf (default: member)
// Mode string `mapstructure:"mode"`
// Map map[string]string `mapstructure:"map"` // If mode is "map", map the LDAP group to the internal group
// }
type Ldap struct {
Enable bool `mapstructure:"enable"`
Url string `mapstructure:"url"`
TlsCaFile string `mapstructure:"tls-ca-file"`
TlsVerify bool `mapstructure:"tls-verify"`
BaseDn string `mapstructure:"base-dn"`
BindDn string `mapstructure:"bind-dn"`
BindPassword string `mapstructure:"bind-password"`
User LdapUser `mapstructure:"user"`
// Group LdapGroup `mapstructure:"group"`
}

22
config/oauth.go Normal file
View File

@@ -0,0 +1,22 @@
package config
type GithubOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
}
type GoogleOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
}
type OidcOauth struct {
Issuer string `mapstructure:"issuer"`
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
}
type LinuxdoOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
}

6
config/proxy.go Normal file
View File

@@ -0,0 +1,6 @@
package config
type Proxy struct {
Enable bool `mapstructure:"enable"`
Host string `mapstructure:"host"`
}

View File

@@ -1,8 +1,40 @@
package config
import (
"os"
)
const (
DefaultIdServerPort = 21116
DefaultRelayServerPort = 21117
)
type Rustdesk struct {
IdServer string `mapstructure:"id-server"`
RelayServer string `mapstructure:"relay-server"`
ApiServer string `mapstructure:"api-server"`
Key string `mapstructure:"key"`
IdServer string `mapstructure:"id-server"`
IdServerPort int `mapstructure:"-"`
RelayServer string `mapstructure:"relay-server"`
RelayServerPort int `mapstructure:"-"`
ApiServer string `mapstructure:"api-server"`
Key string `mapstructure:"key"`
KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"`
//webclient-magic-queryonline
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
WsHost string `mapstructure:"ws-host"`
}
func (rd *Rustdesk) LoadKeyFile() {
// Load key file
if rd.Key != "" {
return
}
if rd.KeyFile != "" {
// Load key from file
b, err := os.ReadFile(rd.KeyFile)
if err != nil {
return
}
rd.Key = string(b)
return
}
}

5
debian/changelog vendored Normal file
View File

@@ -0,0 +1,5 @@
rustdesk-api-server (1.3.6) UNRELEASED; urgency=medium
* Update the version to 1.3.6 to match the client.
-- rustdesk-api <ymwlpoolc@qq.com> Tue, 24 Dec 2024 13:48:34 +0800

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
10

13
debian/control.tpl vendored Normal file
View File

@@ -0,0 +1,13 @@
Source: rustdesk-api-server
Section: net
Priority: optional
Maintainer: ymwl <ymwlpoolc@qq.com>
Build-Depends: debhelper (>= 10), pkg-config
Standards-Version: 4.5.0
Homepage: https://github.com/lejianwen/rustdesk-api/
Package: rustdesk-api-server
Architecture: {{ ARCH }}
Depends: systemd ${misc:Depends}
Description: RustDesk api server
RustDesk api server, it is free and open source.

21
debian/copyright vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-present Lejianwen and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
debian/rules vendored Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_builddeb:
dh_builddeb -- -Zgzip

6
debian/rustdesk-api-server.install vendored Normal file
View File

@@ -0,0 +1,6 @@
bin/rustdesk-api usr/bin
systemd/rustdesk-api.service lib/systemd/system
conf var/lib/rustdesk-api
data var/lib/rustdesk-api
resources var/lib/rustdesk-api
runtime var/lib/rustdesk-api

28
debian/rustdesk-api-server.postinst vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
set -e
SERVICE=rustdesk-api.service
if [ "$1" = "configure" ]; then
mkdir -p /var/log/rustdesk-api
fi
case "$1" in
configure|abort-upgrade|abort-deconfigure|abort-remove)
mkdir -p /var/lib/rustdesk-api/
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then
deb-systemd-invoke enable "${SERVICE}" >/dev/null || true
else
deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true
fi
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
deb-systemd-invoke restart "${SERVICE}" >/dev/null || true
else
deb-systemd-invoke start "${SERVICE}" >/dev/null || true
fi
;;
esac
exit 0

18
debian/rustdesk-api-server.postrm vendored Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
set -e
SERVICE=rustdesk-api.service
systemctl --system daemon-reload >/dev/null || true
if [ "$1" = "purge" ]; then
rm -rf /var/log/rustdesk-api/rustdesk-api.*
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
fi
if [ "$1" = "remove" ]; then
deb-systemd-helper mask "${SERVICE}" >/dev/null || true
fi
exit 0

13
debian/rustdesk-api-server.prerm vendored Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
set -e
SERVICE=rustdesk-api.service
case "$1" in
remove|deconfigure)
deb-systemd-invoke stop "${SERVICE}" >/dev/null || true
deb-systemd-invoke disable "${SERVICE}" >/dev/null || true
;;
esac
exit 0

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (native)

24
docker-compose-dev.yaml Normal file
View File

@@ -0,0 +1,24 @@
services:
rustdesk-api:
build:
context: .
dockerfile: Dockerfile.dev
args:
COUNTRY: CN
FRONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
FRONTEND_GIT_BRANCH: master
# image: lejianwen/rustdesk-api
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
- ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped

17
docker-compose.yaml Normal file
View File

@@ -0,0 +1,17 @@
services:
rustdesk-api:
image: lejianwen/rustdesk-api
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- ./data/rustdesk/api:/app/data # database
# - ./conf:/app/conf # config
# - ./resources:/app/resources # 静态资源
restart: unless-stopped

35
docker-dev.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
set -e
# Define Docker Compose file and cache option
COMPOSE_FILE_NAME="docker-compose-dev.yaml"
CACHE=""
# Uncomment the next line to enable no-cache option
# CACHE="--no-cache"
# Define the base Docker Compose command
DCS="docker compose -f ${COMPOSE_FILE_NAME}"
# Function to build and start services
build_and_run() {
echo "Building services..."
if ! $DCS build ${CACHE}; then
echo "Error: Failed to build services"
exit 1
fi
echo "Starting services..."
if ! $DCS up -d; then
echo "Error: Failed to start services"
exit 1
fi
echo "Services started successfully"
echo "If you want to stop the services, run"
echo "docker compose -f ${COMPOSE_FILE_NAME} down"
echo "If you want to see the logs, run"
echo "docker compose -f ${COMPOSE_FILE_NAME} logs -f"
}
# Execute build and start function
build_and_run

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
docs/admin_webclient.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,15 +6,72 @@ definitions:
example: '{"tags":["tag1","tag2","tag3"],"peers":[{"id":"abc","username":"abv-l","hostname":"","platform":"Windows","alias":"","tags":["tag1","tag2"],"hash":"hash"}],"tag_colors":"{\"tag1\":4288585374,\"tag2\":4278238420,\"tag3\":4291681337}"}'
type: string
type: object
api.AuditConnForm:
properties:
action:
type: string
conn_id:
type: integer
id:
type: string
ip:
type: string
peer:
items:
type: string
type: array
session_id:
type: number
type:
type: integer
uuid:
type: string
type: object
api.AuditFileForm:
properties:
id:
type: string
info:
type: string
is_file:
type: boolean
path:
type: string
peer_id:
type: string
type:
type: integer
uuid:
type: string
type: object
api.DeviceInfoInLogin:
properties:
name:
type: string
os:
type: string
type:
type: string
type: object
api.LoginForm:
properties:
autoLogin:
type: boolean
deviceInfo:
$ref: '#/definitions/api.DeviceInfoInLogin'
id:
type: string
password:
maxLength: 20
maxLength: 32
minLength: 4
type: string
type:
type: string
username:
maxLength: 10
minLength: 4
maxLength: 32
minLength: 2
type: string
uuid:
type: string
required:
- username
@@ -55,6 +112,9 @@ definitions:
properties:
email:
type: string
info:
additionalProperties: true
type: object
is_admin:
type: boolean
name:
@@ -64,8 +124,27 @@ definitions:
status:
type: integer
type: object
model.AddressBookCollection:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
user_id:
type: integer
required:
- name
type: object
model.Tag:
properties:
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
color:
description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度后面6位表示颜色,
可以转成rgba
@@ -81,6 +160,19 @@ definitions:
user_id:
type: integer
type: object
model.TagList:
properties:
list:
items:
$ref: '#/definitions/model.Tag'
type: array
page:
type: integer
page_size:
type: integer
total:
type: integer
type: object
response.DataResponse:
properties:
data: {}
@@ -173,11 +265,17 @@ paths:
summary: 地址更新
tags:
- 地址
/ab/add:
post:
/ab/peer/add/{guid}:
delete:
consumes:
- application/json
description: 标签
description: 删除地址
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
@@ -191,14 +289,101 @@ paths:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签添加
summary: 删除地址
tags:
- 地址
- 地址[Personal]
post:
consumes:
- application/json
description: 添加地址
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 添加地址
tags:
- 地址[Personal]
/ab/peer/update/{guid}:
put:
consumes:
- application/json
description: 更新地址
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 更新地址
tags:
- 地址[Personal]
/ab/peers:
post:
consumes:
- application/json
description: 地址
parameters:
- description: 页码
in: query
name: current
type: integer
- description: 每页数量
in: query
name: pageSize
type: integer
- description: guid
in: query
name: ab
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- BearerAuth: []
summary: 地址列表
tags:
- 地址[Personal]
/ab/personal:
post:
consumes:
- application/json
description: 个人信息
description: 个人地址
parameters:
- description: string valid
in: body
@@ -218,30 +403,253 @@ paths:
$ref: '#/definitions/response.Response'
security:
- BearerAuth: []
summary: 个人信息
summary: 个人地址
tags:
- 用户
/api:
get:
- 地址[Personal]
/ab/settings:
post:
consumes:
- application/json
description: 用户信息
description: 设置
parameters:
- description: string valid
in: body
name: string
schema:
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.UserPayload'
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 用户信息
- BearerAuth: []
summary: 设置
tags:
- 用户
- 地址[Personal]
/ab/shared/profiles:
post:
consumes:
- application/json
description: 共享
parameters:
- description: 页码
in: query
name: current
type: integer
- description: 每页数量
in: query
name: pageSize
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- BearerAuth: []
summary: 共享地址簿
tags:
- 地址[Personal]
/ab/tag/{guid}:
delete:
consumes:
- application/json
description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签删除
tags:
- 地址[Personal]
/ab/tag/add/{guid}:
post:
consumes:
- application/json
description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签添加
tags:
- 地址[Personal]
/ab/tag/rename/{guid}:
put:
consumes:
- application/json
description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签重命名
tags:
- 地址[Personal]
/ab/tag/update/{guid}:
put:
consumes:
- application/json
description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签修改颜色
tags:
- 地址[Personal]
/ab/tags/{guid}:
post:
consumes:
- application/json
description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.TagList'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签
tags:
- 地址[Personal]
/audit/conn:
post:
consumes:
- application/json
description: 审计连接
parameters:
- description: 审计连接
in: body
name: body
required: true
schema:
$ref: '#/definitions/api.AuditConnForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: 审计连接
tags:
- 审计
/audit/file:
post:
consumes:
- application/json
description: 审计文件
parameters:
- description: 审计文件
in: body
name: body
required: true
schema:
$ref: '#/definitions/api.AuditFileForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: 审计文件
tags:
- 审计
/currentUser:
get:
consumes:
@@ -263,6 +671,44 @@ paths:
summary: 用户信息
tags:
- 用户
/device-group/accessible:
get:
consumes:
- application/json
description: 机器
parameters:
- description: 页码
in: query
name: page
type: integer
- description: 每页数量
in: query
name: pageSize
type: integer
- description: 状态
in: query
name: status
type: integer
- description: accessible
in: query
name: accessible
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.DataResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- BearerAuth: []
summary: 设备
tags:
- 群组
/heartbeat:
post:
consumes:
@@ -307,7 +753,7 @@ paths:
tags:
- 登录
/login-options:
post:
get:
consumes:
- application/json
description: 登录选项
@@ -346,6 +792,63 @@ paths:
summary: 登出
tags:
- 登录
/oidc/auth:
post:
consumes:
- application/json
description: OidcAuth
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OidcAuth
tags:
- Oauth
/oidc/auth-query:
get:
consumes:
- application/json
description: OidcAuthQuery
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OidcAuthQuery
tags:
- Oauth
/oidc/callback:
get:
consumes:
- application/json
description: OauthCallback
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OauthCallback
tags:
- Oauth
/peers:
get:
consumes:
@@ -405,6 +908,46 @@ paths:
summary: 服务配置
tags:
- WEBCLIENT
/server-config-v2:
get:
consumes:
- application/json
description: 服务配置,给webclient提供api-server
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 服务配置
tags:
- WEBCLIENT_V2
/shared-peer:
post:
consumes:
- application/json
description: 分享的peer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: 分享的peer
tags:
- WEBCLIENT
/sysinfo:
post:
consumes:
@@ -428,34 +971,28 @@ paths:
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 提交系统信息
tags:
- 地址
/tags:
- System
/sysinfo_ver:
post:
consumes:
- application/json
description: 标签
description: 获取系统版本信息
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/model.Tag'
type: array
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签
summary: 获取系统版本信息
tags:
- 地址
- System
/users:
get:
consumes:
@@ -501,6 +1038,25 @@ paths:
summary: 用户列表
tags:
- 群组
/version:
get:
consumes:
- application/json
description: 版本
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: 版本
tags:
- 首页
securityDefinitions:
BearerAuth:
in: header

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
docs/en_img/pc_ab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/en_img/pc_gr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en_img/pc_login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
docs/en_img/web_admin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
docs/init_admin_pwd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/pc_login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
docs/web_admin_oauth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/web_admin_user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/webclientv2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,4 +1,4 @@
package Gwen
package main
//go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
//go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api

3
generate_run.go Normal file
View File

@@ -0,0 +1,3 @@
package main
//go:generate go run cmd/apimain.go

160
global/apiValidator.go Normal file
View File

@@ -0,0 +1,160 @@
package global
import (
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/es"
"github.com/go-playground/locales/fr"
"github.com/go-playground/locales/ko"
"github.com/go-playground/locales/ru"
"github.com/go-playground/locales/zh_Hans_CN"
"github.com/go-playground/locales/zh_Hant"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
es_translations "github.com/go-playground/validator/v10/translations/es"
fr_translations "github.com/go-playground/validator/v10/translations/fr"
ko_translations "github.com/go-playground/validator/v10/translations/ko"
ru_translations "github.com/go-playground/validator/v10/translations/ru"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw"
"reflect"
)
func ApiInitValidator() {
validate := validator.New()
// 定义不同的语言翻译
enT := en.New()
cn := zh_Hans_CN.New()
koT := ko.New()
ruT := ru.New()
esT := es.New()
frT := fr.New()
zhTwT := zh_Hant.New()
uni := ut.New(enT, cn, koT, ruT, esT, frT, zhTwT)
enTrans, _ := uni.GetTranslator("en")
zhTrans, _ := uni.GetTranslator("zh_Hans_CN")
koTrans, _ := uni.GetTranslator("ko")
ruTrans, _ := uni.GetTranslator("ru")
esTrans, _ := uni.GetTranslator("es")
frTrans, _ := uni.GetTranslator("fr")
zhTwTrans, _ := uni.GetTranslator("zh_Hant")
err := zh_translations.RegisterDefaultTranslations(validate, zhTrans)
if err != nil {
panic(err)
}
err = en_translations.RegisterDefaultTranslations(validate, enTrans)
if err != nil {
panic(err)
}
err = ko_translations.RegisterDefaultTranslations(validate, koTrans)
if err != nil {
panic(err)
}
err = ru_translations.RegisterDefaultTranslations(validate, ruTrans)
if err != nil {
panic(err)
}
err = es_translations.RegisterDefaultTranslations(validate, esTrans)
if err != nil {
panic(err)
}
err = fr_translations.RegisterDefaultTranslations(validate, frTrans)
if err != nil {
panic(err)
}
err = zh_tw_translations.RegisterDefaultTranslations(validate, zhTwTrans)
if err != nil {
panic(err)
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
label := field.Tag.Get("label")
if label == "" {
return field.Name
}
return label
})
Validator.Validate = validate
Validator.UT = uni // 存储 Universal Translator
Validator.VTrans = zhTrans
Validator.ValidStruct = func(ctx *gin.Context, i interface{}) []string {
err := Validator.Validate.Struct(i)
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = Config.Lang
}
trans := getTranslatorForLang(lang)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
Validator.ValidVar = func(ctx *gin.Context, field interface{}, tag string) []string {
err := Validator.Validate.Var(field, tag)
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = Config.Lang
}
trans := getTranslatorForLang(lang)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
}
func getTranslatorForLang(lang string) ut.Translator {
switch lang {
case "zh_CN":
fallthrough
case "zh-CN":
fallthrough
case "zh":
trans, _ := Validator.UT.GetTranslator("zh_Hans_CN")
return trans
case "zh_TW":
fallthrough
case "zh-TW":
fallthrough
case "zh-tw":
trans, _ := Validator.UT.GetTranslator("zh_Hant")
return trans
case "ko":
trans, _ := Validator.UT.GetTranslator("ko")
return trans
case "ru":
trans, _ := Validator.UT.GetTranslator("ru")
return trans
case "es":
trans, _ := Validator.UT.GetTranslator("es")
return trans
case "fr":
trans, _ := Validator.UT.GetTranslator("fr")
return trans
case "en":
fallthrough
default:
trans, _ := Validator.UT.GetTranslator("en")
return trans
}
}

View File

@@ -1,33 +1,40 @@
package global
import (
"Gwen/config"
"Gwen/lib/cache"
"Gwen/lib/jwt"
"Gwen/lib/lock"
"Gwen/lib/upload"
"github.com/gin-gonic/gin"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
"github.com/go-redis/redis/v8"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/lib/cache"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"gorm.io/gorm"
)
var (
DB *gorm.DB
Logger *logrus.Logger
Config config.Config
Viper *viper.Viper
Redis *redis.Client
Cache cache.Handler
Validator struct {
DB *gorm.DB
Logger *logrus.Logger
ConfigPath string = ""
Config config.Config
Viper *viper.Viper
Redis *redis.Client
Cache cache.Handler
Validator struct {
Validate *validator.Validate
UT *ut.UniversalTranslator
VTrans ut.Translator
ValidStruct func(interface{}) []string
ValidVar func(field interface{}, tag string) []string
ValidStruct func(*gin.Context, interface{}) []string
ValidVar func(ctx *gin.Context, field interface{}, tag string) []string
}
Oss *upload.Oss
Jwt *jwt.Jwt
Lock lock.Locker
Oss *upload.Oss
Jwt *jwt.Jwt
Lock lock.Locker
Localizer func(lang string) *i18n.Localizer
LoginLimiter *utils.LoginLimiter
)

52
global/i18n.go Normal file
View File

@@ -0,0 +1,52 @@
package global
import (
"github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
"os"
)
func InitI18n() {
bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
//读取global.Config.Gin.ResourcesPath下的所有语言文件
dir := Config.Gin.ResourcesPath + "/i18n"
fileInfos, err := os.ReadDir(dir)
if err != nil {
panic(err)
}
for _, fileInfo := range fileInfos {
//如果文件名不是.toml结尾
if fileInfo.IsDir() || fileInfo.Name()[len(fileInfo.Name())-5:] != ".toml" {
continue
}
bundle.LoadMessageFile(Config.Gin.ResourcesPath + "/i18n/" + fileInfo.Name())
}
Localizer = func(lang string) *i18n.Localizer {
if lang == "" {
lang = Config.Lang
}
if lang == "en" {
return i18n.NewLocalizer(bundle, "en")
} else {
return i18n.NewLocalizer(bundle, lang, "en")
}
}
//personUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{
// DefaultMessage: &i18n.Message{
// ID: "PersonUnreadEmails",
// },
// PluralCount: 6,
// TemplateData: map[string]interface{}{
// "Name": "LE",
// "PluralCount": 6,
// },
//})
//personUnreadEmails, err := global.Localizer.LocalizeMessage(&i18n.Message{
// ID: "ParamsError",
//})
//fmt.Println(err, personUnreadEmails)
}

48
go.mod
View File

@@ -1,28 +1,41 @@
module Gwen
module github.com/lejianwen/rustdesk-api/v2
go 1.22
go 1.23
toolchain go1.23.10
require (
github.com/BurntSushi/toml v1.3.2
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/fsnotify/fsnotify v1.5.1
github.com/coreos/go-oidc/v3 v3.12.0
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
github.com/gin-gonic/gin v1.9.0
github.com/go-ldap/ldap/v3 v3.4.10
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.11.2
github.com/go-playground/validator/v10 v10.26.0
github.com/go-redis/redis/v8 v8.11.4
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/mojocn/base64Captcha v1.3.6
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.9.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
golang.org/x/crypto v0.33.0
golang.org/x/oauth2 v0.23.0
golang.org/x/text v0.22.0
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7
gorm.io/gorm v1.25.10
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -30,20 +43,30 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
@@ -53,6 +76,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@@ -61,12 +85,12 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@@ -1,12 +1,13 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"encoding/json"
_ "encoding/json"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
@@ -29,16 +30,11 @@ func (ct *AddressBook) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.InfoByRowId(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
return
}
if t.RowId > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -56,25 +52,100 @@ func (ct *AddressBook) Detail(c *gin.Context) {
func (ct *AddressBook) Create(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
}
err := service.AllService.AddressBookService.Create(t)
if err != nil {
response.Fail(c, 101, "创建失败")
if t.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
response.Success(c, u)
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return
}
err := service.AllService.AddressBookService.Create(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// BatchCreate 批量创建地址簿
// @Tags 地址簿
// @Summary 批量创建地址簿
// @Description 批量创建地址簿
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/address_book/batchCreate [post]
// @Security token
func (ct *AddressBook) BatchCreate(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ul := len(f.UserIds)
if ul == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if ul > 1 {
//多用户置空标签
f.Tags = []string{}
//多用户只能创建到默认地址簿
f.CollectionId = 0
}
//创建标签
/*for _, fu := range f.UserIds {
if fu == 0 {
continue
}
for _, ft := range f.Tags {
exTag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(fu, ft, 0)
if exTag.Id == 0 {
service.AllService.TagService.Create(&model.Tag{
UserId: fu,
Name: ft,
})
}
}
}*/
ts := f.ToAddressBooks()
for _, t := range ts {
if t.UserId == 0 {
continue
}
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId == 0 {
service.AllService.AddressBookService.Create(t)
}
}
response.Success(c, nil)
}
// List 列表
@@ -94,18 +165,34 @@ func (ct *AddressBook) Create(c *gin.Context) {
func (ct *AddressBook) List(c *gin.Context) {
query := &admin.AddressBookQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
abCIds := make([]uint, 0)
for _, ab := range res.AddressBooks {
abCIds = append(abCIds, ab.CollectionId)
}
response.Success(c, res)
}
@@ -123,27 +210,31 @@ func (ct *AddressBook) List(c *gin.Context) {
func (ct *AddressBook) Update(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.RowId == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if ex.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AddressBookService.Update(t)
err := service.AllService.AddressBookService.UpdateAll(t)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -163,29 +254,109 @@ func (ct *AddressBook) Update(c *gin.Context) {
func (ct *AddressBook) Delete(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.RowId
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := service.AllService.AddressBookService.InfoByRowId(f.RowId)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
if t.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if u.Id > 0 {
err := service.AllService.AddressBookService.Delete(t)
if err == nil {
response.Success(c, nil)
err := service.AllService.AddressBookService.Delete(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
// ShareByWebClient
// @Tags 地址簿
// @Summary 地址簿分享
// @Description 地址簿分享
// @Accept json
// @Produce json
// @Param body body admin.ShareByWebClientForm true "地址簿信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book/share [post]
// @Security token
func (ct *AddressBook) ShareByWebClient(c *gin.Context) {
f := &admin.ShareByWebClientForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, f.Id)
if ab.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
m := f.ToShareRecord()
m.UserId = u.Id
err := service.AllService.AddressBookService.ShareByWebClient(m)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, &gin.H{
"share_token": m.ShareToken,
})
}
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
f := &admin.BatchCreateFromPeersForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if f.CollectionId != 0 {
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
if collection.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
response.Fail(c, 101, err.Error())
}
pl := int64(len(f.PeerIds))
peers := service.AllService.PeerService.List(1, uint(pl), func(tx *gorm.DB) {
tx.Where("row_id in ?", f.PeerIds)
})
if peers.Total == 0 || pl != peers.Total {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
response.Fail(c, 101, "信息不存在")
tags, _ := json.Marshal(f.Tags)
for _, peer := range peers.Peers {
ab := service.AllService.AddressBookService.FromPeer(peer)
ab.Tags = tags
ab.CollectionId = f.CollectionId
ab.UserId = f.UserId
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(f.UserId, ab.Id, ab.CollectionId)
if ex.RowId != 0 {
continue
}
service.AllService.AddressBookService.Create(ab)
}
response.Success(c, nil)
}

View File

@@ -0,0 +1,172 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
type AddressBookCollection struct {
}
// Detail 地址簿名称
// @Tags 地址簿名称
// @Summary 地址簿名称详情
// @Description 地址簿名称详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/detail/{id} [get]
// @Security token
func (abc *AddressBookCollection) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.CollectionInfoById(uint(iid))
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建地址簿名称
// @Tags 地址簿名称
// @Summary 创建地址簿名称
// @Description 创建地址簿名称
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/create [post]
// @Security token
func (abc *AddressBookCollection) Create(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f
err := service.AllService.AddressBookService.CreateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags 地址簿名称
// @Summary 地址簿名称列表
// @Description 地址簿名称列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/list [get]
// @Security token
func (abc *AddressBookCollection) List(c *gin.Context) {
query := &admin.AddressBookCollectionQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
})
response.Success(c, res)
}
// Update 编辑
// @Tags 地址簿名称
// @Summary 地址簿名称编辑
// @Description 地址簿名称编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/update [post]
// @Security token
func (abc *AddressBookCollection) Update(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f //f.ToAddressBookCollection()
err := service.AllService.AddressBookService.UpdateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 地址簿名称
// @Summary 地址簿名称删除
// @Description 地址簿名称删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/delete [post]
// @Security token
func (abc *AddressBookCollection) Delete(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.AddressBookService.DeleteCollection(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -0,0 +1,222 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
type AddressBookCollectionRule struct {
}
// List 列表
// @Tags 地址簿规则
// @Summary 地址簿规则列表
// @Description 地址簿规则列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Param collection_id query int false "地址簿集合id"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/list [get]
// @Security token
func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
query := &admin.AddressBookCollectionRuleQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.CollectionId > 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Detail 地址簿规则
// @Tags 地址簿规则
// @Summary 地址簿规则详情
// @Description 地址簿规则详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionRule}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/detail/{id} [get]
// @Security token
func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.RuleInfoById(uint(iid))
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// Create 创建地址簿规则
// @Tags 地址簿规则
// @Summary 创建地址簿规则
// @Description 创建地址簿规则
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/create [post]
// @Security token
func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Type != model.ShareAddressBookRuleTypePersonal && f.Type != model.ShareAddressBookRuleTypeGroup {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f
msg, res := abcr.CheckForm(t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.CreateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionRule) (string, bool) {
if t.UserId == 0 {
return "ParamsError", false
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
return "ParamsError", false
}
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "CannotShareToSelf", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
return "ItemNotFound", false
}
} else {
return "ParamsError", false
}
// 重复检查
ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false
}
if t.Id > 0 && ex.Id > 0 && t.Id != ex.Id {
return "ItemExists", false
}
return "", true
}
// Update 编辑
// @Tags 地址簿规则
// @Summary 地址簿规则编辑
// @Description 地址簿规则编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/update [post]
// @Security token
func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f
msg, res := abcr.CheckForm(t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.UpdateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 地址簿规则
// @Summary 地址簿规则删除
// @Description 地址簿规则删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/delete [post]
// @Security token
func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.AddressBookService.DeleteRule(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -0,0 +1,212 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type Audit struct {
}
// ConnList 列表
// @Tags 链接日志
// @Summary 链接日志列表
// @Description 链接日志列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param peer_id query int false "目标设备"
// @Param from_peer query int false "来源设备"
// @Success 200 {object} response.Response{data=model.AuditConnList}
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/list [get]
// @Security token
func (a *Audit) ConnList(c *gin.Context) {
query := &admin.AuditQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.AuditService.AuditConnList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.PeerId != "" {
tx.Where("peer_id like ?", "%"+query.PeerId+"%")
}
if query.FromPeer != "" {
tx.Where("from_peer like ?", "%"+query.FromPeer+"%")
}
tx.Order("id desc")
})
response.Success(c, res)
}
// ConnDelete 删除
// @Tags 链接日志
// @Summary 链接日志删除
// @Description 链接日志删除
// @Accept json
// @Produce json
// @Param body body model.AuditConn true "链接日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/delete [post]
// @Security token
func (a *Audit) ConnDelete(c *gin.Context) {
f := &model.AuditConn{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.AuditService.ConnInfoById(f.Id)
if l.Id > 0 {
err := service.AllService.AuditService.DeleteAuditConn(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchConnDelete 删除
// @Tags 链接日志
// @Summary 链接日志批量删除
// @Description 链接日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.AuditConnLogIds true "链接日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/batchDelete [post]
// @Security token
func (a *Audit) BatchConnDelete(c *gin.Context) {
f := &admin.AuditConnLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AuditService.BatchDeleteAuditConn(f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
// FileList 列表
// @Tags 文件日志
// @Summary 文件日志列表
// @Description 文件日志列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param peer_id query int false "目标设备"
// @Param from_peer query int false "来源设备"
// @Success 200 {object} response.Response{data=model.AuditFileList}
// @Failure 500 {object} response.Response
// @Router /admin/audit_file/list [get]
// @Security token
func (a *Audit) FileList(c *gin.Context) {
query := &admin.AuditQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.AuditService.AuditFileList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.PeerId != "" {
tx.Where("peer_id like ?", "%"+query.PeerId+"%")
}
if query.FromPeer != "" {
tx.Where("from_peer like ?", "%"+query.FromPeer+"%")
}
tx.Order("id desc")
})
response.Success(c, res)
}
// FileDelete 删除
// @Tags 文件日志
// @Summary 文件日志删除
// @Description 文件日志删除
// @Accept json
// @Produce json
// @Param body body model.AuditFile true "文件日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_file/delete [post]
// @Security token
func (a *Audit) FileDelete(c *gin.Context) {
f := &model.AuditFile{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.AuditService.FileInfoById(f.Id)
if l.Id > 0 {
err := service.AllService.AuditService.DeleteAuditFile(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchFileDelete 删除
// @Tags 文件日志
// @Summary 文件日志批量删除
// @Description 文件日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.AuditFileLogIds true "文件日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_file/batchDelete [post]
// @Security token
func (a *Audit) BatchFileDelete(c *gin.Context) {
f := &admin.AuditFileLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AuditService.BatchDeleteAuditFile(f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,97 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"os"
"strings"
)
type Config struct {
}
// ServerConfig RUSTDESK服务配置
// @Tags ADMIN
// @Summary RUSTDESK服务配置
// @Description 服务配置,给webclient提供api-server
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/config/server [get]
// @Security token
func (co *Config) ServerConfig(c *gin.Context) {
cf := &response.ServerConfigResponse{
IdServer: global.Config.Rustdesk.IdServer,
Key: global.Config.Rustdesk.Key,
RelayServer: global.Config.Rustdesk.RelayServer,
ApiServer: global.Config.Rustdesk.ApiServer,
}
response.Success(c, cf)
}
// AppConfig APP服务配置
// @Tags ADMIN
// @Summary APP服务配置
// @Description APP服务配置
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/config/app [get]
// @Security token
func (co *Config) AppConfig(c *gin.Context) {
response.Success(c, &gin.H{
"web_client": global.Config.App.WebClient,
})
}
// AdminConfig ADMIN服务配置
// @Tags ADMIN
// @Summary ADMIN服务配置
// @Description ADMIN服务配置
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/config/admin [get]
// @Security token
func (co *Config) AdminConfig(c *gin.Context) {
u := &model.User{}
token := c.GetHeader("api-token")
if token != "" {
u, _ = service.AllService.UserService.InfoByAccessToken(token)
if !service.AllService.UserService.CheckUserEnable(u) {
u.Id = 0
}
}
if u.Id == 0 {
response.Success(c, &gin.H{
"title": global.Config.Admin.Title,
})
return
}
hello := global.Config.Admin.Hello
if hello == "" {
helloFile := global.Config.Admin.HelloFile
if helloFile != "" {
b, err := os.ReadFile(helloFile)
if err == nil && len(b) > 0 {
hello = string(b)
}
}
}
//replace {{username}} to username
hello = strings.Replace(hello, "{{username}}", u.Username, -1)
response.Success(c, &gin.H{
"title": global.Config.Admin.Title,
"hello": hello,
})
}

View File

@@ -0,0 +1,160 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"strconv"
)
type DeviceGroup struct {
}
// Detail 设备群组
// @Tags 设备群组
// @Summary 设备群组详情
// @Description 设备群组详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/detail/{id} [get]
// @Security token
func (ct *DeviceGroup) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.GroupService.DeviceGroupInfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建设备群组
// @Tags 设备群组
// @Summary 创建设备群组
// @Description 创建设备群组
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "设备群组信息"
// @Success 200 {object} response.Response{data=model.DeviceGroup}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/create [post]
// @Security token
func (ct *DeviceGroup) Create(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToDeviceGroup()
err := service.AllService.GroupService.DeviceGroupCreate(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags 群组
// @Summary 群组列表
// @Description 群组列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.GroupList}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/list [get]
// @Security token
func (ct *DeviceGroup) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.GroupService.DeviceGroupList(query.Page, query.PageSize, nil)
response.Success(c, res)
}
// Update 编辑
// @Tags 设备群组
// @Summary 设备群组编辑
// @Description 设备群组编辑
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "群组信息"
// @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/update [post]
// @Security token
func (ct *DeviceGroup) Update(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToDeviceGroup()
err := service.AllService.GroupService.DeviceGroupUpdate(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 设备群组
// @Summary 设备群组删除
// @Description 设备群组删除
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "群组信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/device_group/delete [post]
// @Security token
func (ct *DeviceGroup) Delete(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.GroupService.DeviceGroupInfoById(f.Id)
if u.Id > 0 {
err := service.AllService.GroupService.DeviceGroupDelete(u)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/response"
"Gwen/lib/upload"
"fmt"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"os"
"time"
)
@@ -38,7 +38,7 @@ func (f *File) Notify(c *gin.Context) {
res := global.Oss.Verify(c.Request)
if !res {
response.Fail(c, 101, "权限错误")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
fm := &FileBack{}

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"strconv"
)
@@ -31,7 +31,7 @@ func (ct *Group) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -49,10 +49,10 @@ func (ct *Group) Detail(c *gin.Context) {
func (ct *Group) Create(c *gin.Context) {
f := &admin.GroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -60,10 +60,10 @@ func (ct *Group) Create(c *gin.Context) {
u := f.ToGroup()
err := service.AllService.GroupService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表
@@ -81,7 +81,7 @@ func (ct *Group) Create(c *gin.Context) {
func (ct *Group) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.GroupService.List(query.Page, query.PageSize, nil)
@@ -102,14 +102,14 @@ func (ct *Group) List(c *gin.Context) {
func (ct *Group) Update(c *gin.Context) {
f := &admin.GroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -117,7 +117,7 @@ func (ct *Group) Update(c *gin.Context) {
u := f.ToGroup()
err := service.AllService.GroupService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -137,11 +137,11 @@ func (ct *Group) Update(c *gin.Context) {
func (ct *Group) Delete(c *gin.Context) {
f := &admin.GroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -153,8 +153,8 @@ func (ct *Group) Delete(c *gin.Context) {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -1,13 +1,17 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/service"
"fmt"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/api"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
apiReq "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
)
type Login struct {
@@ -25,33 +29,103 @@ type Login struct {
// @Router /admin/login [post]
// @Security token
func (ct *Login) Login(c *gin.Context) {
fmt.Println("login")
if global.Config.App.DisablePwdLogin {
response.Fail(c, 101, response.TranslateMsg(c, "PwdLoginDisabled"))
return
}
// 检查登录限制
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
_, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
f := &admin.Login{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, "参数错误")
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
response.Fail(c, 101, errList[0])
return
}
// 检查是否需要验证码
if needCaptcha {
if f.CaptchaId == "" || f.Captcha == "" || !loginLimiter.VerifyCaptcha(f.CaptchaId, f.Captcha) {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError"))
return
}
}
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 {
response.Fail(c, 101, "用户名或密码错误")
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp))
loginLimiter.RecordFailedAttempt(clientIp)
if _, needCaptcha = loginLimiter.CheckSecurityStatus(clientIp); needCaptcha {
response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
} else {
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
}
return
}
ut := service.AllService.UserService.Login(u)
if !service.AllService.UserService.CheckUserEnable(u) {
if needCaptcha {
response.Fail(c, 110, response.TranslateMsg(c, "UserDisabled"))
return
}
response.Fail(c, 101, response.TranslateMsg(c, "UserDisabled"))
return
}
response.Success(c, &adResp.LoginPayload{
Token: ut.Token,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
Uuid: "", //must be empty
Ip: clientIp,
Type: model.LoginLogTypeAccount,
Platform: f.Platform,
})
// 登录成功,清除登录限制
loginLimiter.RemoveAttempts(clientIp)
responseLoginSuccess(c, u, ut.Token)
}
func (ct *Login) Captcha(c *gin.Context) {
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
banned, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
if banned {
response.Fail(c, 101, response.TranslateMsg(c, "LoginBanned"))
return
}
if !needCaptcha {
response.Fail(c, 101, response.TranslateMsg(c, "NoCaptchaRequired"))
return
}
err, captcha := loginLimiter.RequireCaptcha()
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError")+err.Error())
return
}
err, b64 := loginLimiter.DrawCaptcha(captcha.Content)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError")+err.Error())
return
}
response.Success(c, gin.H{
"captcha": gin.H{
"id": captcha.Id,
"b64": b64,
},
})
}
@@ -72,3 +146,97 @@ func (ct *Login) Logout(c *gin.Context) {
}
response.Success(c, nil)
}
// LoginOptions
// @Tags 登录
// @Summary 登录选项
// @Description 登录选项
// @Accept json
// @Produce json
// @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) {
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
banned, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
if banned {
response.Fail(c, 101, response.TranslateMsg(c, "LoginBanned"))
return
}
ops := service.AllService.OauthService.GetOauthProviders()
response.Success(c, gin.H{
"ops": ops,
"register": global.Config.App.Register,
"need_captcha": needCaptcha,
"disable_pwd": global.Config.App.DisablePwdLogin,
"auto_oidc": global.Config.App.DisablePwdLogin && len(ops) == 1,
})
}
// OidcAuth
// @Tags Oauth
// @Summary OidcAuth
// @Description OidcAuth
// @Accept json
// @Produce json
// @Router /admin/oidc/auth [post]
func (ct *Login) OidcAuth(c *gin.Context) {
// o := &api.Oauth{}
// o.OidcAuth(c)
f := &apiReq.OidcAuthRequest{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin,
Op: f.Op,
Id: f.Id,
DeviceType: "webadmin",
// DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid,
Verifier: verifier,
Nonce: nonce,
}, 5*60)
response.Success(c, gin.H{
"code": state,
"url": url,
})
}
// OidcAuthQuery
// @Tags Oauth
// @Summary OidcAuthQuery
// @Description OidcAuthQuery
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=adResp.LoginPayload}
// @Failure 500 {object} response.Response
// @Router /admin/oidc/auth-query [get]
func (ct *Login) OidcAuthQuery(c *gin.Context) {
o := &api.Oauth{}
u, ut := o.OidcAuthQueryPre(c)
if ut == nil {
return
}
responseLoginSuccess(c, u, ut.Token)
}
func responseLoginSuccess(c *gin.Context, u *model.User, token string) {
lp := &adResp.LoginPayload{}
lp.FromUser(u)
lp.Token = token
lp.RouteNames = service.AllService.UserService.RouteNames(u)
response.Success(c, lp)
}

View File

@@ -0,0 +1,133 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
type LoginLog struct {
}
// Detail 登录日志
// @Tags 登录日志
// @Summary 登录日志详情
// @Description 登录日志详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.LoginLog}
// @Failure 500 {object} response.Response
// @Router /admin/login_log/detail/{id} [get]
// @Security token
func (ct *LoginLog) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.LoginLogService.InfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// List 列表
// @Tags 登录日志
// @Summary 登录日志列表
// @Description 登录日志列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param user_id query int false "用户ID"
// @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response
// @Router /admin/login_log/list [get]
// @Security token
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
tx.Order("id desc")
})
response.Success(c, res)
}
// Delete 删除
// @Tags 登录日志
// @Summary 登录日志删除
// @Description 登录日志删除
// @Accept json
// @Produce json
// @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/login_log/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.LoginLogService.InfoById(f.Id)
if l.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.LoginLogService.Delete(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
}
// BatchDelete 删除
// @Tags 登录日志
// @Summary 登录日志批量删除
// @Description 登录日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.LoginLogIds true "登录日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/login_log/batchDelete [post]
// @Security token
func (ct *LoginLog) BatchDelete(c *gin.Context) {
f := &admin.LoginLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.LoginLogService.BatchDelete(f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,271 @@
package my
import (
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type AddressBook struct{}
// List 列表
// @Tags 我的地址簿
// @Summary 地址簿列表
// @Description 地址簿列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.AddressBookList}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/list [get]
// @Security token
func (ct *AddressBook) List(c *gin.Context) {
query := &admin.AddressBookQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
//预加载地址簿名称
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
tx.Where("user_id = ?", query.UserId)
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
abCIds := make([]uint, 0)
for _, ab := range res.AddressBooks {
abCIds = append(abCIds, ab.CollectionId)
}
response.Success(c, res)
}
// Create 创建地址簿
// @Tags 我的地址簿
// @Summary 创建地址簿
// @Description 创建地址簿
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/create [post]
// @Security token
func (ct *AddressBook) Create(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
t.UserId = u.Id
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return
}
err := service.AllService.AddressBookService.Create(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Update 编辑
// @Tags 我的地址簿
// @Summary 地址簿编辑
// @Description 地址簿编辑
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/update [post]
// @Security token
func (ct *AddressBook) Update(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if f.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if ex.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
t := f.ToAddressBook()
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AddressBookService.UpdateAll(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 我的地址簿
// @Summary 地址簿删除
// @Description 地址簿删除
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/delete [post]
// @Security token
func (ct *AddressBook) Delete(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.RowId
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if ex.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.Delete(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
f := &admin.BatchCreateFromPeersForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if f.CollectionId != 0 {
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
if collection.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if collection.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
}
if len(f.PeerIds) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
pl := int64(len(f.PeerIds))
peers := service.AllService.PeerService.List(1, uint(pl), func(tx *gorm.DB) {
tx.Where("row_id in ?", f.PeerIds)
tx.Where("user_id = ?", u.Id)
})
if peers.Total == 0 || pl != peers.Total {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
tags, _ := json.Marshal(f.Tags)
for _, peer := range peers.Peers {
ab := service.AllService.AddressBookService.FromPeer(peer)
ab.Tags = tags
ab.CollectionId = f.CollectionId
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(u.Id, ab.Id, ab.CollectionId)
if ex.RowId != 0 {
continue
}
service.AllService.AddressBookService.Create(ab)
}
response.Success(c, nil)
}
func (ct *AddressBook) BatchUpdateTags(c *gin.Context) {
f := &admin.BatchUpdateTagsForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
abs := service.AllService.AddressBookService.List(1, 999, func(tx *gorm.DB) {
tx.Where("row_id in ?", f.RowIds)
tx.Where("user_id = ?", u.Id)
})
if abs.Total == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.AddressBookService.BatchUpdateTags(abs.AddressBooks, f.Tags)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -0,0 +1,162 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type AddressBookCollection struct {
}
// Create 创建地址簿名称
// @Tags 我的地址簿名称
// @Summary 创建地址簿名称
// @Description 创建地址簿名称
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/create [post]
// @Security token
func (abc *AddressBookCollection) Create(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
f.UserId = u.Id
err := service.AllService.AddressBookService.CreateCollection(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags 我的地址簿名称
// @Summary 地址簿名称列表
// @Description 地址簿名称列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/list [get]
// @Security token
func (abc *AddressBookCollection) List(c *gin.Context) {
query := &admin.AddressBookCollectionQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", query.UserId)
})
response.Success(c, res)
}
// Update 编辑
// @Tags 我的地址簿名称
// @Summary 地址簿名称编辑
// @Description 地址簿名称编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/update [post]
// @Security token
func (abc *AddressBookCollection) Update(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
//if f.UserId != u.Id {
// response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
// return
//}
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.UpdateCollection(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 我的地址簿名称
// @Summary 地址簿名称删除
// @Description 地址簿名称删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/delete [post]
// @Security token
func (abc *AddressBookCollection) Delete(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.DeleteCollection(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -0,0 +1,228 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type AddressBookCollectionRule struct {
}
// List 列表
// @Tags 我的地址簿规则
// @Summary 地址簿规则列表
// @Description 地址簿规则列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Param collection_id query int false "地址簿集合id"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/list [get]
// @Security token
func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
query := &admin.AddressBookCollectionRuleQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", query.UserId)
if query.CollectionId > 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Create 创建地址簿规则
// @Tags 我的地址簿规则
// @Summary 创建地址簿规则
// @Description 创建地址簿规则
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/create [post]
// @Security token
func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Type != model.ShareAddressBookRuleTypePersonal && f.Type != model.ShareAddressBookRuleTypeGroup {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
//t := f.ToAddressBookCollection()
t := f
u := service.AllService.UserService.CurUser(c)
t.UserId = u.Id
msg, res := abcr.CheckForm(u, t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.CreateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.AddressBookCollectionRule) (string, bool) {
if t.UserId != u.Id {
return "NoAccess", false
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
return "ParamsError", false
}
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "CannotShareToSelf", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
//非管理员不能分享给非本组织用户
//if tou.GroupId != u.GroupId {
// return "NoAccess", false
//}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
//非管理员不能分享给其他组
//if t.ToId != u.GroupId {
// return "NoAccess", false
//}
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
return "ItemNotFound", false
}
} else {
return "ParamsError", false
}
// 重复检查
ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false
}
if t.Id > 0 && ex.Id > 0 && t.Id != ex.Id {
return "ItemExists", false
}
return "", true
}
// Update 编辑
// @Tags 我的地址簿规则
// @Summary 地址簿规则编辑
// @Description 地址簿规则编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/update [post]
// @Security token
func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
t := f
msg, res := abcr.CheckForm(u, t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.UpdateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 我的地址簿规则
// @Summary 地址簿规则删除
// @Description 地址簿规则删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/delete [post]
// @Security token
func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.DeleteRule(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -0,0 +1,113 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type LoginLog struct {
}
// List 列表
// @Tags 我的登录日志
// @Summary 登录日志列表
// @Description 登录日志列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param user_id query int false "用户ID"
// @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response
// @Router /admin/my/login_log/list [get]
// @Security token
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ? and is_deleted = ?", u.Id, model.IsDeletedNo)
tx.Order("id desc")
})
response.Success(c, res)
}
// Delete 删除
// @Tags 我的登录日志
// @Summary 登录日志删除
// @Description 登录日志删除
// @Accept json
// @Produce json
// @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/login_log/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.LoginLogService.InfoById(f.Id)
if l.Id == 0 || l.IsDeleted == model.IsDeletedYes {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if l.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.LoginLogService.SoftDelete(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
}
// BatchDelete 删除
// @Tags 我的登录日志
// @Summary 登录日志批量删除
// @Description 登录日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.LoginLogIds true "登录日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/login_log/batchDelete [post]
// @Security token
func (ct *LoginLog) BatchDelete(c *gin.Context) {
f := &admin.LoginLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
err := service.AllService.LoginLogService.BatchSoftDelete(u.Id, f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,59 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"time"
)
type Peer struct {
}
// List 列表
// @Tags 我的设备
// @Summary 设备列表
// @Description 设备列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param time_ago query int false "时间"
// @Param id query string false "ID"
// @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/my/peer/list [get]
// @Security token
func (ct *Peer) List(c *gin.Context) {
query := &admin.PeerQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", u.Id)
if query.TimeAgo > 0 {
lt := time.Now().Unix() - int64(query.TimeAgo)
tx.Where("last_online_time < ?", lt)
}
if query.TimeAgo < 0 {
lt := time.Now().Unix() + int64(query.TimeAgo)
tx.Where("last_online_time > ?", lt)
}
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
})
response.Success(c, res)
}

View File

@@ -0,0 +1,119 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type ShareRecord struct {
}
// List 分享记录列表
// @Tags 我的分享记录
// @Summary 分享记录列表
// @Description 分享记录列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/share_record/list [get]
// @Security token
func (sr *ShareRecord) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.ShareRecordService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", u.Id)
})
response.Success(c, res)
}
// Delete 分享记录删除
// @Tags 我的分享记录
// @Summary 分享记录删除
// @Description 分享记录删除
// @Accept json
// @Produce json
// @Param body body admin.ShareRecordForm true "分享记录信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/share_record/delete [post]
// @Security token
func (sr *ShareRecord) Delete(c *gin.Context) {
f := &admin.ShareRecordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
i := service.AllService.ShareRecordService.InfoById(f.Id)
if i.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if i.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ShareRecordService.Delete(i)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
// BatchDelete 批量删除我的分享记录
// @Tags 我的
// @Summary 批量删除我的分享记录
// @Description 批量删除我的分享记录
// @Accept json
// @Produce json
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/share_record/batchDelete [post]
// @Security token
func (sr *ShareRecord) BatchDelete(c *gin.Context) {
f := &admin.PeerShareRecordBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
var l int64
l = int64(len(f.Ids))
res := service.AllService.ShareRecordService.List(1, uint(l), func(tx *gorm.DB) {
tx.Where("user_id = ?", u.Id)
tx.Where("id in ?", f.Ids)
})
if res.Total != l {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ShareRecordService.BatchDelete(f.Ids)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -0,0 +1,176 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type Tag struct{}
// List 列表
// @Tags 我的标签
// @Summary 标签列表
// @Description 标签列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.TagList}
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/list [get]
// @Security token
func (ct *Tag) List(c *gin.Context) {
query := &admin.TagQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
tx.Where("user_id = ?", query.UserId)
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Create 创建标签
// @Tags 我的标签
// @Summary 创建标签
// @Description 创建标签
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/create [post]
// @Security token
func (ct *Tag) Create(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
t.UserId = u.Id
err := service.AllService.TagService.Create(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Update 编辑
// @Tags 我的标签
// @Summary 标签编辑
// @Description 标签编辑
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/update [post]
// @Security token
func (ct *Tag) Update(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if f.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
t := f.ToTag()
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.TagService.Update(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 标签
// @Summary 标签删除
// @Description 标签删除
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/delete [post]
// @Security token
func (ct *Tag) Delete(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.TagService.Delete(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,296 @@
package admin
import (
"strconv"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
adminReq "github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
)
type Oauth struct {
}
// Info
func (o *Oauth) Info(c *gin.Context) {
code := c.Query("code")
if code == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
v := service.AllService.OauthService.GetOauthCache(code)
if v == nil {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
response.Success(c, v)
}
func (o *Oauth) ToBind(c *gin.Context) {
f := &adminReq.BindOauthForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op)
if utr.Id > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeBind,
Op: f.Op,
UserId: u.Id,
Verifier: verifier,
Nonce: nonce,
}, 5*60)
response.Success(c, gin.H{
"code": state,
"url": url,
})
}
// Confirm 确认授权登录
func (o *Oauth) Confirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if j.Code == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
v := service.AllService.OauthService.GetOauthCache(j.Code)
if v == nil {
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
return
}
u := service.AllService.UserService.CurUser(c)
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(j.Code, v, 0)
response.Success(c, v)
}
func (o *Oauth) BindConfirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if j.Code == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
oauthService := service.AllService.OauthService
oauthCache := oauthService.GetOauthCache(j.Code)
if oauthCache == nil {
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
return
}
oauthUser := oauthCache.ToOauthUser()
user := service.AllService.UserService.CurUser(c)
err = oauthService.BindOauthUser(user.Id, oauthUser, oauthCache.Op)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
return
}
oauthCache.UserId = user.Id
oauthService.SetOauthCache(j.Code, oauthCache, 0)
response.Success(c, oauthCache)
}
func (o *Oauth) Unbind(c *gin.Context) {
f := &adminReq.UnBindOauthForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op)
if utr.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err = service.AllService.OauthService.UnBindOauthUser(u.Id, f.Op)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Detail Oauth
// @Tags Oauth
// @Summary Oauth详情
// @Description Oauth详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.Oauth}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/detail/{id} [get]
// @Security token
func (o *Oauth) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.OauthService.InfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建Oauth
// @Tags Oauth
// @Summary 创建Oauth
// @Description 创建Oauth
// @Accept json
// @Produce json
// @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response{data=model.Oauth}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/create [post]
// @Security token
func (o *Oauth) Create(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToOauth()
err := u.FormatOauthInfo()
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
ex := service.AllService.OauthService.InfoByOp(u.Op)
if ex.Id > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return
}
err = service.AllService.OauthService.Create(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags Oauth
// @Summary Oauth列表
// @Description Oauth列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/list [get]
// @Security token
func (o *Oauth) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.OauthService.List(query.Page, query.PageSize, nil)
response.Success(c, res)
}
// Update 编辑
// @Tags Oauth
// @Summary Oauth编辑
// @Description Oauth编辑
// @Accept json
// @Produce json
// @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response
// @Router /admin/oauth/update [post]
// @Security token
func (o *Oauth) Update(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToOauth()
err := service.AllService.OauthService.Update(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags Oauth
// @Summary Oauth删除
// @Description Oauth删除
// @Accept json
// @Produce json
// @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/oauth/delete [post]
// @Security token
func (o *Oauth) Delete(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.OauthService.InfoById(f.Id)
if u.Id > 0 {
err := service.AllService.OauthService.Delete(u)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -1,21 +1,23 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
"time"
)
type Peer struct {
}
// Detail 机器
// @Tags 机器
// @Summary 机器详情
// @Description 机器详情
// Detail 设备
// @Tags 设备
// @Summary 设备详情
// @Description 设备详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
@@ -31,17 +33,17 @@ func (ct *Peer) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建机器
// @Tags 机器
// @Summary 创建机器
// @Description 创建机器
// Create 创建设备
// @Tags 设备
// @Summary 创建设备
// @Description 创建设备
// @Accept json
// @Produce json
// @Param body body admin.PeerForm true "机器信息"
// @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response
// @Router /admin/peer/create [post]
@@ -49,52 +51,83 @@ func (ct *Peer) Detail(c *gin.Context) {
func (ct *Peer) Create(c *gin.Context) {
f := &admin.PeerForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToPeer()
err := service.AllService.PeerService.Create(u)
p := f.ToPeer()
err := service.AllService.PeerService.Create(p)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表
// @Tags 机器
// @Summary 机器列表
// @Description 机器列表
// @Tags 设备
// @Summary 设备列表
// @Description 设备列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param time_ago query int false "时间"
// @Param id query string false "ID"
// @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/peer/list [get]
// @Security token
func (ct *Peer) List(c *gin.Context) {
query := &admin.PageQuery{}
query := &admin.PeerQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.PeerService.List(query.Page, query.PageSize, nil)
res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.TimeAgo > 0 {
lt := time.Now().Unix() - int64(query.TimeAgo)
tx.Where("last_online_time < ?", lt)
}
if query.TimeAgo < 0 {
lt := time.Now().Unix() + int64(query.TimeAgo)
tx.Where("last_online_time > ?", lt)
}
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Ip != "" {
tx.Where("last_online_ip like ?", "%"+query.Ip+"%")
}
if query.Alias != "" {
tx.Where("alias like ?", "%"+query.Alias+"%")
}
})
response.Success(c, res)
}
// Update 编辑
// @Tags 机器
// @Summary 机器编辑
// @Description 机器编辑
// @Tags 设备
// @Summary 设备编辑
// @Description 设备编辑
// @Accept json
// @Produce json
// @Param body body admin.PeerForm true "机器信息"
// @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response
// @Router /admin/peer/update [post]
@@ -102,14 +135,14 @@ func (ct *Peer) List(c *gin.Context) {
func (ct *Peer) Update(c *gin.Context) {
f := &admin.PeerForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.RowId == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -117,19 +150,19 @@ func (ct *Peer) Update(c *gin.Context) {
u := f.ToPeer()
err := service.AllService.PeerService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 机器
// @Summary 机器删除
// @Description 机器删除
// @Tags 设备
// @Summary 设备删除
// @Description 设备删除
// @Accept json
// @Produce json
// @Param body body admin.PeerForm true "机器信息"
// @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post]
@@ -137,11 +170,11 @@ func (ct *Peer) Update(c *gin.Context) {
func (ct *Peer) Delete(c *gin.Context) {
f := &admin.PeerForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.RowId
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -153,8 +186,55 @@ func (ct *Peer) Delete(c *gin.Context) {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 批量删除
// @Tags 设备
// @Summary 批量设备删除
// @Description 批量设备删除
// @Accept json
// @Produce json
// @Param body body admin.PeerBatchDeleteForm true "设备id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/batchDelete [post]
// @Security token
func (ct *Peer) BatchDelete(c *gin.Context) {
f := &admin.PeerBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.RowIds) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.PeerService.BatchDelete(f.RowIds)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
func (ct *Peer) SimpleData(c *gin.Context) {
f := &admin.SimpleDataQuery{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
res := service.AllService.PeerService.List(1, 99999, func(tx *gorm.DB) {
//可以公开的情报
tx.Select("id,version")
tx.Where("id in (?)", f.Ids)
})
response.Success(c, res)
}

View File

@@ -1,30 +1,137 @@
package admin
import (
"Gwen/global"
"Gwen/http/response"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
)
type Rustdesk struct {
}
// ServerConfig 服务配置
// @Tags ADMIN
// @Summary 服务配置
// @Description 服务配置,给webclient提供api-server
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/server-config [get]
// @Security token
func (r *Rustdesk) ServerConfig(c *gin.Context) {
cf := &response.ServerConfigResponse{
IdServer: global.Config.Rustdesk.IdServer,
Key: global.Config.Rustdesk.Key,
RelayServer: global.Config.Rustdesk.RelayServer,
ApiServer: global.Config.Rustdesk.ApiServer,
}
response.Success(c, cf)
type RustdeskCmd struct {
Cmd string `json:"cmd"`
Option string `json:"option"`
Target string `json:"target"`
}
func (r *Rustdesk) CmdList(c *gin.Context) {
q := &admin.PageQuery{}
if err := c.ShouldBindQuery(q); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.ServerCmdService.List(q.Page, 9999)
//在列表前添加系统命令
list := make([]*model.ServerCmd, 0)
list = append(list, model.SysIdServerCmds...)
list = append(list, model.SysRelayServerCmds...)
list = append(list, res.ServerCmds...)
res.ServerCmds = list
response.Success(c, res)
}
func (r *Rustdesk) CmdDelete(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.ServerCmdService.Info(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ServerCmdService.Delete(ex)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) CmdCreate(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
err := service.AllService.ServerCmdService.Create(f)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) CmdUpdate(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.ServerCmdService.Info(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ServerCmdService.Update(f)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) SendCmd(c *gin.Context) {
rc := &RustdeskCmd{}
if err := c.ShouldBindJSON(rc); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if rc.Cmd == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if rc.Target == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if rc.Target != model.ServerCmdTargetIdServer && rc.Target != model.ServerCmdTargetRelayServer {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
port := 0
switch rc.Target {
case model.ServerCmdTargetIdServer:
port = global.Config.Admin.IdServerPort - 1
case model.ServerCmdTargetRelayServer:
port = global.Config.Admin.RelayServerPort
}
res, err := service.AllService.ServerCmdService.SendCmd(port, rc.Cmd, rc.Option)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, res)
}

View File

@@ -0,0 +1,105 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type ShareRecord struct {
}
// List 列表
// @Tags 分享记录
// @Summary 分享记录列表
// @Description 分享记录列表
// @Accept json
// @Produce json
// @Param user_id query int false "用户ID"
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/share_record/list [get]
// @Security token
func (sr *ShareRecord) List(c *gin.Context) {
query := &admin.ShareRecordQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.ShareRecordService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
})
response.Success(c, res)
}
// Delete 删除
// @Tags 分享记录
// @Summary 分享记录删除
// @Description 分享记录删除
// @Accept json
// @Produce json
// @Param body body admin.ShareRecordForm true "分享记录信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/share_record/delete [post]
// @Security token
func (sr *ShareRecord) Delete(c *gin.Context) {
f := &admin.ShareRecordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
i := service.AllService.ShareRecordService.InfoById(f.Id)
if i.Id > 0 {
err := service.AllService.ShareRecordService.Delete(i)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 批量删除
// @Tags 分享记录
// @Summary 批量分享记录
// @Description 批量分享记录
// @Accept json
// @Produce json
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/share_record/batchDelete [post]
// @Security token
func (sr *ShareRecord) BatchDelete(c *gin.Context) {
f := &admin.PeerShareRecordBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.ShareRecordService.BatchDelete(f.Ids)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -1,11 +1,11 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
@@ -30,14 +30,14 @@ func (ct *Tag) Detail(c *gin.Context) {
t := service.AllService.TagService.InfoById(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -55,25 +55,25 @@ func (ct *Tag) Detail(c *gin.Context) {
func (ct *Tag) Create(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) {
t.UserId = u.Id
if t.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.TagService.Create(t)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表
@@ -93,17 +93,19 @@ func (ct *Tag) Create(c *gin.Context) {
func (ct *Tag) List(c *gin.Context) {
query := &admin.TagQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
@@ -122,27 +124,27 @@ func (ct *Tag) List(c *gin.Context) {
func (ct *Tag) Update(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
return
}
err := service.AllService.TagService.Update(t)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -162,29 +164,24 @@ func (ct *Tag) Update(c *gin.Context) {
func (ct *Tag) Delete(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := service.AllService.TagService.InfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if u.Id > 0 {
err := service.AllService.TagService.Delete(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
err := service.AllService.TagService.Delete(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, err.Error())
}

View File

@@ -1,12 +1,14 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/service"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"gorm.io/gorm"
"strconv"
)
@@ -33,7 +35,7 @@ func (ct *User) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -51,10 +53,10 @@ func (ct *User) Detail(c *gin.Context) {
func (ct *User) Create(c *gin.Context) {
f := &admin.UserForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -62,10 +64,10 @@ func (ct *User) Create(c *gin.Context) {
u := f.ToUser()
err := service.AllService.UserService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
response.Success(c, nil)
}
// List 列表
@@ -84,7 +86,7 @@ func (ct *User) Create(c *gin.Context) {
func (ct *User) List(c *gin.Context) {
query := &admin.UserQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.UserService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
@@ -109,14 +111,14 @@ func (ct *User) List(c *gin.Context) {
func (ct *User) Update(c *gin.Context) {
f := &admin.UserForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误:"+err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -124,7 +126,7 @@ func (ct *User) Update(c *gin.Context) {
u := f.ToUser()
err := service.AllService.UserService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -144,11 +146,11 @@ func (ct *User) Update(c *gin.Context) {
func (ct *User) Delete(c *gin.Context) {
f := &admin.UserForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -163,7 +165,7 @@ func (ct *User) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// UpdatePassword 修改密码
@@ -180,22 +182,22 @@ func (ct *User) Delete(c *gin.Context) {
func (ct *User) UpdatePassword(c *gin.Context) {
f := &admin.UserPasswordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.InfoById(f.Id)
if u.Id == 0 {
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.UserService.UpdatePassword(u, f.Password)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -215,12 +217,7 @@ func (ct *User) Current(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
token, _ := c.Get("token")
t := token.(string)
response.Success(c, &adResp.LoginPayload{
Token: t,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
responseLoginSuccess(c, u, t)
}
// ChangeCurPwd 修改当前用户密码
@@ -237,25 +234,115 @@ func (ct *User) Current(c *gin.Context) {
func (ct *User) ChangeCurPwd(c *gin.Context) {
f := &admin.ChangeCurPasswordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, "旧密码错误")
return
// Verify the old password only when the account already has one set
if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
ok, _, err := utils.VerifyPassword(u.Password, f.OldPassword)
if err != nil || !ok {
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
}
}
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// MyOauth
// @Tags 用户
// @Summary 我的授权
// @Description 我的授权
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]adResp.UserOauthItem}
// @Failure 500 {object} response.Response
// @Router /admin/user/myOauth [get]
// @Security token
func (ct *User) MyOauth(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
oal := service.AllService.OauthService.List(1, 100, nil)
ops := make([]string, 0)
for _, oa := range oal.Oauths {
ops = append(ops, oa.Op)
}
uts := service.AllService.UserService.UserThirdsByUserId(u.Id)
var res []*adResp.UserOauthItem
for _, oa := range oal.Oauths {
item := &adResp.UserOauthItem{
Op: oa.Op,
}
for _, ut := range uts {
if ut.Op == oa.Op {
item.Status = 1
break
}
}
res = append(res, item)
}
response.Success(c, res)
}
// groupUsers
func (ct *User) GroupUsers(c *gin.Context) {
aG := service.AllService.GroupService.List(1, 999, nil)
aU := service.AllService.UserService.List(1, 9999, nil)
response.Success(c, gin.H{
"groups": aG.Groups,
"users": aU.Users,
})
}
// Register
func (ct *User) Register(c *gin.Context) {
if !global.Config.App.Register {
response.Fail(c, 101, response.TranslateMsg(c, "RegisterClosed"))
return
}
f := &admin.RegisterForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
regStatus := model.StatusCode(global.Config.App.RegisterStatus)
// 注册状态可能未配置,默认启用
if regStatus != model.COMMON_STATUS_DISABLED && regStatus != model.COMMON_STATUS_ENABLE {
regStatus = model.COMMON_STATUS_ENABLE
}
u := service.AllService.UserService.Register(f.Username, f.Email, f.Password, regStatus)
if u == nil || u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed"))
return
}
if regStatus == model.COMMON_STATUS_DISABLED {
// 需要管理员审核
response.Fail(c, 101, response.TranslateMsg(c, "RegisterSuccessWaitAdminConfirm"))
return
}
// 注册成功后自动登录
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
Uuid: "",
Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount,
})
responseLoginSuccess(c, u, ut.Token)
}

View File

@@ -0,0 +1,113 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type UserToken struct {
}
// List 列表
// @Tags 登录凭证
// @Summary 登录凭证列表
// @Description 登录凭证列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param user_id query int false "用户ID"
// @Success 200 {object} response.Response{data=model.UserTokenList}
// @Failure 500 {object} response.Response
// @Router /admin/user_token/list [get]
// @Security token
func (ct *UserToken) List(c *gin.Context) {
query := &admin.LoginTokenQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.UserService.TokenList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
tx.Order("id desc")
})
response.Success(c, res)
}
// Delete 删除
// @Tags 登录凭证
// @Summary 登录凭证删除
// @Description 登录凭证删除
// @Accept json
// @Produce json
// @Param body body model.UserToken true "登录凭证信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/user_token/delete [post]
// @Security token
func (ct *UserToken) Delete(c *gin.Context) {
f := &model.UserToken{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
l := service.AllService.UserService.TokenInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if l.Id > 0 {
err := service.AllService.UserService.DeleteToken(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 批量删除
// @Tags 登录凭证
// @Summary 登录凭证批量删除
// @Description 登录凭证批量删除
// @Accept json
// @Produce json
// @Param body body admin.UserTokenBatchDeleteForm true "登录凭证信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/user_token/batchDelete [post]
// @Security token
func (ct *UserToken) BatchDelete(c *gin.Context) {
f := &admin.UserTokenBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
ids := f.Ids
if len(ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.UserService.BatchDeleteUserToken(ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
}

View File

@@ -1,15 +1,19 @@
package api
import (
requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"encoding/json"
"fmt"
"errors"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"net/http"
"strconv"
"strings"
)
type Ab struct {
@@ -28,8 +32,8 @@ type Ab struct {
func (a *Ab) Ab(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000)
tags := service.AllService.TagService.ListByUserId(user.Id)
al := service.AllService.AddressBookService.ListByUserIdAndCollectionId(user.Id, 0, 1, 1000)
tags := service.AllService.TagService.ListByUserIdAndCollectionId(user.Id, 0)
tagColors := map[string]uint{}
//将tags中的name转成一个以逗号分割的字符串
@@ -66,85 +70,658 @@ func (a *Ab) UpAb(c *gin.Context) {
abf := &requstform.AddressBookForm{}
err := c.ShouldBindJSON(&abf)
if err != nil {
fmt.Println(err)
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
abd := &requstform.AddressBookFormData{}
err = json.Unmarshal([]byte(abf.Data), abd)
if err != nil {
response.Error(c, "系统错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
tc := map[string]uint{}
err = json.Unmarshal([]byte(abd.TagColors), &tc)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(abd)
//for _, peer := range abd.Peers {
// fmt.Println(peer)
//}
user := service.AllService.UserService.CurUser(c)
err = service.AllService.AddressBookService.UpdateAddressBook(abd.Peers, user.Id)
if err != nil {
c.Abort()
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
tc := map[string]uint{}
err = json.Unmarshal([]byte(abd.TagColors), &tc)
if err != nil {
fmt.Println(err)
response.Error(c, "系统错误")
return
} else {
service.AllService.TagService.UpdateTags(user.Id, tc)
}
service.AllService.TagService.UpdateTags(user.Id, tc)
c.JSON(http.StatusOK, nil)
}
// Tags
// @Tags 地址
// PTags
// @Tags 地址[Personal]
// @Summary 标签
// @Description 标签
// @Accept json
// @Produce json
// @Success 200 {object} []model.Tag
// @Param guid path string true "guid"
// @Success 200 {object} model.TagList
// @Failure 500 {object} response.ErrorResponse
// @Router /tags [post]
// @Router /ab/tags/{guid} [post]
// @Security BearerAuth
func (a *Ab) Tags(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
func (a *Ab) PTags(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
tags := service.AllService.TagService.ListByUserId(user.Id)
//check privileges
if !service.AllService.AddressBookService.CheckUserReadPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tags := service.AllService.TagService.ListByUserIdAndCollectionId(uid, cid)
c.JSON(http.StatusOK, tags.Tags)
}
// TagAdd
// @Tags 地址
// @Tags 地址[Personal]
// @Summary 标签添加
// @Description 标签
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/add [post]
// @Router /ab/tag/add/{guid} [post]
// @Security BearerAuth
func (a *Ab) TagAdd(c *gin.Context) {
t := &model.Tag{}
err := c.ShouldBindJSON(t)
if err != nil {
fmt.Println(err)
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Name, cid)
if tag != nil && tag.Id != 0 {
response.Error(c, response.TranslateMsg(c, "ItemExists"))
return
}
t.UserId = uid
t.CollectionId = cid
err = service.AllService.TagService.Create(t)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
}
// TagRename
// @Tags 地址[Personal]
// @Summary 标签重命名
// @Description 标签
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/rename/{guid} [put]
// @Security BearerAuth
func (a *Ab) TagRename(c *gin.Context) {
t := &requstform.TagRenameForm{}
err := c.ShouldBindJSON(t)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Old, cid)
if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
ntag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.New, cid)
if ntag != nil && ntag.Id != 0 {
response.Error(c, response.TranslateMsg(c, "ItemExists"))
return
}
tag.Name = t.New
err = service.AllService.TagService.Update(tag)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
}
// TagUpdate
// @Tags 地址[Personal]
// @Summary 标签修改颜色
// @Description 标签
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/update/{guid} [put]
// @Security BearerAuth
func (a *Ab) TagUpdate(c *gin.Context) {
t := &requstform.TagColorForm{}
err := c.ShouldBindJSON(t)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Name, cid)
if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
tag.Color = t.Color
err = service.AllService.TagService.Update(tag)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
}
// TagDel
// @Tags 地址[Personal]
// @Summary 标签删除
// @Description 标签
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/{guid} [delete]
// @Security BearerAuth
func (a *Ab) TagDel(c *gin.Context) {
t := &[]string{}
err := c.ShouldBind(t)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(t)
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserFullControlPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
for _, name := range *t {
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, name, cid)
if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
err = service.AllService.TagService.Delete(tag)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
c.String(http.StatusOK, "")
}
// Personal
// @Tags 地址[Personal]
// @Summary 个人地址
// @Description 个人地址
// @Accept json
// @Produce json
// @Param string body string false "string valid"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /ab/personal [post]
// @Security BearerAuth
func (a *Ab) Personal(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
/**
guid = json['guid'] ?? '',
name = json['name'] ?? '',
owner = json['owner'] ?? '',
note = json['note'] ?? '',
rule = json['rule'] ?? 0;
*/
if global.Config.Rustdesk.Personal == 1 {
guid := a.ComposeGuid(user.GroupId, user.Id, 0)
//如果返回了guid后面的请求会有变化
c.JSON(http.StatusOK, gin.H{
"guid": guid,
"name": user.Username,
"rule": 3,
})
} else {
c.JSON(http.StatusOK, nil)
}
}
// Settings
// @Tags 地址[Personal]
// @Summary 设置
// @Description 设置
// @Accept json
// @Produce json
// @Param string body string false "string valid"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /ab/settings [post]
// @Security BearerAuth
func (a *Ab) Settings(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"max_peer_one_ab": 0, //最大peer数0表示不限制
})
}
// SharedProfiles
// @Tags 地址[Personal]
// @Summary 共享地址簿
// @Description 共享
// @Accept json
// @Produce json
// @Param current query int false "页码"
// @Param pageSize query int false "每页数量"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /ab/shared/profiles [post]
// @Security BearerAuth
func (a *Ab) SharedProfiles(c *gin.Context) {
var res []*api.SharedProfilesPayload
user := service.AllService.UserService.CurUser(c)
myAbCollectionList := service.AllService.AddressBookService.ListCollectionByUserId(user.Id)
for _, ab := range myAbCollectionList.AddressBookCollection {
res = append(res, &api.SharedProfilesPayload{
Guid: a.ComposeGuid(user.GroupId, user.Id, ab.Id),
Name: ab.Name,
Owner: user.Username,
Rule: model.ShareAddressBookRuleRuleFullControl,
})
}
allAbIds := make(map[uint]int) //用map去重并保留最大Rule
allUserIds := make(map[uint]*model.User)
rules := service.AllService.AddressBookService.CollectionReadRules(user)
for _, rule := range rules {
//先判断是否存在
r, ok := allAbIds[rule.CollectionId]
if ok {
//再判断权限大小
if r < rule.Rule {
allAbIds[rule.CollectionId] = rule.Rule
}
} else {
allAbIds[rule.CollectionId] = rule.Rule
allUserIds[rule.UserId] = nil
}
}
//u := service.AllService.UserService.CurUser(c)
abids := utils.Keys(allAbIds)
collections := service.AllService.AddressBookService.ListCollectionByIds(abids)
//err = service.AllService.TagService.UpdateTags(t.Name, t.Color, user.Id)
//if err != nil {
// response.Error(c, "操作失败")
// return
//}
c.JSON(http.StatusOK, "")
ids := utils.Keys(allUserIds)
allUsers := service.AllService.UserService.ListByIds(ids)
for _, u := range allUsers {
allUserIds[u.Id] = u
}
for _, collection := range collections {
_u, ok := allUserIds[collection.UserId]
if !ok {
continue
}
res = append(res, &api.SharedProfilesPayload{
Guid: a.ComposeGuid(_u.GroupId, _u.Id, collection.Id),
Name: collection.Name,
Owner: _u.Username,
Rule: allAbIds[collection.Id],
})
}
c.JSON(http.StatusOK, gin.H{
"total": 0, //len(res),
"data": res,
})
}
// ParseGuid
func (a *Ab) ParseGuid(guid string) (gid, uid, cid uint) {
//用-切割 guid
guids := strings.Split(guid, "-")
if len(guids) < 2 {
return 0, 0, 0
}
if len(guids) != 3 {
cid = 0
} else {
s, err := strconv.Atoi(guids[2])
if err != nil {
return 0, 0, 0
}
cid = uint(s)
}
g, err := strconv.Atoi(guids[0])
if err != nil {
return 0, 0, 0
}
gid = uint(g)
u, err := strconv.Atoi(guids[1])
if err != nil {
return 0, 0, 0
}
uid = uint(u)
return
}
// ComposeGuid
func (a *Ab) ComposeGuid(gid, uid, cid uint) string {
return strconv.Itoa(int(gid)) + "-" + strconv.Itoa(int(uid)) + "-" + strconv.Itoa(int(cid))
}
// CheckGuid
func (a *Ab) CheckGuid(cu *model.User, guid string) (gid, uid, cid uint, err error) {
gid, uid, cid = a.ParseGuid(guid)
err = nil
if gid == 0 || uid == 0 {
err = errors.New("ParamsError")
return
}
u := &model.User{}
if cu.Id == uid {
u = cu
} else {
u = service.AllService.UserService.InfoById(uid)
}
if u == nil || u.Id == 0 {
err = errors.New("ParamsError")
return
}
if u.GroupId != gid {
err = errors.New("ParamsError")
return
}
if cid == 0 && cu.Id != uid {
err = errors.New("ParamsError")
return
}
if cid > 0 {
c := service.AllService.AddressBookService.CollectionInfoById(cid)
if c == nil || c.Id == 0 {
err = errors.New("ParamsError")
return
}
if c.UserId != uid {
err = errors.New("ParamsError")
return
}
}
return
}
// Peers
// @Tags 地址[Personal]
// @Summary 地址列表
// @Description 地址
// @Accept json
// @Produce json
// @Param current query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param ab query string false "guid"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /ab/peers [post]
// @Security BearerAuth
func (a *Ab) Peers(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
guid := c.Query("ab")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserReadPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
al := service.AllService.AddressBookService.ListByUserIdAndCollectionId(uid, cid, 1, 1000)
c.JSON(http.StatusOK, gin.H{
"total": al.Total,
"data": al.AddressBooks,
"licensed_devices": 99999,
})
}
// PeerAdd
// @Tags 地址[Personal]
// @Summary 添加地址
// @Description 添加地址
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/peer/add/{guid} [post]
// @Security BearerAuth
func (a *Ab) PeerAdd(c *gin.Context) {
// forceAlwaysRelay永远是字符串"false"
//f := &gin.H{}
f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f)
f.UserId = uid
ab := f.ToAddressBook()
ab.CollectionId = cid
if ab.Platform == "" || ab.Username == "" || ab.Hostname == "" {
peer := service.AllService.PeerService.FindById(ab.Id)
if peer.RowId != 0 {
ab.Platform = service.AllService.AddressBookService.PlatformFromOs(peer.Os)
ab.Username = peer.Username
ab.Hostname = peer.Hostname
}
}
err = service.AllService.AddressBookService.AddAddressBook(ab)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
}
// PeerDel
// @Tags 地址[Personal]
// @Summary 删除地址
// @Description 删除地址
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/peer/add/{guid} [delete]
// @Security BearerAuth
func (a *Ab) PeerDel(c *gin.Context) {
f := &[]string{}
err := c.ShouldBind(f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserFullControlPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
for _, id := range *f {
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, id, cid)
if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
err = service.AllService.AddressBookService.Delete(ab)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
c.String(http.StatusOK, "")
}
// PeerUpdate
// @Tags 地址[Personal]
// @Summary 更新地址
// @Description 更新地址
// @Accept json
// @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/peer/update/{guid} [put]
// @Security BearerAuth
func (a *Ab) PeerUpdate(c *gin.Context) {
f := gin.H{}
//f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(&f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f)
//判断f["Id"]是否存在
fid, ok := f["id"]
if !ok {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
fidstr := fid.(string)
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, fidstr, cid)
if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
//允许的字段
allowUp := []string{"password", "hash", "tags", "alias"}
//f中的字段如果不在allowUp中就删除
for k := range f {
if !utils.InArray(k, allowUp) {
delete(f, k)
}
}
//fmt.Println(f)
if tags, _ok := f["tags"]; _ok {
f["tags"], _ = json.Marshal(tags)
}
err = service.AllService.AddressBookService.UpdateByMap(ab, f)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
}

View File

@@ -0,0 +1,84 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
request "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"time"
)
type Audit struct {
}
// AuditConn
// @Tags 审计
// @Summary 审计连接
// @Description 审计连接
// @Accept json
// @Produce json
// @Param body body request.AuditConnForm true "审计连接"
// @Success 200 {string} string ""
// @Failure 500 {object} response.Response
// @Router /audit/conn [post]
func (a *Audit) AuditConn(c *gin.Context) {
af := &request.AuditConnForm{}
err := c.ShouldBindBodyWith(af, binding.JSON)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
/*ttt := &gin.H{}
c.ShouldBindBodyWith(ttt, binding.JSON)
fmt.Println(ttt)*/
ac := af.ToAuditConn()
if af.Action == model.AuditActionNew {
service.AllService.AuditService.CreateAuditConn(ac)
} else if af.Action == model.AuditActionClose {
ex := service.AllService.AuditService.InfoByPeerIdAndConnId(af.Id, af.ConnId)
if ex.Id != 0 {
ex.CloseTime = time.Now().Unix()
service.AllService.AuditService.UpdateAuditConn(ex)
}
} else if af.Action == "" {
ex := service.AllService.AuditService.InfoByPeerIdAndConnId(af.Id, af.ConnId)
if ex.Id != 0 {
up := &model.AuditConn{
IdModel: model.IdModel{Id: ex.Id},
FromPeer: ac.FromPeer,
FromName: ac.FromName,
SessionId: ac.SessionId,
Type: ac.Type,
}
service.AllService.AuditService.UpdateAuditConn(up)
}
}
response.Success(c, "")
}
// AuditFile
// @Tags 审计
// @Summary 审计文件
// @Description 审计文件
// @Accept json
// @Produce json
// @Param body body request.AuditFileForm true "审计文件"
// @Success 200 {string} string ""
// @Failure 500 {object} response.Response
// @Router /audit/file [post]
func (a *Audit) AuditFile(c *gin.Context) {
aff := &request.AuditFileForm{}
err := c.ShouldBindBodyWith(aff, binding.JSON)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//ttt := &gin.H{}
//c.ShouldBindBodyWith(ttt, binding.JSON)
//fmt.Println(ttt)
af := aff.ToAuditFile()
service.AllService.AuditService.CreateAuditFile(af)
response.Success(c, "")
}

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